kong/spec/03-plugins/25-oauth2/04-invalidations_spec.lua (431 lines of code) (raw):

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