This is an automated email from the ASF dual-hosted git repository.

wenming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 5fff97d  feature: Add wolf rbac plugin (#1095)
5fff97d is described below

commit 5fff97d0b1650dbcc0a0761b83e5e9419ea6d79f
Author: iGeeky <igeeky...@gmail.com>
AuthorDate: Thu Feb 6 15:22:49 2020 +0800

    feature: Add wolf rbac plugin (#1095)
---
 .travis/ASF-Release.cfg           |   1 -
 conf/config.yaml                  |   1 +
 doc/images/plugin/wolf-rbac-1.png | Bin 0 -> 79987 bytes
 doc/images/plugin/wolf-rbac-2.png | Bin 0 -> 85769 bytes
 doc/plugins/wolf-rbac-cn.md       | 207 ++++++++++++++++++++
 doc/plugins/wolf-rbac.md          | 209 +++++++++++++++++++++
 lua/apisix/plugins/wolf-rbac.lua  | 386 ++++++++++++++++++++++++++++++++++++++
 t/admin/plugins.t                 |   2 +-
 t/debug/debug-mode.t              |   1 +
 t/lib/server.lua                  |  58 +++++-
 t/plugin/wolf-rbac.t              | 334 +++++++++++++++++++++++++++++++++
 11 files changed, 1191 insertions(+), 8 deletions(-)

diff --git a/.travis/ASF-Release.cfg b/.travis/ASF-Release.cfg
index e967bdc..703a361 100644
--- a/.travis/ASF-Release.cfg
+++ b/.travis/ASF-Release.cfg
@@ -87,7 +87,6 @@ grpc_server_example
 .travis.yml
 grpcurl
 t/servroot
-grpcurl
 
 conf
 .travis/openwhisk-utilities
diff --git a/conf/config.yaml b/conf/config.yaml
index 274a8cf..57efc2a 100644
--- a/conf/config.yaml
+++ b/conf/config.yaml
@@ -96,6 +96,7 @@ plugins:                          # plugin list
   - response-rewrite
   - fault-injection
   - udp-logger
+  - wolf-rbac
 
 stream_plugins:
   - mqtt-proxy
diff --git a/doc/images/plugin/wolf-rbac-1.png 
b/doc/images/plugin/wolf-rbac-1.png
new file mode 100644
index 0000000..ce888d8
Binary files /dev/null and b/doc/images/plugin/wolf-rbac-1.png differ
diff --git a/doc/images/plugin/wolf-rbac-2.png 
b/doc/images/plugin/wolf-rbac-2.png
new file mode 100644
index 0000000..e1223c9
Binary files /dev/null and b/doc/images/plugin/wolf-rbac-2.png differ
diff --git a/doc/plugins/wolf-rbac-cn.md b/doc/plugins/wolf-rbac-cn.md
new file mode 100644
index 0000000..37c9dd7
--- /dev/null
+++ b/doc/plugins/wolf-rbac-cn.md
@@ -0,0 +1,207 @@
+<!--
+#
+# 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.
+#
+-->
+
+[English](wolf-rbac.md)
+
+# 目录
+
+- [**名字**](#名字)
+- [**属性**](#属性)
+- [**依赖项**](#依赖项)
+- [**如何启用**](#如何启用)
+- [**测试插件**](#测试插件)
+- [**禁用插件**](#禁用插件)
+
+## 名字
+
+`wolf-rbac` 是一个认证及授权(rbac)插件,它需要与 `consumer` 一起配合才能工作。同时需要添加 `wolf-rbac` 到一个 
`service` 或 `route` 中。
+rbac功能由[wolf](https://github.com/iGeeky/wolf)提供, 有关 `wolf` 的更多信息, 
请参考[wolf文档](https://github.com/iGeeky/wolf)。
+
+
+## 属性
+
+* `server`: 设置 `wolf-server` 的访问地址, 如果未设置, 默认为: `http://127.0.0.1:10080`.
+* `appid`: 设置应用id, 该应用id, 需要是在 `wolf-console` 中已经添加的应用id.
+
+
+## 依赖项
+
+### 安装 wolf, 并启动服务
+
+[Wolf快速起步](https://github.com/iGeeky/wolf/blob/master/quick-start-with-docker/README-CN.md)
+
+### 添加应用, 管理员, 普通用户, 权限, 资源 及给用户授权.
+
+[Wolf管理使用](https://github.com/iGeeky/wolf/blob/master/docs/usage.md)
+
+
+## 如何启用
+
+1. 创建一个 consumer 对象,并设置插件 `wolf-rbac` 的值。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -X PUT -d '
+{
+  "username":"wolf_rbac",
+  "plugins":{
+    "wolf-rbac":{
+      "server":"http://127.0.0.1:10080";,
+      "appid":"restful"
+    }
+  },
+  "desc":"wolf-rbac"
+}'
+```
+
+你可以使用浏览器打开 dashboard:`http://127.0.0.1:9080/apisix/dashboard/`,通过 web 
界面来完成上面的操作,先增加一个 consumer:
+![](../images/plugin/wolf-rbac-1.png)
+
+然后在 consumer 页面中添加 wolf-rbac 插件:
+![](../images/plugin/wolf-rbac-2.png)
+
+注意: 上面填写的 `appid` 需要在wolf控制台中已经存在的.
+
+2. 创建 Route 或 Service 对象,并开启 `wolf-rbac` 插件。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/*",
+    "plugins": {
+        "wolf-rbac": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "www.baidu.com:80": 1
+        }
+    }
+}'
+```
+
+## 测试插件
+
+#### 首先进行登录获取 `wolf-rbac` token:
+
+下面的 `appid`, `username`, `password` 必须为wolf系统中真实存在的.
+
+* 以POST application/json方式登陆.
+
+```shell
+curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \
+-H "Content-Type: application/json" \
+-d '{"appid": "restful", "username":"test", "password":"user-password"}'
+
+HTTP/1.1 200 OK
+Date: Wed, 24 Jul 2019 10:33:31 GMT
+Content-Type: text/plain
+Transfer-Encoding: chunked
+Connection: keep-alive
+Server: APISIX web server
+{"rbac_token":"V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts","user_info":{"nickname":"test","username":"test","id":"749"}}
+```
+
+* 以POST x-www-form-urlencoded方式登陆
+
+```shell
+curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \
+-H "Content-Type: application/x-www-form-urlencoded" \
+-d 'appid=restful&username=test&password=user-password'
+```
+
+
+#### 使用获取到的 token 进行请求尝试
+
+* 缺少 token
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" -i
+
+HTTP/1.1 401 Unauthorized
+...
+{"message":"Missing rbac token in request"}
+```
+
+* token 放到请求头(Authorization)中:
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \
+-H 'Authorization: 
V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -i
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* token 放到请求头(x-rbac-token)中:
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \
+-H 'x-rbac-token: 
V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* token 放到请求参数中:
+
+```shell
+curl 
'http://127.0.0.1:9080?rbac_token=V1%23restful%23eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -H"Host: www.baidu.com" -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* token 放到 cookie 中:
+
+```shell
+curl http://127.0.0.1:9080 -H"Host: www.baidu.com" \
+--cookie 
x-rbac-token=V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts
 -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+## 禁用插件
+
+当你想去掉 `rbac-wolf` 插件的时候,很简单,在routes中的插件配置中把对应的 `插件` 配置删除即可,无须重启服务,即刻生效:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/*",
+    "plugins": {
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "www.baidu.com:80": 1
+        }
+    }
+}'
+```
+
diff --git a/doc/plugins/wolf-rbac.md b/doc/plugins/wolf-rbac.md
new file mode 100644
index 0000000..639070b
--- /dev/null
+++ b/doc/plugins/wolf-rbac.md
@@ -0,0 +1,209 @@
+<!--
+#
+# 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.
+#
+-->
+
+[中文](wolf-rbac-cn.md)
+
+# Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**Dependencies**](#dependencies)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+`wolf-rbac` is an authentication and authorization (rbac) plugin. It needs to 
work with `consumer`. Also need to add `wolf-rbac` to a` service` or `route`.
+The rbac feature is provided by [wolf] (https://github.com/iGeeky/wolf). For 
more information about `wolf`, please refer to [wolf documentation] 
(https://github.com/iGeeky/wolf).
+
+
+## Attributes
+
+* `server`: Set the service address of` wolf-server`. If not set, the default 
is: `http://127.0.0.1:10080`.
+* `appid`: Set the app id. The app id must be added in wolf-console.
+
+
+## Dependencies
+
+### Install wolf and start the service
+
+[Wolf quick 
start](https://github.com/iGeeky/wolf/blob/master/quick-start-with-docker/README.md)
+
+### Add `application`,` admin`, `normal user`,` permission`, `resource` and 
user authorize
+
+[Wolf-console usage](https://github.com/iGeeky/wolf/blob/master/docs/usage.md)
+
+
+## How To Enable
+
+1. set a consumer and config the value of the `wolf-rbac`。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -X PUT -d '
+{
+  "username":"wolf_rbac",
+  "plugins":{
+    "wolf-rbac":{
+      "server":"http://127.0.0.1:10080";,
+      "appid":"restful"
+    }
+  },
+  "desc":"wolf-rbac"
+}'
+```
+
+You can visit the dashboard: `http://127.0.0.1:9080/apisix/dashboard/`, to 
complete the above operations through the web interface, first add a consumer:
+![](../images/plugin/wolf-rbac-1.png)
+
+Then add the wolf-rbac plugin to the consumer page:
+![](../images/plugin/wolf-rbac-2.png)
+
+Notes: The `appid` filled in above needs to already exist in the wolf system.
+
+1. Add a `Route` or `Service` and enable the wolf-rbac plugin.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/*",
+    "plugins": {
+        "wolf-rbac": {}
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "www.baidu.com:80": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+#### Login and get `wolf-rbac` token:
+
+The following `appid`,` username`, and `password` must be real ones in the 
wolf system.
+
+* Login as `POST application/json`
+
+```shell
+curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \
+-H "Content-Type: application/json" \
+-d '{"appid": "restful", "username":"test", "password":"user-password"}'
+
+HTTP/1.1 200 OK
+Date: Wed, 24 Jul 2019 10:33:31 GMT
+Content-Type: text/plain
+Transfer-Encoding: chunked
+Connection: keep-alive
+Server: APISIX web server
+{"rbac_token":"V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts","user_info":{"nickname":"test","username":"test","id":"749"}}
+```
+
+* Login as `POST x-www-form-urlencoded`
+
+```shell
+curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \
+-H "Content-Type: application/x-www-form-urlencoded" \
+-d 'appid=restful&username=test&password=user-password'
+```
+
+
+#### try request with token
+
+* without token
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" -i
+
+HTTP/1.1 401 Unauthorized
+...
+{"message":"Missing rbac token in request"}
+```
+
+* request header(Authorization) with token:
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \
+-H 'Authorization: 
V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -i
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* request header(x-rbac-token) with token:
+
+```shell
+curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \
+-H 'x-rbac-token: 
V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* request params with token:
+
+```shell
+curl 
'http://127.0.0.1:9080?rbac_token=V1%23restful%23eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts'
 -H"Host: www.baidu.com" -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+* request cookie with token:
+
+```shell
+curl http://127.0.0.1:9080 -H"Host: www.baidu.com" \
+--cookie 
x-rbac-token=V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts
 -i
+
+
+HTTP/1.1 200 OK
+
+<!DOCTYPE html>
+```
+
+## Disable Plugin
+
+When you want to disable the `wolf-rbac` plugin, it is very simple,
+ you can delete the corresponding json configuration in the plugin 
configuration,
+  no need to restart the service, it will take effect immediately:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/*",
+    "plugins": {
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "www.baidu.com:80": 1
+        }
+    }
+}'
+```
+
diff --git a/lua/apisix/plugins/wolf-rbac.lua b/lua/apisix/plugins/wolf-rbac.lua
new file mode 100644
index 0000000..b38501e
--- /dev/null
+++ b/lua/apisix/plugins/wolf-rbac.lua
@@ -0,0 +1,386 @@
+--
+-- 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 core     = require("apisix.core")
+local consumer = require("apisix.consumer")
+local json     = require("apisix.core.json")
+local ngx_re = require("ngx.re")
+local http     = require("resty.http")
+local ipairs   = ipairs
+local ngx      = ngx
+local tostring = tostring
+local rawget   = rawget
+local rawset   = rawset
+local setmetatable = setmetatable
+local type     = type
+local string   = string
+
+local plugin_name = "wolf-rbac"
+
+
+local schema = {
+    type = "object",
+    properties = {
+        appid = {
+            type = "string",
+            default = "unset"
+        },
+        server = {
+            type = "string",
+            default = "http://127.0.0.1:10080";
+        },
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2555,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema,
+}
+
+
+local create_consume_cache
+do
+    local consumer_ids = {}
+
+    function create_consume_cache(consumers)
+        core.table.clear(consumer_ids)
+
+        for _, consumer in ipairs(consumers.nodes) do
+            core.log.info("consumer node: ", core.json.delay_encode(consumer))
+            consumer_ids[consumer.auth_conf.appid] = consumer
+        end
+
+        return consumer_ids
+    end
+
+end -- do
+
+local token_version = 'V1'
+local function create_rbac_token(appid, wolf_token)
+    return token_version .. "#" .. appid .. "#" .. wolf_token
+end
+
+local function parse_rbac_token(rbac_token)
+    local res, err = ngx_re.split(rbac_token, "#", nil, nil, 3)
+    if not res then
+        return nil, err
+    end
+
+    if #res ~= 3 or res[1] ~= token_version then
+        return nil, 'invalid rbac token: version'
+    end
+    local appid = res[2]
+    local wolf_token = res[3]
+
+    return {appid = appid, wolf_token = wolf_token}
+end
+
+local function new_headers()
+    local t = {}
+    local lt = {}
+    local _mt = {
+        __index = function(t, k)
+            return rawget(lt, string.lower(k))
+        end,
+        __newindex = function(t, k, v)
+            rawset(t, k, v)
+            rawset(lt, string.lower(k), v)
+        end,
+     }
+    return setmetatable(t, _mt)
+end
+
+-- timeout in ms
+local function http_req(method, uri, body, myheaders, timeout)
+    if myheaders == nil then myheaders = new_headers() end
+
+    local httpc = http.new()
+    if timeout then
+        httpc:set_timeout(timeout)
+    end
+
+    local params = {method = method, headers = myheaders, body = body, 
ssl_verify = false}
+    local res, err = httpc:request_uri(uri, params)
+    if err then
+        core.log.error("FAIL REQUEST [ ",core.json.delay_encode(
+            {method = method, uri = uri, body = body, headers = myheaders}),
+            " ] failed! res is nil, err:", err)
+        return nil, err
+    end
+
+    return res
+end
+
+local function http_get(uri, myheaders, timeout)
+    return http_req("GET", uri, nil, myheaders, timeout)
+end
+
+local function http_post(uri, body, myheaders, timeout)
+    return http_req("POST", uri, body, myheaders, timeout)
+end
+
+function _M.check_schema(conf)
+    core.log.info("input conf: ", core.json.delay_encode(conf))
+
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then
+        return false, err
+    end
+
+    return true
+end
+
+
+local function fetch_rbac_token(ctx)
+    if ctx.var.arg_rbac_token then
+        return ngx.unescape_uri(ctx.var.arg_rbac_token)
+    end
+
+    if ctx.var.http_authorization then
+        return ctx.var.http_authorization
+    end
+
+    if ctx.var.http_x_rbac_token then
+        return ctx.var.http_x_rbac_token
+    end
+
+    return ctx.var['cookie_x-rbac-token']
+end
+
+
+local function check_url_permission(server, appid, action, resName, clientIP, 
wolf_token)
+    local retry_max = 3
+    local errmsg
+    local userInfo
+    local res
+    local err
+    local access_check_url = server .. "/wolf/rbac/access_check"
+    local headers = new_headers()
+    headers["x-rbac-token"] = wolf_token
+    headers["Content-Type"] = "application/json; charset=utf-8"
+    local args = { appID = appid, resName = resName, action = action, clientIP 
= clientIP}
+    local url = access_check_url .. "?" .. ngx.encode_args(args)
+    local timeout = 1000 * 10
+
+    for i = 1, retry_max do
+        -- TODO: read apisix info.
+        res, err = http_get(url, headers, timeout)
+        if err then
+            break
+        else
+            core.log.info("check permission request:", url, ", status:", 
res.status,
+                            ",body:", core.json.delay_encode(res.body))
+            if res.status < 500 then
+                break
+            else
+                core.log.info("request [curl -v ", url, "] failed! status:", 
res.status)
+                if i < retry_max then
+                    ngx.sleep(0.1)
+                end
+            end
+        end
+    end
+
+    if err then
+        core.log.error("fail request: ", url, ", err:", err)
+        return {status = 500, err = "request to wolf-server failed, err:" .. 
tostring(err)}
+    end
+
+    if res.status ~= 200 and res.status ~= 401 then
+        return {status = 500, err = 'request to wolf-server failed, status:' 
.. tostring(res.status)}
+    end
+
+    local body, err = json.decode(res.body)
+    if err then
+        errmsg = 'check permission failed! parse response json failed!'
+        core.log.error( "json.decode(", res.body, ") failed! err:", err)
+        return {status = res.status, err = errmsg}
+    else
+        if body.data then
+            userInfo = body.data.userInfo
+        end
+        errmsg = body.reason
+        return {status = res.status, err = errmsg, userInfo = userInfo}
+    end
+end
+
+
+function _M.rewrite(conf, ctx)
+    local url = ctx.var.uri
+    local action = ctx.var.request_method
+    local clientIP = core.request.get_ip(ctx)
+    local permItem = {action = action, url = url, clientIP = clientIP}
+    core.log.info("hit wolf-rbac rewrite")
+
+    local rbac_token = fetch_rbac_token(ctx)
+    if rbac_token == nil then
+        core.log.info("no permission to access ", 
core.json.delay_encode(permItem), ", need login!")
+        return 401, {message = "Missing rbac token in request"}
+    end
+
+    local tokenInfo, err = parse_rbac_token(rbac_token)
+    core.log.info("token info: ", core.json.delay_encode(tokenInfo), ", err: 
", err)
+    if err then
+        return 401, {message = 'invalid rbac token: parse failed'}
+    end
+
+    local appid = tokenInfo.appid
+    local wolf_token = tokenInfo.wolf_token
+    permItem.appid = appid
+    permItem.wolf_token = wolf_token
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return 401, {message = "Missing related consumer"}
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consume_cache, consumer_conf)
+
+    core.log.info("------ consumers: ", core.json.delay_encode(consumers))
+    local consumer = consumers[appid]
+    if not consumer then
+        core.log.error("consumer [", appid, "] not found")
+        return 401, {message = "Invalid appid in rbac token"}
+    end
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+    local server = consumer.auth_conf.server
+
+    local url = ctx.var.uri
+    local action = ctx.var.request_method
+    local clientIP = core.request.get_ip(ctx)
+    local permItem = {appid = appid, action = action, url = url, clientIP = 
clientIP, wolf_token = wolf_token}
+
+    local res = check_url_permission(server, appid, action, url, clientIP, 
wolf_token)
+    core.log.info(" check_url_permission(", core.json.delay_encode(permItem), 
") res: ",core.json.delay_encode(res))
+
+    local username = nil
+    local nickname = nil
+    if type(res.userInfo) == 'table' then
+        local userInfo = res.userInfo
+        core.response.set_header("X-UserId", userInfo.id)
+        core.response.set_header("X-Username", userInfo.username)
+        core.response.set_header("X-Nickname", 
ngx.escape_uri(userInfo.nickname) or userInfo.username)
+        ctx.userInfo = userInfo
+        username = userInfo.username
+        nickname = userInfo.nickname
+    end
+
+    if res.status ~= 200 then
+        -- no permission.
+        core.log.error(" check_url_permission(", 
core.json.delay_encode(permItem),
+            ") failed, res: ",core.json.delay_encode(res))
+        return 401, {message = res.err, username = username, nickname = 
nickname}
+    end
+    core.log.info("wolf-rbac check permission passed")
+end
+
+local function get_args()
+    local ctx = ngx.ctx.api_ctx
+    local args, err
+    ngx.req.read_body()
+    if string.find(ctx.var.http_content_type or "","application/json", 1, 
true) then
+        args, err = json.decode(ngx.req.get_body_data())
+        if err then
+            core.log.error("json.decode(", ngx.req.get_body_data(), ") failed! 
", err)
+        end
+    else
+        args = ngx.req.get_post_args()
+    end
+    return args
+end
+
+local function login()
+    local args = get_args()
+    if not args then
+        return core.response.exit(400, {message = "invalid request"})
+    end
+    if not args.appid then
+        return core.response.exit(400, {message = "appid is missing"})
+    end
+
+    local appid = args.appid
+
+    local consumer_conf = consumer.plugin(plugin_name)
+    if not consumer_conf then
+        return core.response.exit(500)
+    end
+
+    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
+            consumer_conf.conf_version,
+            create_consume_cache, consumer_conf)
+
+    core.log.info("------ consumers: ", core.json.delay_encode(consumers))
+    local consumer = consumers[appid]
+    if not consumer then
+        core.log.info("request appid [", appid, "] not found")
+        return core.response.exit(400, {message = "appid [" .. tostring(appid) 
.. "] not found"})
+    end
+
+    core.log.info("consumer: ", core.json.delay_encode(consumer))
+
+    local uri = consumer.auth_conf.server .. '/wolf/rbac/login.rest'
+    local headers = new_headers()
+    headers["Content-Type"] = "application/json; charset=utf-8"
+    local timeout = 1000 * 5
+    local request_debug = core.json.delay_encode(
+        {method = 'POST', uri = uri, body = args, headers = headers,timeout = 
timeout})
+    core.log.info("login request [", request_debug, "] ....")
+    local res, err = http_post(uri, core.json.encode(args), headers, timeout)
+    if err or not res then
+        core.log.error("login request [", request_debug, "] failed! err: ", 
err)
+        return core.response.exit(500, {message = "request to wolf-server 
failed! " .. tostring(err)})
+    end
+    core.log.info("login request [", request_debug, "] status: ", res.status, 
", body: ", res.body)
+
+    if res.status ~= 200 then
+        core.log.error("login request [", request_debug, "] failed! status: ", 
res.status)
+        return core.response.exit(500, {message = "request to wolf-server 
failed! status:" .. tostring(res.status) })
+    end
+    local body, err = json.decode(res.body)
+    if err or not body then
+        core.log.error("login request [", request_debug, "] failed! err:", err)
+        return core.response.exit(500, {message = "request to wolf-server 
failed!"})
+    end
+    if not body.ok then
+        core.log.error("user login [", request_debug, "] failed! response 
body:", core.json.delay_encode(body))
+        return core.response.exit(200, {message = body.reason})
+    end
+    core.log.info("user login [", request_debug, "] success! response body:", 
core.json.delay_encode(body))
+
+    local userInfo = body.data.userInfo
+    local wolf_token = body.data.token
+
+    local rbac_token = create_rbac_token(appid, wolf_token)
+    core.response.exit(200, {rbac_token = rbac_token, user_info = userInfo})
+end
+
+function _M.api()
+    return {
+        {
+            methods = {"POST"},
+            uri = "/apisix/plugin/wolf-rbac/login",
+            handler = login,
+        }
+    }
+end
+
+return _M
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index b36a491..031eaff 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -30,6 +30,6 @@ __DATA__
 --- request
 GET /apisix/admin/plugins/list
 --- response_body_like eval
-qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection",["udp-logger"]\]/
+qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac"\]/
 --- no_error_log
 [error]
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 23b6e90..b069a23 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -59,6 +59,7 @@ loaded plugin and sort by priority: 11000 name: 
fault-injection
 loaded plugin and sort by priority: 10000 name: serverless-pre-function
 loaded plugin and sort by priority: 3000 name: ip-restriction
 loaded plugin and sort by priority: 2599 name: openid-connect
+loaded plugin and sort by priority: 2555 name: wolf-rbac
 loaded plugin and sort by priority: 2520 name: basic-auth
 loaded plugin and sort by priority: 2510 name: jwt-auth
 loaded plugin and sort by priority: 2500 name: key-auth
diff --git a/t/lib/server.lua b/t/lib/server.lua
index 60f57d9..a843158 100644
--- a/t/lib/server.lua
+++ b/t/lib/server.lua
@@ -15,6 +15,7 @@
 -- limitations under the License.
 --
 local json_decode = require("cjson").decode
+local json_encode = require("cjson").encode
 
 local _M = {}
 
@@ -31,6 +32,9 @@ end
 function _M.server_port()
     ngx.print(ngx.var.server_port)
 end
+_M.server_port_route2 = _M.server_port
+_M.server_port_hello = _M.server_port
+_M.server_port_aa = _M.server_port
 
 
 function _M.limit_conn()
@@ -69,6 +73,8 @@ function _M.uri()
         ngx.say(k, ": ", v)
     end
 end
+_M.uri_plugin_proxy_rewrite = _M.uri
+_M.uri_plugin_proxy_rewrite_args = _M.uri
 
 function _M.old_uri()
     -- ngx.sleep(1)
@@ -112,6 +118,50 @@ function _M.mock_zipkin()
     end
 end
 
+function _M.wolf_rbac_login_rest()
+    ngx.req.read_body()
+    local data = ngx.req.get_body_data()
+    local args = json_decode(data)
+    if not args.username then
+        ngx.say(json_encode({ok=false, reason="ERR_USERNAME_MISSING"}))
+        ngx.exit(0)
+    end
+    if not args.password then
+        ngx.say(json_encode({ok=false, reason="ERR_PASSWORD_MISSING"}))
+        ngx.exit(0)
+    end
+    if args.username ~= "admin" then
+        ngx.say(json_encode({ok=false, reason="ERR_USER_NOT_FOUND"}))
+        ngx.exit(0)
+    end
+    if args.password ~= "123456" then
+        ngx.say(json_encode({ok=false, reason="ERR_PASSWORD_ERROR"}))
+        ngx.exit(0)
+    end
+
+    ngx.say(json_encode({ok=true, data={token="wolf-rbac-token",
+        userInfo={nickname="administrator",username="admin", id="100"}}}))
+end
+
+function _M.wolf_rbac_access_check()
+    local headers = ngx.req.get_headers()
+    local token = headers['x-rbac-token']
+    if token ~= 'wolf-rbac-token' then
+        ngx.say(json_encode({ok=false, reason="ERR_TOKEN_INVALID"}))
+        ngx.exit(0)
+    end
+
+    local args = ngx.req.get_uri_args()
+    local resName = args.resName
+    if resName == '/hello' then
+        ngx.say(json_encode({ok=true, data={ 
userInfo={nickname="administrator",username="admin", id="100"} }}))
+    else
+        ngx.status = 401
+        ngx.say(json_encode({ok=false, reason="no permission to access"}))
+    end
+end
+
+
 function _M.websocket_handshake()
     local websocket = require "resty.websocket.server"
     local wb, err = websocket:new()
@@ -120,15 +170,11 @@ function _M.websocket_handshake()
         return ngx.exit(400)
     end
 end
-
+_M.websocket_handshake_route = _M.websocket_handshake
 
 function _M.go()
     local action = string.sub(ngx.var.uri, 2)
-    local find = string.find(action, "/", 1, true)
-    if find then
-        action = string.sub(action, 1, find - 1)
-    end
-
+    action = string.gsub(action, "[/\\.]", "_")
     if not action or not _M[action] then
         return ngx.exit(404)
     end
diff --git a/t/plugin/wolf-rbac.t b/t/plugin/wolf-rbac.t
new file mode 100644
index 0000000..e90e307
--- /dev/null
+++ b/t/plugin/wolf-rbac.t
@@ -0,0 +1,334 @@
+#
+# 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();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.wolf-rbac")
+            local conf = {
+
+            }
+
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say(require("cjson").encode(conf))
+        }
+    }
+--- request
+GET /t
+--- response_body_like eval
+qr/{"appid":"unset","server":"http:\\\/\\\/127\.0\.0\.1:10080"}/
+--- no_error_log
+[error]
+
+=== TEST 2: wrong type of string
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.wolf-rbac")
+            local ok, err = plugin.check_schema({appid = 123})
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "appid" validation failed: wrong type: expected string, got number
+done
+--- no_error_log
+[error]
+
+
+=== TEST 3: add consumer with username and plugins
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "wolf_rbac_unit_test",
+                    "plugins": {
+                        "wolf-rbac": {
+                            "appid": "wolf-rbac-app",
+                            "server": "http://127.0.0.1:1982";
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "wolf_rbac_unit_test",
+                            "plugins": {
+                                "wolf-rbac": {
+                                    "appid": "wolf-rbac-app",
+                                    "server": "http://127.0.0.1:1982";
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+=== TEST 4: enable wolf rbac plugin using 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": {
+                        "wolf-rbac": {}
+                    },
+                    "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 5: login failed, appid is missing
+--- request
+POST /apisix/plugin/wolf-rbac/login
+username=admin&password=123456
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 400
+--- response_body_like eval
+qr/appid is missing/
+--- no_error_log
+[error]
+
+
+=== TEST 6: login failed, appid not found
+--- request
+POST /apisix/plugin/wolf-rbac/login
+appid=not-found&username=admin&password=123456
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 400
+--- response_body_like eval
+qr/appid \[not-found\] not found/
+--- no_error_log
+[error]
+
+
+=== TEST 7: login failed, username missing
+--- request
+POST /apisix/plugin/wolf-rbac/login
+appid=wolf-rbac-app&password=123456
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 200
+--- response_body_like eval
+qr/ERR_USERNAME_MISSING/
+
+
+=== TEST 8: login failed, password missing
+--- request
+POST /apisix/plugin/wolf-rbac/login
+appid=wolf-rbac-app&username=admin
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 200
+--- response_body_like eval
+qr/ERR_PASSWORD_MISSING/
+
+
+=== TEST 9: login failed, username not found
+--- request
+POST /apisix/plugin/wolf-rbac/login
+appid=wolf-rbac-app&username=not-found&password=123456
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 200
+--- response_body_like eval
+qr/ERR_USER_NOT_FOUND/
+
+
+=== TEST 10: login failed, wrong password
+--- request
+POST /apisix/plugin/wolf-rbac/login
+appid=wolf-rbac-app&username=admin&password=wrong-password
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- error_code: 200
+--- response_body_like eval
+qr/ERR_PASSWORD_ERROR/
+
+
+=== TEST 11: login successfully
+--- request
+POST /apisix/plugin/wolf-rbac/login
+{"appid": "wolf-rbac-app", "username": "admin","password": "123456"}
+--- more_headers
+Content-Type: application/json
+--- error_code: 200
+--- response_body_like eval
+qr/{"rbac_token":"V1#wolf-rbac-app#wolf-rbac-token","user_info":{"nickname":"administrator","username":"admin","id":"100"}}/
+--- no_error_log
+[error]
+
+
+
+=== TEST 21: verify, missing token
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"Missing rbac token in request"}
+--- no_error_log
+[error]
+
+
+=== TEST 22: verify: invalid rbac token
+--- request
+GET /hello
+--- error_code: 401
+--- more_headers
+x-rbac-token: invalid-rbac-token
+--- response_body
+{"message":"invalid rbac token: parse failed"}
+--- no_error_log
+[error]
+
+
+=== TEST 23: verify: invalid appid in rbac token
+--- request
+GET /hello
+--- error_code: 401
+--- more_headers
+x-rbac-token: V1#invalid-appid#rbac-token
+--- response_body
+{"message":"Invalid appid in rbac token"}
+
+
+=== TEST 24: verify: failed
+--- request
+GET /hello1
+--- error_code: 401
+--- more_headers
+x-rbac-token: V1#wolf-rbac-app#wolf-rbac-token
+--- response_body
+{"message":"no permission to access"}
+
+
+=== TEST 25: verify (in argument)
+--- request
+GET /hello?rbac_token=V1%23wolf-rbac-app%23wolf-rbac-token
+--- response_headers
+X-UserId: 100
+X-Username: admin
+X-Nickname: administrator
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+=== TEST 26: verify (in header Authorization)
+--- request
+GET /hello
+--- more_headers
+Authorization: V1#wolf-rbac-app#wolf-rbac-token
+--- response_headers
+X-UserId: 100
+X-Username: admin
+X-Nickname: administrator
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+=== TEST 27: verify (in header x-rbac-token)
+--- request
+GET /hello
+--- more_headers
+x-rbac-token: V1#wolf-rbac-app#wolf-rbac-token
+--- response_headers
+X-UserId: 100
+X-Username: admin
+X-Nickname: administrator
+--- response_body
+hello world
+--- no_error_log
+[error]
+
+
+=== TEST 28: verify (in cookie)
+--- request
+GET /hello
+--- more_headers
+Cookie: x-rbac-token=V1#wolf-rbac-app#wolf-rbac-token
+--- response_headers
+X-UserId: 100
+X-Username: admin
+X-Nickname: administrator
+--- response_body
+hello world
+--- no_error_log
+[error]

Reply via email to