Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions database/migrations/cassandra/2015-06-09-170921_0.4.0.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ local Migration = {
CREATE TABLE IF NOT EXISTS oauth2_authorization_codes(
id uuid,
code text,
authenticated_username text,
authenticated_userid text,
scope text,
created_at timestamp,
PRIMARY KEY (id)
) WITH default_time_to_live = 300;

CREATE INDEX IF NOT EXISTS ON oauth2_authorization_codes(code);
CREATE INDEX IF NOT EXISTS ON oauth2_authorization_codes(authenticated_userid);

CREATE TABLE IF NOT EXISTS oauth2_tokens(
id uuid,
Expand All @@ -37,7 +37,6 @@ local Migration = {
token_type text,
refresh_token text,
expires_in int,
authenticated_username text,
authenticated_userid text,
scope text,
created_at timestamp,
Expand All @@ -46,6 +45,7 @@ local Migration = {

CREATE INDEX IF NOT EXISTS ON oauth2_tokens(access_token);
CREATE INDEX IF NOT EXISTS ON oauth2_tokens(refresh_token);
CREATE INDEX IF NOT EXISTS ON oauth2_tokens(authenticated_userid);

]]
end,
Expand Down
14 changes: 6 additions & 8 deletions kong/plugins/oauth2/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,17 @@ local GRANT_TYPE = "grant_type"
local GRANT_AUTHORIZATION_CODE = "authorization_code"
local GRANT_REFRESH_TOKEN = "refresh_token"
local ERROR = "error"
local AUTHENTICATED_USERNAME = "authenticated_username"
local AUTHENTICATED_USERID = "authenticated_userid"

local AUTHORIZE_URL = "^%s/oauth2/authorize/?$"
local TOKEN_URL = "^%s/oauth2/token/?$"

-- TODO: Expire token (using TTL ?)
local function generate_token(conf, credential, authenticated_username, authenticated_userid, scope, state, expiration)
local function generate_token(conf, credential, authenticated_userid, scope, state, expiration)
local token_expiration = expiration or conf.token_expiration

local token, err = dao.oauth2_tokens:insert({
credential_id = credential.id,
authenticated_username = authenticated_username,
authenticated_userid = authenticated_userid,
expires_in = token_expiration,
scope = scope
Expand Down Expand Up @@ -86,6 +84,8 @@ local function authorize(conf)

if conf.provision_key ~= parameters.provision_key then
response_params = {[ERROR] = "invalid_provision_key", error_description = "Invalid Kong provision_key"}
elseif not parameters.authenticated_userid or stringy.strip(parameters.authenticated_userid) == "" then
response_params = {[ERROR] = "invalid_authenticated_userid", error_description = "Missing authenticated_userid parameter"}
else
local response_type = parameters[RESPONSE_TYPE]
-- Check response_type
Expand Down Expand Up @@ -121,7 +121,6 @@ local function authorize(conf)
if not response_params[ERROR] then
if response_type == CODE then
local authorization_code, err = dao.oauth2_authorization_codes:insert({
authenticated_username = parameters[AUTHENTICATED_USERNAME],
authenticated_userid = parameters[AUTHENTICATED_USERID],
scope = table.concat(scopes, " ")
})
Expand All @@ -135,7 +134,7 @@ local function authorize(conf)
}
else
-- Implicit grant, override expiration to zero
response_params = generate_token(conf, client, parameters[AUTHENTICATED_USERNAME], parameters[AUTHENTICATED_USERID], table.concat(scopes, " "), state, 0)
response_params = generate_token(conf, client, parameters[AUTHENTICATED_USERID], table.concat(scopes, " "), state, 0)
end
end
end
Expand Down Expand Up @@ -186,15 +185,15 @@ local function issue_token(conf)
if not authorization_code then
response_params = {[ERROR] = "invalid_request", error_description = "Invalid "..CODE}
else
response_params = generate_token(conf, client, authorization_code.authenticated_username, authorization_code.authenticated_userid, authorization_code.scope, state)
response_params = generate_token(conf, client, authorization_code.authenticated_userid, authorization_code.scope, state)
end
elseif grant_type == GRANT_REFRESH_TOKEN then
local refresh_token = parameters[REFRESH_TOKEN]
local token = refresh_token and dao.oauth2_tokens:find_by_keys({refresh_token = refresh_token})[1] or nil
if not token then
response_params = {[ERROR] = "invalid_request", error_description = "Invalid "..REFRESH_TOKEN}
else
response_params = generate_token(conf, client, token.authenticated_username, token.authenticated_userid, token.scope, state)
response_params = generate_token(conf, client, token.authenticated_userid, token.scope, state)
dao.oauth2_tokens:delete({id=token.id}) -- Delete old token
end
end
Expand Down Expand Up @@ -321,7 +320,6 @@ function _M.execute(conf)
ngx.req.set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx.req.set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx.req.set_header("x-authenticated-scope", token.scope)
ngx.req.set_header("x-authenticated-username", token.authenticated_username)
ngx.req.set_header("x-authenticated-userid", token.authenticated_userid)
ngx.ctx.authenticated_entity = credential
end
Expand Down
6 changes: 6 additions & 0 deletions kong/plugins/oauth2/api.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
local crud = require "kong.api.crud_helpers"

return {
["/oauth2/"] = {
GET = function(self, dao_factory, helpers)
crud.paginated_set(self, dao_factory.oauth2_credentials)
end
},

["/consumers/:username_or_id/oauth2/"] = {
before = function(self, dao_factory, helpers)
crud.find_consumer_by_username_or_id(self, dao_factory, helpers)
Expand Down
2 changes: 0 additions & 2 deletions kong/plugins/oauth2/daos.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ local OAUTH2_AUTHORIZATION_CODES_SCHEMA = {
fields = {
id = { type = "id", dao_insert_value = true },
code = { type = "string", required = false, unique = true, queryable = true, immutable = true, func = generate_if_missing },
authenticated_username = { type = "string", required = false },
authenticated_userid = { type = "string", required = false },
scope = { type = "string" },
created_at = { type = "timestamp", dao_insert_value = true }
Expand All @@ -51,7 +50,6 @@ local OAUTH2_TOKENS_SCHEMA = {
access_token = { type = "string", required = false, unique = true, queryable = true, immutable = true, func = generate_if_missing },
refresh_token = { type = "string", required = false, unique = true, queryable = true, immutable = true, func = generate_refresh_token },
expires_in = { type = "number", required = true },
authenticated_username = { type = "string", required = false },
authenticated_userid = { type = "string", required = false },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as queryable if you're leaving the INDEX on it so it avoids the ALLOW FILTERING in queries

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like 467c1bc ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

scope = { type = "string" },
created_at = { type = "timestamp", dao_insert_value = true }
Expand Down
44 changes: 27 additions & 17 deletions spec/plugins/oauth2/access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ local STUB_GET_URL = spec_helper.STUB_GET_URL
local STUB_POST_URL = spec_helper.STUB_POST_URL

local function provision_code()
local response = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello", authenticated_username = "user123", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local response = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local body = cjson.decode(response)
local matches = rex.gmatch(body.redirect_uri, "^http://google\\.com/kong\\?code=([\\w]{32,32})&state=hello$")
local code
Expand Down Expand Up @@ -92,76 +92,89 @@ describe("Authentication Plugin", function()
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(2, utils.table_size(body))
assert.are.equal("invalid_authenticated_userid", body.error)
assert.are.equal("Missing authenticated_userid parameter", body.error_description)
end)

it("should return an error when only provision_key and authenticated_userid are sent", function()
local response, status, headers = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(2, utils.table_size(body))
assert.are.equal("invalid_request", body.error)
assert.are.equal("Invalid client_id", body.error_description)

-- Checking headers
assert.are.equal("no-store", headers["cache-control"])
assert.are.equal("no-cache", headers["pragma"])
end)

it("should return an error when only the client_is being sent", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(1, utils.table_size(body))
assert.are.equal("http://google.com/kong?error=invalid_scope&error_description=You%20must%20specify%20a%20scope", body.redirect_uri)
end)

it("should return an error when an invalid scope is being sent", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "wot" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "wot" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(1, utils.table_size(body))
assert.are.equal("http://google.com/kong?error=invalid_scope&error_description=%22wot%22%20is%20an%20invalid%20scope", body.redirect_uri)
end)

it("should return an error when no response_type is being sent", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(1, utils.table_size(body))
assert.are.equal("http://google.com/kong?error=unsupported_response_type&error_description=Invalid%20response_type", body.redirect_uri)
end)

it("should return an error with a state when no response_type is being sent", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", state = "somestate" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", state = "somestate" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(1, utils.table_size(body))
assert.are.equal("http://google.com/kong?error=unsupported_response_type&state=somestate&error_description=Invalid%20response_type", body.redirect_uri)
end)

it("should return error when the redirect_uri does not match", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code", redirect_uri = "http://hello.com/" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code", redirect_uri = "http://hello.com/" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(400, status)
assert.are.equal(1, utils.table_size(body))
assert.are.equal("http://google.com/kong?error=invalid_request&error_description=Invalid%20redirect_uri%20that%20does%20not%20match%20with%20the%20one%20created%20with%20the%20application", body.redirect_uri)
end)

it("should return success", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
assert.truthy(rex.match(body.redirect_uri, "^http://google\\.com/kong\\?code=[\\w]{32,32}$"))
end)

it("should return success with a path", function()
local response, status = http_client.post(PROXY_URL.."/somepath/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "mockbin-path.com"})
local response, status = http_client.post(PROXY_URL.."/somepath/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "mockbin-path.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
assert.truthy(rex.match(body.redirect_uri, "^http://google\\.com/kong\\?code=[\\w]{32,32}$"))
end)

it("should return success when requesting the url with final slash", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize/", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize/", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
assert.truthy(rex.match(body.redirect_uri, "^http://google\\.com/kong\\?code=[\\w]{32,32}$"))
end)

it("should return success with a state", function()
local response, status, headers = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello" }, {host = "oauth2.com"})
local response, status, headers = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
Expand All @@ -173,7 +186,7 @@ describe("Authentication Plugin", function()
end)

it("should return success and store authenticated user properties", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello", authenticated_username = "user123", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "code", state = "hello", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
Expand All @@ -188,7 +201,6 @@ describe("Authentication Plugin", function()
assert.are.equal(1, #data)
assert.are.equal(code, data[1].code)

assert.are.equal("user123", data[1].authenticated_username)
assert.are.equal("userid123", data[1].authenticated_userid)
assert.are.equal("email", data[1].scope)
end)
Expand All @@ -197,7 +209,7 @@ describe("Authentication Plugin", function()

describe("Implicit Grant", function()
it("should return success", function()
local response, status, headers = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "token" }, {host = "oauth2.com"})
local response, status, headers = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "token" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
Expand All @@ -208,15 +220,15 @@ describe("Authentication Plugin", function()
assert.are.equal("no-cache", headers["pragma"])
end)
it("should return success and the state", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email", response_type = "token", state = "wot" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email", response_type = "token", state = "wot" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
assert.truthy(rex.match(body.redirect_uri, "^http://google\\.com/kong\\?token_type=bearer&state=wot&access_token=[\\w]{32,32}$"))
end)

it("should return success and store authenticated user properties", function()
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", client_id = "clientid123", scope = "email profile", response_type = "token", authenticated_username = "user123", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local response, status = http_client.post(PROXY_URL.."/oauth2/authorize", { provision_key = "provision123", authenticated_userid = "id123", client_id = "clientid123", scope = "email profile", response_type = "token", authenticated_userid = "userid123" }, {host = "oauth2.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal(1, utils.table_size(body))
Expand All @@ -231,7 +243,6 @@ describe("Authentication Plugin", function()
assert.are.equal(1, #data)
assert.are.equal(access_token, data[1].access_token)

assert.are.equal("user123", data[1].authenticated_username)
assert.are.equal("userid123", data[1].authenticated_userid)
assert.are.equal("email profile", data[1].scope)

Expand Down Expand Up @@ -382,7 +393,6 @@ describe("Authentication Plugin", function()

assert.are.equal(consumer.id, body.headers["x-consumer-id"])
assert.are.equal(consumer.username, body.headers["x-consumer-username"])
assert.are.equal("user123", body.headers["x-authenticated-username"])
assert.are.equal("userid123", body.headers["x-authenticated-userid"])
assert.are.equal("email", body.headers["x-authenticated-scope"])
end)
Expand Down