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/09/16 08:30:41 UTC

[GitHub] [apisix] kingluo opened a new pull request, #7932: feat: add cas-auth plugin

kingluo opened a new pull request, #7932:
URL: https://github.com/apache/apisix/pull/7932

   ### Description
   
   Implement cas-auth plugin
   
   ### Checklist
   
   - [x] I have explained the need for this PR and the problem it solves
   - [x] I have explained the changes or the new features added to this PR
   - [x] I have added tests corresponding to this change
   - [x] I have updated the documentation to reflect this change
   - [x] I have verified that this change is backward compatible (If not, please discuss on the [APISIX mailing list](https://github.com/apache/apisix/tree/master#community) first)
   
   <!--
   
   Note
   
   1. Mark the PR as draft until it's ready to be reviewed.
   2. Always add/update tests for any changes unless you have a good reason.
   3. Always update the documentation to reflect the changes made in the PR.
   4. Make a new commit to resolve conversations instead of `push -f`.
   5. To resolve merge conflicts, merge master instead of rebasing.
   6. Use "request review" to notify the reviewer after making changes.
   7. Only a reviewer can mark a conversation as resolved.
   
   -->
   


-- 
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 #7932: feat: add cas-auth plugin

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

   Please address https://github.com/apache/apisix/pull/7932#discussion_r974910582


-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974093272


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,193 @@
+--
+---- 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 ngx = ngx
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)
+    end
+    return nil
+end
+
+local function validate_with_cas(conf, ctx, ticket)
+    local user = validate(conf, ctx, ticket)
+    if user and set_store_and_cookie(ticket, user) then
+        local request_uri = ctx.var["cookie_" .. CAS_REQUEST_URI]
+        set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0")
+        core.log.info("ticket: ", ticket,
+            ", cookie: ", ctx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user)
+        core.response.set_header("Location", request_uri)
+        return ngx.HTTP_MOVED_TEMPORARILY
+    else
+        return ngx.HTTP_UNAUTHORIZED, {message = "invalid ticket"}
+    end
+end
+
+local function logout(conf, ctx)
+    local session_id = get_session_id(ctx)
+    if session_id == nil then
+        return ngx.HTTP_UNAUTHORIZED
+    end
+
+    core.log.info("logout: ticket=", session_id, ", cookie=", ctx.var.http_cookie)
+    store:delete(session_id)
+    set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+
+    core.response.set_header("Location", conf.idp_uri .. "/logout")
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+function _M.access(conf, ctx)
+    local method = ngx.req.get_method()

Review Comment:
   ```suggestion
       local method = core.request.get_method()
   ```



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

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

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


[GitHub] [apisix] tzssangglass commented on a diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974096657


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,193 @@
+--
+---- 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 ngx = ngx
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)
+    end
+    return nil
+end
+
+local function validate_with_cas(conf, ctx, ticket)
+    local user = validate(conf, ctx, ticket)
+    if user and set_store_and_cookie(ticket, user) then
+        local request_uri = ctx.var["cookie_" .. CAS_REQUEST_URI]
+        set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0")
+        core.log.info("ticket: ", ticket,
+            ", cookie: ", ctx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user)
+        core.response.set_header("Location", request_uri)
+        return ngx.HTTP_MOVED_TEMPORARILY
+    else
+        return ngx.HTTP_UNAUTHORIZED, {message = "invalid ticket"}
+    end
+end
+
+local function logout(conf, ctx)
+    local session_id = get_session_id(ctx)
+    if session_id == nil then
+        return ngx.HTTP_UNAUTHORIZED
+    end
+
+    core.log.info("logout: ticket=", session_id, ", cookie=", ctx.var.http_cookie)
+    store:delete(session_id)
+    set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+
+    core.response.set_header("Location", conf.idp_uri .. "/logout")
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+function _M.access(conf, ctx)
+    local method = ngx.req.get_method()
+    local uri = ctx.var.uri
+
+    if method == "GET" and uri == conf.logout_uri then
+        return logout(conf, ctx)
+    elseif method == "POST" and uri == conf.cas_callback_uri then
+        ngx.req.read_body()
+        local data = ngx.req.get_body_data()

Review Comment:
   use `core.request.get_body()`



-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974101017


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,193 @@
+--
+---- 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 ngx = ngx
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");

Review Comment:
   ```
   local local ngx_re_match      = ngx.re.match
   
   local m = ngx_re_match(res.body, "<cas:user>(.*?)</cas:user>");
   ```



-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974094738


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,193 @@
+--
+---- 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 ngx = ngx
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)
+    end
+    return nil
+end
+
+local function validate_with_cas(conf, ctx, ticket)
+    local user = validate(conf, ctx, ticket)
+    if user and set_store_and_cookie(ticket, user) then
+        local request_uri = ctx.var["cookie_" .. CAS_REQUEST_URI]
+        set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0")
+        core.log.info("ticket: ", ticket,
+            ", cookie: ", ctx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user)
+        core.response.set_header("Location", request_uri)
+        return ngx.HTTP_MOVED_TEMPORARILY
+    else
+        return ngx.HTTP_UNAUTHORIZED, {message = "invalid ticket"}
+    end
+end
+
+local function logout(conf, ctx)
+    local session_id = get_session_id(ctx)
+    if session_id == nil then
+        return ngx.HTTP_UNAUTHORIZED
+    end
+
+    core.log.info("logout: ticket=", session_id, ", cookie=", ctx.var.http_cookie)
+    store:delete(session_id)
+    set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+
+    core.response.set_header("Location", conf.idp_uri .. "/logout")
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+function _M.access(conf, ctx)
+    local method = ngx.req.get_method()
+    local uri = ctx.var.uri
+
+    if method == "GET" and uri == conf.logout_uri then
+        return logout(conf, ctx)
+    elseif method == "POST" and uri == conf.cas_callback_uri then

Review Comment:
   ```suggestion
       if method == "GET" and uri == conf.logout_uri then
           return logout(conf, ctx)
       end
       
       if method == "POST" and uri == conf.cas_callback_uri then
   ```
   
   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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974106625


##########
docs/en/latest/plugins/cas-auth.md:
##########
@@ -0,0 +1,107 @@
+---
+title: cas-auth
+keywords:
+  - APISIX
+  - Plugin
+  - CAS AUTH
+  - cas-auth
+description: This document contains information about the Apache APISIX cas-auth Plugin.
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Description
+
+The `cas-auth` Plugin can be used to access CAS (Central Authentication Service 2.0) IdP (Identity Provider)
+to do authentication, from the SP (service provider) perspective.
+
+## Attributes
+
+| Name      | Type | Required      | Description |
+| ----------- | ----------- | ----------- | ----------- |
+| `idp_uri`      | string       | True      | URI of IdP.       |
+| `cas_callback_uri`      | string       | True      | redirect uri used to callback the SP from IdP after login or logout.       |
+| `logout_uri`      | string       | True      | logout uri to trigger logout.       |
+
+## Enabling the Plugin
+
+You can enable the Plugin on a specific Route as shown below:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/cas1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET", "POST"],
+    "host" : "127.0.0.1",
+    "uri": "/anything/*",
+    "plugins": {
+          "cas-auth": {
+              "idp_uri": "http://127.0.0.1:8080/realms/test/protocol/cas",
+              "cas_callback_uri": "/anything/cas_callback",
+              "logout_uri": "/anything/logout"
+          }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "httpbin.org": 1
+        }
+    }
+}'
+
+```
+
+## Configuration description
+
+Once you have enabled the Plugin, a new user visiting this Route would first be processed by the `cas-auth` Plugin.
+If no login session exists, the user would be redirected to the login page of `idp_uri`.
+
+After successfully logging in from IdP, IdP will redirect this user to the `cas_callback_uri` with
+GET parameters CAS ticket specified. If the ticket gets verified, the login session would be created.
+
+This process is only done once and subsequent requests are left uninterrupted.
+Once this is done, the user is redirected to the original URL they wanted to visit.
+
+Later, the user could visit `logout_uri` to start logout process. The user would be redirected to `idp_uri` to do logout.
+
+Note that, `cas_callback_uri` and `logout_uri` should be
+either full qualified address (e.g. `http://127.0.0.1:9080/anything/logout`),
+or path only (e.g. `/anything/logout`), but it is recommended to be path only to keep consistent.
+
+These uris need to be captured by the route where the current APISIX is located.
+For example, if the `uri` of the current route is `/api/v1/*`, `cas_callback_uri` can be filled in as `/api/v1/cas_callback`.
+
+## Disable Plugin
+
+To disable the `cas-auth` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/cas1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET", "POST"],
+    "uri": "/anything/*",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "httpbin.org:80": 1
+        }
+    }
+}'
+```

Review Comment:
   Can we add the process of how to start and configure IdP, as well as show some key images.



-- 
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 #7932: feat: add cas-auth plugin

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

   Please make the CI pass, 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] kingluo commented on a diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
kingluo commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974933945


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,197 @@
+--
+---- 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 ngx = ngx
+local ngx_re_match = ngx.re.match
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        else
+            core.log.error("CAS cookie store: ", err)
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx_re_match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)

Review Comment:
   Only if `res and res.status == ngx.HTTP_OK and res.body ~= nil`, the validate process is successful. Otherwise, log the `res` and `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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
spacewander commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r975096167


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,197 @@
+--
+---- 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 ngx = ngx
+local ngx_re_match = ngx.re.match
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        else
+            core.log.error("CAS cookie store: ", err)
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx_re_match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)

Review Comment:
   @kingluo 
   Yes, but if res is not nil, will it raise an error when logging a table directly?



-- 
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] kingluo commented on a diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
kingluo commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974255997


##########
docs/en/latest/plugins/cas-auth.md:
##########
@@ -0,0 +1,107 @@
+---
+title: cas-auth
+keywords:
+  - APISIX
+  - Plugin
+  - CAS AUTH
+  - cas-auth
+description: This document contains information about the Apache APISIX cas-auth Plugin.
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Description
+
+The `cas-auth` Plugin can be used to access CAS (Central Authentication Service 2.0) IdP (Identity Provider)
+to do authentication, from the SP (service provider) perspective.
+
+## Attributes
+
+| Name      | Type | Required      | Description |
+| ----------- | ----------- | ----------- | ----------- |
+| `idp_uri`      | string       | True      | URI of IdP.       |
+| `cas_callback_uri`      | string       | True      | redirect uri used to callback the SP from IdP after login or logout.       |
+| `logout_uri`      | string       | True      | logout uri to trigger logout.       |
+
+## Enabling the Plugin
+
+You can enable the Plugin on a specific Route as shown below:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/cas1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET", "POST"],
+    "host" : "127.0.0.1",
+    "uri": "/anything/*",
+    "plugins": {
+          "cas-auth": {
+              "idp_uri": "http://127.0.0.1:8080/realms/test/protocol/cas",
+              "cas_callback_uri": "/anything/cas_callback",
+              "logout_uri": "/anything/logout"
+          }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "httpbin.org": 1
+        }
+    }
+}'
+
+```
+
+## Configuration description
+
+Once you have enabled the Plugin, a new user visiting this Route would first be processed by the `cas-auth` Plugin.
+If no login session exists, the user would be redirected to the login page of `idp_uri`.
+
+After successfully logging in from IdP, IdP will redirect this user to the `cas_callback_uri` with
+GET parameters CAS ticket specified. If the ticket gets verified, the login session would be created.
+
+This process is only done once and subsequent requests are left uninterrupted.
+Once this is done, the user is redirected to the original URL they wanted to visit.
+
+Later, the user could visit `logout_uri` to start logout process. The user would be redirected to `idp_uri` to do logout.
+
+Note that, `cas_callback_uri` and `logout_uri` should be
+either full qualified address (e.g. `http://127.0.0.1:9080/anything/logout`),
+or path only (e.g. `/anything/logout`), but it is recommended to be path only to keep consistent.
+
+These uris need to be captured by the route where the current APISIX is located.
+For example, if the `uri` of the current route is `/api/v1/*`, `cas_callback_uri` can be filled in as `/api/v1/cas_callback`.
+
+## Disable Plugin
+
+To disable the `cas-auth` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/cas1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET", "POST"],
+    "uri": "/anything/*",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "httpbin.org:80": 1
+        }
+    }
+}'
+```

Review Comment:
   I think that should be left to blog. I would link the blog into the doc later.



-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974100264


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,193 @@
+--
+---- 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 ngx = ngx
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        end

Review Comment:
   log the origin `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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
spacewander commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r973888027


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])
+    table.insert(h, cookie_str)
+    ngx.header['Set-Cookie'] = h
+end
+
+local function uri_without_ticket(conf)
+    return ngx.var.scheme .. "://" .. ngx.var.host .. ":" ..

Review Comment:
   Could you use `ctx.var` instead of `ngx.var`?



##########
.github/workflows/build.yml:
##########
@@ -97,6 +97,10 @@ jobs:
           rm -rf $(ls -1 --ignore=*.tgz --ignore=ci --ignore=t --ignore=utils --ignore=.github)
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
+      - name: download keycloak cas provider
+        run: |
+          sudo wget https://github.com/jacekkow/keycloak-protocol-cas/releases/download/18.0.2/keycloak-protocol-cas-18.0.2.jar -O /opt/keycloak-protocol-cas-18.0.2.jar

Review Comment:
   Let's move it to https://github.com/apache/apisix/blob/master/ci/init-plugin-test-service.sh



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])
+    table.insert(h, cookie_str)
+    ngx.header['Set-Cookie'] = h
+end
+
+local function uri_without_ticket(conf)
+    return ngx.var.scheme .. "://" .. ngx.var.host .. ":" ..
+        ngx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id()
+    return ngx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    set_cookie(name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf) })
+    ngx.log(ngx.INFO, "first access: ", login_uri,
+        ", cookie: ", ngx.var.http_cookie, ", request_uri: ", ngx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ngx.var.request_uri)
+    ngx.redirect(login_uri, ngx.HTTP_MOVED_TEMPORARILY)
+end
+
+local function with_session_id(conf, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    ngx.log(ngx.INFO, "ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        first_access(conf)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            ngx.log(ngx.INFO, "CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            ngx.log(ngx.EMERG, "CAS cookie store is out of memory")
+        elseif err == "exists" then
+            ngx.log(ngx.ERR, "Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate", { query = { ticket = ticket, service = uri_without_ticket(conf) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            ngx.log(ngx.INFO, "CAS serviceValidate failed: " .. res.body)

Review Comment:
   `"CAS serviceValidate failed: ", res.body` is enough



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])

Review Comment:
   We can use `core.response.add_header` if we don't check if the same cookie exists?



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])
+    table.insert(h, cookie_str)
+    ngx.header['Set-Cookie'] = h
+end
+
+local function uri_without_ticket(conf)
+    return ngx.var.scheme .. "://" .. ngx.var.host .. ":" ..
+        ngx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id()
+    return ngx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    set_cookie(name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf) })
+    ngx.log(ngx.INFO, "first access: ", login_uri,
+        ", cookie: ", ngx.var.http_cookie, ", request_uri: ", ngx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ngx.var.request_uri)
+    ngx.redirect(login_uri, ngx.HTTP_MOVED_TEMPORARILY)
+end
+
+local function with_session_id(conf, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    ngx.log(ngx.INFO, "ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        first_access(conf)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            ngx.log(ngx.INFO, "CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            ngx.log(ngx.EMERG, "CAS cookie store is out of memory")
+        elseif err == "exists" then
+            ngx.log(ngx.ERR, "Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate", { query = { ticket = ticket, service = uri_without_ticket(conf) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx.re.match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            ngx.log(ngx.INFO, "CAS serviceValidate failed: " .. res.body)
+        end
+    else
+        ngx.log(ngx.ERR, "validate ticket failed: res=", res, ", err=", err)
+    end
+    return nil
+end
+
+local function validate_with_cas(conf, ticket)
+    local user = validate(conf, ticket)
+    if user and set_store_and_cookie(ticket, user) then
+        local request_uri = ngx.var["cookie_" .. CAS_REQUEST_URI]
+        set_our_cookie(CAS_REQUEST_URI, "deleted; Max-Age=0")
+        ngx.log(ngx.INFO, "ticket: ", ticket,
+            ", cookie: ", ngx.var.http_cookie, ", request_uri: ", request_uri, ", user=", user)
+        ngx.redirect(request_uri, ngx.HTTP_MOVED_TEMPORARILY)
+    else
+        return ngx.HTTP_UNAUTHORIZED, {message = "invalid ticket"}
+    end
+end
+
+local function logout(conf)
+    local session_id = get_session_id()
+    if session_id == nil then
+        return ngx.HTTP_UNAUTHORIZED
+    end
+
+    ngx.log(ngx.INFO, "logout: ticket=", session_id, ", cookie=", ngx.var.http_cookie)
+    store:delete(session_id)
+    set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+
+    ngx.redirect(conf.idp_uri .. "/logout")

Review Comment:
   ngx.redirect works like `ngx.exit`. As we avoid `ngx.exit`, we prefer to do it by ourselves, see
   https://github.com/apache/apisix/blob/9129572f6302300ddeee3f2234393445d8ead781/apisix/plugins/redirect.lua#L257 



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])
+    table.insert(h, cookie_str)
+    ngx.header['Set-Cookie'] = h
+end
+
+local function uri_without_ticket(conf)
+    return ngx.var.scheme .. "://" .. ngx.var.host .. ":" ..
+        ngx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id()
+    return ngx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    set_cookie(name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf) })
+    ngx.log(ngx.INFO, "first access: ", login_uri,
+        ", cookie: ", ngx.var.http_cookie, ", request_uri: ", ngx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ngx.var.request_uri)
+    ngx.redirect(login_uri, ngx.HTTP_MOVED_TEMPORARILY)
+end
+
+local function with_session_id(conf, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    ngx.log(ngx.INFO, "ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        first_access(conf)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            ngx.log(ngx.INFO, "CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            ngx.log(ngx.EMERG, "CAS cookie store is out of memory")
+        elseif err == "exists" then
+            ngx.log(ngx.ERR, "Same CAS ticket validated twice, this should never happen!")
+        end
+    end
+    return success
+end
+
+local function validate(conf, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate", { query = { ticket = ticket, service = uri_without_ticket(conf) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if string.find(res.body, "<cas:authenticationSuccess>") then

Review Comment:
   Let's use core.string



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,208 @@
+--
+---- 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 ngx = ngx
+local type = type
+local table = table
+local string = string
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function to_table(v)
+    if v == nil then
+        return {}
+    elseif type(v) == "table" then
+        return v
+    else
+        return {v}
+    end
+end
+
+local function set_cookie(cookie_str)
+    local h = to_table(ngx.header['Set-Cookie'])
+    table.insert(h, cookie_str)
+    ngx.header['Set-Cookie'] = h
+end
+
+local function uri_without_ticket(conf)
+    return ngx.var.scheme .. "://" .. ngx.var.host .. ":" ..
+        ngx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id()
+    return ngx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    set_cookie(name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf) })
+    ngx.log(ngx.INFO, "first access: ", login_uri,

Review Comment:
   Please use `core.log`



-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
spacewander commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974910582


##########
.github/workflows/build.yml:
##########
@@ -97,6 +97,10 @@ jobs:
           rm -rf $(ls -1 --ignore=*.tgz --ignore=ci --ignore=t --ignore=utils --ignore=.github)
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
+      - name: download keycloak cas provider
+        run: |
+          sudo wget https://github.com/jacekkow/keycloak-protocol-cas/releases/download/18.0.2/keycloak-protocol-cas-18.0.2.jar -O /opt/keycloak-protocol-cas-18.0.2.jar

Review Comment:
   @kingluo 
   What about moving it into `Start CI env (PLUGIN_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 merged pull request #7932: feat: add cas-auth plugin

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


-- 
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 #7932: feat: add cas-auth plugin

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

   Before we can merge it, what is your idea about https://github.com/apache/apisix/pull/7932#discussion_r975096167?


-- 
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 diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
spacewander commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r974913245


##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,197 @@
+--
+---- 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 ngx = ngx
+local ngx_re_match = ngx.re.match
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        else
+            core.log.error("CAS cookie store: ", err)
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx_re_match(res.body, "<cas:user>(.*?)</cas:user>");
+            if m then
+                return m[1]
+            end
+        else
+            core.log.info("CAS serviceValidate failed: ", res.body)
+        end
+    else
+        core.log.error("validate ticket failed: res=", res, ", err=", err)

Review Comment:
   This may cause an error if res.status ~= ngx.HTTP_OK?



##########
apisix/plugins/cas-auth.lua:
##########
@@ -0,0 +1,197 @@
+--
+---- 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 ngx = ngx
+local ngx_re_match = ngx.re.match
+
+local CAS_REQUEST_URI = "CAS_REQUEST_URI"
+local COOKIE_NAME = "CAS_SESSION"
+local COOKIE_PARAMS = "; Path=/; HttpOnly"
+local SESSION_LIFETIME = 3600
+local STORE_NAME = "cas_sessions"
+
+local store = ngx.shared[STORE_NAME]
+
+
+local plugin_name = "cas-auth"
+local schema = {
+    type = "object",
+    properties = {
+        idp_uri = {type = "string"},
+        cas_callback_uri = {type = "string"},
+        logout_uri = {type = "string"},
+    },
+    required = {
+        "idp_uri", "cas_callback_uri", "logout_uri"
+    }
+}
+
+local _M = {
+    version = 0.1,
+    priority = 2597,
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+local function uri_without_ticket(conf, ctx)
+    return ctx.var.scheme .. "://" .. ctx.var.host .. ":" ..
+        ctx.var.server_port .. conf.cas_callback_uri
+end
+
+local function get_session_id(ctx)
+    return ctx.var["cookie_" .. COOKIE_NAME]
+end
+
+local function set_our_cookie(name, val)
+    core.response.add_header("Set-Cookie", name .. "=" .. val .. COOKIE_PARAMS)
+end
+
+local function first_access(conf, ctx)
+    local login_uri = conf.idp_uri .. "/login?" ..
+        ngx.encode_args({ service = uri_without_ticket(conf, ctx) })
+    core.log.info("first access: ", login_uri,
+        ", cookie: ", ctx.var.http_cookie, ", request_uri: ", ctx.var.request_uri)
+    set_our_cookie(CAS_REQUEST_URI, ctx.var.request_uri)
+    core.response.set_header("Location", login_uri)
+    return ngx.HTTP_MOVED_TEMPORARILY
+end
+
+local function with_session_id(conf, ctx, session_id)
+    -- does the cookie exist in our store?
+    local user = store:get(session_id);
+    core.log.info("ticket=", session_id, ", user=", user)
+    if user == nil then
+        set_our_cookie(COOKIE_NAME, "deleted; Max-Age=0")
+        return first_access(conf, ctx)
+    else
+        -- refresh the TTL
+        store:set(session_id, user, SESSION_LIFETIME)
+    end
+end
+
+local function set_store_and_cookie(session_id, user)
+    -- place cookie into cookie store
+    local success, err, forcible = store:add(session_id, user, SESSION_LIFETIME)
+    if success then
+        if forcible then
+            core.log.info("CAS cookie store is out of memory")
+        end
+        set_our_cookie(COOKIE_NAME, session_id)
+    else
+        if err == "no memory" then
+            core.log.emerg("CAS cookie store is out of memory")
+        elseif err == "exists" then
+            core.log.error("Same CAS ticket validated twice, this should never happen!")
+        else
+            core.log.error("CAS cookie store: ", err)
+        end
+    end
+    return success
+end
+
+local function validate(conf, ctx, ticket)
+    -- send a request to CAS to validate the ticket
+    local httpc = http.new()
+    local res, err = httpc:request_uri(conf.idp_uri ..
+        "/serviceValidate",
+        { query = { ticket = ticket, service = uri_without_ticket(conf, ctx) } })
+
+    if res and res.status == ngx.HTTP_OK and res.body ~= nil then
+        if core.string.find(res.body, "<cas:authenticationSuccess>") then
+            local m = ngx_re_match(res.body, "<cas:user>(.*?)</cas:user>");

Review Comment:
   Missing `jo` in the re match?



-- 
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] kingluo commented on a diff in pull request #7932: feat: add cas-auth plugin

Posted by GitBox <gi...@apache.org>.
kingluo commented on code in PR #7932:
URL: https://github.com/apache/apisix/pull/7932#discussion_r973928162


##########
.github/workflows/build.yml:
##########
@@ -97,6 +97,10 @@ jobs:
           rm -rf $(ls -1 --ignore=*.tgz --ignore=ci --ignore=t --ignore=utils --ignore=.github)
           tar zxvf ${{ steps.branch_env.outputs.fullname }}
 
+      - name: download keycloak cas provider
+        run: |
+          sudo wget https://github.com/jacekkow/keycloak-protocol-cas/releases/download/18.0.2/keycloak-protocol-cas-18.0.2.jar -O /opt/keycloak-protocol-cas-18.0.2.jar

Review Comment:
   @spacewander No, that scirpt runs after docker compose, but this download file must be set as volume in docker compose file.



-- 
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