544 lines
18 KiB
Lua
544 lines
18 KiB
Lua
local cjson = require "cjson"
|
|
local helpers = require "spec.helpers"
|
|
local admin_api = require "spec.fixtures.admin_api"
|
|
|
|
|
|
for _, strategy in helpers.each_strategy() do
|
|
describe("Plugin: oauth2 (invalidations) [#" .. strategy .. "]", function()
|
|
local admin_client
|
|
local proxy_ssl_client
|
|
local db
|
|
|
|
lazy_setup(function()
|
|
local _
|
|
_, db = helpers.get_db_utils(strategy, {
|
|
"routes",
|
|
"services",
|
|
"consumers",
|
|
"plugins",
|
|
"oauth2_tokens",
|
|
"oauth2_credentials",
|
|
"oauth2_authorization_codes",
|
|
})
|
|
|
|
assert(helpers.start_kong({
|
|
database = strategy,
|
|
nginx_conf = "spec/fixtures/custom_nginx.template",
|
|
}))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
helpers.stop_kong()
|
|
end)
|
|
|
|
local service
|
|
local route
|
|
local plugin
|
|
local consumer
|
|
local credential
|
|
|
|
before_each(function()
|
|
service = admin_api.services:insert()
|
|
|
|
route = assert(admin_api.routes:insert {
|
|
hosts = { "oauth2.com" },
|
|
protocols = { "http", "https" },
|
|
service = service,
|
|
})
|
|
|
|
plugin = admin_api.plugins:insert {
|
|
name = "oauth2",
|
|
route = { id = route.id },
|
|
config = {
|
|
scopes = { "email", "profile" },
|
|
enable_authorization_code = true,
|
|
mandatory_scope = true,
|
|
provision_key = "provision123",
|
|
token_expiration = 5,
|
|
enable_implicit_grant = true,
|
|
},
|
|
}
|
|
|
|
consumer = admin_api.consumers:insert {
|
|
username = "bob",
|
|
}
|
|
|
|
credential = admin_api.oauth2_credentials:insert {
|
|
client_id = "clientid123",
|
|
client_secret = "secret123",
|
|
redirect_uris = { "http://google.com/kong" },
|
|
name = "testapp",
|
|
consumer = { id = consumer.id },
|
|
}
|
|
|
|
admin_client = helpers.admin_client()
|
|
proxy_ssl_client = helpers.proxy_ssl_client()
|
|
end)
|
|
|
|
after_each(function()
|
|
admin_api.oauth2_credentials:remove({ id = credential.id })
|
|
admin_api.consumers:remove({ id = consumer.id })
|
|
admin_api.plugins:remove({ id = plugin.id })
|
|
admin_api.routes:remove({ id = route.id })
|
|
admin_api.services:remove({ id = service.id })
|
|
|
|
if admin_client and proxy_ssl_client then
|
|
admin_client:close()
|
|
proxy_ssl_client:close()
|
|
end
|
|
end)
|
|
|
|
local function provision_code(client_id)
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/authorize",
|
|
body = {
|
|
provision_key = "provision123",
|
|
client_id = client_id,
|
|
scope = "email",
|
|
response_type = "code",
|
|
state = "hello",
|
|
authenticated_userid = "userid123"
|
|
},
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
local raw_body = res:read_body()
|
|
local body = cjson.decode(raw_body)
|
|
if body.redirect_uri then
|
|
local iterator, err = ngx.re.gmatch(body.redirect_uri, "^http://google\\.com/kong\\?code=([\\w]{32,32})&state=hello$")
|
|
assert.is_nil(err)
|
|
local m, err = iterator()
|
|
assert.is_nil(err)
|
|
return m[1]
|
|
end
|
|
end
|
|
|
|
describe("OAuth2 Credentials entity invalidation", function()
|
|
it("invalidates when OAuth2 Credential entity is deleted", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.response(res).has.status(200)
|
|
|
|
-- Check that cache is populated
|
|
local cache_key = db.oauth2_credentials:cache_key("clientid123")
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {},
|
|
query = { cache = "lua" },
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local credential = assert.response(res).has.jsonbody()
|
|
|
|
-- Delete OAuth2 credential (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "DELETE",
|
|
path = "/consumers/bob/oauth2/" .. credential.id,
|
|
headers = {}
|
|
})
|
|
assert.response(res).has.status(204)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- It should not work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.response(res).has.status(400)
|
|
end)
|
|
|
|
it("invalidates when OAuth2 Credential entity is updated", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- It should not work
|
|
local code = provision_code("updated_clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(400, res)
|
|
|
|
-- Check that cache is populated
|
|
local cache_key = db.oauth2_credentials:cache_key("clientid123")
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {}
|
|
})
|
|
local credential = cjson.decode(assert.res_status(200, res))
|
|
|
|
-- Update OAuth2 credential (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "PATCH",
|
|
path = "/consumers/bob/oauth2/" .. credential.id,
|
|
body = {
|
|
client_id = "updated_clientid123"
|
|
},
|
|
headers = {
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- It should work
|
|
local code = provision_code("updated_clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "updated_clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- It should not work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(400, res)
|
|
end)
|
|
end)
|
|
|
|
describe("Consumer entity invalidation", function()
|
|
it("invalidates when Consumer entity is deleted", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
local token = cjson.decode(assert.res_status(200, res))
|
|
assert.is_table(token)
|
|
|
|
-- Check that cache is populated
|
|
local cache_key = db.oauth2_credentials:cache_key("clientid123")
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- The token should work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- Delete Consumer (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "DELETE",
|
|
path = "/consumers/bob",
|
|
headers = {}
|
|
})
|
|
assert.res_status(204, res)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- It should not work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(400, res)
|
|
|
|
-- The route should not be consumed anymore
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(401, res)
|
|
end)
|
|
end)
|
|
|
|
describe("OAuth2 access token entity invalidation", function()
|
|
it("invalidates when OAuth2 token entity is deleted", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
local token = cjson.decode(assert.res_status(200, res))
|
|
assert.is_table(token)
|
|
|
|
-- The token should work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
local cache_key
|
|
helpers.wait_until(function()
|
|
-- Check that cache is populated
|
|
cache_key = db.oauth2_tokens:cache_key(token.access_token)
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {}
|
|
})
|
|
|
|
-- Discard body in case we need to retry. Otherwise the connection will mess up.
|
|
res:read_body()
|
|
if res.status ~= 200 then
|
|
assert.logfile().has.no.line("[error]", true)
|
|
end
|
|
return 200 == res.status
|
|
end, 5)
|
|
|
|
local res = db.oauth2_tokens:select_by_access_token(token.access_token)
|
|
local token_id = res.id
|
|
assert.is_string(token_id)
|
|
|
|
-- Delete token (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "DELETE",
|
|
path = "/oauth2_tokens/" .. token_id,
|
|
headers = {}
|
|
})
|
|
assert.res_status(204, res)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- It should not work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(401, res)
|
|
end)
|
|
|
|
it("invalidates when Oauth2 token entity is updated", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
local token = cjson.decode(assert.res_status(200, res))
|
|
assert.is_table(token)
|
|
|
|
helpers.wait_for_all_config_update()
|
|
|
|
-- The token should work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- Check that cache is populated
|
|
local cache_key = db.oauth2_tokens:cache_key(token.access_token)
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
local res = db.oauth2_tokens:select_by_access_token(token.access_token)
|
|
local token_id = res.id
|
|
assert.is_string(token_id)
|
|
|
|
-- Update OAuth 2 token (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "PATCH",
|
|
path = "/oauth2_tokens/" .. token_id,
|
|
body = {
|
|
access_token = "updated_token"
|
|
},
|
|
headers = {
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- It should work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=updated_token",
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- It should not work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(401, res)
|
|
end)
|
|
end)
|
|
|
|
describe("OAuth2 client entity invalidation", function()
|
|
it("invalidates token when OAuth2 client entity is deleted", function()
|
|
-- It should properly work
|
|
local code = provision_code("clientid123")
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "POST",
|
|
path = "/oauth2/token",
|
|
body = { code = code, client_id = "clientid123", client_secret = "secret123", grant_type = "authorization_code" },
|
|
headers = {
|
|
["Host"] = "oauth2.com",
|
|
["Content-Type"] = "application/json"
|
|
}
|
|
})
|
|
local token = cjson.decode(assert.res_status(200, res))
|
|
assert.is_table(token)
|
|
|
|
-- The token should work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- Check that cache is populated
|
|
local cache_key = db.oauth2_tokens:cache_key(token.access_token)
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key,
|
|
headers = {}
|
|
})
|
|
assert.res_status(200, res)
|
|
|
|
-- Retrieve credential ID
|
|
local cache_key_credential = db.oauth2_credentials:cache_key("clientid123")
|
|
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/cache/" .. cache_key_credential,
|
|
headers = {}
|
|
})
|
|
local credential = cjson.decode(assert.res_status(200, res))
|
|
|
|
-- Delete OAuth2 client (which triggers invalidation)
|
|
local res = assert(admin_client:send {
|
|
method = "DELETE",
|
|
path = "/consumers/bob/oauth2/" .. credential.id,
|
|
headers = {}
|
|
})
|
|
assert.res_status(204, res)
|
|
|
|
-- ensure cache is invalidated
|
|
helpers.wait_for_invalidation(cache_key)
|
|
|
|
-- it should not work
|
|
local res = assert(proxy_ssl_client:send {
|
|
method = "GET",
|
|
path = "/status/200?access_token=" .. token.access_token,
|
|
headers = {
|
|
["Host"] = "oauth2.com"
|
|
}
|
|
})
|
|
assert.res_status(401, res)
|
|
end)
|
|
end)
|
|
end)
|
|
end
|