You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2021/07/29 18:10:40 UTC

[GitHub] [apisix] rushitote opened a new pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

rushitote opened a new pull request #4710:
URL: https://github.com/apache/apisix/pull/4710


   ### What this PR does / why we need it:
   <!--- Why is this change required? What problem does it solve? -->
   <!--- If it fixes an open issue, please link to the issue here. -->
   fix: #4674 
   
   This PR adds an authorization plugin: `authz-casbin` to APISIX. This is based on Lua Casbin which is the Lua implementation of Casbin library. The plugin supports enforcement of powerful authorization scenarios based on various access control models supported by Casbin.
   
   The plugin works on the basis of a model file and a policy file. The model acts as a configuration for the policies and policy enforcement. The plugin currently also supports direct model/policy text in absence of files through plugin metadata.
   
   An example of authz model is:
   
   ```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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)
   ```
   
   And example of authz policy is:
   
   ```
   p, *, /, GET
   p, admin, *, *
   g, alice, admin
   ```
   
   This means that any user (subject) can access the homepage which is at `/` via `GET` request but only admins can a access other pages and use other request methods. And here, as defined in policy `alice` has a role as `admin` and hence she has admin access.
   
   
   
   ### Pre-submission checklist:
   
   * [x] Did you explain what problem does this PR solve? Or what new features have been added?
   * [x] Have you added corresponding test cases?
   * [x] Have you modified the corresponding document?
   * [x] Is this PR backward compatible? **If it is not backward compatible, please discuss on the [mailing list](https://github.com/apache/apisix/tree/master#community) first**
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680540246



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       @spacewander Sorry, I found this now. But is there any reason for us to not use lrucache? It worked well for two different routes with different casbin enforcers when I tested it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#issuecomment-890888265


   To make the misc checker pass, you need to update https://github.com/apache/apisix/blob/9f01ef8f2ca753557289ac9dc91f5212432fae73/ci/ASF-Release.cfg#L59
   To make the doc linter pass, you need to update https://github.com/apache/apisix/blob/master/docs/en/latest/config.json


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681644103



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       I thought for a while and was confused by the intention of the metadata, is it used as the default policy? And why not also support using text format in the plugin configuration?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680503928



##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,358 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files

Review comment:
       Yes, removed that unnecessary part from test 4.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r683113011



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       @tokers So, should it be worded like this:
   
   'The plugin route configuration has a higher precedence than the plugin metadata configuration. Hence if the model/policy configuration is present in the plugin route config, the plugin will use that instead of the metadata config.'




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681545972



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,124 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then

Review comment:
       if `username` is always required, you can use `required = {"username"}` in `schema`, such as https://github.com/apache/apisix/blob/1b247c8e1ebd324efe66abbe33d5647a93f0426e/apisix/plugins/example-plugin.lua#L22-L32

##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,401 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"

Review comment:
       ```suggestion
                       "uri": "/hello"
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681538163



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       I think this is good, I have added a commit with this.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680996261



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       @spacewander Can you please tell me which check we could remove? I feel all are necessary and don't see any similar checks.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679714865



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       Sure I will do that.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r682566688



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -28,9 +28,14 @@ local schema = {
     properties = {
         model_path = { type = "string" },
         policy_path = { type = "string" },
+        model = { type = "string" },
+        policy = { type = "string" },
         username = { type = "string"}
     },
-    required = {"model_path", "policy_path", "username"},
+    anyOf = {

Review comment:
       Changed it.

##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       @tokers So, should it be worded like this:
   
   'The plugin route configuration has a higher precedence than the plugin metadata configuration. Hence if the model/policy configuration is present in the plugin route config, the plugin will use that instead of the metadata config.'




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680498417



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       Sorry, I rethought about it afternoon, and found it isn't a good idea.
   It is more suitable to bind the casbin with the conf like:
   https://github.com/apache/apisix/blob/38561dc0cd13ee412d05a96153dee80018f1e799/apisix/plugins/uri-blocker.lua#L83
   
   We also need to ensure the global casbin is update-to-date with the metadata via https://github.com/apache/apisix/pull/4710#discussion_r679712809




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Yiyiyimu commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
Yiyiyimu commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680569232



##########
File path: .github/workflows/build.yml
##########
@@ -89,7 +89,7 @@ jobs:
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
       - name: Linux Get dependencies
-        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl
+        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev

Review comment:
       Sorry I made a mistake 😿 We need to add the dependency to the dockerfile but not on the host machine. Do you mind file a PR about the change on the dockerfile: https://github.com/apache/apisix-docker/blob/master/alpine-local/Dockerfile, which is used as building apisix image used in chaos testing
   
   Also we need to change docs about dependencies installation to inform users to install them: https://github.com/apache/apisix/blob/master/docs/en/latest/install-dependencies.md




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681606581



##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,401 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: no username header passed
+--- request
+GET /hello
+--- error_code: 403
+--- response_body_like eval
+qr/"Access Denied"/
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: username passed but user not authorized
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: authorized user
+--- request
+GET /hello
+--- more_headers
+user: admin
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: authorized user (rbac)
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: unauthorized user before policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: update model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    p, jack, /hello, GET
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: authorized user after policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: enable authz-casbin using model/policy files
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "model_path": "t/plugin/authz-casbin/model.conf",
+                            "policy_path": "t/plugin/authz-casbin/policy.csv",
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"

Review comment:
       ```suggestion
                       "uri": "/hello"
   ```

##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,401 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: no username header passed
+--- request
+GET /hello
+--- error_code: 403
+--- response_body_like eval
+qr/"Access Denied"/
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: username passed but user not authorized
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: authorized user
+--- request
+GET /hello
+--- more_headers
+user: admin
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: authorized user (rbac)
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: unauthorized user before policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: update model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    p, jack, /hello, GET
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: authorized user after policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: enable authz-casbin using model/policy files
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "model_path": "t/plugin/authz-casbin/model.conf",
+                            "policy_path": "t/plugin/authz-casbin/policy.csv",
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: authorized user as per policy
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: unauthorized user as per policy
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: disable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {},
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"

Review comment:
       ditto
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680961580



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       @spacewander I was thinking of something like this:
   ```lua
   local casbin_enforcer
   
   local function new_enforcer(conf, modifiedIndex)
       local model_path = conf.model_path
       local policy_path = conf.policy_path
   
       if model_path and policy_path then
           conf.type = "file"
           conf.casbin_enforcer = casbin:new(model_path, policy_path)
           return
       end
   
       local metadata = plugin.plugin_metadata(plugin_name)
       if metadata and metadata.value.model and metadata.value.policy then
           conf.type = "metadata"
           if not casbin_enforcer or casbin_enforcer.modifiedIndex ~= modifiedIndex then
               local model = metadata.value.model
               local policy = metadata.value.policy
               casbin_enforcer = casbin:newEnforcerFromText(model, policy)
               casbin_enforcer.modifiedIndex = modifiedIndex
           end
       end
   end
   
   
   function _M.rewrite(conf, ctx)
       -- creates an enforcer when request sent for the first time
       local metadata = plugin.plugin_metadata(plugin_name)
   
       if (not conf.casbin_enforcer and conf.type ~= "metadata") or
       (conf.type == "metadata" and casbin_enforcer.modifiedIndex ~= metadata.modifiedIndex) then
           new_enforcer(conf, metadata.modifiedIndex)
       end
   
       local path = ctx.var.uri
       local method = ctx.var.method
       local username = get_headers()[conf.username]
       if not username then username = "anonymous" end
   
       if conf.casbin_enforcer then
           if not conf.casbin_enforcer:enforce(username, path, method) then
               return 403, {message = "Access Denied"}
           end
       else
           if not casbin_enforcer:enforce(username, path, method) then
               return 403, {message = "Access Denied"}
           end
       end
   end
   ```
   
   So, we divide routes into two types - either `file` or `metadata`. The `file` one has their own enforcer binded to the conf while all the `metadata` based routes use the same `casbin_enforcer`. In case of the metadata based route, enforcer is created only when either there isn't one or when the `modifiedIndex` has changed. And since this separates any route into one of the two types, one type will not override the other. Will this solve the issue?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680496397



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,119 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    local casbin_enforcer = lrucache(plugin_name, metadata.modifiedIndex, new_enforcer, conf)
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then

Review comment:
       Yes, right - this is redundant here.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679585648



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")

Review comment:
       The plugin_metadata should be only set on the CP side. It's forbidden to let configuration flow from the DP to the CP.

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       Since we already use metadata to store the configuration, there is no need to provide APIs on the DP side. Everything should be done with the plugin metadata's API on the CP side.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r682566688



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -28,9 +28,14 @@ local schema = {
     properties = {
         model_path = { type = "string" },
         policy_path = { type = "string" },
+        model = { type = "string" },
+        policy = { type = "string" },
         username = { type = "string"}
     },
-    required = {"model_path", "policy_path", "username"},
+    anyOf = {

Review comment:
       Changed it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r682511353



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -28,9 +28,14 @@ local schema = {
     properties = {
         model_path = { type = "string" },
         policy_path = { type = "string" },
+        model = { type = "string" },
+        policy = { type = "string" },
         username = { type = "string"}
     },
-    required = {"model_path", "policy_path", "username"},
+    anyOf = {

Review comment:
       It should be `oneOf`.

##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       It seems that now we should claim the precedence between route config and metadata config, rather than the file and text.

##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       That's good enough 👍.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681361420



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       What about:
   ```
   local casbin_enforcer
   
   local function new_enforcer_if_need(conf)
       local model_path = conf.model_path
       local policy_path = conf.policy_path
   
       if model_path and policy_path then
           if not conf.casbin_enforcer then
               conf.casbin_enforcer = casbin:new(model_path, policy_path)
           end
           return true
       end
   
       local metadata = plugin.plugin_metadata(plugin_name)
       if not (metadata and metadata.value.model and metadata.value.policy) then
           return nil, "not enough configuration to create enforcer"
       end
   
       local modifiedIndex = metadata.modifiedIndex
       if not casbin_enforcer or casbin_enforcer.modifiedIndex ~= modifiedIndex then
           local model = metadata.value.model
           local policy = metadata.value.policy
           casbin_enforcer = casbin:newEnforcerFromText(model, policy)
           casbin_enforcer.modifiedIndex = modifiedIndex
       end
       return true
   end
   
   
   function _M.rewrite(conf, ctx)
       -- creates an enforcer when request sent for the first time
       local ok, err = new_enforcer_if_need(conf)
       if not ok then
           return 503, {message = err}
       end
   
       local path = ctx.var.uri
       local method = ctx.var.method
       local username = get_headers()[conf.username]
       if not username then username = "anonymous" end
   
       if conf.casbin_enforcer then
           if not conf.casbin_enforcer:enforce(username, path, method) then
               return 403, {message = "Access Denied"}
           end
       else
           if not casbin_enforcer:enforce(username, path, method) then
               return 403, {message = "Access Denied"}
           end
       end
   end
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679700831



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       @spacewander 
   Thanks for your review, so we will remove all the API functions and if the model/policy text in plugin metadata is updated (from the CP side), we will update the `casbin_enforcer` through that model/policy too. Is this correct?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] Yiyiyimu commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
Yiyiyimu commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680410641



##########
File path: .github/workflows/build.yml
##########
@@ -89,7 +89,7 @@ jobs:
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
       - name: Linux Get dependencies
-        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl
+        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev

Review comment:
       Hi @rushitote I think you need to also install pcre lib in chaos testing ci to make it pass, see [error log](https://github.com/apache/apisix/pull/4710/checks?check_run_id=3207414954#step:4:932). 
   You could add it before building docker image [here](https://github.com/apache/apisix/blob/master/.github/workflows/chaos.yml#L32)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680495886



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,205 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text

Review comment:
       Please specify the precedence between this one and the path way.

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,119 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    local casbin_enforcer = lrucache(plugin_name, metadata.modifiedIndex, new_enforcer, conf)
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then

Review comment:
       We don't need to check the existence for these three variables if we use them in the HTTP sub-system.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681654637



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       @spacewander What's your opinion? If the policy in plugin metadata is used to be the default one, we'd better to keep the configuration items consistent.

##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       @spacewander What's your opinion? If the policy in plugin metadata is used to be the default one, we'd better keep the configuration items consistent.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680767574



##########
File path: .github/workflows/build.yml
##########
@@ -89,7 +89,7 @@ jobs:
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
       - name: Linux Get dependencies
-        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl
+        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev

Review comment:
       @Yiyiyimu Thanks again, I have created a PR for that at [apisix-docker#202](https://github.com/apache/apisix-docker/pull/202) and added PCRE in the dependency installation doc.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679808989



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       I added a condition that if the schema changes, the `casbin_enforcer` will be recreated.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679700831



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       @spacewander 
   Thanks for your review, so we will remove all the API functions and if the model/policy text in plugin metadata is updated (from the CP side), we will update the `CasbinEnforcer` through that model/policy too. Is this correct?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r682209686



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       @rushitote @tokers 
   Yes, let's do it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680503986



##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,358 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: no username header passed
+--- request
+GET /hello
+--- error_code: 403
+--- response_body_like eval
+qr/"Access Denied"/
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: username passed but user not authorized
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: authorized user
+--- request
+GET /hello
+--- more_headers
+user: admin
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: authorized user (rbac)
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: unauthorized user before policy update

Review comment:
       Added the tests for enforcer from files.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680822076



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)

Review comment:
       IMHO, it surprises me that the enforcer from metadata can override the one from the route. Normally, the route level configuration can override the global one.

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       We don't need to bind the enforcer from metadata with the route configuration as it is global. Recreating the global enforcer when the request hits another route is not a good idea.

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"
+        conf.modifiedIndex = modifiedIndex
+    end
+
+    conf.casbin_enforcer = e
+end
+
+
+function _M.rewrite(conf)

Review comment:
       ```suggestion
   function _M.rewrite(conf, ctx)
   ```

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"
+        conf.modifiedIndex = modifiedIndex
+    end
+
+    conf.casbin_enforcer = e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if (not conf.casbin_enforcer) or
+    (conf.type == "metadata" and conf.modifiedIndex ~= metadata.modifiedIndex) then
+        new_enforcer(conf, metadata.modifiedIndex)
+    end
+
+    local path = ngx.var.request_uri

Review comment:
       ```suggestion
       local path = ctx.var.uri
   ```
   
   It is more suitable to use the normalized one? See http://nginx.org/en/docs/http/ngx_http_core_module.html#var_uri

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"
+        conf.modifiedIndex = modifiedIndex
+    end
+
+    conf.casbin_enforcer = e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if (not conf.casbin_enforcer) or
+    (conf.type == "metadata" and conf.modifiedIndex ~= metadata.modifiedIndex) then
+        new_enforcer(conf, metadata.modifiedIndex)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method

Review comment:
       ```suggestion
       local method = ctx.var.method
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681457803



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,115 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"
+        conf.modifiedIndex = modifiedIndex
+    end
+
+    conf.casbin_enforcer = e
+end
+
+
+function _M.rewrite(conf, ctx)
+    -- creates an enforcer when request sent for the first time
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if (not conf.casbin_enforcer) or
+    (conf.type == "metadata" and conf.modifiedIndex ~= metadata.modifiedIndex) then
+        new_enforcer(conf, metadata.modifiedIndex)
+    end
+
+    local path = ctx.var.uri
+    local method = ctx.var.method
+    local username = get_headers()[conf.username]

Review comment:
       ```suggestion
       local username = get_headers()[conf.username] or "anonymous"
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679713617



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       And I think you can submit a minimum viable pull request so that we can check & merge it early. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679808989



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       I added a condition that if the configuration changes, the `casbin_enforcer` will be recreated.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681595426



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,124 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then

Review comment:
       It is already there ([here](https://github.com/apache/apisix/pull/4710/files#diff-b18fc191c270634f4d1d599ad10a5dbb1d646ec9ff4c9f9e6224195e45a96a8bR33)). This was meant to check if the route will use the plugin metadata (instead of files) and hence it requires separate schema check.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r683384109



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       Replaced it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680892577



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       > > @spacewander so, should we create a new `casbin_enforcer` local variable specifically for global config? Then we can also have (if any) route enforcer override the global one.
   > 
   > Do you mean a new global variable?
   
   local, similar as that of the first commit ([here](https://github.com/apache/apisix/pull/4710/commits/cb37e5fdce3df9cd42f25fa1502a5001aa25a95c#diff-b18fc191c270634f4d1d599ad10a5dbb1d646ec9ff4c9f9e6224195e45a96a8bR25))




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander merged pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander merged pull request #4710:
URL: https://github.com/apache/apisix/pull/4710


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680483656



##########
File path: .github/workflows/build.yml
##########
@@ -89,7 +89,7 @@ jobs:
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
       - name: Linux Get dependencies
-        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl
+        run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev

Review comment:
       @Yiyiyimu Thanks for this, I have made the change.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680814955



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       > 
   > 
   > @spacewander Sorry, I found this now. But is there any reason for us to not use lrucache? It worked well for two different routes with different casbin enforcers when I tested it.
   
   It works fine, but makes the life cycle complex. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r683067882



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,250 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| model       | string | required    |         |       | The Casbin model configuration in text format.               |
+| policy      | string | required    |         |       | The Casbin policy in text format.                            |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify `model_path`, `policy_path` and `username` in plugin config or specify `model`, `policy` and `username` in the plugin config for the configuration to be valid. Or if you wish to use a global Casbin configuration, you can first specify `model` and `policy` in the plugin metadata and only `username` in the plugin configuration, all routes will use the plugin metadata configuration in this way.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+            "policy": "p, *, /, GET
+            p, admin, *, *
+            g, alice, admin",
+
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy text at your first request.
+
+### By using model/policy text using plugin metadata
+
+First, send a `PUT` request to add the model and policy text to the plugin's metadata using the Admin API. All routes configured in this way will use a single Casbin enforcer with plugin metadata configuration. You can also update the model/policy this way, the plugin will automatically update itself with the updated configuration.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/authz-casbin -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
+{
+"model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+"policy": "p, *, /, GET
+p, admin, *, *
+g, alice, admin"
+}'
+```
+
+Then add this plugin on a route by sending the following request. Note, there is no requirement for model/policy now.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+**NOTE**: The model/policy file paths have a higher precedence, hence if the model/policy file paths are present in the configuration the plugin will use the files accordingly.

Review comment:
       It seems that now we should claim the precedence between route config and metadata config, rather than the file and text.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r679712809



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,347 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local plugin_metadata = require("apisix.admin.plugin_metadata")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)
+    end
+
+    local path = ngx.var.request_uri
+    local method = ngx.var.request_method
+    local username = get_headers()[conf.username]
+    if not username then username = "anonymous" end
+
+    if path and method and username then
+        if not casbin_enforcer:enforce(username, path, method) then
+            return 403, {message = "Access Denied"}
+        end
+    else
+        return 403, {message = "Access Denied"}
+    end
+end
+
+
+local function save_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    if casbin_enforcer.type == "metadata" then
+        local metadata = plugin.plugin_metadata(plugin_name)
+        local conf = {
+            model = metadata.value.model,
+            policy = casbin_enforcer.model:savePolicyToText()
+        }
+
+        local ok, err = plugin_metadata.put(plugin_name, conf)
+        if not ok then
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        else
+            return 200
+        end
+    else
+        local _, err = pcall(function ()
+            casbin_enforcer:savePolicy()
+        end)
+        if not err then
+            return 200, {message = "Successfully saved policy."}
+        else
+            core.log.error("Save Policy error: " .. err)
+            return 400, {message = "Failed to save policy, see logs."}
+        end
+    end
+end
+
+
+local function add_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddPolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:AddGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully added grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function remove_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemovePolicy(subject, object, action) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:RemoveGroupingPolicy(user, role) then
+            local ok, _ = save_policy()
+            if ok == 400 then
+                return 400, {message = "Failed to save policy, see logs."}
+            end
+            return 200, {message = "Successfully removed grouping policy."}
+        else
+            return 400, {message = "Invalid policy request."}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function has_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local subject = headers["subject"]
+        local object = headers["object"]
+        local action = headers["action"]
+
+        if not subject or not object or not action then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasPolicy(subject, object, action) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    elseif type == "g" then
+        local user = headers["user"]
+        local role = headers["role"]
+
+        if not user or not role then
+            return 400, {message = "Invalid policy request."}
+        end
+
+        if casbin_enforcer:HasGroupingPolicy(user, role) then
+            return 200, {data = "true"}
+        else
+            return 200, {data = "false"}
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+local function get_policy()
+    if not casbin_enforcer then
+        return 400, {message = "Enforcer not created yet."}
+    end
+
+    local headers = get_headers()
+    local type = headers["type"]
+
+    if type == "p" then
+        local policy = casbin_enforcer:GetPolicy()
+        if policy then
+            return 200, {data = policy}
+        else
+            return 400
+        end
+    elseif type == "g" then
+        local groupingPolicy = casbin_enforcer:GetGroupingPolicy()
+        if groupingPolicy then
+            return 200, {data = groupingPolicy}
+        else
+            return 400
+        end
+    else
+        return 400, {message = "Invalid policy type."}
+    end
+end
+
+
+function _M.api()
+    return {

Review comment:
       Yes, you can use `metadata.modifiedIndex` to ensure casbin_enforcer is created with the latest model/policy, like this one:
   https://github.com/apache/apisix/blob/fd8f875429bd43462430bd662788940f6a579ff9/apisix/http/router/radixtree_uri.lua#L31-L36




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680880465



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       @spacewander so, should we create a new `casbin_enforcer` local variable specifically for global config? Then we can also have (if any) route enforcer override the global one.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#issuecomment-890943440


   > To make the misc checker pass, you need to update
   > 
   > https://github.com/apache/apisix/blob/9f01ef8f2ca753557289ac9dc91f5212432fae73/ci/ASF-Release.cfg#L59
   > 
   > 
   > To make the doc linter pass, you need to update https://github.com/apache/apisix/blob/master/docs/en/latest/config.json
   
   Thanks, changed it.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681651508



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       @tokers I think it is meant to be kind of global configuration for all routes. Should I add the support for using model/policy text from plugin conf?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680986533



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       LGTM. The only issue is that both new_enforcer and rewrite check if we need to create a new enforcer. I think we can just leave the check in one method.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681613073



##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,401 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: no username header passed
+--- request
+GET /hello
+--- error_code: 403
+--- response_body_like eval
+qr/"Access Denied"/
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: username passed but user not authorized
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: authorized user
+--- request
+GET /hello
+--- more_headers
+user: admin
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: authorized user (rbac)
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: unauthorized user before policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: update model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    p, jack, /hello, GET
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: authorized user after policy update
+--- request
+GET /hello
+--- more_headers
+user: jack
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: enable authz-casbin using model/policy files
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "model_path": "t/plugin/authz-casbin/model.conf",
+                            "policy_path": "t/plugin/authz-casbin/policy.csv",
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: authorized user as per policy
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: unauthorized user as per policy
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: disable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {},
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"

Review comment:
       Changed it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680597992



##########
File path: t/plugin/authz-casbin/policy.csv
##########
@@ -0,0 +1,3 @@
+p, *, /, GET
+p, admin, *, *
+g, alice, admin

Review comment:
       EOL symbol is required. Please check out the editor configuration.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680483896



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       Yes, you are right. I have changed it now.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680888412



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,118 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"

Review comment:
       > 
   > 
   > @spacewander so, should we create a new `casbin_enforcer` local variable specifically for global config? Then we can also have (if any) route enforcer override the global one.
   
   Do you mean a new global variable?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680769568



##########
File path: t/plugin/authz-casbin/policy.csv
##########
@@ -0,0 +1,3 @@
+p, *, /, GET
+p, admin, *, *
+g, alice, admin

Review comment:
       Added the EOL symbol in model/policy files.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680496501



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,205 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                            |
+| ----------- | ------ | ----------- | ------- | ----- | ---------------------------------------------------------------------- |
+| model       | string | required    |         |       | The Casbin model configuration in text format.                         |
+| policy      | string | required    |         |       | The Casbin policy in text format.                                      |
+
+## How To Enable
+
+You can enable the plugin on any route either by using the model/policy file paths or directly using the model/policy text.
+
+### By using file paths
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "plugins": {
+        "authz-casbin": {
+            "model_path": "/path/to/model.conf",
+            "policy_path": "/path/to/policy.csv",
+            "username": "user"
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1980": 1
+        },
+        "type": "roundrobin"
+    },
+    "uri": "/*"
+}'
+```
+
+This will create a Casbin enforcer from the model and policy files at your first request.
+
+### By using model/policy text

Review comment:
       Sure, I will make it more clear there.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681538755



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,115 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(conf, modifiedIndex)
+    local model_path = conf.model_path
+    local policy_path = conf.policy_path
+
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        conf.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy and not e then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        conf.type = "metadata"
+        conf.modifiedIndex = modifiedIndex
+    end
+
+    conf.casbin_enforcer = e
+end
+
+
+function _M.rewrite(conf, ctx)
+    -- creates an enforcer when request sent for the first time
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if (not conf.casbin_enforcer) or
+    (conf.type == "metadata" and conf.modifiedIndex ~= metadata.modifiedIndex) then
+        new_enforcer(conf, metadata.modifiedIndex)
+    end
+
+    local path = ctx.var.uri
+    local method = ctx.var.method
+    local username = get_headers()[conf.username]

Review comment:
       Thanks, added it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680453855



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       Multiple routes will use the same casbin enforcer? I think we should store different casbin via lrucache, like this:
   https://github.com/apache/apisix/blob/9db2dd2399c3970df1e1b7fa0f8b7dcd92d26b1c/apisix/plugins/error-log-logger.lua#L144




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680770800



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,123 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local casbin_enforcer
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        local ok, err = core.schema.check(metadata_schema, conf)
+        if ok then
+            casbin_enforcer = nil
+            return true
+        else
+            return false, err
+        end
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        casbin_enforcer = nil
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then
+            casbin_enforcer = nil
+            return true
+        end
+    end
+    return false, err
+end
+
+
+local function new_enforcer(model_path, policy_path)
+    local e
+
+    if model_path and policy_path then
+        e = casbin:new(model_path, policy_path)
+        e.type = "file"
+    end
+
+    local metadata = plugin.plugin_metadata(plugin_name)
+    if metadata and metadata.value.model and metadata.value.policy then
+        local model = metadata.value.model
+        local policy = metadata.value.policy
+        e = casbin:newEnforcerFromText(model, policy)
+        e.type = "metadata"
+    end
+
+    return e
+end
+
+
+function _M.rewrite(conf)
+    -- creates an enforcer when request sent for the first time
+    if not casbin_enforcer then
+        casbin_enforcer = new_enforcer(conf.model_path, conf.policy_path)

Review comment:
       I have removed `lrucache` and replaced it by binding the `casbin_enforcer` to conf. Also we will use the `modifiedIndex` to keep the casbin from metadata updated.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] rushitote commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
rushitote commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681651508



##########
File path: docs/en/latest/plugins/authz-casbin.md
##########
@@ -0,0 +1,207 @@
+---
+title: authz-casbin
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+-->
+
+## Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Metadata**](#metadata)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+## Name
+
+`authz-casbin` is an authorization plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This plugin supports powerful authorization scenarios based on various access control models.
+
+For detailed documentation on how to create model and policy, refer [Casbin](https://casbin.org/docs/en/supported-models).
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| model_path  | string | required    |         |       | The path of the Casbin model configuration file.             |
+| policy_path | string | required    |         |       | The path of the Casbin policy file.                          |
+| username    | string | required    |         |       | The header you will be using in request to pass the username (subject). |
+
+**NOTE**: You must either specify both the `model_path` and `policy_path` in plugin config or specify both the `model` and `policy` in the plugin metadata for the schema to be valid.
+
+## Metadata

Review comment:
       @tokers I think it is meant to be kind of global configuration for all routes, like you can update the metadata once and for all routes using it will get updated. Should I add the support for using model/policy text from plugin conf?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r681605842



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,124 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value and conf.username then

Review comment:
       got




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r680498708



##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,358 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files

Review comment:
       Test 4 already contains Test 3?

##########
File path: t/plugin/authz-casbin.t
##########
@@ -0,0 +1,358 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv",
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: username missing
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local conf = {
+                model_path = "/path/to/model.conf",
+                policy_path = "/path/to/policy.csv"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            else
+                ngx.say("done")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "username" is required
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: put model and policy text in metadata
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: Enforcer from text without files
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casbin")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_metadata/authz-casbin',
+                ngx.HTTP_PUT,
+                [[{
+                    "model": "[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.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
+
+                    "policy": "p, *, /, GET
+                    p, admin, *, *
+                    g, alice, admin"
+                }]]
+                )
+
+            local conf = {
+                username = "user"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: enable authz-casbin by Admin API
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "authz-casbin": {
+                            "username" : "user"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/*"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: no username header passed
+--- request
+GET /hello
+--- error_code: 403
+--- response_body_like eval
+qr/"Access Denied"/
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: username passed but user not authorized
+--- request
+GET /hello
+--- more_headers
+user: bob
+--- error_code: 403
+--- response_body
+{"message":"Access Denied"}
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: authorized user
+--- request
+GET /hello
+--- more_headers
+user: admin
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: authorized user (rbac)
+--- request
+GET /hello
+--- more_headers
+user: alice
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: unauthorized user before policy update

Review comment:
       Need a similar test for the policy_path configuration.

##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -0,0 +1,119 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You 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.
+--
+
+local casbin          = require("casbin")
+local core            = require("apisix.core")
+local plugin          = require("apisix.plugin")
+local ngx             = ngx
+local get_headers     = ngx.req.get_headers
+local lrucache        = core.lrucache.new({
+    ttl = 300, count = 32
+})
+
+local plugin_name = "authz-casbin"
+
+local schema = {
+    type = "object",
+    properties = {
+        model_path = { type = "string" },
+        policy_path = { type = "string" },
+        username = { type = "string"}
+    },
+    required = {"model_path", "policy_path", "username"},
+    additionalProperties = false
+}
+
+local metadata_schema = {
+    type = "object",
+    properties = {
+        model = {type = "string"},
+        policy = {type = "string"},
+    },
+    required = {"model", "policy"},
+    additionalProperties = false
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2560,
+    name = plugin_name,
+    schema = schema,
+    metadata_schema = metadata_schema
+}
+
+function _M.check_schema(conf, schema_type)
+    if schema_type == core.schema.TYPE_METADATA then
+        return core.schema.check(metadata_schema, conf)
+    end
+    local ok, err = core.schema.check(schema, conf)
+    if ok then
+        return true
+    else
+        local metadata = plugin.plugin_metadata(plugin_name)
+        if metadata and metadata.value.model and metadata.value.policy and conf.username then

Review comment:
       ```suggestion
           if metadata and metadata.value and conf.username then
   ```
   
   Since we have checked the metadata schema.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tokers commented on a change in pull request #4710: feat: Added authz-casbin plugin and doc and tests for it

Posted by GitBox <gi...@apache.org>.
tokers commented on a change in pull request #4710:
URL: https://github.com/apache/apisix/pull/4710#discussion_r682511353



##########
File path: apisix/plugins/authz-casbin.lua
##########
@@ -28,9 +28,14 @@ local schema = {
     properties = {
         model_path = { type = "string" },
         policy_path = { type = "string" },
+        model = { type = "string" },
+        policy = { type = "string" },
         username = { type = "string"}
     },
-    required = {"model_path", "policy_path", "username"},
+    anyOf = {

Review comment:
       It should be `oneOf`.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org