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 2022/02/19 05:52:28 UTC

[GitHub] [apisix] ComradeProgrammer opened a new pull request #6382: feat: add authz plugin for casdoor

ComradeProgrammer opened a new pull request #6382:
URL: https://github.com/apache/apisix/pull/6382


   ### 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. -->
   
   ### Pre-submission checklist:
   
   <!--
   Please follow the PR manners:
   1. Use Draft if the PR is not ready to be reviewed
   2. Test is required for the feat/fix PR, unless you have a good reason
   3. Doc is required for the feat PR
   4. Use a new commit to resolve review instead of `push -f`
   5. If you need to resolve merge conflicts after the PR is reviewed, please merge master but do not rebase
   6. Use "request review" to notify the reviewer once you have resolved the review
   7. Only reviewer can click "Resolve conversation" to mark the reviewer's review resolved
   -->
   
   * [ *] Did you explain what problem does this PR solve? Or what new features have been added?
   * [ ] Have you added corresponding test cases?
   * [* ] Have you modified the corresponding document?
   * [ *] 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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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';

Review comment:
       tests have been added into this file for the requirements above




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,172 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(ctx, conf, state_in_session)

Review comment:
       We can pass args instead of ctx to this function. So that the scope is limited.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503, err

Review comment:
       I think we don't need to return the err msg to the client in 5xx status? There is no need to let the client know the detail. Let's update similar places.

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"

Review comment:
       I think we should move this part out of fetch_access_token and return 400 if the client doesn't pass valid arguments.




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions have been made


-- 
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 #6382: feat: add auth plugin for casdoor

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


   @Steve0x2a
   Please help us to confirm if the part with casdoor is 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] ComradeProgrammer edited a comment on pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
ComradeProgrammer edited a comment on pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#issuecomment-1056488837


   @spacewander @tzssangglass what kind of "chaos-test" is this? Besides, comparing with my last previous commit, the only change was format in test file t/plugin/auth-casdoor.t, but I passed all the tests in the previous commit.


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions have been made accordingly


-- 
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 #6382: feat: add auth plugin for casdoor

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


   @ComradeProgrammer 
   Let's add casdoor to the README


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| endpoint_addr  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+*Note: endpoint_addr and callback_url should not end with '/'*
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "endpoint_addr":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### Explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*". Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented completely by the plugin so that there is no need to modify the server to implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.

Review comment:
       fixed




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       in t/plugin/auth-casdoor.t Test7 is the special test case which will cover this, where no session was given.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| endpoint_addr  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+*Note: endpoint_addr and callback_url should not end with '/'*
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "endpoint_addr":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### Explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*". Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented completely by the plugin so that there is no need to modify the server to implement this callback.
+
+The second parameter "endpoint_addr" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.
+
+#### How it works?
+
+Suppose a new user who has never visited this route before is going to visit it (http://localhost:9080/anything/d?param1=foo&param2=bar), considering that "auth-casdoor" is enabled, this visit would be processed by "auth-casdoor" plugin first. After checking the session and confirming that this user hasn't been authenticated, the visit will be intercepted. With the original url user wants to visit kept, he will be redirected to the login page of Casdoor.
+
+After successfully logging in with username and password(or whatever method he uses), Casdoor will redirect this user to the "callback_url" with GET parameter "code " and "state" specified. Because the "callback_url" is known by the plugin, when the visit toward the "callback_url" is intercepted this time, the logic of "Authorization code Grant Flow" in Oauth2 will be triggered, which means this plugin will request the access token to confirm whether this user is really logged in. After this confirmation, this plugin will redirect this user to the original url user wants to visit, which was kept by us previously. The logged-in status will also be kept in the session.

Review comment:
       ```suggestion
   After successfully logging in with username and password(or whatever method he uses), Casdoor will redirect this user to the "callback_url" with GET parameter "code" and "state" specified. Because the "callback_url" is known by the plugin, when the visit toward the "callback_url" is intercepted this time, the logic of "Authorization code Grant Flow" in Oauth2 will be triggered, which means this plugin will request the access token to confirm whether this user is really logged in. After this confirmation, this plugin will redirect this user to the original url user wants to visit, which was kept by us previously. The logged-in status will also be kept in the session.
   ```




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,126 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},

Review comment:
       In order to ensure that the url doesn't end up with '/', so I can directly extend this url by using something like `endpoint_addr .. "/callback"`




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,148 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf,state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil,"invalid state"

Review comment:
       ```suggestion
           return nil, "invalid state"
   ```
   
   Please fix other similar places.

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,148 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf,state_in_session)

Review comment:
       ```suggestion
   local function fetch_access_token(ctx, conf, state_in_session)
   ```

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,148 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf,state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil,"invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"

Review comment:
       Incorrect err msg.
   
   @Steve0x2a 
   Is the expiry required?




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then

Review comment:
       Need a comment about why expires_in is checked?

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err

Review comment:
       ```suggestion
           return nil, nil, err
   ```

##########
File path: docs/en/latest/plugins/authz-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: authz-casdoor
+---
+
+<!--
+#
+# 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

Review comment:
       @ComradeProgrammer 
   Need to handle this

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503, err
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503, err
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(ctx, conf, state_in_session)
+        if err then
+            core.log.error(err)
+            return 503, err
+        end
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                err = "no original_url found in session"
+                core.log.error(err)
+                return 503, err
+            end
+            local session_obj_write = session.new {
+                cookie = {lifetime = lifetime}
+            }
+            session_obj_write:start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else

Review comment:
       Look like this branch is unreachable?




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: conf/config-default.yaml
##########
@@ -340,6 +340,7 @@ plugins:                          # plugin list (sorted by priority)
   - request-validation             # priority: 2800
   - openid-connect                 # priority: 2599
   - authz-casbin                   # priority: 2560
+  - authz-casdoor                   # priority: 2559

Review comment:
       ```suggestion
     - authz-casdoor                  # priority: 2559
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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';

Review comment:
       Let's add tests to cover:
   1. `return nil, "invalid state"`
   2. 
   ```
   local access_token, err =
               fetch_access_token(ctx, conf, state_in_session)
           if err then
               core.log.error(err)
               return 503, err
           end
   ```

##########
File path: docs/en/latest/plugins/authz-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: authz-casdoor
+---
+
+<!--
+#
+# 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

Review comment:
       Let's remove the Summary like other plugins now

##########
File path: docs/en/latest/plugins/authz-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: authz-casdoor
+---
+
+<!--
+#
+# 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

Review comment:
       ```suggestion
   ## Description
   ```

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)")
+    if err or not m then
+        core.log.error(err)
+        return 503, err
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503, err
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503, err
+        end
+        local access_token, err =
+            fetch_access_token(ctx, conf, state_in_session)
+        if err then
+            core.log.error(err)
+            return 503, err
+        end
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                err = "no original_url found in session"
+                core.log.error(err)
+                return 503, err
+            end
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else
+            return 503, err

Review comment:
       err is nil in this branch?

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"

Review comment:
       And will you support "expires_in" as the session's expiry later?

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"

Review comment:
       > Yes, it is not enough to just check whether the token is empty, if the returned token is an error message, then expires_in information is 0.
   
   Let's add comment for it

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then

Review comment:
       ```suggestion
       if args.state ~= state_in_session then
   ```




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }

Review comment:
       `test` is not in `Attributes` of plugin docs

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()

Review comment:
       what this for?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"

Review comment:
       ```suggestion
           url = get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)
+    if ok then
+        --check whether contains extra ? or /
+        if string.find(conf.callback_url,"?",1) then

Review comment:
       use `ngx.re.find` is better?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)
+    if ok then
+        --check whether contains extra ? or /
+        if string.find(conf.callback_url,"?",1) then
+            return false,"callback_url should not contain get parameters or end up with /"
+        else
+            return true,nil
+        end
+    else
+        return false,err
+    end
+end
+
+function _M.access(conf, ctx)
+    log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.callback_url) then
+        local access_token,err = fetch_access_token(conf)
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503,"no original_url found in session"
+            end
+
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            ngx.redirect(original_url, 302)
+            return

Review comment:
       It seems a bit strange that here return does not reverse anything, but just exits the current function?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)
+    if ok then
+        --check whether contains extra ? or /
+        if string.find(conf.callback_url,"?",1) then
+            return false,"callback_url should not contain get parameters or end up with /"
+        else
+            return true,nil
+        end
+    else
+        return false,err
+    end
+end
+
+function _M.access(conf, ctx)
+    log.info("hit auth-casdoor access")

Review comment:
       This line looks like it's only for debugging?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then

Review comment:
       it's better to  determine if `args` is empty

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)
+    if ok then
+        --check whether contains extra ? or /
+        if string.find(conf.callback_url,"?",1) then
+            return false,"callback_url should not contain get parameters or end up with /"
+        else
+            return true,nil
+        end
+    else
+        return false,err
+    end

Review comment:
       ```suggestion
       if not ok then 
           return false, err
       end
   
       --check whether contains extra ? or /
       local has_extra = string.find(conf.callback_url,"?",1) 
       if has_extra then
           return false, "callback_url should not contain get parameters or end up with /"
       end 
       
       return true, nil
   ```
   is better?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)

Review comment:
       ```suggestion
       local ok, err = core.schema.check(schema, conf)
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )

Review comment:
       ```suggestion
       local res, err = client:request_uri(
       url, {
               method = "POST",
               query = {
                   code = args.code,
                   grant_type = "authorization_code",
                   client_id = conf.client_id,
                   client_secret = conf.client_secret
               }
            }
       )
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()

Review comment:
       ```suggestion
       local args = core.request.get_uri_args(ctx)
   ```
   
   and should pass `ctx` to this function

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"

Review comment:
       ```suggestion
       local url = get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil
+end
+
+function _M.check_schema(conf)
+    local ok,err=core.schema.check(schema, conf)
+    if ok then
+        --check whether contains extra ? or /
+        if string.find(conf.callback_url,"?",1) then
+            return false,"callback_url should not contain get parameters or end up with /"
+        else
+            return true,nil
+        end
+    else
+        return false,err
+    end
+end
+
+function _M.access(conf, ctx)
+    log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.callback_url) then
+        local access_token,err = fetch_access_token(conf)
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503,"no original_url found in session"

Review comment:
       ```suggestion
                   return 503, "no original_url found in session"
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()
+        return nil,"failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url=get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url=get_path(conf.casdoor_endpoint) .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(
+    url,
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.client_id,
+                client_secret = conf.client_secret
+            }
+        }
+    )
+    if not res then
+        return nil,err
+    end
+    local data = cjson.decode(res.body)
+    if not data or not data.access_token then
+        return nil,"failed when accessing token: no access_token contained"
+    end
+    return data.access_token,nil

Review comment:
       ```suggestion
       return data.access_token, nil
   ```




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")

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] spacewander commented on pull request #6382: feat: add auth plugin for casdoor

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


   OK
   @leslie-tsang 
   Let's open an issue about what needs to be proved.


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       Well perhaps I ... prefer to let the  plugin's name remain as it is now, and I wonder whether it is ok.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"
+            end
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503, "no original_url found in session"
+            end
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else
+            return 503, err
+        end
+    end
+
+    -- step 2: check whether session exists
+    if session_present and session_obj_read.data.access_token then
+        -- session exists
+        log.info("session exists")

Review comment:
       It seems that this log is not used in the test cases and can be removed?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"
+            end
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503, "no original_url found in session"
+            end
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else
+            return 503, err
+        end
+    end
+
+    -- step 2: check whether session exists
+    if session_present and session_obj_read.data.access_token then
+        -- session exists
+        log.info("session exists")
+    else
+        -- session not exists, redirect to login page
+        log.info("session not exists")
+        local session_obj_write = session.start()
+        session_obj_write.data.original_uri = current_uri
+        session_obj_write:save()
+        local redirect_url = conf.endpoint_addr ..
+                                 "/login/oauth/authorize?response_type=code&scope=read" ..
+                                 "&state=casdoor&client_id=" .. conf.client_id ..
+                                 "&redirect_uri=" .. conf.callback_url
+        core.response.set_header("Location", redirect_url)
+        return 302
+    end

Review comment:
       ```suggestion
       if not (session_present and session_obj_read.data.access_token) then
           -- session not exists, redirect to login page
           local session_obj_write = session.start()
           session_obj_write.data.original_uri = current_uri
           session_obj_write:save()
           local redirect_url = conf.endpoint_addr ..
                                    "/login/oauth/authorize?response_type=code&scope=read" ..
                                    "&state=casdoor&client_id=" .. conf.client_id ..
                                    "&redirect_uri=" .. conf.callback_url
           core.response.set_header("Location", redirect_url)
           return 302
       end
   ```
   
   is better?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end

Review comment:
       ```suggestion
           if err or not data then
           err = "failed to parse casdoor response data: " .. err
           return nil, err
       end
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then

Review comment:
       `string.match` currently cannot JIT-compilable, so try to use the `ngx.re.match`.

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       ```suggestion
                   core.response.exit(503, "no session found")
   ```
   
   is better?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"
+            end
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503, "no original_url found in session"

Review comment:
       ditto

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"

Review comment:
       what is `cofde`? typo err?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)

Review comment:
       ```suggestion
       local data, err = core.json.decode(res.body)
   ```
   
   no need cjson 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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       Well I was given clear instruction that the name of this plugin should be this. It is because that authz means authorization, but we are actually doing is mostly (authn) authentication, but authorization is also involved. Therefore maybe auth is better.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: docs/en/latest/plugins/authz-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: authz-casdoor
+---
+
+<!--
+#
+# 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

Review comment:
       @ComradeProgrammer 
   Need to handle this, see https://github.com/apache/apisix/pull/6382#discussion_r828661882




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions have been made according to the requirements.
   @spacewander @tzssangglass @leslie-tsang


-- 
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] leslie-tsang commented on a change in pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#discussion_r832793712



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end

Review comment:
       ```suggestion
               ok, err = plugin.check_schema(conf2)
               if ok then
                   ngx.say(err)
               end
   ```
   We can also re-defined var `ok` and `err` to achieve the goal as well.
   
   BTW, it should be `err2` instead of `err`, right ?

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+

Review comment:
       ```suggestion
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")

Review comment:
       ```suggestion
               local m, err=ngx.re.match(re_url, "state=([0-9]*)")
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       ```suggestion
               if res2.code ~= 302 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then

Review comment:
       ```suggestion
               if code ~= 302 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       ```suggestion
               if res2.code ~= 302 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then

Review comment:
       ```suggestion
               if code ~= 200 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)

Review comment:
       ```suggestion
                   ngx.say(err3)
   ```
   It should be `err3` instead of `err` ?

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log

Review comment:
       ```suggestion
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then

Review comment:
       ```suggestion
               if res1.status ~= 503 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then

Review comment:
       ```suggestion
               if res3.status ~= 503 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       ```suggestion
               if res2.code ~= 302 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then

Review comment:
       ```suggestion
               if res3.status ~= 503 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then

Review comment:
       ```suggestion
                   if arg == "wrong" then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then

Review comment:
       ```suggestion
               if res3.status ~= 200 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   @spacewander @tzssangglass Most of the revisions in your comments have been implemented, and tests are also added. I think this code is ready to be reviewed now, THX.


-- 
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 pull request #6382: feat: add auth plugin for casdoor

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


   > what kind of "chaos-test" is this? Besides, comparing with my last previous commit, the only change was format in test file t/plugin/auth-casdoor.t, but I passed all the tests in the previous commit.
   
   chaos-test is unstable lately, so if I see it I can rerun it for you.


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   > 
   > hi @ComradeProgrammer, after the CI run is finished, please fix the failure of the CI.
   
   I have made changes according to the CI information, and I wonder whether I can have CI run another time , so that I can confirm whether I can pass CI 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] tzssangglass commented on pull request #6382: feat: add auth plugin for casdoor

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


   > I have made changes according to the CI information, and I wonder whether I can have CI run another time , so that I can confirm whether I can pass CI now?
   
   hi @ComradeProgrammer , there are still some code lint check failed.


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   @spacewander @tzssangglass  what about this time? (revisions have been made)


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,126 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},

Review comment:
       Get it. Let's add as a comment.




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   @tzssangglass  emmm chaos test failed again, could you please rerun this test?


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,121 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = core.cjson.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    local real_callback_url=ngx.re.match(conf.callback_url, ".-//[^/]+(/.*)")

Review comment:
       ```suggestion
       local m, err = ngx.re.match(conf.callback_url, ".-//[^/]+(/.*)", "jo")
   ```
   
   Please refer to https://github.com/openresty/lua-nginx-module/tree/master#ngxrematch for how to use it.
   Now real_callback_url is either nil or array.

##########
File path: t/plugin/auth-casdoor.t
##########
@@ -0,0 +1,280 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa" }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "auth-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])
+            if not code == 200 then ngx.say("failed to set up routing rule") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then ngx.say("should have redirected") end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then ngx.say(err) end
+
+            local data = cjson.decode(res.body)
+            if not data then ngx.say("invalid res.body") end
+
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then ngx.say("invalid token") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then ngx.say(err1) end
+
+            local cookie = res1.headers["Set-Cookie"]
+
+            local res2, err2 = httpc:request_uri(callback_url, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then ngx.say(err) end
+            if not res2.code == 302 then log.error(res2.code) end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then ngx.say(err) end
+            if not res3.status == 200 then log.error(res3.status) end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})

Review comment:
       Let's check the error log too

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| endpoint_addr  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+*Note: endpoint_addr and callback_url should not end with '/'*
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.

Review comment:
       ```suggestion
   You can enable the plugin on any route by giving out all attributes mentioned above.
   ```
   would be better

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       Please log down the err, like:
   ```
   local err = "no session found"
   core.log.error(err)
   return 503, err
   ```




-- 
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] ComradeProgrammer edited a comment on pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
ComradeProgrammer edited a comment on pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#issuecomment-1067935059


   Now state has been randomized, other recommended revisions have also been made, and now they are in different commits as mentioned before. tests have also been modified, and special case for "state" is added


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   Now state has been randomized, other recommended revisions have also been made, and now they are in different commits as mentioned before.


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf3)
+            if ok then
+                ngx.say("err")

Review comment:
       I have made revisions to make the  information more clear




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       It seems that after this modification, the plugin is unable to pass the test `./utils/check-plugins-code.sh `




-- 
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] leslie-tsang commented on a change in pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#discussion_r834222045



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf3)
+            if ok then
+                ngx.say("err")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then

Review comment:
       ```suggestion
               if code ~= 200 then
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf3)
+            if ok then
+                ngx.say("err")

Review comment:
       ```suggestion
                   ngx.say(err)
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")

Review comment:
       ```suggestion
                   ngx.say(err)
   ```




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}

Review comment:
       please put each field per line, like other plugins

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.

Review comment:
       ```suggestion
   The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*". Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented completely by the plugin so that there is no need to modify the server to implement this callback.
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        log.err()
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = get_path(conf.casdoor_endpoint) ..

Review comment:
       Could we require the endpoint not to have `?` so we don't need to use `get_path`?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        log.err()
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = get_path(conf.casdoor_endpoint) ..
+                    "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = get_path(conf.casdoor_endpoint) ..
+                  "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then return false, err end
+    -- check whether contains extra ? or /
+    local has_extra = string.find(conf.callback_url, "?", 1)

Review comment:
       We can do the check in the schema via "pattern":
   https://github.com/apache/apisix/blob/acf7a0cc7f05aa15de21eb1aac798566f45bcb66/apisix/schema_def.lua#L34

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin

Review comment:
       ```suggestion
   #### Explanations about parameters of this plugin
   ```

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.
+
+#### How it works?
+
+Suppose a new user who have never visited this route before is going to visit it (http://localhost:9080/anything/d?param1=foo&param2=bar), considering that "auth-casdoor" is enabled, this visit would be processed by "auth-casdoor" plugin first. After checking the session and confirming that this use hasn't been authentication, the visit will be intercepted. After remembering the original url user wants to visit, he will be redirected to the login page of Casdoor.
+
+After successfully logging in with username and password(or whatever method he use), Casdoor will redirect this user to the "callback_url" with GET parameter "code " and "state" specified. Because the "callback_url" is known by the plugin, so when the visit toward the "callback_url" is intercepted this time, the logic of "Authorization code Grant Flow" in Oauth2 will be triggered, which means this plugin will request the access token to confirm whether this user is really logged in. After this confirmation, this plugin will redirect this user to the original url user wants to visit, which was remembered by us previously. The logged in status will also be remembered by session
+
+Next time this user want to visit url behind this route (for example, http://localhost:9080/anything/d), after discovering that this user have been authentication previously, this plugin won't redirect this user any more so that this user can visit whatever he wants under this route without being interfered

Review comment:
       ```suggestion
   Next time this user want to visit url behind this route (for example, http://localhost:9080/anything/d), after discovering that this user has been authenticated previously, this plugin won't redirect this user anymore so that this user can visit whatever he wants under this route without being interfered
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        log.err()
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = get_path(conf.casdoor_endpoint) ..
+                    "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = get_path(conf.casdoor_endpoint) ..
+                  "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then return false, err end
+    -- check whether contains extra ? or /
+    local has_extra = string.find(conf.callback_url, "?", 1)
+    if has_extra then
+        return false,
+               "callback_url should not contain get parameters or end up with /"
+    end
+
+    return true, nil
+end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.callback_url) then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503, "no original_url found in session"
+            end
+
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            ngx.redirect(original_url, 302)

Review comment:
       Would be better to use this way:
   https://github.com/apache/apisix/blob/acf7a0cc7f05aa15de21eb1aac798566f45bcb66/apisix/plugins/redirect.lua#L215-L216
   
   So we can have a common place to handle redirection, as we don't use `ngx.exit` directly.

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        log.err()
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = get_path(conf.casdoor_endpoint) ..
+                    "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = get_path(conf.casdoor_endpoint) ..
+                  "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then return false, err end
+    -- check whether contains extra ? or /
+    local has_extra = string.find(conf.callback_url, "?", 1)
+    if has_extra then
+        return false,
+               "callback_url should not contain get parameters or end up with /"
+    end
+
+    return true, nil
+end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.callback_url) then

Review comment:
       The `ctx.var.uri` only contains path

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},

Review comment:
       ```suggestion
           endpoint_addr = {type = "string"},
   ```
   would be better?

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.
+
+#### How it works?
+
+Suppose a new user who have never visited this route before is going to visit it (http://localhost:9080/anything/d?param1=foo&param2=bar), considering that "auth-casdoor" is enabled, this visit would be processed by "auth-casdoor" plugin first. After checking the session and confirming that this use hasn't been authentication, the visit will be intercepted. After remembering the original url user wants to visit, he will be redirected to the login page of Casdoor.

Review comment:
       ```suggestion
   Suppose a new user who has never visited this route before is going to visit it (http://localhost:9080/anything/d?param1=foo&param2=bar), considering that "auth-casdoor" is enabled, this visit would be processed by "auth-casdoor" plugin first. After checking the session and confirming that this user hasn't been authenticated, the visit will be intercepted. With the original url user wants to visit kept, he will be redirected to the login page of Casdoor.
   ```

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.

Review comment:
       ```suggestion
   The second parameter "endpoint_addr" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.
   ```

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.
+
+#### How it works?
+
+Suppose a new user who have never visited this route before is going to visit it (http://localhost:9080/anything/d?param1=foo&param2=bar), considering that "auth-casdoor" is enabled, this visit would be processed by "auth-casdoor" plugin first. After checking the session and confirming that this use hasn't been authentication, the visit will be intercepted. After remembering the original url user wants to visit, he will be redirected to the login page of Casdoor.
+
+After successfully logging in with username and password(or whatever method he use), Casdoor will redirect this user to the "callback_url" with GET parameter "code " and "state" specified. Because the "callback_url" is known by the plugin, so when the visit toward the "callback_url" is intercepted this time, the logic of "Authorization code Grant Flow" in Oauth2 will be triggered, which means this plugin will request the access token to confirm whether this user is really logged in. After this confirmation, this plugin will redirect this user to the original url user wants to visit, which was remembered by us previously. The logged in status will also be remembered by session

Review comment:
       ```suggestion
   After successfully logging in with username and password(or whatever method he uses), Casdoor will redirect this user to the "callback_url" with GET parameter "code " and "state" specified. Because the "callback_url" is known by the plugin, when the visit toward the "callback_url" is intercepted this time, the logic of "Authorization code Grant Flow" in Oauth2 will be triggered, which means this plugin will request the access token to confirm whether this user is really logged in. After this confirmation, this plugin will redirect this user to the original url user wants to visit, which was kept by us previously. The logged-in status will also be kept in the session.
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()

Review comment:
       The `err` doesn't have parameters in it

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,138 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local string = require("string")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = {type = "string"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "casdoor_endpoint", "client_id", "client_secret"
+    }
+}
+
+local _M = {version = 0.1, priority = 2559, name = plugin_name, schema = schema}
+
+local function get_path(uri)

Review comment:
       We can use
   https://github.com/apache/apisix/blob/acf7a0cc7f05aa15de21eb1aac798566f45bcb66/apisix/plugins/http-logger.lua#L88
   to get the path instead of writing own solution.




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,146 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        client_id = { type = "string" },
+        client_secret = { type = "string" },
+        callback_url = { type = "string" },
+        test = { type= "boolean", default = false }
+    },
+    required = {
+        "callback_url",
+        "casdoor_endpoint",
+        "client_id",
+        "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = core.request.get_uri_args()
+    if not args.code or not args.state then
+        log.err()

Review comment:
       Do you mean this function? Extract "code" and "state" parameters and use them to launch to casdoor api, in order to fetch "access_token" so that we can confirm whether the user have really logged in




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },

Review comment:
       > Let's remove the casdoor_ prefix
   
   all other casdoor_ prefixes are removed except casdoor_endpoint, because i think just using "endpoint" may be a little confusing.




-- 
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] ComradeProgrammer removed a comment on pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
ComradeProgrammer removed a comment on pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#issuecomment-1080542558


   > @spacewander @ComradeProgrammer The documentation in the PR still needs to be optimized, let's merge this PR first and deal with the documentation in the next PR ?
   I am glad to hear 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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       Well I was given clear instruction that the name of this plugin should be this. It is because that authz means authorization, but we are actually doing is mostly (authn) authentication, while authorization is also involved. Therefore maybe auth is better.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then
+            core.log.error(err)
+            return 503
+        end
+        if access_token then

Review comment:
       This branch can be saved

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then

Review comment:
       ```suggestion
           if not access_token then
   ```

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then
+            core.log.error(err)
+            return 503
+        end
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                err = "no original_url found in session"
+                core.log.error(err)
+                return 503
+            end
+            local session_obj_write = session.new {
+                cookie = {lifetime = lifetime}
+            }
+            session_obj_write:start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        end
+    end
+
+    -- step 2: check whether session exists
+    if not (session_present and session_obj_read.data.access_token) then
+        -- session not exists, redirect to login page
+        local state = rand(0x7fffffff)
+        local session_obj_write = session.start()
+        session_obj_write.data.original_uri = current_uri
+        session_obj_write.data.state = state
+        session_obj_write:save()
+
+        local redirect_url=conf.endpoint_addr .. "/login/oauth/authorize?" .. ngx.encode_args({

Review comment:
       ```suggestion
           local redirect_url = conf.endpoint_addr .. "/login/oauth/authorize?" .. ngx.encode_args({
   ```




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,126 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = conf.endpoint_addr .. "/casdoor_fake_access_token_api"

Review comment:
       We can configure the endpoint directly in test?

##########
File path: t/plugin/auth-casdoor.t
##########
@@ -0,0 +1,284 @@
+#
+# 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;
+

Review comment:
       Let's use
   https://github.com/apache/apisix/blob/54ffdeed114bdb4f63ff72c8a867377d122a6b84/t/plugin/clickhouse-logger.t#L28-L34
   and remove duplicate sections with
   ```
   #!/usr/bin/env python
   # coding: utf-8
   # Usage: ./simpify.py $test_filename
   import sys
   with open(sys.argv[1]) as f:
       lines = [l.rstrip() for l in f.readlines()]
   newlines = []
   for i, l in enumerate(lines):
       if ((l == "GET /t" and lines[i-1] == "--- request") or
           (l == "[error]" and lines[i-1] == "--- no_error_log")):
           newlines = newlines[:-1]
           continue
       newlines.append(l)
   res = "\n".join(newlines)
   # print(res)
   with open(sys.argv[1], "w") as f:
      f.write(res + '\n')
   ```

##########
File path: t/lib/server.lua
##########
@@ -482,6 +482,11 @@ function _M.google_logging_token()
     }))
 end
 
+function _M.casdoor_fake_access_token_api()

Review comment:
       We can put fake api in the test directly, like:
   https://github.com/apache/apisix/blob/54ffdeed114bdb4f63ff72c8a867377d122a6b84/t/plugin/clickhouse-logger.t#L36

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+|test|boolean|not required|false| |used for unit test, no need to set it for users|
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_endpoint":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*" Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented competely by plugin so that there is no need to modify the server implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.

Review comment:
       Missing this one?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,126 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},

Review comment:
       Why add `[^/]$`?




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,126 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = conf.endpoint_addr .. "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session.present then

Review comment:
       ```suggestion
               if not session_present  then
   ```
   and need a test to cover 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] leslie-tsang commented on a change in pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#discussion_r830616596



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+

Review comment:
       ```suggestion
   
   
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,453 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log

Review comment:
       ```suggestion
   ```
   Please remove unused code. :)

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+

Review comment:
       ```suggestion
   
   
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,453 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log

Review comment:
       ```suggestion
   ```
   Please remove unused code. :)

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,453 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log

Review comment:
       ```suggestion
   ```
   Please remove unused code. :)

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,164 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, nil, "failed when accessing token. Invalid code or state"
+    end
+    if args.state ~= tostring(state_in_session) then
+        return nil, nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503, err
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503, err
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(ctx, conf, state_in_session)
+        if err then
+            core.log.error(err)
+            return 503, err
+        end
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                err = "no original_url found in session"
+                core.log.error(err)
+                return 503, err
+            end
+            local session_obj_write = session.new {
+                cookie = {lifetime = lifetime}
+            }
+            session_obj_write:start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else
+            err = "failed to retrieve access_token"
+            return 503, err
+        end
+    end
+
+    -- step 2: check whether session exists
+    if not (session_present and session_obj_read.data.access_token) then
+        -- session not exists, redirect to login page
+        local state = rand(0x7fffffff)
+        local session_obj_write = session.start()
+        session_obj_write.data.original_uri = current_uri
+        session_obj_write.data.state = state
+        session_obj_write:save()
+        local redirect_url = conf.endpoint_addr ..

Review comment:
       Would be better to use [`ngx.encode_args`](https://github.com/openresty/lua-nginx-module#ngxencode_args) to process 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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+--- error_log
+invalid state
+
+
+
+=== TEST 9: test incorrect access_token
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=wrong&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end

Review comment:
       Done




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")

Review comment:
       Considering that the condition is "if ok then", during this time err will be empty. To illustrate that conf2 are not supposed to be allowed, we should output  something here to tell the test suit to  make a false assertion




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf3)
+            if ok then
+                ngx.say("err")

Review comment:
       if ok==true or err here is empty, then this test should fail, so I think it's right to use "err" rather than err




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions made


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   Revisions made


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },

Review comment:
       Let's remove the casdoor_ prefix

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },

Review comment:
       Let's remove the casdoor_ prefix

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")

Review comment:
       Let's sort the imports like https://github.com/apache/apisix/blob/462413551e2eca0ed1ced54742d3c961b9dc5f06/apisix/plugins/authz-casbin.lua#L20

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)
+    if not data.access_token then
+        log.err("failed when accessing token: no access_token contained")
+        return nil
+    end
+    return data.access_token
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    log.warn("hit auth-casdoor access")
+    local current_uri = ngx.var.request_uri

Review comment:
       ```suggestion
       local current_uri = ctx.var.uri
   ```

##########
File path: conf/config-default.yaml
##########
@@ -340,6 +340,7 @@ plugins:                          # plugin list (sorted by priority)
   - request-validation             # priority: 2800
   - openid-connect                 # priority: 2599
   - authz-casbin                   # priority: 2560
+  - auth-casdoor                  # priority: 2561

Review comment:
       Please sort according to the priority and fix the indent

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,

Review comment:
       priority mismatch

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)

Review comment:
       Need to check if decode succeed

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()

Review comment:
       ```suggestion
       local args = core.request.get_uri_args()
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil

Review comment:
       ```suggestion
           return nil, err
   ```
   and log the err outside would be better

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,76 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| casdoor_endpoint  | string | required    |         |       | The url of casdoor.             |
+| casdoor_client_id | string | required    |         |       | The client id in casdoor.                          |
+| casdoor_client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| casdoor_callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "casdoor_callback_url":"http://localhost:9080/anything/callback",
+        "casdoor_endpoint":"http://localhost:8000",
+        "casdoor_client_id":"7ceb9b7fda4a9061ec1c",
+        "casdoor_client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+   },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+By doing this all requests toward `/anything/*` will be intercepted and whether this user has logged in via Casdoor will be checked (by checking session). If not, the user will be redirected to login page of casdoor, thus implementing authentication without modifying server's code.

Review comment:
       Could you provide a step-by-step example?
   I still can't fully understand it after reading it twice...
   It looks like the callback_url needs to contain part of the request host & path?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)
+    if not data.access_token then
+        log.err("failed when accessing token: no access_token contained")
+        return nil
+    end
+    return data.access_token
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    log.warn("hit auth-casdoor access")
+    local current_uri = ngx.var.request_uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.casdoor_callback_url) then
+        local access_token = fetch_access_token(conf)
+        if not access_token then
+            return 500, "failed to fetch access token"

Review comment:
       ```suggestion
               return 503, "failed to fetch access token"
   ```

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)
+    if not data.access_token then
+        log.err("failed when accessing token: no access_token contained")
+        return nil
+    end
+    return data.access_token
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    log.warn("hit auth-casdoor access")

Review comment:
       We should remove such a per-req log

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)
+    if not data.access_token then
+        log.err("failed when accessing token: no access_token contained")
+        return nil
+    end
+    return data.access_token
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    log.warn("hit auth-casdoor access")
+    local current_uri = ngx.var.request_uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.casdoor_callback_url) then
+        local access_token = fetch_access_token(conf)
+        if not access_token then
+            return 500, "failed to fetch access token"
+        else
+            local original_url = session_obj_read.data.original_uri
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            ngx.redirect(original_url, 302)
+            return
+        end
+    end
+
+    --step 2: check whether session exists
+
+    if session_obj_read.data.access_token then
+        -- session exists
+        log.warn("session exists")

Review comment:
       Ditto. Warn log should not be used in per-req level

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",
+        {
+            method = "POST",
+            query = {
+                code = args.code,
+                grant_type = "authorization_code",
+                client_id = conf.casdoor_client_id,
+                client_secret = conf.casdoor_client_secret
+            }
+        }
+    )
+    if not res then
+        log.err("failed when accessing token. ", err)
+        return nil
+    end
+    local data = cjson.decode(res.body)
+    if not data.access_token then
+        log.err("failed when accessing token: no access_token contained")
+        return nil
+    end
+    return data.access_token
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    log.warn("hit auth-casdoor access")
+    local current_uri = ngx.var.request_uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+    if get_path(current_uri) == get_path(conf.casdoor_callback_url) then
+        local access_token = fetch_access_token(conf)
+        if not access_token then
+            return 500, "failed to fetch access token"
+        else

Review comment:
       ```
   if ...
       return
   end
   -- the other branch
   ```
   is prefer

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },
+        casdoor_client_id = { type = "string" },
+        casdoor_client_secret = { type = "string" },
+        casdoor_callback_url = { type = "string" },
+    },
+    required = {
+        "casdoor_callback_url",
+        "casdoor_endpoint",
+        "casdoor_client_id",
+        "casdoor_client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 12,
+    name = plugin_name,
+    schema = schema,
+}
+
+local function get_path(uri)
+    local without_query = uri:match("(.-)%?") or uri
+    return without_query:match(".-//[^/]+(/.*)") or without_query
+end
+
+local function fetch_access_token(conf)
+    local args = ngx.req.get_uri_args()
+    if not args.code or not args.state then
+        log.err("failed when accessing token. Invalid code or state ")
+        return nil
+    end
+    local client = http.new()
+    local res, err = client:request_uri(
+    get_path(conf.casdoor_endpoint) .. "/api/login/oauth/access_token",

Review comment:
       Let's check if the endpoint contains `?` in the schema instead of split it at runtime




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   Well I revoked to the previous correct commit and then manually modified the format, this time maybe it's okay.


-- 
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] leslie-tsang commented on pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#issuecomment-1063710953


   Hello there, Plz don't force push during the <ins>review</ins>.
   
   Commit history allows reviewers to see what has changed since the last review, which is <ins>useful</ins> for them.
   
   A force-push might <ins>**ruin**</ins> the review experience and progress. which may leave this PR without anyone <ins>**willing**</ins> to review. :)


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       This plugin implements a variant of oauth2 which is for authorization, just like authz-keycloak.




-- 
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] leslie-tsang commented on a change in pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#discussion_r828701154



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end

Review comment:
       ```suggestion
   
   function _M.check_schema(conf)
       return core.schema.check(schema, conf)
   end
   
   ```
   Hello there, we have a [CODE_STYLE](https://github.com/apache/apisix/blob/master/CODE_STYLE.md#blank-line) to follow. :)

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end

Review comment:
       ```suggestion
       if not res then
           return nil, err
       end
   ```
   Hello there, we have a [CODE_STYLE](https://github.com/apache/apisix/blob/master/CODE_STYLE.md#blank-line) to follow. :)

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "authz-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])
+            if not code == 200 then ngx.say("failed to set up routing rule") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then ngx.say("should have redirected") end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then ngx.say(err) end
+
+            local data = cjson.decode(res.body)
+            if not data then ngx.say("invalid res.body") end
+
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then ngx.say("invalid token") end

Review comment:
       ```suggestion
               if not res then
                   ngx.say(err)
               end
   
               local data = cjson.decode(res.body)
               if not data then
                   ngx.say("invalid res.body")
               end
   
               if not data.access_token == "aaaaaaaaaaaaaaaa" then
                   ngx.say("invalid token")
               end
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "authz-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])
+            if not code == 200 then ngx.say("failed to set up routing rule") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then ngx.say("should have redirected") end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then ngx.say(err) end
+
+            local data = cjson.decode(res.body)
+            if not data then ngx.say("invalid res.body") end
+
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then ngx.say("invalid token") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then ngx.say(err1) end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            state=m[1]

Review comment:
       ```suggestion
               local m, err = ngx.re.match(re_url, "state=([0-9]*)")
               if err or not m then
                   log.error(err)
                   ngx.exit()
               end
               state = m[1]
   ```

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end

Review comment:
       Ditto

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)")

Review comment:
       ```suggestion
       local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
   ```
   Would be better to add "jo" option ?

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "authz-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])
+            if not code == 200 then ngx.say("failed to set up routing rule") end

Review comment:
       Ditto

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "authz-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])

Review comment:
       ```suggestion
               local code, body = t('/apisix/admin/routes/1',
                   ngx.HTTP_PUT, 
                   [[{
                       "methods": ["GET"],
                       "uri": "/anything/*",
                       "plugins": {
                           "authz-casdoor": {
                               "callback_url":"]] .. callback_url .. [[",
                               "endpoint_addr":"]] .. fake_uri .. [[",
                               "client_id":"7ceb9b7fda4a9061ec1c",
                               "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
                           }
                       },
                       "upstream": {
                           "type": "roundrobin",
                           "nodes": {
                           "httpbin.org:80": 1
                           }
                       }
                   }]]
               )
   ```

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,150 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil, "invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err

Review comment:
       Would it be better to record the `res.body` at the same time to help the user figure out what's going on ?

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,338 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then ngx.say(err) end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then ngx.say(err) end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then ngx.say(err) end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
+                                "methods": ["GET"],
+                                "uri": "/anything/*",
+                                "plugins": {
+                                    "authz-casdoor": {
+                                        "callback_url":"]] .. callback_url .. [[",
+                                        "endpoint_addr":"]] .. fake_uri .. [[",
+                                        "client_id":"7ceb9b7fda4a9061ec1c",
+                                        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                                    }
+                                },
+                                "upstream": {
+                                    "type": "roundrobin",
+                                    "nodes": {
+                                    "httpbin.org:80": 1
+                                    }
+                                }
+                            }]])
+            if not code == 200 then ngx.say("failed to set up routing rule") end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then ngx.say("should have redirected") end

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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   rebase made(force-pushed was caused by rebase)


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then
+            core.log.error(err)
+            return 503
+        end
+        if access_token then
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                err = "no original_url found in session"
+                core.log.error(err)
+                return 503
+            end
+            local session_obj_write = session.new {
+                cookie = {lifetime = lifetime}
+            }
+            session_obj_write:start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        end
+    end
+
+    -- step 2: check whether session exists
+    if not (session_present and session_obj_read.data.access_token) then
+        -- session not exists, redirect to login page
+        local state = rand(0x7fffffff)
+        local session_obj_write = session.start()
+        session_obj_write.data.original_uri = current_uri
+        session_obj_write.data.state = state
+        session_obj_write:save()
+
+        local redirect_url=conf.endpoint_addr .. "/login/oauth/authorize?" .. ngx.encode_args({

Review comment:
       revised

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then
+            core.log.error(err)
+            return 503
+        end
+        if access_token then

Review comment:
       revised

##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,175 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(code, conf)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        if not args.code then
+            err = "invalid code"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(args.code, conf)
+        if err then

Review comment:
       revised




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   @spacewander @tzssangglass what kind of test is this? Besides, comparing with my last previous commit, the only change was format in test file t/plugin/auth-casdoor.t, but I passed all the tests in the previous commit.


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| endpoint_addr  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+*Note: endpoint_addr and callback_url should not end with '/'*
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "endpoint_addr":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.

Review comment:
       ```suggestion
   In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of Casdoor.
   ```




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,127 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr ..
+                    "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = conf.endpoint_addr ..
+                "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()

Review comment:
       should we check if session exist? ref: https://github.com/bungle/lua-resty-session/issues/12#issuecomment-127621307

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,127 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+        test = {type = "boolean", default = false}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr ..
+                    "/api/login/oauth/access_token"
+
+    if conf.test then
+        url = conf.endpoint_addr ..
+                "/casdoor_fake_access_token_api"
+    end
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read = session.open()
+    -- step 1: check whether hits the callback
+
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then

Review comment:
       ```suggestion
   
       -- step 1: check whether hits the callback
       if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
   ```

##########
File path: t/plugin/auth-casdoor.t
##########
@@ -0,0 +1,271 @@
+#
+# 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.auth-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/casdoor_fake_access_token_api"
+            local callback_url="http://127.0.0.1:" .. ngx.var.server_port .. "/anything/callback"
+            local conf = {
+                callback_url=callback_url,
+                endpoint_addr=fake_uri,
+                client_id="7ceb9b7fda4a9061ec1c",
+                client_secret="3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url=callback_url.."/?code=aaa",
+                endpoint_addr=fake_uri,
+                client_id="7ceb9b7fda4a9061ec1c",
+                client_secret="3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url=callback_url,
+                endpoint_addr=fake_uri.."/",
+                client_id="7ceb9b7fda4a9061ec1c",
+                client_secret="3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url="http://127.0.0.1:" .. ngx.var.server_port .. "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "auth-casdoor": {
+                            "callback_url":"]]..callback_url..[[",
+                            "endpoint_addr":"]]..fake_uri..[[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04",
+                            "test":true
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code ==200 then
+                ngx.say("failed to set up routing rule")
+            end
+
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar',ngx.HTTP_GET,[[]])
+            if not code ==302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/casdoor_fake_access_token_api",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local httpc= require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/casdoor_fake_access_token_api"
+
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+
+            if not data.access_token=="aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.auth-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/anything/d?param1=foo&param2=bar"
+            local callback_url="http://127.0.0.1:" .. ngx.var.server_port .. "/anything/callback?code=aaa&state=bbb"
+
+
+            local httpc= require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+
+
+            local res2,err2=httpc:request_uri(callback_url, {

Review comment:
       ```suggestion
               local res2,err2 = httpc:request_uri(callback_url, {
   ```
   
   Please pay more attention to similar formatting issues in this PR.

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,93 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management

Review comment:
       ```suggestion
   `auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
   ```




-- 
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] Steve0x2a commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,121 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = core.cjson.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    local real_callback_url=ngx.re.match(conf.callback_url, ".-//[^/]+(/.*)")
+    if current_uri == real_callback_url then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"
+            end
+            local original_url = session_obj_read.data.original_uri
+            if not original_url then
+                return 503, "no original_url found in session"
+            end
+            local session_obj_write = session.start()
+            session_obj_write.data.access_token = access_token
+            session_obj_write:save()
+            core.response.set_header("Location", original_url)
+            return 302
+        else
+            return 503, err
+        end
+    end
+
+    -- step 2: check whether session exists
+    if not (session_present and session_obj_read.data.access_token) then
+        -- session not exists, redirect to login page
+        local session_obj_write = session.start()
+        session_obj_write.data.original_uri = current_uri
+        session_obj_write:save()
+        local redirect_url = conf.endpoint_addr ..
+                                 "/login/oauth/authorize?response_type=code&scope=read" ..
+                                 "&state=casdoor&client_id=" .. conf.client_id ..

Review comment:
       The state should be generated randomly, and the state returned by casdoor should be checked to see if it is the same as the one generated.

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,121 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = core.cjson.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then

Review comment:
       Currently, when casdoor gets an accessToken error, it will return an accessToken like `error: invalid client_id`, which seems to be a bug if it only determines whether the accessToken is null.




-- 
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 #6382: feat: add auth plugin for casdoor

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


   > Hi @spacewander , is there a recommended IDE and code formatter for formatting APISIX code? like a Lua plugin for VSCode or IntellJ IDEA? We tried the most popular Lua plugin in VSCode (from Tencent), but the formatted code has a lot of diff with original code. (we chose a random Lua file in APISIX and just format it directly). It makes us unable to format the code in our PR. We want a Lua formatter that is compatible with APISIX's coding format?
   > 
   > ![image](https://user-images.githubusercontent.com/3787410/158534637-b635611a-2d04-4f3b-8666-5da41d6e64bb.png)
   > 
   > ![image](https://user-images.githubusercontent.com/3787410/158534734-eba58441-e385-4398-ae1d-e10312083d77.png)
   
   Yes. We also tried LuaFormatter before, but it also format all the code instead of focussing a few places like adding space around the operator. So currently we have not found a suitable tool to do it automatically.


-- 
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] hsluoyz commented on pull request #6382: feat: add auth plugin for casdoor

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


   Hi @spacewander , is there a recommended IDE and code formatter for formatting APISIX code? like a Lua plugin for VSCode or IntellJ IDEA? We tried the most popular Lua plugin in VSCode (from Tencent), but the formatted code has a lot of diff with original code. (we chose a random Lua file in APISIX and just format it directly). It makes us unable to format the code in our PR. We want a Lua formatter that is compatible with APISIX's coding format?
   
   ![image](https://user-images.githubusercontent.com/3787410/158534637-b635611a-2d04-4f3b-8666-5da41d6e64bb.png)
   
   ![image](https://user-images.githubusercontent.com/3787410/158534734-eba58441-e385-4398-ae1d-e10312083d77.png)
   


-- 
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 pull request #6382: feat: add auth plugin for casdoor

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


   > I cannot see the reason why CI failed this time because these tests don't fail when I run it locally. Besides, it seems irrelevant with the code I submitted.
   
   rerun


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions have been made according to the requirements.
   @spacewander @tzssangglass @leslie-tsang 


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")

Review comment:
       Considering that the condition is "if ok then", during this time err will be empty. To illustrate that conf2 are not supposed to be allowed, we should output  something here to make the test suit  to make a false assertion




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+

Review comment:
       Done




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   revisions have been made according to the requirements.
   @spacewander @tzssangglass @leslie-tsang


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   > @spacewander @ComradeProgrammer The documentation in the PR still needs to be optimized, let's merge this PR first and deal with the documentation in the next PR ?
   I am glad to hear 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] spacewander merged pull request #6382: feat: add auth plugin for casdoor

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


   


-- 
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 #6382: feat: add auth plugin for casdoor

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


   Let's handle https://github.com/apache/apisix/pull/6382#discussion_r832883057?


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   I am extremely flattered, delighted and grateful for this extremely detailed reply and advice from you. Revisions will be made accordingly soon and tests will be added soon together. @spacewander 


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,128 @@
+--
+-- 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 log = core.log
+local ngx = ngx
+local session = require("resty.session")
+local http = require("resty.http")
+local cjson = require("cjson")
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        casdoor_endpoint = { type = "string" },

Review comment:
       @ComradeProgrammer 
   Please request me to review it once you are ready. Thanks!




-- 
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 pull request #6382: feat: add auth plugin for casdoor

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


   > @spacewander @tzssangglass Most of the revisions in your comments have been implemented, and tests are also added. I think this code is ready to be reviewed now, THX.
   
   hi @ComradeProgrammer, after the CI run is finished, please fix the failure of the CI.


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   @spacewander revisions have been made


-- 
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] Steve0x2a commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,148 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf,state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid code or state"
+    end
+    if not args.state == state_in_session then
+        return nil,"invalid state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+
+    if not res then return nil, err end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err
+        return nil, err
+    end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, "failed when accessing token: invalid access_token"

Review comment:
       @spacewander Yes, it is not enough to just check whether the token is empty, if the returned token is an error message, then  `expires_in` information is 0.




-- 
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 pull request #6382: feat: add auth plugin for casdoor

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


   LGTM


-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       in t/plugin/auth-casdoor.t Test7 is the special test case which willcover here, where no session was given.




-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       > but we are actually doing is mostly (authn) authentication
   
   No, but we are actually doing is mostly (authz) authorization.
   
   1. OAuth 2.0 is an authorization protocol and NOT an authentication protocol. 
   2. the endpoints in this plugin also indicates that: "/api/login/oauth/access_token" and "/login/oauth/authorize".




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: docs/en/latest/plugins/authz-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: authz-casdoor
+---
+
+<!--
+#
+# 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

Review comment:
       sorry, I didn't understand the meaning of this comment. Should I remove the whole Summary section? I find that most of the documents of other plugins contain this section so I am confused.




-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   I cannot see the reason why CI failed this time because these tests don't fail when I run it locally. Besides, it seems irrelevant with the code I submitted.


-- 
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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   1. Thanks! Please merge this PR.
   2. I will work on the docs for this plugin later in another PR .


-- 
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] leslie-tsang commented on a change in pull request #6382: feat: add auth plugin for casdoor

Posted by GitBox <gi...@apache.org>.
leslie-tsang commented on a change in pull request #6382:
URL: https://github.com/apache/apisix/pull/6382#discussion_r834625457



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,444 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg == "wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf2)
+            if ok then
+                ngx.say("err")
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            ok, err = plugin.check_schema(conf3)
+            if ok then
+                ngx.say("err")

Review comment:
       `"err"` confuses me, and the information it displays does not help me understand what is happening.
   
   Maybe let's do it this way ?
   ```
   if not ok then
       ngx.say(err)
   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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then
+                    ngx.status = 200
+                    ngx.say(json_encode({ access_token = "bbbbbbbbbb", expires_in = 0 }))
+                    return
+                end
+
+                ngx.status = 200
+                ngx.say(json_encode({ access_token = "aaaaaaaaaaaaaaaa", expires_in = 1000000 }))
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+});
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local conf = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+            end
+
+            local conf2 = {
+                callback_url = callback_url .. "/?code=aaa",
+                endpoint_addr = fake_uri,
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok2, err2 = plugin.check_schema(conf2)
+            if ok2 then
+                ngx.say(err)
+            end
+
+            local conf3 = {
+                callback_url = callback_url,
+                endpoint_addr = fake_uri .. "/",
+                client_id = "7ceb9b7fda4a9061ec1c",
+                client_secret = "3416238e1edf915eac08b8fe345b2b95cdba7e04"
+            }
+            local ok3, err3 = plugin.check_schema(conf3)
+            if ok3 then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 2: enable plugin test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+
+            local fake_uri = "http://127.0.0.1:10420"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback"
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/anything/*",
+                    "plugins": {
+                        "authz-casdoor": {
+                            "callback_url":"]] .. callback_url .. [[",
+                            "endpoint_addr":"]] .. fake_uri .. [[",
+                            "client_id":"7ceb9b7fda4a9061ec1c",
+                            "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                        "httpbin.org:80": 1
+                        }
+                    }
+                }]]
+            )
+            if not code == 200 then
+                ngx.say("failed to set up routing rule")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 3: test redirect
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/anything/d?param1=foo&param2=bar', ngx.HTTP_GET, [[]])
+            if not code == 302 then
+                ngx.say("should have redirected")
+            end
+
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 4: enable fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+
+                        "uri": "/api/login/oauth/access_token",
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                }]]
+                )
+
+            if not code == 200 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: test fake casdoor
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local httpc = require("resty.http").new()
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:10420/api/login/oauth/access_token"
+
+            local res, err = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+            end
+            local data = cjson.decode(res.body)
+            if not data then
+                ngx.say("invalid res.body")
+            end
+            if not data.access_token == "aaaaaaaaaaaaaaaa" then
+                ngx.say("invalid token")
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 6: test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m, err = ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+                ngx.exit()
+            end
+            local state = m[1]
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 200 then
+                log.error(res3.status)
+            end
+            ngx.say("done")
+
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 7: incorrect test code handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state=bbb"
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(callback_url, {method = "GET"})
+            if not res1.status == 503 then
+                ngx.say(res1.status)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+no session found
+
+
+
+=== TEST 8: incorrect state handling
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.authz-casdoor")
+            local core = require("apisix.core")
+            local log = core.log
+            local t = require("lib.test_admin").test
+            local cjson = require("cjson")
+            local fake_uri = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                "/anything/d?param1=foo&param2=bar"
+            local callback_url = "http://127.0.0.1:" .. ngx.var.server_port ..
+                                    "/anything/callback?code=aaa&state="
+
+            local httpc = require("resty.http").new()
+            local res1, err1 = httpc:request_uri(fake_uri, {method = "GET"})
+            if not res1 then
+                ngx.say(err1)
+            end
+
+            local cookie = res1.headers["Set-Cookie"]
+            local re_url = res1.headers["Location"]
+            local m,err=ngx.re.match(re_url, "state=([0-9]*)")
+            if err or not m then
+                log.error(err)
+            end
+            local state = m[1]+10
+
+            local res2, err2 = httpc:request_uri(callback_url..state, {
+                method = "GET",
+                headers = {Cookie = cookie}
+            })
+            if not res2 then
+                ngx.say(err)
+            end
+            if not res2.code == 302 then
+                log.error(res2.code)
+            end
+
+            local cookie2 = res1.headers["Set-Cookie"]
+            local res3, err3 = httpc:request_uri(fake_uri, {
+                method = "GET",
+                headers = {Cookie = cookie2}
+            })
+            if not res3 then
+                ngx.say(err)
+            end
+            if not res3.status == 503 then

Review comment:
       Done




-- 
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] ComradeProgrammer commented on a change in pull request #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/authz-casdoor.lua
##########
@@ -0,0 +1,172 @@
+--
+-- 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 http = require("resty.http")
+local session = require("resty.session")
+local ngx = ngx
+local rand = math.random
+local tostring = tostring
+
+
+local plugin_name = "authz-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        -- Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf, state_in_session)
+    local args = core.request.get_uri_args(ctx)
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        body =  ngx.encode_args({
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }),
+        headers = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+    })
+
+    if not res then
+        return nil, nil, err
+    end
+    local data, err = core.json.decode(res.body)
+
+    if err or not data then
+        err = "failed to parse casdoor response data: " .. err .. ", body: " .. res.body
+        return nil, nil, err
+    end
+
+    if not data.access_token then
+        return nil, nil,
+               "failed when accessing token: no access_token contained"
+    end
+    -- In the reply of casdoor, setting expires_in to 0 indicates that the access_token is invalid.
+    if not data.expires_in or data.expires_in == 0 then
+        return nil, nil, "failed when accessing token: invalid access_token"
+    end
+
+    return data.access_token, data.expires_in, nil
+end
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.access(conf, ctx)
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+    -- step 1: check whether hits the callback
+    local m, err = ngx.re.match(conf.callback_url, ".+//[^/]+(/.*)", "jo")
+    if err or not m then
+        core.log.error(err)
+        return 503
+    end
+    local real_callback_url = m[1]
+    if current_uri == real_callback_url then
+        if not session_present then
+            err = "no session found"
+            core.log.error(err)
+            return 503
+        end
+        local state_in_session = session_obj_read.data.state
+        if not state_in_session then
+            err = "no state found in session"
+            core.log.error(err)
+            return 503
+        end
+        local args = core.request.get_uri_args(ctx)
+        if not args or not args.code or not args.state then
+            err = "failed when accessing token. Invalid code or state"
+            core.log.error(err)
+            return 400, err
+        end
+        if args.state ~= tostring(state_in_session) then
+            err = "invalid state"
+            core.log.error(err)
+            return 400, err
+        end
+        local access_token, lifetime, err =
+            fetch_access_token(ctx, conf, state_in_session)

Review comment:
       Done

##########
File path: t/plugin/authz-casdoor.t
##########
@@ -0,0 +1,447 @@
+#
+# 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();
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 10420;
+        location /api/login/oauth/access_token {
+            content_by_lua_block {
+                local json_encode = require("toolkit.json").encode
+                ngx.req.read_body()
+                local arg = ngx.req.get_post_args()["code"]
+
+                local core = require("apisix.core")
+                local log = core.log
+
+                if arg=="wrong" then

Review comment:
       Done




-- 
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 pull request #6382: feat: add auth plugin for casdoor

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


   > @tzssangglass emmm chaos test failed again, could you please rerun this test?
   
   it's ok 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] ComradeProgrammer commented on pull request #6382: feat: add auth plugin for casdoor

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


   > Hello there, Plz don't force push during the review.
   > 
   > Commit history allows reviewers to see what has changed since the last review, which is useful for them.
   > 
   > A force-push might **ruin** the review experience and progress. which may leave this PR without anyone **willing** to review. :)
   
   I see, I shall pay attention to this in the future.


-- 
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 #6382: feat: add auth plugin for casdoor

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



##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"
+local schema = {
+    type = "object",
+    properties = {
+        --Note: endpoint_addr and callback_url should not end with '/'
+        endpoint_addr = {type = "string", pattern = "^[^%?]+[^/]$"},
+        client_id = {type = "string"},
+        client_secret = {type = "string"},
+        callback_url = {type = "string", pattern = "^[^%?]+[^/]$"},
+    },
+    required = {
+        "callback_url", "endpoint_addr", "client_id", "client_secret"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2559,
+    name = plugin_name,
+    schema = schema
+}
+
+local function fetch_access_token(ctx, conf)
+    local args = core.request.get_uri_args(ctx)
+    if not args or not args.code or not args.state then
+        return nil, "failed when accessing token. Invalid cofde or state"
+    end
+    local client = http.new()
+    local url = conf.endpoint_addr .. "/api/login/oauth/access_token"
+
+    local res, err = client:request_uri(url, {
+        method = "POST",
+        query = {
+            code = args.code,
+            grant_type = "authorization_code",
+            client_id = conf.client_id,
+            client_secret = conf.client_secret
+        }
+    })
+    if not res then return nil, err end
+    local data, err = cjson.decode(res.body)
+
+    if err or not data then return nil, err end
+
+    if not data.access_token then
+        return nil, "failed when accessing token: no access_token contained"
+    end
+
+    return data.access_token, nil
+end
+
+function _M.check_schema(conf) return core.schema.check(schema, conf) end
+
+function _M.access(conf, ctx)
+    -- log.info("hit auth-casdoor access")
+    local current_uri = ctx.var.uri
+    local session_obj_read, session_present = session.open()
+
+    -- step 1: check whether hits the callback
+    if current_uri == conf.callback_url:match(".-//[^/]+(/.*)") then
+        local access_token, err = fetch_access_token(ctx, conf)
+        if access_token then
+            if not session_present then
+                return 503, "no session found"

Review comment:
       There is no test to cover this branch?

##########
File path: apisix/plugins/auth-casdoor.lua
##########
@@ -0,0 +1,122 @@
+--
+-- 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 cjson = require("cjson")
+local http = require("resty.http")
+local session = require("resty.session")
+local log = core.log
+
+local plugin_name = "auth-casdoor"

Review comment:
       Should be authz-casdoor like authz-keycloak and so on?

##########
File path: docs/en/latest/plugins/auth-casdoor.md
##########
@@ -0,0 +1,94 @@
+---
+title: auth-casdoor
+---
+
+<!--
+#
+# 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
+
+`auth-casdoor` is an authorization plugin based on [Casdoor](https://casdoor.org/). Casdoor is a centralized authentication / Single-Sign-On (SSO) platform supporting OAuth 2.0, OIDC and SAML, integrated with Casbin RBAC and ABAC permission management.
+
+## Attributes
+
+| Name        | Type   | Requirement | Default | Valid | Description                                                  |
+| ----------- | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
+| endpoint_addr  | string | required    |         |       | The url of casdoor.             |
+| client_id | string | required    |         |       | The client id in casdoor.                          |
+| client_secret       | string | required    |         |       | The client secret in casdoor.               |
+| callback_url      | string | required    |         |       | The callback url which is used to receive state and code.                            |
+
+*Note: endpoint_addr and callback_url should not end with '/'*
+
+## How To Enable
+
+You can enable the plugin on any route by giving out all four attributes mentioned above.
+
+### Example
+
+```shell
+curl "http://127.0.0.1:9080/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
+{
+  "methods": ["GET"],
+  "uri": "/anything/*",
+  "plugins": {
+    "auth-casdoor": {
+        "endpoint_addr":"http://localhost:8000",
+        "callback_url":"http://localhost:9080/anything/callback",
+        "client_id":"7ceb9b7fda4a9061ec1c",
+        "client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
+    }
+  },
+  "upstream": {
+    "type": "roundrobin",
+    "nodes": {
+      "httpbin.org:80": 1
+    }
+  }
+}'
+
+```
+
+In this example, using apisix's admin API we created a route "/anything/*" pointed to "httpbin.org:80", and with "auth-casdoor" enabled. This route is now under authentication protection of casdoor.
+
+#### Explanations about parameters of this plugin
+
+In the configuration of "auth-casdoor" plugin we can see four parameters.
+
+The first one is "callback_url". This is exactly the callback url in OAuth2. It should be emphasized that this callback url **must belong to the "uri" you specified for the route**, for example, in this example, http://localhost:9080/anything/callback obviously belong to "/anything/*". Only by this way can the visit toward callback_url can be intercepted and utilized by the plugin(so that the plugin can get the code and state in Oauth2). The logic of callback_url is implemented completely by the plugin so that there is no need to modify the server to implement this callback.
+
+The second parameter "callback_url" is obviously the url of Casdoor. The third and fourth parameters are "client_id" and "client_secret", which you can acquire from Casdoor when you register an app.

Review comment:
       Please fix 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