diff --git a/config/default.yml b/config/default.yml index a1b7a5352..3964a7ec5 100644 --- a/config/default.yml +++ b/config/default.yml @@ -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 @@ -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 @@ -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 @@ -368,4 +370,4 @@ ban: message: "Please contact us for assistance." ember_server: port: 14050 - live_reload_port: 14051 +live_reload_port: 14051 \ No newline at end of file diff --git a/src/api-umbrella/proxy/hooks/rewrite.lua b/src/api-umbrella/proxy/hooks/rewrite.lua index af0a2268f..d238ea134 100644 --- a/src/api-umbrella/proxy/hooks/rewrite.lua +++ b/src/api-umbrella/proxy/hooks/rewrite.lua @@ -17,6 +17,7 @@ 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 @@ -24,6 +25,7 @@ else 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 @@ -69,4 +71,4 @@ else else error_handler(api_err) end -end +end \ No newline at end of file diff --git a/src/api-umbrella/proxy/middleware/api_key_validator.lua b/src/api-umbrella/proxy/middleware/api_key_validator.lua index ab1447b32..5c6e691b1 100644 --- a/src/api-umbrella/proxy/middleware/api_key_validator.lua +++ b/src/api-umbrella/proxy/middleware/api_key_validator.lua @@ -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 @@ -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"] @@ -76,4 +91,4 @@ return function(settings) end return user -end +end \ No newline at end of file diff --git a/src/api-umbrella/proxy/user_store.lua b/src/api-umbrella/proxy/user_store.lua index 6ca87e4e2..6b943af71 100644 --- a/src/api-umbrella/proxy/user_store.lua +++ b/src/api-umbrella/proxy/user_store.lua @@ -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", @@ -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 @@ -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 \ No newline at end of file diff --git a/src/api-umbrella/utils/invert_table.lua b/src/api-umbrella/utils/invert_table.lua index 5d0000f22..126b8f38e 100644 --- a/src/api-umbrella/utils/invert_table.lua +++ b/src/api-umbrella/utils/invert_table.lua @@ -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 \ No newline at end of file diff --git a/src/api-umbrella/utils/pep.lua b/src/api-umbrella/utils/pep.lua new file mode 100644 index 000000000..33e7119d1 --- /dev/null +++ b/src/api-umbrella/utils/pep.lua @@ -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 \ No newline at end of file