Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make oauth tokens TTL configurable #448

Merged
merged 4 commits into from
Oct 9, 2017
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Upgraded to OpenResty 1.11.2.5-1 [PR #428](https://github.com/3scale/apicast/pull/428)
- `/oauth/token` endpoint returns an error status code, when the access token couldn't be stored in 3scale backend [PR #436](https://github.com/3scale/apicast/pull/436)]
- URI params in POST requests are now taken into account when matching mapping rules [PR #437](https://github.com/3scale/apicast/pull/437)
- Make OAuth tokens TTL configurable [PR #448](https://github.com/3scale/apicast/pull/448)

### Fixed

Expand Down
15 changes: 13 additions & 2 deletions apicast/src/oauth/apicast_oauth/get_token.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
local cjson = require 'cjson'
local ts = require 'threescale_utils'
local re = require 'ngx.re'
local env = require 'resty.env'
local tonumber = tonumber

local oauth_tokens_default_ttl = 604800 -- 7 days

-- As per RFC for Authorization Code flow: extract params from Authorization header and body
-- If implementation deviates from RFC, this function should be over-ridden
Expand Down Expand Up @@ -28,6 +32,10 @@ local function extract_params()
return params
end

local function oauth_tokens_ttl()
return tonumber(env.get('APICAST_OAUTH_TOKENS_TTL')) or oauth_tokens_default_ttl
end

-- Returns the access token (stored in redis) for the client identified by the id
-- This needs to be called within a minute of it being stored, as it expires and is deleted
local function request_token(params)
Expand All @@ -40,7 +48,10 @@ local function request_token(params)
local client_data = red:array_to_hash(ok)
params.user_id = client_data.user_id
if params.code == client_data.code then
return { ["status"] = 200, ["body"] = { ["access_token"] = client_data.access_token, ["token_type"] = "bearer", ["expires_in"] = 604800 } }
return { ["status"] = 200,
["body"] = { ["access_token"] = client_data.access_token,
["token_type"] = "bearer",
["expires_in"] = oauth_tokens_ttl() } }
else
return { ["status"] = 403, ["body"] = '{"error": "invalid authorization code"}' }
end
Expand Down Expand Up @@ -83,7 +94,7 @@ local function check_credentials(params)
return res.status == 200
end

-- Stores the token in 3scale. You can change the default ttl value of 604800 seconds (7 days) to your desired ttl.
-- Stores the token in 3scale.
local function store_token(params, token)
local body = ts.build_query({ app_id = params.client_id, token = token.access_token, user_id = params.user_id, ttl = token.expires_in })
local stored = ngx.location.capture( "/_threescale/oauth_store_token", {
Expand Down
7 changes: 7 additions & 0 deletions doc/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ APIcast requires a running Redis instance for OAuth 2.0 flow. `REDIS_PORT` param

APIcast requires a running Redis instance for OAuth 2.0 flow. `REDIS_URL` parameter can be used to set the full URI as DSN format like: `redis://PASSWORD@HOST:PORT/DB`. Takes precedence over `REDIS_PORT` and `REDIS_HOST`.

### `APICAST_OAUTH_TOKENS_TTL`

**Values:** _a number_
**Default:** 604800

When configured to authenticate using OAuth, this param specifies the TTL (in seconds) of the tokens created.

### `RESOLVER`

Allows to specify a custom DNS resolver that will be used by OpenResty. If the `RESOLVER` parameter is empty, the DNS resolver will be autodiscovered.
Expand Down
6 changes: 6 additions & 0 deletions openshift/apicast-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ objects:
value: "${CONFIGURATION_CACHE}"
- name: REDIS_URL
value: "${REDIS_URL}"
- name: APICAST_OAUTH_TOKENS_TTL
value: "${OAUTH_TOKENS_TTL}"
- name: APICAST_MANAGEMENT_API
value: "${MANAGEMENT_API}"
- name: OPENSSL_VERIFY
Expand Down Expand Up @@ -152,6 +154,10 @@ parameters:
- description: "Redis URL. Required for OAuth2 integration. ex: redis://[email protected]:6379/0"
name: REDIS_URL
required: false
- description: "TTL (in seconds) of the oauth tokens created."
name: OAUTH_TOKENS_TTL
required: false
value: "604800"
- name: MANAGEMENT_API
description: "Scope of the Management API. Can be disabled, status or debug. At least status required for health checks."
required: false
Expand Down
179 changes: 177 additions & 2 deletions t/005-apicast-oauth.t
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ Location: http://example.com/redirect\?code=\w+&state=clientstate
--- main_config
env REDIS_HOST=$TEST_NGINX_REDIS_HOST;
env RESOLVER=$TEST_NGINX_RESOLVER;
env APICAST_OAUTH_TOKENS_TTL=123;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_by_lua_block {
Expand Down Expand Up @@ -363,13 +364,19 @@ Location: http://example.com/redirect\?code=\w+&state=clientstate

location = /backend/services/42/oauth_access_tokens.xml {
content_by_lua_block {
ngx.exit(200)
local ttl = tonumber(ngx.req.get_post_args()['ttl'])
if ttl ~= 123 then
ngx.log(ngx.ERR, 'Backend did not receive the correct TTL.')
ngx.exit(400)
else
ngx.exit(200)
end
}
}
--- request
GET /t
--- response_body_like
{"token_type":"bearer","expires_in":604800,"access_token":"\w+"}
{"token_type":"bearer","expires_in":123,"access_token":"\w+"}
--- error_code: 200
--- no_error_log
[error]
Expand Down Expand Up @@ -719,6 +726,7 @@ GET /t
--- main_config
env REDIS_HOST=$TEST_NGINX_REDIS_HOST;
env RESOLVER=$TEST_NGINX_RESOLVER;
env APICAST_OAUTH_TOKENS_TTL=123;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_by_lua_block {
Expand Down Expand Up @@ -774,6 +782,12 @@ GET /t

location = /backend/services/42/oauth_access_tokens.xml {
content_by_lua_block {
local ttl = tonumber(ngx.req.get_post_args()['ttl'])
if ttl ~= 123 then
ngx.log(ngx.ERR, 'Backend did not receive the correct TTL.')
ngx.exit(400)
end

if ngx.var.http_content_type then
ngx.log(ngx.ERR, 'Invalid Content-Type: ', ngx.var.http_content_type)
ngx.status = 400
Expand All @@ -785,6 +799,167 @@ GET /t
}
}

--- request
GET /t
--- more_headers
Content-Type: application/json
--- error_code: 200
--- response_body
{"token_type":"bearer","expires_in":123,"access_token":"token"}
--- no_error_log
[error]

=== TEST 18: default token TTL when not specified
When a token TTL is not specified, it applies a default of 7 days (604800 s)
--- main_config
env REDIS_HOST=$TEST_NGINX_REDIS_HOST;
env RESOLVER=$TEST_NGINX_RESOLVER;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_by_lua_block {
require('configuration_loader').mock({
services = {
{ id = 42, backend_version = 'oauth' }
}
})
}
--- config
include $TEST_NGINX_APICAST_CONFIG;

lua_need_request_body on;
location = /t {
content_by_lua_block {
local authorize = require('oauth.apicast_oauth.authorize')
local authorized_callback = require('oauth.apicast_oauth.authorized_callback')
local code = 'authcode'
local params = { user_id = 'someuser' }
local client_data = {
client_id = 'foo',
secret_id = 'bar',
redirect_uri = 'redirect',
access_token = 'token'
}

assert(authorized_callback.persist_code(client_data, params, code))

ngx.req.set_method(ngx.HTTP_POST)
ngx.req.set_body_data('grant_type=authorization_code&client_id=foo&client_secret=bar&redirect_uri=redirect&code=' .. code)
ngx.exec('/oauth/token')
}
}

set $backend_endpoint 'http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend';
set $backend_host '127.0.0.1';
set $service_id 42;
set $backend_authentication_type 'provider_key';
set $backend_authentication_value 'fookey';

location = /backend/transactions/oauth_authorize.xml {
content_by_lua_block {
expected = "provider_key=fookey&service_id=42&app_key=bar&app_id=foo&redirect_uri=redirect"
if ngx.var.args == expected and ngx.var.host == ngx.var.backend_host then
ngx.say('<?xml version="1.0" encoding="UTF-8"?><status><authorized>true</authorized><application><key>bar</key></application></status>')
ngx.exit(200)
else
ngx.log(ngx.ERR, 'expected: ' .. expected .. ' got: ' .. ngx.var.args)
ngx.exit(403)
end
}
}

location = /backend/services/42/oauth_access_tokens.xml {
content_by_lua_block {
local ttl = tonumber(ngx.req.get_post_args()['ttl'])
if ttl ~= 604800 then
ngx.log(ngx.ERR, 'Backend did not receive the correct TTL.')
ngx.exit(400)
else
ngx.exit(200)
end
}
}

--- request
GET /t
--- more_headers
Content-Type: application/json
--- error_code: 200
--- response_body
{"token_type":"bearer","expires_in":604800,"access_token":"token"}
--- no_error_log
[error]

=== TEST 19: default token TTL when given an empty one
When an empty token TTL is received, Apicast applies a default of 7 days (604800 s)
--- main_config
env REDIS_HOST=$TEST_NGINX_REDIS_HOST;
env RESOLVER=$TEST_NGINX_RESOLVER;
env APICAST_OAUTH_TOKENS_TTL=;
--- http_config
lua_package_path "$TEST_NGINX_LUA_PATH";
init_by_lua_block {
require('configuration_loader').mock({
services = {
{ id = 42, backend_version = 'oauth' }
}
})
}
--- config
include $TEST_NGINX_APICAST_CONFIG;

lua_need_request_body on;
location = /t {
content_by_lua_block {
local authorize = require('oauth.apicast_oauth.authorize')
local authorized_callback = require('oauth.apicast_oauth.authorized_callback')
local code = 'authcode'
local params = { user_id = 'someuser' }
local client_data = {
client_id = 'foo',
secret_id = 'bar',
redirect_uri = 'redirect',
access_token = 'token'
}

assert(authorized_callback.persist_code(client_data, params, code))

ngx.req.set_method(ngx.HTTP_POST)
ngx.req.set_body_data('grant_type=authorization_code&client_id=foo&client_secret=bar&redirect_uri=redirect&code=' .. code)
ngx.exec('/oauth/token')
}
}

set $backend_endpoint 'http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend';
set $backend_host '127.0.0.1';
set $service_id 42;
set $backend_authentication_type 'provider_key';
set $backend_authentication_value 'fookey';

location = /backend/transactions/oauth_authorize.xml {
content_by_lua_block {
expected = "provider_key=fookey&service_id=42&app_key=bar&app_id=foo&redirect_uri=redirect"
if ngx.var.args == expected and ngx.var.host == ngx.var.backend_host then
ngx.say('<?xml version="1.0" encoding="UTF-8"?><status><authorized>true</authorized><application><key>bar</key></application></status>')
ngx.exit(200)
else
ngx.log(ngx.ERR, 'expected: ' .. expected .. ' got: ' .. ngx.var.args)
ngx.exit(403)
end
}
}

location = /backend/services/42/oauth_access_tokens.xml {
content_by_lua_block {
local ttl = tonumber(ngx.req.get_post_args()['ttl'])
if ttl ~= 604800 then
ngx.log(ngx.ERR, 'Backend did not receive the correct TTL.')
ngx.exit(400)
else
ngx.exit(200)
end
}
}

--- request
GET /t
--- more_headers
Expand Down