Skip to content

Commit

Permalink
Merge pull request #1 from ging/token_validation
Browse files Browse the repository at this point in the history
New feature for OAuth2 Token Validation using Fiware Wilma PEP-Proxy, v0.1 OAuth2 authentication with FIWARE Pep Proxy only, next version need to allow OAuth2 token validation with any authentication service provider
  • Loading branch information
anmunoz authored Oct 26, 2017
2 parents 1566eef + bbf9f0b commit f0839eb
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 38 deletions.
18 changes: 10 additions & 8 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ gatekeeper:
- header
- getParam
- basicAuthUsername
api_key_cache: true
api_key_cache: false
pep_host: 127.0.0.1 # ip or hostname of Pep Proxy host
pep_port: 8090 # listen port of Pep Proxy
trafficserver:
host: 127.0.0.1
port: 14009
Expand Down Expand Up @@ -282,12 +284,12 @@ apiSettings:
message: The requested URL was not found on this server.
api_key_missing:
status_code: 403
code: API_KEY_MISSING
message: No api_key was supplied. Get one at {{signup_url}}
code: API_KEY_OR_TOKEN_MISSING
message: No api_key or token was supplied. Get one at {{signup_url}}
api_key_invalid:
status_code: 403
code: API_KEY_INVALID
message: An invalid api_key was supplied. Get one at {{signup_url}}
code: API_KEY_OR_TOKEN_INVALID
message: An invalid api_key or token was supplied. Get one at {{signup_url}}
api_key_disabled:
status_code: 403
code: API_KEY_DISABLED
Expand All @@ -298,8 +300,8 @@ apiSettings:
message: The api_key supplied has not been verified yet. Please check your e-mail to verify the API key. Contact us at {{contact_url}} for assistance
api_key_unauthorized:
status_code: 403
code: API_KEY_UNAUTHORIZED
message: The api_key supplied is not authorized to access the given service. Contact us at {{contact_url}} for assistance
code: API_KEY_OR_TOKEN_UNAUTHORIZED
message: The api_key or token supplied is not authorized to access the given service. Contact us at {{contact_url}} for assistance
over_rate_limit:
status_code: 429
code: OVER_RATE_LIMIT
Expand Down Expand Up @@ -368,4 +370,4 @@ ban:
message: "Please contact us for assistance."
ember_server:
port: 14050
live_reload_port: 14051
live_reload_port: 14051
4 changes: 3 additions & 1 deletion src/api-umbrella/proxy/hooks/rewrite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ wait_for_setup()
-- ngx.var lookups are apparently somewhat expensive.
ngx.ctx.args = ngx_var.args
ngx.ctx.arg_api_key = ngx_var.arg_api_key
ngx.ctx.arg_token = ngx_var.arg_token
if(config["router"]["match_x_forwarded_host"]) then
ngx.ctx.host = ngx_var.http_x_forwarded_host or ngx_var.http_host or ngx_var.host
else
ngx.ctx.host = ngx_var.http_host or ngx_var.host
end
ngx.ctx.host_normalized = host_normalize(ngx.ctx.host)
ngx.ctx.http_x_api_key = ngx_var.http_x_api_key
ngx.ctx.http_x_auth_token = ngx_var.http_x_auth_token
ngx.ctx.port = ngx_var.real_port
ngx.ctx.protocol = ngx_var.real_scheme
ngx.ctx.remote_addr = ngx_var.remote_addr
Expand Down Expand Up @@ -69,4 +71,4 @@ else
else
error_handler(api_err)
end
end
end
41 changes: 28 additions & 13 deletions src/api-umbrella/proxy/middleware/api_key_validator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,47 @@ local is_empty = types.is_empty

local function resolve_api_key()
local api_key_methods = config["gatekeeper"]["api_key_methods"]
local api_key
local key = {key_value="", key_type="" }

-- The api_key variable is a dictionary compose by two elements, the key_value which stores
-- the api_key value or the user token value and the key_type field in where is stored
-- the type of key that was provided by the user, it value could be an api_key or a token.
-- The validation process is made for all the api_key_methods (except basicAuthUsername)
-- declared in the configuration file checking if the user sends an api_key or token
-- Only the header and get_param methods are supported by the token validation.
for _, method in ipairs(api_key_methods) do
if method == "header" then
api_key = ngx.ctx.http_x_api_key
elseif method == "getParam" then
api_key = ngx.ctx.arg_api_key
elseif method == "basicAuthUsername" then
api_key = ngx.ctx.remote_user
if method == "header" and ngx.ctx.http_x_api_key then
key.key_value = ngx.ctx.http_x_api_key
key.key_type = "api_key"
elseif ngx.ctx.http_x_auth_token then
key.key_value = ngx.ctx.http_x_auth_token
key.key_type = "token"
elseif method == "getParam" and ngx.ctx.arg_api_key then
key.key_value = ngx.ctx.arg_api_key
key.key_type = "api_key"
elseif ngx.ctx.arg_token then
key.key_value = ngx.ctx.arg_token
key.key_type = "token"
elseif method == "basicAuthUsername" and ngx.ctx.remote_user then
key.key_value = ngx.ctx.remote_user
key.key_type = "api_key"
end

if not is_empty(api_key) then
if not is_empty(key["key_value"]) then
break
end
end

-- Store the api key for logging.
ngx.ctx.api_key = api_key
ngx.ctx.api_key = key["key_value"]

return api_key
return key
end

return function(settings)
-- Find the API key in the header, query string, or HTTP auth.
local api_key = resolve_api_key()
if is_empty(api_key) then
if is_empty(api_key["key_value"]) then
if settings and settings["disable_api_key"] then
return nil
else
Expand All @@ -47,7 +62,7 @@ return function(settings)

-- Store the api key on the user object for easier access (the user object
-- doesn't contain it directly, to save memory storage in the lookup table).
user["api_key"] = api_key
user["api_key"] = api_key["key_value"]

-- Store user details for logging.
ngx.ctx.user_id = user["id"]
Expand Down Expand Up @@ -76,4 +91,4 @@ return function(settings)
end

return user
end
end
56 changes: 43 additions & 13 deletions src/api-umbrella/proxy/user_store.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,33 @@ local mongo = require "api-umbrella.utils.mongo"
local shcache = require "shcache"
local types = require "pl.types"
local utils = require "api-umbrella.proxy.utils"
local pep = require "api-umbrella.utils.pep"

local cache_computed_settings = utils.cache_computed_settings
local is_empty = types.is_empty

local function lookup_user(api_key)
local raw_user, err = mongo.first("api_users", {
query = {
api_key = api_key,
},
})

if err then
ngx.log(ngx.ERR, "failed to fetch user from mongodb: ", err)
local raw_user
local db_err
local pep_err

-- Checking the field of api_key ["key_type"], if the key_type is api_key
-- the api_key value is checked in the database and retrieve the user information
-- else if the key_type is token, the token is checked using PEP Proxy and
-- the user information is retrieved
if not api_key["key_type"] or api_key["key_type"] == "api_key" then
raw_user, db_err = mongo.first("api_users", {
query = {
api_key = api_key["key_value"],
},
})
elseif api_key["key_type"] == "token" then
raw_user, pep_err = pep.first(config["gatekeeper"]["pep_host"],config["gatekeeper"]["pep_port"],api_key["key_value"])
end
if pep_err then
ngx.log(ngx.ERR, "failed to autenticate , status code:", pep_err)
elseif db_err then
ngx.log(ngx.ERR, "failed to fetch user from mongodb", db_err)
elseif raw_user then
local user = utils.pick_where_present(raw_user, {
"created_at",
Expand All @@ -36,15 +50,31 @@ local function lookup_user(api_key)
-- Ensure IDs get stored as strings, even if Mongo ObjectIds are in use.
if raw_user["_id"] and raw_user["_id"]["$oid"] then
user["id"] = raw_user["_id"]["$oid"]
elseif raw_user.Nick_Name then
user["id"] = raw_user.Nick_Name
if not raw_user.Email then
user["email"] = raw_user.Nick_Name
else
user["email"] = raw_user.Email
end
else
user["id"] = raw_user["_id"]
user["id"] = raw_user["_id"]
end
-- If the validation was made using a token, the Nick_Name associate to the token
-- is assigned to the id attribute of the user
if raw_user.Nick_Name then
user["id"] = raw_user.Nick_Name
end

-- Invert the array of roles into a hashy table for more optimized
-- lookups (so we can just check if the key exists, rather than
-- looping over each value).
-- Moreover, in case that the user information have been retrieved using a token validation,
-- the roles associated with the token are stored in user ["roles"]
if user["roles"] then
user["roles"] = invert_table(user["roles"])
elseif raw_user.Roles then
user["roles"] = invert_table(raw_user.Roles)
end

if user["created_at"] and user["created_at"]["$date"] then
Expand Down Expand Up @@ -103,14 +133,14 @@ function _M.get(api_key)
return nil
end

user = shared_cache:load(api_key)
user = shared_cache:load(api_key["key_value"])
if user then
local_cache:set(api_key, user, 2)
local_cache:set(api_key["key_value"], user, 2)
else
local_cache:set(api_key, EMPTY_DATA, 2)
local_cache:set(api_key["key_value"], EMPTY_DATA, 2)
end

return user
end

return _M
return _M
16 changes: 13 additions & 3 deletions src/api-umbrella/utils/invert_table.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
return function(table)
local numItems = 0
local inverted = {}
for key, value in pairs(table) do
inverted[value] = key
if type(value)=="string" then
inverted[value] = key
else
for k,v in pairs(value) do
numItems = numItems + 1
end
if numItems > 1 then
value = value["name"]
end
inverted[value] = key
end
end

return inverted
end
end
27 changes: 27 additions & 0 deletions src/api-umbrella/utils/pep.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local http = require "resty.http"
local cjson = require "cjson"
local _M = {}
-- Function to connect with the Pep Proxy service for checking if the token is valid and retrieve
-- the user properties. The function takes the PEP Proxy host and port as parameters
-- and sends a request with the header X-Auth-Token with the value of the token provided
-- by the user. If the token is valid, PEP proxy sends a response with the user information
-- asociated to the token, otherwise, it sends a message indicating the result of the
-- validation process with his status, 404 , 402, etc.
function _M.first(host, port, token)
local result
local httpc = http.new()
httpc:set_timeout(45000)
httpc:connect(host,port)
local res, err = httpc:request({headers = {["X-Auth-Token"] = token}})
if res and res.status == 200 then
local body, body_err = res:read_body()
if not body then
return nil, body_err
end
result = cjson.decode(body)
end

return result, err
end

return _M

0 comments on commit f0839eb

Please sign in to comment.