forked from github/dataease
Merge pull request #10586 from dataease/pr@dev-v2_add_cas_plugin
feat: 添加 APISIX CAS 插件相关
This commit is contained in:
commit
d24ebafc19
199
installer/dataease/apisix/plugins/cas-auth.lua
Normal file
199
installer/dataease/apisix/plugins/cas-auth.lua
Normal file
@ -0,0 +1,199 @@
|
||||
--
|
||||
---- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
---- contributor license agreements. See the NOTICE file distributed with
|
||||
---- this work for additional information regarding copyright ownership.
|
||||
---- The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
---- (the "License"); you may not use this file except in compliance with
|
||||
---- the License. You may obtain a copy of the License at
|
||||
----
|
||||
---- http://www.apache.org/licenses/LICENSE-2.0
|
||||
----
|
||||
---- Unless required by applicable law or agreed to in writing, software
|
||||
---- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
---- WITHOUT 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)
|
||||
core.request.set_header("X-CAS-USER", user)
|
||||
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>", "jo");
|
||||
if m then
|
||||
return m[1]
|
||||
end
|
||||
else
|
||||
core.log.info("CAS serviceValidate failed: ", res.body)
|
||||
end
|
||||
else
|
||||
core.log.error("validate ticket failed: status=", (res and res.status),
|
||||
", has_body=", (res and res.body ~= nil or false), ", 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 = core.request.get_method()
|
||||
local uri = ctx.var.uri
|
||||
|
||||
if method == "GET" and uri == conf.logout_uri then
|
||||
return logout(conf, ctx)
|
||||
end
|
||||
|
||||
if method == "POST" and uri == conf.cas_callback_uri then
|
||||
local data = core.request.get_body()
|
||||
local ticket = data:match("<samlp:SessionIndex>(.*)</samlp:SessionIndex>")
|
||||
if ticket == nil then
|
||||
return ngx.HTTP_BAD_REQUEST,
|
||||
{message = "invalid logout request from IdP, no ticket"}
|
||||
end
|
||||
core.log.info("Back-channel logout (SLO) from IdP: LogoutRequest: ", data)
|
||||
local session_id = ticket
|
||||
local user = store:get(session_id);
|
||||
if user then
|
||||
store:delete(session_id)
|
||||
core.log.info("SLO: user=", user, ", tocket=", ticket)
|
||||
end
|
||||
else
|
||||
local session_id = get_session_id(ctx)
|
||||
if session_id ~= nil then
|
||||
return with_session_id(conf, ctx, session_id)
|
||||
end
|
||||
|
||||
local ticket = ctx.var.arg_ticket
|
||||
if ticket ~= nil and uri == conf.cas_callback_uri then
|
||||
return validate_with_cas(conf, ctx, ticket)
|
||||
else
|
||||
return first_access(conf, ctx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
@ -21,6 +21,7 @@ services:
|
||||
volumes:
|
||||
- ${DE_BASE}/dataease2.0/apisix/apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
|
||||
- ${DE_BASE}/dataease2.0/apisix/logs:/usr/local/apisix/logs
|
||||
- ${DE_BASE}/dataease2.0/apisix/plugins/cas-auth.lua:/usr/local/apisix/apisix/plugins/cas-auth.lua
|
||||
depends_on:
|
||||
- etcd
|
||||
ports:
|
||||
|
Loading…
Reference in New Issue
Block a user