Skip to content

Commit 21ba004

Browse files
Added support for runtime customizaiton of the oauth token validator (#15)
1 parent 2526999 commit 21ba004

File tree

5 files changed

+132
-44
lines changed

5 files changed

+132
-44
lines changed

src/lua/api-gateway/validation/factory.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ local function _generateHmacSignature()
101101
return hmacSignatureValidator:generateSignature()
102102
end
103103

104-
local function _validateOAuthToken()
105-
local oauthTokenValidator = OAuthTokenValidator:new()
104+
local function _validateOAuthToken(config)
105+
local oauthTokenValidator = OAuthTokenValidator:new(config)
106106
return oauthTokenValidator:validateRequest()
107107
end
108108

src/lua/api-gateway/validation/oauth2/oauthTokenValidator.lua

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,24 @@
4141
local BaseValidator = require "api-gateway.validation.validator"
4242
local cjson = require "cjson"
4343

44-
local _M = BaseValidator:new()
45-
46-
local RESPONSES = {
47-
MISSING_TOKEN = { error_code = "403010", message = "Oauth token is missing" },
48-
INVALID_TOKEN = { error_code = "401013", message = "Oauth token is not valid" },
49-
-- TOKEN_MISSMATCH is reserved for classes overwriting the isTokenValid method
50-
TOKEN_MISSMATCH = { error_code = "401014", message = "Token not allowed in the current context" },
51-
SCOPE_MISMATCH = { error_code = "401015", message = "Scope mismatch" },
52-
UNKNOWN_ERROR = { error_code = "503010", message = "Could not validate the oauth token" }
53-
}
44+
local _M = BaseValidator:new({
45+
RESPONSES = {
46+
MISSING_TOKEN = { error_code = "403010", message = "Oauth token is missing" },
47+
INVALID_TOKEN = { error_code = "401013", message = "Oauth token is not valid" },
48+
-- TOKEN_MISSMATCH is reserved for classes overwriting the isTokenValid method
49+
TOKEN_MISSMATCH = { error_code = "401014", message = "Token not allowed in the current context" },
50+
SCOPE_MISMATCH = { error_code = "401015", message = "Scope mismatch" },
51+
UNKNOWN_ERROR = { error_code = "503010", message = "Could not validate the oauth token" }
52+
}
53+
})
5454

5555
---
5656
-- Maximum time in seconds specifying how long to cache a valid token in GW's memory
5757
local LOCAL_CACHE_TTL = 60
5858

5959
-- Hook to override the logic verifying if a token is valid
60-
function _M:istokenValid(json)
61-
return json.valid or false, RESPONSES.INVALID_TOKEN
60+
function _M:isTokenValid(json)
61+
return json.valid or false, self.RESPONSES.INVALID_TOKEN
6262
end
6363

6464
-- override this if other checks need to be in place
@@ -133,7 +133,7 @@ function _M:checkResponseFromAuth(res, cacheLookupKey)
133133
local json = cjson.decode(res.body)
134134
if json ~= nil then
135135

136-
local tokenValidity, error = self:istokenValid(json)
136+
local tokenValidity, error = self:isTokenValid(json)
137137
if not tokenValidity and error ~= nil then
138138
return tokenValidity, error
139139
end
@@ -166,14 +166,13 @@ function _M:getTokenFromCache(cacheLookupKey)
166166
return nil;
167167
end
168168

169-
-- imsAuth will validate the service token passed in "Authorization" header --
170-
function _M:validate_ims_token()
169+
function _M:validateOAuthToken()
170+
171171
local oauth_host = ngx.var.oauth_host
172-
local oauth_token = ngx.var.authtoken
172+
local oauth_token = self.authtoken or ngx.var.authtoken
173173

174-
-- ngx.var.authtoken needs to be set before calling this method
175174
if oauth_token == nil or oauth_token == "" then
176-
return self:exitFn(RESPONSES.MISSING_TOKEN.error_code, cjson.encode(RESPONSES.MISSING_TOKEN))
175+
return self.RESPONSES.MISSING_TOKEN.error_code, cjson.encode(self.RESPONSES.MISSING_TOKEN)
177176
end
178177

179178
--1. try to get token info from the cache first ( local or redis cache )
@@ -190,37 +189,40 @@ function _M:validate_ims_token()
190189
ngx.log(ngx.DEBUG, "Caching locally a new token for " .. tostring(local_expire_in) .. " s, out of a total validity of " .. tostring(tokenValidity ) .. " s.")
191190
self:setKeyInLocalCache(cacheLookupKey, cachedToken, local_expire_in , "cachedOauthTokens")
192191
self:setContextProperties(obj)
193-
return self:exitFn(ngx.HTTP_OK)
192+
return ngx.HTTP_OK
194193
end
195194
-- at this point the cached token is not valid
196195
ngx.log(ngx.WARN, "Invalid OAuth Token found in cache. OAuth host=" .. tostring(oauth_host))
197196
if (error == nil) then
198-
error = RESPONSES.INVALID_TOKEN
197+
error = self.RESPONSES.INVALID_TOKEN
199198
end
200-
error.error_code = error.error_code or RESPONSES.INVALID_TOKEN.error_code
201-
return self:exitFn(error.error_code, cjson.encode(error))
199+
error.error_code = error.error_code or self.RESPONSES.INVALID_TOKEN.error_code
200+
return error.error_code, cjson.encode(error)
202201
end
203202

204203
-- 2. validate the token with the OAuth endpoint
205-
local res = ngx.location.capture("/validate-token", { share_all_vars = true })
204+
local res = ngx.location.capture("/validate-token", {
205+
share_all_vars = true,
206+
args = { authtoken = oauth_token}
207+
})
206208
if res.status == ngx.HTTP_OK then
207209
local tokenValidity, error = self:checkResponseFromAuth(res, cacheLookupKey)
208210
if (tokenValidity == true) then
209-
return self:exitFn(ngx.HTTP_OK)
211+
return ngx.HTTP_OK
210212
end
211213
-- at this point the token is not valid
212214
ngx.log(ngx.WARN, "Invalid OAuth Token returned. OAuth host=" .. tostring(oauth_host))
213215
if (error == nil) then
214-
error = RESPONSES.INVALID_TOKEN
216+
error = self.RESPONSES.INVALID_TOKEN
215217
end
216-
error.error_code = error.error_code or RESPONSES.INVALID_TOKEN.error_code
217-
return self:exitFn(error.error_code, cjson.encode(error))
218+
error.error_code = error.error_code or self.RESPONSES.INVALID_TOKEN.error_code
219+
return error.error_code, cjson.encode(error)
218220
end
219-
return self:exitFn(res.status, cjson.encode(RESPONSES.UNKNOWN_ERROR));
221+
return res.status, cjson.encode(self.RESPONSES.UNKNOWN_ERROR);
220222
end
221223

222-
function _M:validateRequest(obj)
223-
return self:validate_ims_token()
224+
function _M:validateRequest()
225+
return self:exitFn(self:validateOAuthToken())
224226
end
225227

226228

src/lua/api-gateway/validation/oauth2/userProfileValidator.lua

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,11 @@ function _M:extractContextVars(profile)
186186
return cachingObj
187187
end
188188

189-
function _M:validateRequest()
189+
function _M:validateUserProfile()
190190
-- ngx.var.authtoken needs to be set before calling this method
191191
local oauth_token = ngx.var.authtoken
192192
if oauth_token == nil or oauth_token == "" then
193-
--return self:exitFn(ngx.HTTP_BAD_REQUEST)
194-
return self:exitFn(RESPONSES.P_MISSING_TOKEN.error_code, cjson.encode(RESPONSES.P_MISSING_TOKEN))
193+
return RESPONSES.P_MISSING_TOKEN.error_code, cjson.encode(RESPONSES.P_MISSING_TOKEN)
195194
end
196195

197196
--1. try to get user's profile from the cache first ( local or redis cache )
@@ -205,9 +204,9 @@ function _M:validateRequest()
205204
end
206205
self:setContextProperties(self:getContextPropertiesObject(cachedUserProfile))
207206
if ( self:isProfileValid(cachedUserProfile) == true ) then
208-
return self:exitFn(ngx.HTTP_OK)
207+
return ngx.HTTP_OK
209208
else
210-
return self:exitFn(RESPONSES.INVALID_PROFILE.error_code, cjson.encode(RESPONSES.INVALID_PROFILE))
209+
return RESPONSES.INVALID_PROFILE.error_code, cjson.encode(RESPONSES.INVALID_PROFILE)
211210
end
212211
end
213212

@@ -223,9 +222,9 @@ function _M:validateRequest()
223222
self:storeProfileInCache(cacheLookupKey, cachingObj)
224223

225224
if ( self:isProfileValid(cachingObj) == true ) then
226-
return self:exitFn(ngx.HTTP_OK)
225+
return ngx.HTTP_OK
227226
else
228-
return self:exitFn(RESPONSES.INVALID_PROFILE.error_code, cjson.encode(RESPONSES.INVALID_PROFILE))
227+
return RESPONSES.INVALID_PROFILE.error_code, cjson.encode(RESPONSES.INVALID_PROFILE)
229228
end
230229
else
231230
ngx.log(ngx.WARN, "Could not decode /validate-user response:" .. tostring(res.body) )
@@ -234,11 +233,15 @@ function _M:validateRequest()
234233
-- ngx.log(ngx.WARN, "Could not read /ims-profile. status=" .. res.status .. ".body=" .. res.body .. ". token=" .. ngx.var.authtoken)
235234
ngx.log(ngx.WARN, "Could not read /validate-user. status=" .. res.status .. ".body=" .. res.body )
236235
if ( res.status == ngx.HTTP_UNAUTHORIZED or res.status == ngx.HTTP_BAD_REQUEST ) then
237-
return self:exitFn(RESPONSES.NOT_ALLOWED.error_code, cjson.encode(RESPONSES.NOT_ALLOWED))
236+
return RESPONSES.NOT_ALLOWED.error_code, cjson.encode(RESPONSES.NOT_ALLOWED)
238237
end
239238
end
240239
--ngx.log(ngx.WARN, "Error validating Profile for Token:" .. tostring(ngx.var.authtoken))
241-
return self:exitFn(RESPONSES.P_UNKNOWN_ERROR.error_code, cjson.encode(RESPONSES.P_UNKNOWN_ERROR))
240+
return RESPONSES.P_UNKNOWN_ERROR.error_code, cjson.encode(RESPONSES.P_UNKNOWN_ERROR)
241+
end
242+
243+
function _M:validateRequest()
244+
return self:exitFn(self:validateUserProfile())
242245
end
243246

244247
return _M

src/lua/api-gateway/validation/validator.lua

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,44 @@ function BaseValidator:getRedisUpstream(upstream_name)
9191
end
9292

9393
-- retrieves a saved information from the Redis cache --
94-
-- the method uses HGET redis command --
94+
-- the method uses GET redis command --
9595
-- it returns the value of the key, when found in the cache, nil otherwise --
96+
-- for backward compatibility this method accepts a second argument, in which case it will perform a HGET instead.
9697
function BaseValidator:getKeyFromRedis(key, hash_name)
98+
99+
if hash_name ~= nil then
100+
return self:getHashValueFromRedis(key, hash_name)
101+
end
102+
103+
local redisread = redis:new()
104+
local redis_host, redis_port = self:getRedisUpstream(redis_RO_upstream)
105+
local ok, err = redisread:connect(redis_host, redis_port)
106+
if ok then
107+
local result, err = redisread:get(key)
108+
redisread:set_keepalive(30000, 100)
109+
if ( not result and err ~= nil ) then
110+
ngx.log(ngx.WARN, "Failed to read key " .. tostring(key) .. " from Redis cache:[", redis_host, ":", redis_port, "]. Error:", err)
111+
return nil
112+
else
113+
if (type(result) == 'string') then
114+
return result
115+
end
116+
end
117+
else
118+
ngx.log(ngx.WARN, "Failed to read key " .. tostring(key) .. " from Redis cache:[", redis_host, ":", redis_port, "]. Error:", err)
119+
end
120+
return nil;
121+
end
122+
123+
-- retrieves a saved information from the Redis cache --
124+
-- the method uses HGET redis command --
125+
-- it returns the value of the key, when found in the cache, nil otherwise --
126+
function BaseValidator:getHashValueFromRedis(key, hash_field)
97127
local redisread = redis:new()
98128
local redis_host, redis_port = self:getRedisUpstream(redis_RO_upstream)
99129
local ok, err = redisread:connect(redis_host, redis_port)
100130
if ok then
101-
local redis_key, selecterror = redisread:hget(key, hash_name)
131+
local redis_key, selecterror = redisread:hget(key, hash_field)
102132
redisread:set_keepalive(30000, 100)
103133
if (type(redis_key) == 'string') then
104134
return redis_key

test/perl/api-gateway/validation/oauth2/oauthTokenValidator.t

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use Cwd qw(cwd);
3131

3232
repeat_each(2);
3333

34-
plan tests => repeat_each() * (blocks() * 9);
34+
plan tests => repeat_each() * (blocks() * 9) - 6;
3535

3636
my $pwd = cwd();
3737

@@ -350,3 +350,56 @@ GET /test-oauth-validation
350350
--- no_error_log
351351
[error]
352352
353+
=== TEST 6: test that validation behaviour can be customized
354+
--- http_config eval: $::HttpConfig
355+
--- config
356+
include ../../api-gateway/default_validators.conf;
357+
358+
error_log ../test-logs/oauthTokenValidator_test6_error.log debug;
359+
360+
location /validate_custom_oauth_token {
361+
internal;
362+
363+
content_by_lua_block {
364+
365+
ngx.apiGateway.validation.validateOAuthToken({
366+
authtoken = ngx.var.custom_token_var,
367+
RESPONSES = {
368+
MISSING_TOKEN = { error_code = "401110", message = "User token is missing" },
369+
INVALID_TOKEN = { error_code = "403113", message = "User token is not valid" },
370+
TOKEN_MISSMATCH = { error_code = "401114", message = "User token not allowed in the current context" },
371+
SCOPE_MISMATCH = { error_code = "401115", message = "User token scope mismatch" },
372+
UNKNOWN_ERROR = { error_code = "503110", message = "Could not validate the user token" }
373+
}
374+
});
375+
}
376+
}
377+
378+
location /test-custom-oauth {
379+
set $validate_oauth_token "on; path=/validate_custom_oauth_token; order=1;";
380+
set $custom_token_var $arg_custom_token;
381+
access_by_lua "ngx.apiGateway.validation.validateRequest()";
382+
content_by_lua "ngx.say('ims token is valid.')";
383+
}
384+
385+
location /validate-token {
386+
internal;
387+
set_by_lua $generated_expires_at 'return ((os.time() + 4) * 1000 )';
388+
return 200 '{"valid":false,"expires_at":$generated_expires_at,"token":{"id":"1234","scope":"openid email profile","user_id":"21961FF44F97F8A10A490D36","expires_in":"86400000","client_id":"test_Client_ID","type":"access_token"}}';
389+
}
390+
391+
--- pipelined_requests eval
392+
[
393+
"GET /test-custom-oauth",
394+
"GET /test-custom-oauth?custom_token=SOME_OAUTH_TOKEN_TEST6"
395+
]
396+
--- response_body_like eval
397+
[
398+
'^{"error_code":"401110","message":"User token is missing"}+',
399+
'^{"error_code":"403113","message":"User token is not valid"}+'
400+
]
401+
--- error_code_like eval
402+
[401,403]
403+
--- no_error_log
404+
[error]
405+

0 commit comments

Comments
 (0)