This is an automated email from the ASF dual-hosted git repository. mhamann pushed a commit to branch scope-fixes in repository https://gitbox.apache.org/repos/asf/openwhisk-apigateway.git
commit e83218ce6d63837bdf1cd5655610e830535814bc Author: Matt Hamann <[email protected]> AuthorDate: Mon Aug 3 11:28:14 2020 -0400 fix(core): upstream openresty fixes; lua global scope pollution --- .dockerignore | 1 + Dockerfile | 4 +- scripts/lua/lib/dataStore.lua | 4 +- scripts/lua/lib/redis.lua | 330 ++++++++++++------------ scripts/lua/lib/request.lua | 4 +- scripts/lua/lib/utils.lua | 2 +- scripts/lua/management/lib/apis.lua | 54 ++-- scripts/lua/management/lib/swagger.lua | 212 +++++++-------- scripts/lua/management/lib/tenants.lua | 103 ++++---- scripts/lua/management/lib/validation.lua | 154 +++++------ scripts/lua/management/routes/apis.lua | 54 ++-- scripts/lua/management/routes/subscriptions.lua | 164 ++++++------ scripts/lua/management/routes/tenants.lua | 62 ++--- scripts/lua/oauth/facebook.lua | 37 +-- scripts/lua/oauth/google.lua | 2 +- scripts/lua/policies/backendRouting.lua | 16 +- scripts/lua/policies/mapping.lua | 176 ++++++------- scripts/lua/policies/rateLimit.lua | 2 +- scripts/lua/policies/security.lua | 2 +- scripts/lua/policies/security/apiKey.lua | 30 +-- scripts/lua/policies/security/clientSecret.lua | 99 ++++--- scripts/lua/policies/security/oauth2.lua | 53 ++-- scripts/lua/routing.lua | 83 +++--- tools/lua-releng | 104 ++++++++ 24 files changed, 919 insertions(+), 833 deletions(-) diff --git a/.dockerignore b/.dockerignore index 1b5d582..b6643df 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ CONTRIBUTING.md Dockerfile Makefile README.md +tools/ diff --git a/Dockerfile b/Dockerfile index 30a35ce..ce8d088 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # apigateway # -# VERSION 1.15.8.3 +# VERSION 1.17.8.2 # # From https://hub.docker.com/_/alpine/ # @@ -37,7 +37,7 @@ RUN apk update && \ && rm -rf /var/cache/apk/* # openresty build -ENV OPENRESTY_VERSION=1.15.8.3 \ +ENV OPENRESTY_VERSION=1.17.8.2 \ PCRE_VERSION=8.37 \ TEST_NGINX_VERSION=0.24 \ OPM_VERSION=0.0.5 \ diff --git a/scripts/lua/lib/dataStore.lua b/scripts/lua/lib/dataStore.lua index 57a86cc..453576a 100644 --- a/scripts/lua/lib/dataStore.lua +++ b/scripts/lua/lib/dataStore.lua @@ -34,7 +34,7 @@ end function DataStore:setSnapshotId(tenant) self.snapshotId = self.impl.getSnapshotId(self.ds, tenant) - self:lockSnapshot(snapshotId) + self:lockSnapshot(self.snapshotId) if self.snapshotId == ngx.null then self.snapshotId = nil end @@ -159,7 +159,7 @@ end function DataStore:getSubscriptions(artifactId, tenantId) self:singleInit() - return self.impl.deleteSubscription(self.ds, key, self.snapshotId) + return self.impl.getSubscriptions(self.ds, artifactId, tenantId, self.snapshotId) end function DataStore:healthCheck() diff --git a/scripts/lua/lib/redis.lua b/scripts/lua/lib/redis.lua index bd6701f..5c4da83 100644 --- a/scripts/lua/lib/redis.lua +++ b/scripts/lua/lib/redis.lua @@ -102,6 +102,169 @@ function _M.close(red) end end +-- LRU Caching methods + +--- Call function with retry logic +-- @param func function to call +-- @param args arguments to pass in to function +local function call(func, args) + local res, err = func(unpack(args)) + local retryCount = REDIS_RETRY_COUNT + while not res and retryCount > 0 do + res, err = func(unpack(args)) + retryCount = retryCount - 1 + end + return res, err +end + +local function exists(red, key, snapshotId) + if snapshotId ~= nil then + key = utils.concatStrings({'snapshots:', snapshotId, ':', key}) + end + if CACHING_ENABLED then + local cached = c:get(key) + if cached ~= nil then + return 1 + end + -- if it isn't in the cache, try and load it in there + if red == nil then + red = _M.init() + end + local result = red:get(key) + if result ~= ngx.null then + c:set(key, result, CACHE_TTL) + return 1, red + end + return 0 + else + if red == nil then + red = _M.init() + end + return call(red.exists, {red, key}), red + end +end + +local function get(red, key) + if CACHING_ENABLED then + local cached, stale = c:get(key) + if cached ~= nil then + return cached + else + if red == nil then + red = _M.init() + end + local result = red:get(key) + c:set(key, result, CACHE_TTL) + return result, red + end + else + if red == nil then + red = _M.init() + end + return call(red.get, {red, key}) + end +end + +local function hget(red, key, id) + if CACHING_ENABLED then + local cachedmap, stale = c:get(key) + if cachedmap ~= nil then + local cached = cachedmap:get(id) + if cached ~= nil then + return cached + else + if red == nil then + red = _M.init() + end + local result = red:hget(key, id) + cachedmap:set(id, result, CACHE_TTL) + c:set(key, cachedmap, CACHE_TTL) + return result, red + end + else + if red == nil then + red = _M.init() + end + local result = red:hget(key, id) + local newcache = lrucache.new(CACHE_SIZE) + newcache:set(id, result, CACHE_TTL) + c:set(key, newcache, CACHE_TTL) + return result, red + end + else + if red == nil then + red = _M.init() + end + return call(red.hget, {red, key, id}), red + end +end + +local function hgetall(red, key) + return call(red.hgetall, {red, key}) +end + +local function hset(red, key, id, value) + if CACHING_ENABLED then + local cachedmap = c:get(key) + if cachedmap ~= nil then + cachedmap:set(id, value, CACHE_TTL) + c:set(key, cachedmap, CACHE_TTL) + return red:hset(key, id, value) + else + local val = lrucache.new(CACHE_SIZE) + val:set(id, value, CACHE_TTL) + c:set(key, val, CACHE_TTL) + end + end + return call(red.hset, {red, key, id, value}) +end + +local function expire(red, key, ttl) + if CACHING_ENABLED then + local cached = c:get(key) + local value = '' + if cached ~= nil then -- just put it back in the cache with a ttl + value = cached + end + c:set(key, value, ttl) + end + return call(red.expire, {red, ttl}) +end + +local function del(red, key) + if CACHING_ENABLED then + c:delete(key) + end + return call(red.del, {red, key}) +end + +local function hdel(red, key, id) + if CACHING_ENABLED then + local cachecontents = c:get(key) + if cachecontents ~= nil then + cachecontents:del(id) + c:set(key, cachecontents, CACHE_TTL) + end + end + return call(red.hdel, {red, key, id}) +end + +local function set(red, key, value) + return call(red.set, {red, key, value}) +end + +local function smembers(red, key) + return call(red.smembers, {red, key}) +end + +local function srem(red, key, id) + return call(red.srem, {red, key, id}) +end + +local function sadd(red, key, id) + return call(red.sadd, {red, key, id}) +end + --------------------------- ----------- APIs ---------- --------------------------- @@ -597,8 +760,8 @@ function _M.optimizeLookup(red, tenant, resourceKey, pathStr) if get(red, startingString) == nil then set(red, startingString, '') end - path = {} - key = {} + local path = {} + local key = {} for p in string.gmatch(pathStr, '[^/]*') do if p ~= '' then table.insert(path, p) @@ -632,169 +795,6 @@ function _M.lockSnapshot(red, snapshotId) red:expire(utils.concatStrings({'lock:snapshots:', snapshotId}), 60) end --- LRU Caching methods - -function exists(red, key, snapshotId) - if snapshotId ~= nil then - key = utils.concatStrings({'snapshots:', snapshotId, ':', key}) - end - if CACHING_ENABLED then - local cached = c:get(key) - if cached ~= nil then - return 1 - end - -- if it isn't in the cache, try and load it in there - if red == nil then - red = _M.init() - end - local result = red:get(key) - if result ~= ngx.null then - c:set(key, result, CACHE_TTL) - return 1, red - end - return 0 - else - if red == nil then - red = _M.init() - end - return call(red.exists, {red, key}), red - end -end - -function get(red, key) - if CACHING_ENABLED then - local cached, stale = c:get(key) - if cached ~= nil then - return cached - else - if red == nil then - red = _M.init() - end - local result = red:get(key) - c:set(key, result, CACHE_TTL) - return result, red - end - else - if red == nil then - red = _M.init() - end - return call(red.get, {red, key}) - end -end - -function hget(red, key, id) - if CACHING_ENABLED then - local cachedmap, stale = c:get(key) - if cachedmap ~= nil then - local cached = cachedmap:get(id) - if cached ~= nil then - return cached - else - if red == nil then - red = _M.init() - end - local result = red:hget(key, id) - cachedmap:set(id, result, CACHE_TTL) - c:set(key, cachedmap, CACHE_TTL) - return result, red - end - else - if red == nil then - red = _M.init() - end - local result = red:hget(key, id) - local newcache = lrucache.new(CACHE_SIZE) - newcache:set(id, result, CACHE_TTL) - c:set(key, newcache, CACHE_TTL) - return result, red - end - else - if red == nil then - red = _M.init() - end - return call(red.hget, {red, key, id}), red - end -end - -function hgetall(red, key) - return call(red.hgetall, {red, key}) -end - -function hset(red, key, id, value) - if CACHING_ENABLED then - local cachedmap = c:get(key) - if cachedmap ~= nil then - cachedmap:set(id, value, CACHE_TTL) - c:set(key, cachedmap, CACHE_TTL) - return red:hset(key, id, value) - else - local val = lrucache.new(CACHE_SIZE) - val:set(id, value, CACHE_TTL) - c:set(key, val, CACHE_TTL) - end - end - return call(red.hset, {red, key, id, value}) -end - -function expire(red, key, ttl) - if CACHING_ENABLED then - local cached = c:get(key) - local value = '' - if cached ~= nil then -- just put it back in the cache with a ttl - value = cached - end - c:set(key, value, ttl) - end - return call(red.expire, {red, ttl}) -end - -function del(red, key) - if CACHING_ENABLED then - c:delete(key) - end - return call(red.del, {red, key}) -end - -function hdel(red, key, id) - if CACHING_ENABLED then - local cachecontents = c:get(key) - if cachecontents ~= nil then - cachecontents:del(id) - c:set(key, cachecontents, CACHE_TTL) - end - end - return call(red.hdel, {red, key, id}) -end - -function set(red, key, value) - return call(red.set, {red, key, value}) -end - -function smembers(red, key) - return call(red.smembers, {red, key}) -end - -function srem(red, key, id) - return call(red.srem, {red, key, id}) -end - -function sadd(red, key, id) - return call(red.sadd, {red, key, id}) -end - ---- Call function with retry logic --- @param func function to call --- @param args arguments to pass in to function -function call(func, args) - local res, err = func(unpack(args)) - local retryCount = REDIS_RETRY_COUNT - while not res and retryCount > 0 do - res, err = func(unpack(args)) - retryCount = retryCount - 1 - end - return res, err -end - _M.get = get _M.set = set _M.exists = exists diff --git a/scripts/lua/lib/request.lua b/scripts/lua/lib/request.lua index faa2caa..38f6eab 100644 --- a/scripts/lua/lib/request.lua +++ b/scripts/lua/lib/request.lua @@ -27,7 +27,7 @@ local _Request = {} --- Error function to call when request is malformed -- @param code error code -- @param msg error message -function err(code, msg) +local function err(code, msg) ngx.header.content_type = "application/json; charset=utf-8" ngx.status = code local errObj = cjson.encode({ @@ -41,7 +41,7 @@ end --- Function to call when request is successful -- @param code status code -- @param obj JSON encoded object to return -function success(code, obj) +local function success(code, obj) ngx.status = code if obj ~= nil then ngx.say(obj) diff --git a/scripts/lua/lib/utils.lua b/scripts/lua/lib/utils.lua index e9c2977..4c58f83 100644 --- a/scripts/lua/lib/utils.lua +++ b/scripts/lua/lib/utils.lua @@ -67,7 +67,7 @@ end -- @return concatenated string of (?<path_pathParam>(\\w+)) function _Utils.convertTemplatedPathParam(m) local x = m:gsub("{", ""):gsub("}", "") - return concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"}) + return _Utils.concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"}) end --- Generate random uuid diff --git a/scripts/lua/management/lib/apis.lua b/scripts/lua/management/lib/apis.lua index 04161f2..b2efbd3 100644 --- a/scripts/lua/management/lib/apis.lua +++ b/scripts/lua/management/lib/apis.lua @@ -32,6 +32,33 @@ GATEWAY_URL = (GATEWAY_URL ~= nil and GATEWAY_URL ~= '') and GATEWAY_URL or util local _M = {} +--- Filter APIs based on query parameters +-- @param apis list of apis +-- @param queryParams query parameters to filter tenants +local function filterAPIs(apis, queryParams) + local basePath = queryParams['filter[where][basePath]'] + basePath = basePath == nil and queryParams['basePath'] or basePath + local name = queryParams['filter[where][name]'] + name = name == nil and queryParams['title'] or name + -- missing or invalid query parameters + if (basePath == nil and name == nil) then + return nil + end + -- filter tenants + local apiList = {} + for k, v in pairs(apis) do + if k%2 == 0 then + local api = cjson.decode(v) + if (basePath ~= nil and name == nil and api.basePath == basePath) or + (name ~= nil and basePath == nil and api.name == name) or + (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name) then + apiList[#apiList+1] = api + end + end + end + return apiList +end + --- Get all APIs in redis -- @param ds dataStore.client -- @param queryParams object containing optional query parameters @@ -137,31 +164,4 @@ function _M.deleteAPI(dataStore, id) return {} end ---- Filter APIs based on query parameters --- @param apis list of apis --- @param queryParams query parameters to filter tenants -function filterAPIs(apis, queryParams) - local basePath = queryParams['filter[where][basePath]'] - basePath = basePath == nil and queryParams['basePath'] or basePath - local name = queryParams['filter[where][name]'] - name = name == nil and queryParams['title'] or name - -- missing or invalid query parameters - if (basePath == nil and name == nil) then - return nil - end - -- filter tenants - local apiList = {} - for k, v in pairs(apis) do - if k%2 == 0 then - local api = cjson.decode(v) - if (basePath ~= nil and name == nil and api.basePath == basePath) or - (name ~= nil and basePath == nil and api.name == name) or - (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name) then - apiList[#apiList+1] = api - end - end - end - return apiList -end - return _M diff --git a/scripts/lua/management/lib/swagger.lua b/scripts/lua/management/lib/swagger.lua index d70c08c..b02397e 100644 --- a/scripts/lua/management/lib/swagger.lua +++ b/scripts/lua/management/lib/swagger.lua @@ -18,63 +18,51 @@ --- @module swagger -- Module for parsing swagger file -local _M = {} local utils = require "lib/utils" --- Convert passed-in swagger body to valid lua table --- @param swagger swagger file to parse -function _M.parseSwagger(swagger) - local backends = parseBackends(swagger) - local policies = parseSwaggerPolicies(swagger) - local security = parseSecurity(swagger) - local corsObj = parseCors(swagger) - local decoded = { - name = swagger.info.title, - basePath = swagger.basePath, - resources = {} - } - for path, verbObj in pairs(swagger.paths) do - decoded.resources[path] = { operations = {} } - decoded.resources[path].cors = corsObj - for verb, value in pairs(verbObj) do - decoded.resources[path].operations[verb] = {} - local verbObj = decoded.resources[path].operations[verb] - verbObj.policies = utils.deepCloneTable(policies) or {} - verbObj.security = security - if backends ~= nil then - local backend = (backends["all"] ~= nil) and backends["all"] or backends[value.operationId] - verbObj.backendUrl = backend.backendUrl - verbObj.backendMethod = (backend.backendMethod == 'keep') and verb or backend.backendMethod - if backend.policy ~= nil then - local globalReqMappingPolicy = nil; - for _, policy in pairs(verbObj.policies) do - if policy.type == 'reqMapping' then - globalReqMappingPolicy = policy; - end - end - if globalReqMappingPolicy ~= nil then - for _, v in pairs(backend.policy.value) do - globalReqMappingPolicy.value[#globalReqMappingPolicy.value+1] = v - end - else - verbObj.policies[#verbObj.policies+1] = { - type = 'reqMapping', - value = backend.policy.value +local _M = {} + +--- Parse request mapping +local function parseRequestMapping(configObj) + local valueList = {} + if configObj ~= nil then + for _, obj in pairs(configObj.execute) do + for policy, v in pairs(obj) do + if policy == "set-variable" then + for _, actionObj in pairs(v.actions) do + local fromValue = actionObj.value + local toParsedArray = {string.match(actionObj.set, "([^.]+).([^.]+).([^.]+)") } + local toName = toParsedArray[3] + local toLocation = toParsedArray[2] + toLocation = toLocation == "headers" and "header" or toLocation + valueList[#valueList+1] = { + action = "insert", + from = { + value = fromValue + }, + to = { + name = toName, + location = toLocation + } } end end - else - verbObj.backendUrl = '' - verbObj.backendMethod = verb end end end - return decoded + if next(valueList) ~= nil then + return { + type = "reqMapping", + value = valueList + } + else + return nil + end end --- Parse backendUrl and backendMethod -- @param swagger swagger file to parse -function parseBackends(swagger) +local function parseBackends(swagger) local configObj = swagger["x-gateway-configuration"] configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj if configObj ~= nil then @@ -113,32 +101,10 @@ function parseBackends(swagger) end end ---- Parse policies in swagger --- @param swagger swagger file to parse -function parseSwaggerPolicies(swagger) - local policies = {} - -- parse rate limit - local rlObj = swagger["x-gateway-rate-limit"] - rlObj = (rlObj == nil) and swagger["x-ibm-rate-limit"] or rlObj - local rateLimitPolicy = parseRateLimit(rlObj) - if rateLimitPolicy ~= nil then - policies[#policies+1] = rateLimitPolicy - end - -- parse set-variable - local configObj = swagger["x-gateway-configuration"] - configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj - if configObj ~= nil then - local reqMappingPolicy = parseRequestMapping(configObj.assembly) - if reqMappingPolicy ~= nil then - policies[#policies+1] = reqMappingPolicy - end - end - return policies -end - --- Parse rate limit -function parseRateLimit(rlObj) +local function parseRateLimit(rlObj) if rlObj ~= nil and rlObj[1] ~= nil then + local unit rlObj = rlObj[1] if rlObj.unit == "second" then unit = 1 @@ -164,52 +130,37 @@ function parseRateLimit(rlObj) return nil end ---- Parse request mapping -function parseRequestMapping(configObj) - local valueList = {} +--- Parse policies in swagger +-- @param swagger swagger file to parse +local function parsePolicies(swagger) + local policies = {} + -- parse rate limit + local rlObj = swagger["x-gateway-rate-limit"] + rlObj = (rlObj == nil) and swagger["x-ibm-rate-limit"] or rlObj + local rateLimitPolicy = parseRateLimit(rlObj) + if rateLimitPolicy ~= nil then + policies[#policies+1] = rateLimitPolicy + end + -- parse set-variable + local configObj = swagger["x-gateway-configuration"] + configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj if configObj ~= nil then - for _, obj in pairs(configObj.execute) do - for policy, v in pairs(obj) do - if policy == "set-variable" then - for _, actionObj in pairs(v.actions) do - local fromValue = actionObj.value - local toParsedArray = {string.match(actionObj.set, "([^.]+).([^.]+).([^.]+)") } - local toName = toParsedArray[3] - local toLocation = toParsedArray[2] - toLocation = toLocation == "headers" and "header" or toLocation - valueList[#valueList+1] = { - action = "insert", - from = { - value = fromValue - }, - to = { - name = toName, - location = toLocation - } - } - end - end - end + local reqMappingPolicy = parseRequestMapping(configObj.assembly) + if reqMappingPolicy ~= nil then + policies[#policies+1] = reqMappingPolicy end end - if next(valueList) ~= nil then - return { - type = "reqMapping", - value = valueList - } - else - return nil - end + return policies end --- Parse security in swagger -- @param swagger swagger file to parse -function parseSecurity(swagger) +local function parseSecurity(swagger) local security = {} if swagger["securityDefinitions"] ~= nil then local secObject = swagger["securityDefinitions"] if utils.tableLength(secObject) == 2 then - secObj = { + local secObj = { type = 'clientSecret', scope = 'api' } @@ -242,7 +193,7 @@ function parseSecurity(swagger) return security end -function parseCors(swagger) +local function parseCors(swagger) local cors = { origin = nil, methods = nil } local configObj = swagger["x-gateway-configuration"] configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj @@ -257,4 +208,55 @@ function parseCors(swagger) return nil end +-- Convert passed-in swagger body to valid lua table +-- @param swagger swagger file to parse +function _M.parseSwagger(swagger) + local backends = parseBackends(swagger) + local policies = parsePolicies(swagger) + local security = parseSecurity(swagger) + local corsObj = parseCors(swagger) + local decoded = { + name = swagger.info.title, + basePath = swagger.basePath, + resources = {} + } + for path, verbObj in pairs(swagger.paths) do + decoded.resources[path] = { operations = {} } + decoded.resources[path].cors = corsObj + for verb, value in pairs(verbObj) do + decoded.resources[path].operations[verb] = {} + local verbObj = decoded.resources[path].operations[verb] + verbObj.policies = utils.deepCloneTable(policies) or {} + verbObj.security = security + if backends ~= nil then + local backend = (backends["all"] ~= nil) and backends["all"] or backends[value.operationId] + verbObj.backendUrl = backend.backendUrl + verbObj.backendMethod = (backend.backendMethod == 'keep') and verb or backend.backendMethod + if backend.policy ~= nil then + local globalReqMappingPolicy = nil; + for _, policy in pairs(verbObj.policies) do + if policy.type == 'reqMapping' then + globalReqMappingPolicy = policy; + end + end + if globalReqMappingPolicy ~= nil then + for _, v in pairs(backend.policy.value) do + globalReqMappingPolicy.value[#globalReqMappingPolicy.value+1] = v + end + else + verbObj.policies[#verbObj.policies+1] = { + type = 'reqMapping', + value = backend.policy.value + } + end + end + else + verbObj.backendUrl = '' + verbObj.backendMethod = verb + end + end + end + return decoded +end + return _M diff --git a/scripts/lua/management/lib/tenants.lua b/scripts/lua/management/lib/tenants.lua index a83c773..4fb7a8a 100644 --- a/scripts/lua/management/lib/tenants.lua +++ b/scripts/lua/management/lib/tenants.lua @@ -19,7 +19,6 @@ -- Management interface for tenants for the gateway local cjson = require "cjson" -local redis = require "lib/redis" local utils = require "lib/utils" local request = require "lib/request" local apis = require "management/lib/apis" @@ -38,30 +37,10 @@ function _M.addTenant(dataStore, decoded, existingTenant) return cjson.decode(tenantObj) end ---- Get all tenants in redis --- @param ds redis client --- @param queryParams object containing optional query parameters -function _M.getAllTenants(dataStore, queryParams) - local tenants = dataStore:getAllTenants() - local tenantList - if next(queryParams) ~= nil then - tenantList = filterTenants(tenants, queryParams); - end - if tenantList == nil then - tenantList = {} - for k, v in pairs(tenants) do - if k%2 == 0 then - tenantList[#tenantList+1] = cjson.decode(v) - end - end - end - return tenantList -end - --- Filter tenants based on query parameters -- @param tenants list of tenants -- @param queryParams query parameters to filter tenants -function filterTenants(tenants, queryParams) +local function filterTenants(tenants, queryParams) local namespace = queryParams['filter[where][namespace]'] local instance = queryParams['filter[where][instance]'] -- missing or invalid query parameters @@ -82,49 +61,41 @@ function filterTenants(tenants, queryParams) return tenantList end ---- Get tenant by its id --- @param ds redis client --- @param id tenant id -function _M.getTenant(dataStore, id) - local tenant = dataStore:getTenant(id) - if tenant == nil then - request.err(404, utils.concatStrings({"Unknown tenant id ", id })) - end - return tenant -end - ---- Get APIs associated with tenant +--- Get all tenants in redis -- @param ds redis client --- @param id tenant id -- @param queryParams object containing optional query parameters -function _M.getTenantAPIs(dataStore, id, queryParams) - local apis = dataStore:getAllAPIs() - local apiList +function _M.getAllTenants(dataStore, queryParams) + local tenants = dataStore:getAllTenants() + local tenantList if next(queryParams) ~= nil then - apiList = filterTenantAPIs(id, apis, queryParams); + tenantList = filterTenants(tenants, queryParams); end - if apiList == nil then - apiList = {} - for k, v in pairs(apis) do + if tenantList == nil then + tenantList = {} + for k, v in pairs(tenants) do if k%2 == 0 then - local decoded = cjson.decode(v) - if decoded.tenantId == id then - apiList[#apiList+1] = decoded - end + tenantList[#tenantList+1] = cjson.decode(v) end end end - if (((queryParams['skip'] == nil or queryParams['skip'] == 'undefined') and (queryParams['limit'] == nil or queryParams['limit'] == 'undefined')) or table.getn(apiList) == 0) then - return apiList - else - return applyPagingToAPIs(apiList, queryParams) + return tenantList +end + +--- Get tenant by its id +-- @param ds redis client +-- @param id tenant id +function _M.getTenant(dataStore, id) + local tenant = dataStore:getTenant(id) + if tenant == nil then + request.err(404, utils.concatStrings({"Unknown tenant id ", id })) end + return tenant end -- Apply paging on apis -- @param apis the list of apis -- @param queryparams object containing optional query parameters -function applyPagingToAPIs(apiList, queryParams) +local function applyPagingToAPIs(apiList, queryParams) local skip = queryParams['skip'] == nil and 1 or queryParams['skip'] local limit = queryParams['limit'] == nil and table.getn(apiList) or queryParams['limit'] @@ -155,7 +126,7 @@ end --- Filter apis based on query paramters -- @param queryParams query parameters to filter apis -function filterTenantAPIs(id, apis, queryParams) +local function filterTenantAPIs(id, apis, queryParams) local basePath = queryParams['filter[where][basePath]'] basePath = basePath == nil and queryParams['basePath'] or basePath local name = queryParams['filter[where][name]'] @@ -180,6 +151,34 @@ function filterTenantAPIs(id, apis, queryParams) return apiList end +--- Get APIs associated with tenant +-- @param ds redis client +-- @param id tenant id +-- @param queryParams object containing optional query parameters +function _M.getTenantAPIs(dataStore, id, queryParams) + local apis = dataStore:getAllAPIs() + local apiList + if next(queryParams) ~= nil then + apiList = filterTenantAPIs(id, apis, queryParams); + end + if apiList == nil then + apiList = {} + for k, v in pairs(apis) do + if k%2 == 0 then + local decoded = cjson.decode(v) + if decoded.tenantId == id then + apiList[#apiList+1] = decoded + end + end + end + end + if (((queryParams['skip'] == nil or queryParams['skip'] == 'undefined') and (queryParams['limit'] == nil or queryParams['limit'] == 'undefined')) or table.getn(apiList) == 0) then + return apiList + else + return applyPagingToAPIs(apiList, queryParams) + end +end + --- Delete tenant from gateway -- @param ds redis client -- @param id id of tenant to delete diff --git a/scripts/lua/management/lib/validation.lua b/scripts/lua/management/lib/validation.lua index 4c8ed58..8eef460 100644 --- a/scripts/lua/management/lib/validation.lua +++ b/scripts/lua/management/lib/validation.lua @@ -23,53 +23,67 @@ local utils = require "lib/utils" local _M = {} -function _M.validate(dataStore, decoded) - local fields = {"name", "basePath", "tenantId", "resources"} - for _, v in pairs(fields) do - local res, err = isValid(dataStore, v, decoded[v]) - if res == false then - return err +--- Error checking for policies and security +-- @param policies policies object +-- @param security security object +local function checkOptionalPolicies(policies, security) + if policies then + for _, v in pairs(policies) do + local validTypes = {"reqMapping", "rateLimit", "backendRouting"} + if (v.type == nil or v.value == nil) then + return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"value\"." } + elseif utils.tableContains(validTypes, v.type) == false then + return false, { statusCode = 400, message = "Invalid type in policy object. Valid: " .. cjson.encode(validTypes) } + end + end + end + if security then + for _, sec in ipairs(security) do + local validScopes = {"tenant", "api", "resource"} + if (sec.type == nil or sec.scope == nil) then + return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." } + elseif utils.tableContains(validScopes, sec.scope) == false then + return false, { statusCode = 400, message = "Invalid scope in security object. Valid: " .. cjson.encode(validScopes) } + end end end - return nil end ---- Check JSON body fields for errors --- @param ds edis client instance --- @param field name of field --- @param object field object -function isValid(dataStore, field, object) - -- Check that field exists in body - if not object then - return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) } +--- Error checking for operations +-- @param operations operations object +local function checkOperations(operations) + if not operations or next(operations) == nil then + return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." } end - -- Additional check for basePath - if field == "basePath" then - local basePath = object - if string.match(basePath, "'") then - return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." } + local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true} + for verb, verbObj in pairs(operations) do + if allowedVerbs[verb:upper()] == nil then + return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) } end - end - -- Additional check for tenantId - if field == "tenantId" then - local tenant = dataStore:getTenant(object) - if tenant == nil then - return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) } + -- Check required fields + local requiredFields = {"backendMethod", "backendUrl"} + for k, v in pairs(requiredFields) do + if verbObj[v] == nil then + return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) } + end + if v == "backendMethod" then + local backendMethod = verbObj[v] + if allowedVerbs[backendMethod:upper()] == nil then + return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) } + end + end end - end - if field == "resources" then - local res, err = checkResources(object) + -- Check optional fields + local res, err = checkOptionalPolicies(verbObj.policies, verbObj.security) if res ~= nil and res == false then return res, err end end - -- All error checks passed - return true end --- Error checking for resources -- @param resources resources object -function checkResources(resources) +local function checkResources(resources) if next(resources) == nil then return false, { statusCode = 400, message = "Empty resources object." } end @@ -90,62 +104,48 @@ function checkResources(resources) end end ---- Error checking for operations --- @param operations operations object -function checkOperations(operations) - if not operations or next(operations) == nil then - return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." } +--- Check JSON body fields for errors +-- @param ds edis client instance +-- @param field name of field +-- @param object field object +local function isValid(dataStore, field, object) + -- Check that field exists in body + if not object then + return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) } end - local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true} - for verb, verbObj in pairs(operations) do - if allowedVerbs[verb:upper()] == nil then - return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) } + -- Additional check for basePath + if field == "basePath" then + local basePath = object + if string.match(basePath, "'") then + return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." } end - -- Check required fields - local requiredFields = {"backendMethod", "backendUrl"} - for k, v in pairs(requiredFields) do - if verbObj[v] == nil then - return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) } - end - if v == "backendMethod" then - local backendMethod = verbObj[v] - if allowedVerbs[backendMethod:upper()] == nil then - return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) } - end - end + end + -- Additional check for tenantId + if field == "tenantId" then + local tenant = dataStore:getTenant(object) + if tenant == nil then + return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) } end - -- Check optional fields - local res, err = checkOptionalPolicies(verbObj.policies, verbObj.security) + end + if field == "resources" then + local res, err = checkResources(object) if res ~= nil and res == false then return res, err end end + -- All error checks passed + return true end ---- Error checking for policies and security --- @param policies policies object --- @param security security object -function checkOptionalPolicies(policies, security) - if policies then - for _, v in pairs(policies) do - local validTypes = {"reqMapping", "rateLimit", "backendRouting"} - if (v.type == nil or v.value == nil) then - return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"value\"." } - elseif utils.tableContains(validTypes, v.type) == false then - return false, { statusCode = 400, message = "Invalid type in policy object. Valid: " .. cjson.encode(validTypes) } - end - end - end - if security then - for _, sec in ipairs(security) do - local validScopes = {"tenant", "api", "resource"} - if (sec.type == nil or sec.scope == nil) then - return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." } - elseif utils.tableContains(validScopes, sec.scope) == false then - return false, { statusCode = 400, message = "Invalid scope in security object. Valid: " .. cjson.encode(validScopes) } - end +function _M.validate(dataStore, decoded) + local fields = {"name", "basePath", "tenantId", "resources"} + for _, v in pairs(fields) do + local res, err = isValid(dataStore, v, decoded[v]) + if res == false then + return err end end + return nil end return _M diff --git a/scripts/lua/management/routes/apis.lua b/scripts/lua/management/routes/apis.lua index 73b0904..45086c1 100644 --- a/scripts/lua/management/routes/apis.lua +++ b/scripts/lua/management/routes/apis.lua @@ -33,22 +33,22 @@ local REDIS_PASS = os.getenv("REDIS_PASS") local _M = {} ---- Request handler for routing API calls appropriately -function _M.requestHandler(dataStore) - local requestMethod = ngx.req.get_method() - ngx.header.content_type = "application/json; charset=utf-8" - if requestMethod == "GET" then - getAPIs(dataStore) - elseif requestMethod == 'POST' or requestMethod == 'PUT' then - addAPI(dataStore) - elseif requestMethod == "DELETE" then - deleteAPI(dataStore) - else - request.err(400, "Invalid verb.") +--- Check for api id from uri and use existing API if it already exists in redis +-- @param red Redis client instance +-- @param id API id to check +local function checkForExistingAPI(dataStore, id) + local existing + if id ~= nil and id ~= '' then + existing = dataStore:getAPI(id) + if existing == nil then + dataStore:close() + request.err(404, utils.concatStrings({"Unknown API id ", id})) + end end + return existing end -function getAPIs(dataStore) +local function getAPIs(dataStore) local queryParams = ngx.req.get_uri_args() local id = ngx.var.api_id local version = ngx.var.version @@ -103,7 +103,7 @@ function getAPIs(dataStore) end end -function addAPI(dataStore) +local function addAPI(dataStore) local id = ngx.var.api_id local existingAPI = checkForExistingAPI(dataStore, id) ngx.req.read_body() @@ -159,7 +159,7 @@ function addAPI(dataStore) end end -function deleteAPI(dataStore) +local function deleteAPI(dataStore) local id = ngx.var.api_id if id == nil or id == '' then dataStore:close() @@ -177,19 +177,19 @@ function deleteAPI(dataStore) end end ---- Check for api id from uri and use existing API if it already exists in redis --- @param red Redis client instance --- @param id API id to check -function checkForExistingAPI(dataStore, id) - local existing - if id ~= nil and id ~= '' then - existing = dataStore:getAPI(id) - if existing == nil then - dataStore:close() - request.err(404, utils.concatStrings({"Unknown API id ", id})) - end +--- Request handler for routing API calls appropriately +function _M.requestHandler(dataStore) + local requestMethod = ngx.req.get_method() + ngx.header.content_type = "application/json; charset=utf-8" + if requestMethod == "GET" then + getAPIs(dataStore) + elseif requestMethod == 'POST' or requestMethod == 'PUT' then + addAPI(dataStore) + elseif requestMethod == "DELETE" then + deleteAPI(dataStore) + else + request.err(400, "Invalid verb.") end - return existing end return _M; diff --git a/scripts/lua/management/routes/subscriptions.lua b/scripts/lua/management/routes/subscriptions.lua index 6993f56..6d9406a 100644 --- a/scripts/lua/management/routes/subscriptions.lua +++ b/scripts/lua/management/routes/subscriptions.lua @@ -30,35 +30,69 @@ local REDIS_PASS = os.getenv("REDIS_PASS") local _M = {} -function _M.requestHandler(dataStore) - local version = ngx.var.version - if version == "v2" then - v2(dataStore) - elseif version == "v1" then - v1(dataStore) +local function validateSubscriptionBody(dataStore) + -- Read in the PUT JSON Body + ngx.req.read_body() + local args = ngx.req.get_body_data() + if not args then + dataStore:close() + request.err(400, "Missing request body.") + end + -- Convert json into Lua table + local decoded = cjson.decode(args) + -- Check required fields + local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"}) + if res == false then + dataStore:close() + request.err(err.statusCode, err.message) + end + -- Check if we're using tenant or resource or api + local resource = decoded.resource + local apiId = decoded.apiId + local redisKey + dataStore:setSnapshotId(decoded.tenantId) + local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId}) + if decoded.scope == "tenant" then + redisKey = prefix + elseif decoded.scope == "resource" then + if resource ~= nil then + redisKey = utils.concatStrings({prefix, ":resource:", resource}) + else + dataStore:close() + request.err(400, "\"resource\" missing from request body.") + end + elseif decoded.scope == "api" then + if apiId ~= nil then + redisKey = utils.concatStrings({prefix, ":api:", apiId}) + else + dataStore:close() + request.err(400, "\"apiId\" missing from request body.") + end else - request.err(404, "404 Not found") + dataStore:close() + request.err(400, "Invalid scope") end + redisKey = utils.concatStrings({redisKey, ":key:", decoded.key}) + return redisKey end +local function addSubscription(dataStore) + local redisKey = validateSubscriptionBody(dataStore) + dataStore:createSubscription(redisKey) + dataStore:close() + request.success(200, "Subscription created.") +end --- v2 -- - -function v2(dataStore) - local requestMethod = ngx.req.get_method() - if requestMethod == "POST" or requestMethod == "PUT" then - v2AddSubscription(dataStore) - elseif requestMethod == "GET" then - v2GetSubscriptions(dataStore) - elseif requestMethod == "DELETE" then - v2DeleteSubscription(dataStore) - else - dataStore:close() - request.err(400, "Invalid verb") - end +local function deleteSubscription(dataStore) + local redisKey = validateSubscriptionBody(dataStore) + dataStore:deleteSubscription(redisKey) + dataStore:close() + request.success(200, "Subscription deleted.") end -function v2AddSubscription(dataStore) +-- v2 -- + +local function v2AddSubscription(dataStore) ngx.req.read_body() local args = ngx.req.get_body_data() if not args then @@ -83,19 +117,19 @@ function v2AddSubscription(dataStore) request.success(200, cjson.encode(result)) end -function v2GetSubscriptions(dataStore) +local function v2GetSubscriptions(dataStore) local tenantId = ngx.var.tenant_id local artifactId = ngx.req.get_uri_args()["artifact_id"] if artifactId == nil or artifactId == "" then request.err(400, "Missing artifact_id") end local subscriptionList = subscriptions.getSubscriptions(dataStore, artifactId, tenantId) - redis.close(red) + dataStore:close() ngx.header.content_type = "application/json; charset=utf-8" request.success(200, cjson.encode(subscriptionList)) end -function v2DeleteSubscription(dataStore) +local function v2DeleteSubscription(dataStore) local clientId = ngx.var.client_id local tenantId = ngx.var.tenant_id local artifactId = ngx.req.get_uri_args()["artifact_id"] @@ -109,14 +143,27 @@ function v2DeleteSubscription(dataStore) if res == false then request.err(404, "Subscription doesn't exist") end - redis.close(red) + dataStore:close() request.success(204) end +local function v2(dataStore) + local requestMethod = ngx.req.get_method() + if requestMethod == "POST" or requestMethod == "PUT" then + v2AddSubscription(dataStore) + elseif requestMethod == "GET" then + v2GetSubscriptions(dataStore) + elseif requestMethod == "DELETE" then + v2DeleteSubscription(dataStore) + else + dataStore:close() + request.err(400, "Invalid verb") + end +end -- v1 -- -function v1(dataStore) +local function v1(dataStore) local requestMethod = ngx.req.get_method() if requestMethod == "POST" or requestMethod == "PUT" then addSubscription(dataStore) @@ -128,64 +175,15 @@ function v1(dataStore) end end -function addSubscription(dataStore) - local redisKey = validateSubscriptionBody(dataStore) - dataStore:createSubscription(redisKey) - dataStore:close() - request.success(200, "Subscription created.") -end - -function deleteSubscription(dataStore) - local redisKey = validateSubscriptionBody(dataStore) - dataStore:deleteSubscription(redisKey) - dataStore:close() - request.success(200, "Subscription deleted.") -end - -function validateSubscriptionBody(dataStore) - -- Read in the PUT JSON Body - ngx.req.read_body() - local args = ngx.req.get_body_data() - if not args then - dataStore:close() - request.err(400, "Missing request body.") - end - -- Convert json into Lua table - local decoded = cjson.decode(args) - -- Check required fields - local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"}) - if res == false then - dataStore:close() - request.err(err.statusCode, err.message) - end - -- Check if we're using tenant or resource or api - local resource = decoded.resource - local apiId = decoded.apiId - local redisKey - dataStore:setSnapshotId(decoded.tenantId) - local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId}) - if decoded.scope == "tenant" then - redisKey = prefix - elseif decoded.scope == "resource" then - if resource ~= nil then - redisKey = utils.concatStrings({prefix, ":resource:", resource}) - else - dataStore:close() - request.err(400, "\"resource\" missing from request body.") - end - elseif decoded.scope == "api" then - if apiId ~= nil then - redisKey = utils.concatStrings({prefix, ":api:", apiId}) - else - dataStore:close() - request.err(400, "\"apiId\" missing from request body.") - end +function _M.requestHandler(dataStore) + local version = ngx.var.version + if version == "v2" then + v2(dataStore) + elseif version == "v1" then + v1(dataStore) else - dataStore:close() - request.err(400, "Invalid scope") + request.err(404, "404 Not found") end - redisKey = utils.concatStrings({redisKey, ":key:", decoded.key}) - return redisKey end return _M diff --git a/scripts/lua/management/routes/tenants.lua b/scripts/lua/management/routes/tenants.lua index 9e26020..4b92bdd 100644 --- a/scripts/lua/management/routes/tenants.lua +++ b/scripts/lua/management/routes/tenants.lua @@ -29,22 +29,23 @@ local REDIS_PASS = os.getenv("REDIS_PASS") local _M = {}; ---- Request handler for routing tenant calls appropriately -function _M.requestHandler(dataStore) - local requestMethod = ngx.req.get_method() - ngx.header.content_type = "application/json; charset=utf-8" - if requestMethod == "GET" then - getTenants(dataStore) - elseif requestMethod == "PUT" or requestMethod == "POST" then - addTenant(dataStore) - elseif requestMethod == "DELETE" then - deleteTenant(dataStore) - else - request.err(400, "Invalid verb.") +--- Check for tenant id from uri and use existing tenant if it already exists in redis +-- @param red Redis client instance +local function checkForExistingTenant(dataStore) + local id = ngx.var.tenant_id + local existing + -- Get object from redis + if id ~= nil and id ~= '' then + existing = dataStore:getTenant(id) + if existing == nil then + dataStore:close() + request.err(404, utils.concatStrings({"Unknown Tenant id ", id})) + end end + return existing end -function addTenant(dataStore) +local function addTenant(dataStore) -- Open connection to redis or use one from connection pool -- Check for tenant id and use existingTenant if it already exists in redis local existingTenant = checkForExistingTenant(dataStore) @@ -76,25 +77,9 @@ function addTenant(dataStore) request.success(200, tenantObj) end ---- Check for tenant id from uri and use existing tenant if it already exists in redis --- @param red Redis client instance -function checkForExistingTenant(dataStore) - local id = ngx.var.tenant_id - local existing - -- Get object from redis - if id ~= nil and id ~= '' then - existing = dataStore:getTenant(id) - if existing == nil then - dataStore:close() - request.err(404, utils.concatStrings({"Unknown Tenant id ", id})) - end - end - return existing -end - --- Get one or all tenants from the gateway -- GET /v1/tenants -function getTenants(dataStore) +local function getTenants(dataStore) local queryParams = ngx.req.get_uri_args() local id = ngx.var.tenant_id if id == '' then @@ -125,7 +110,7 @@ end --- Delete tenant from gateway -- DELETE /v1/tenants/<id> -function deleteTenant(dataStore) +local function deleteTenant(dataStore) local id = ngx.var.tenant_id if id == nil or id == '' then request.err(400, "No id specified.") @@ -139,4 +124,19 @@ function deleteTenant(dataStore) request.success(200, cjson.encode({})) end +--- Request handler for routing tenant calls appropriately +function _M.requestHandler(dataStore) + local requestMethod = ngx.req.get_method() + ngx.header.content_type = "application/json; charset=utf-8" + if requestMethod == "GET" then + getTenants(dataStore) + elseif requestMethod == "PUT" or requestMethod == "POST" then + addTenant(dataStore) + elseif requestMethod == "DELETE" then + deleteTenant(dataStore) + else + request.err(400, "Invalid verb.") + end +end + return _M diff --git a/scripts/lua/oauth/facebook.lua b/scripts/lua/oauth/facebook.lua index a27b295..08b1f5b 100644 --- a/scripts/lua/oauth/facebook.lua +++ b/scripts/lua/oauth/facebook.lua @@ -20,25 +20,8 @@ local cjson = require 'cjson' local utils = require "lib/utils" local _M = {} -function _M.process(dataStore, token) - - local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_") - - local facebookAppToken = ngx.var[headerName] - if facebookAppToken == nil then - request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header') - return nil - end - local result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken})) - if result ~= ngx.null then - return cjson.decode(result) - end - - return exchangeOAuthToken(dataStore, token, facebookAppToken) -end - -function exchangeOAuthToken(dataStore, token, facebookAppToken) +local function exchangeOAuthToken(dataStore, token, facebookAppToken) local http = require 'resty.http' local request = require "lib/request" local httpc = http.new() @@ -73,4 +56,22 @@ function exchangeOAuthToken(dataStore, token, facebookAppToken) return json_resp end +function _M.process(dataStore, token) + + local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_") + + local facebookAppToken = ngx.var[headerName] + if facebookAppToken == nil then + request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header') + return nil + end + + local result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken})) + if result ~= ngx.null then + return cjson.decode(result) + end + + return exchangeOAuthToken(dataStore, token, facebookAppToken) +end + return _M diff --git a/scripts/lua/oauth/google.lua b/scripts/lua/oauth/google.lua index bd8e11b..efdbf28 100644 --- a/scripts/lua/oauth/google.lua +++ b/scripts/lua/oauth/google.lua @@ -29,7 +29,7 @@ function _M.process (dataStore, token) local httpc = http.new() if result ~= ngx.null then - json_resp = cjson.decode(result) + local json_resp = cjson.decode(result) ngx.header['X-OIDC-Sub'] = json_resp['sub'] ngx.header['X-OIDC-Email'] = json_resp['email'] ngx.header['X-OIDC-Scope'] = json_resp['scope'] diff --git a/scripts/lua/policies/backendRouting.lua b/scripts/lua/policies/backendRouting.lua index 646de94..e426e53 100644 --- a/scripts/lua/policies/backendRouting.lua +++ b/scripts/lua/policies/backendRouting.lua @@ -26,6 +26,14 @@ local backendOverride = os.getenv("BACKEND_HOST") local _M = {} +local function setUpstream(u) + local upstream = utils.concatStrings({u.scheme, '://', u.host}) + if u.port ~= nil and u.port ~= '' then + upstream = utils.concatStrings({upstream, ':', u.port}) + end + ngx.var.upstream = upstream +end + --- Set upstream based on the backendUrl function _M.setRoute(backendUrl, gatewayPath) _M.setRouteWithOverride(backendUrl, gatewayPath, backendOverride) @@ -109,12 +117,4 @@ function _M.getUriPath(backendPath) end end -function setUpstream(u) - local upstream = utils.concatStrings({u.scheme, '://', u.host}) - if u.port ~= nil and u.port ~= '' then - upstream = utils.concatStrings({upstream, ':', u.port}) - end - ngx.var.upstream = upstream -end - return _M diff --git a/scripts/lua/policies/mapping.lua b/scripts/lua/policies/mapping.lua index 63ba789..3ed6385 100644 --- a/scripts/lua/policies/mapping.lua +++ b/scripts/lua/policies/mapping.lua @@ -30,33 +30,58 @@ local query local headers local path ---- Implementation for the mapping policy. --- @param map The mapping object that contains details about request tranformations -function processMap(map) - getRequestParams() - for k, v in pairs(map) do - if v.action == "insert" then - insertParam(v) - elseif v.action == "remove" then - removeParam(v) - elseif v.action == "transform" then - transformParam(v) - elseif v.action == "default" then - checkDefault(v) - else - logger.err(utils.concatStrings({'Map action not recognized. Skipping... ', v.action})) - end +local function insertHeader(k, v) + ngx.req.set_header(k, v) + headers[k] = v +end + +local function insertQuery(k, v) + query[k] = v +end + +local function insertBody(k, v) + body[k] = v +end + +local function insertPath(k, v) + v = ngx.unescape_uri(v) + path = path:gsub(utils.concatStrings({"%{", k ,"%}"}), v) + ngx.req.set_uri(path) +end + +local function removeHeader(k) + ngx.req.clear_header(k) +end + +local function removeQuery(k) + query[k] = nil +end + +local function removeBody(k) + body[k] = nil +end + +local function decodeQuery(param) + local decoded = param:gsub('+', ' '):gsub('%%(%x%x)', + function(hex) return string.char(tonumber(hex, 16)) end) + return decoded +end + +local function parseUrl(url) + local map = {} + for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do + map[ k ] = decodeQuery(v) end - finalize() + return map end --- Get request body, params, and headers from incoming requests -function getRequestParams() +local function getRequestParams() ngx.req.read_body() body = ngx.req.get_body_data() if body ~= nil then -- decode body if json - decoded, err = cjson.decode(body) + local decoded, err = cjson.decode(body) if err == nil then body = decoded end @@ -74,7 +99,7 @@ end --- Insert parameter value to header, body, or query params into request -- @param m Parameter value to add to request -function insertParam(m) +local function insertParam(m) local v local k = m.to.name if m.from.value ~= nil then @@ -102,7 +127,7 @@ end --- Remove parameter value to header, body, or query params from request -- @param m Parameter value to remove from request -function removeParam(m) +local function removeParam(m) if m.from.location == "header" then removeHeader(m.from.name) elseif m.from.location == "query" then @@ -112,35 +137,12 @@ function removeParam(m) end end ---- Move parameter value from one location to another in the request --- @param m Parameter value to move within request -function transformParam(m) - if m.from.name == '*' then - transformAllParams(m.from.location, m.to.location) - else - insertParam(m) - removeParam(m) - end -end - ---- Checks if the header has been set, and sets the header to a value if found to be null. --- @param m Header name and value to be set, if header is null. -function checkDefault(m) - if m.to.location == "header" and headers[m.to.name] == nil then - insertHeader(m.to.name, m.from.value) - elseif m.to.location == "query" and query[m.to.name] == nil then - insertQuery(m.to.name, m.from.value) - elseif m.to.location == "body" and body[m.to.name] == nil then - insertBody(m.to.name, m.from.value) - end -end - --- Function to handle wildcarding in the transform process. -- If the value in the from object is '*', this function will pull all values from the incoming request -- and move them to the location provided in the to object -- @param s The source object from which we pull all parameters -- @param d The destination object that we will move all found parameters to. -function transformAllParams(s, d) +local function transformAllParams(s, d) if s == 'query' then for k, v in pairs(query) do local t = {} @@ -192,57 +194,55 @@ function transformAllParams(s, d) end end -function finalize() - if type(body) == 'table' and next(body) ~= nil then - local bodyJson = cjson.encode(body) - ngx.req.set_body_data(bodyJson) +--- Move parameter value from one location to another in the request +-- @param m Parameter value to move within request +local function transformParam(m) + if m.from.name == '*' then + transformAllParams(m.from.location, m.to.location) + else + insertParam(m) + removeParam(m) end - ngx.req.set_uri_args(query) -end - -function insertHeader(k, v) - ngx.req.set_header(k, v) - headers[k] = v -end - -function insertQuery(k, v) - query[k] = v end -function insertBody(k, v) - body[k] = v -end - -function insertPath(k, v) - v = ngx.unescape_uri(v) - path = path:gsub(utils.concatStrings({"%{", k ,"%}"}), v) - ngx.req.set_uri(path) -end - -function removeHeader(k) - ngx.req.clear_header(k) -end - -function removeQuery(k) - query[k] = nil -end - -function removeBody(k) - body[k] = nil +--- Checks if the header has been set, and sets the header to a value if found to be null. +-- @param m Header name and value to be set, if header is null. +local function checkDefault(m) + if m.to.location == "header" and headers[m.to.name] == nil then + insertHeader(m.to.name, m.from.value) + elseif m.to.location == "query" and query[m.to.name] == nil then + insertQuery(m.to.name, m.from.value) + elseif m.to.location == "body" and body[m.to.name] == nil then + insertBody(m.to.name, m.from.value) + end end -function parseUrl(url) - local map = {} - for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do - map[ k ] = decodeQuery(v) +local function finalize() + if type(body) == 'table' and next(body) ~= nil then + local bodyJson = cjson.encode(body) + ngx.req.set_body_data(bodyJson) end - return map + ngx.req.set_uri_args(query) end -function decodeQuery(param) - local decoded = param:gsub('+', ' '):gsub('%%(%x%x)', - function(hex) return string.char(tonumber(hex, 16)) end) - return decoded +--- Implementation for the mapping policy. +-- @param map The mapping object that contains details about request tranformations +local function processMap(map) + getRequestParams() + for k, v in pairs(map) do + if v.action == "insert" then + insertParam(v) + elseif v.action == "remove" then + removeParam(v) + elseif v.action == "transform" then + transformParam(v) + elseif v.action == "default" then + checkDefault(v) + else + logger.err(utils.concatStrings({'Map action not recognized. Skipping... ', v.action})) + end + end + finalize() end _M.processMap = processMap diff --git a/scripts/lua/policies/rateLimit.lua b/scripts/lua/policies/rateLimit.lua index 5f2ca98..468852f 100644 --- a/scripts/lua/policies/rateLimit.lua +++ b/scripts/lua/policies/rateLimit.lua @@ -26,7 +26,7 @@ local _M = {} -- @param dataStore the datastore object -- @param obj rateLimit object containing interval, rate, scope, subscription fields -- @param apiKey optional api key to use if subscription is set to true -function limit(dataStore, obj, apiKey) +local function limit(dataStore, obj, apiKey) local rate = obj.interval / obj.rate local tenantId = ngx.var.tenant local gatewayPath = ngx.var.gatewayPath diff --git a/scripts/lua/policies/security.lua b/scripts/lua/policies/security.lua index 2bcdeb0..4afe76c 100644 --- a/scripts/lua/policies/security.lua +++ b/scripts/lua/policies/security.lua @@ -24,7 +24,7 @@ local utils = require "lib/utils" --- Allow or block a request by calling a loaded security policy -- @param dataStore the datastore object -- @param securityObj an object out of the security array in a given tenant / api / resource -function process(dataStore, securityObj) +local function process(dataStore, securityObj) local ok, result = pcall(require, utils.concatStrings({'policies/security/', securityObj.type})) if not ok then ngx.log(ngx.ERR, 'An unexpected error ocurred while processing the security policy: ' .. securityObj.type) diff --git a/scripts/lua/policies/security/apiKey.lua b/scripts/lua/policies/security/apiKey.lua index 409d6d9..7abee5b 100644 --- a/scripts/lua/policies/security/apiKey.lua +++ b/scripts/lua/policies/security/apiKey.lua @@ -18,13 +18,9 @@ --- @module apiKey -- Check a subscription with an API Key -local dataStore = require "lib/dataStore" local utils = require "lib/utils" local request = require "lib/request" - -local REDIS_HOST = os.getenv("REDIS_HOST") -local REDIS_PORT = os.getenv("REDIS_PORT") -local REDIS_PASS = os.getenv("REDIS_PASS") +local logger = require "lib/logger" local _M = {} @@ -36,7 +32,7 @@ local _M = {} -- @param scope scope of the subscription -- @param apiKey the subscription api key -- @param return boolean value indicating if the subscription exists in redis -function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey) +local function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey) -- Open connection to redis or use one from connection pool local k if scope == 'tenant' then @@ -54,27 +50,24 @@ function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey) end end -function process(dataStore, securityObj) - return processWithHashFunction(dataStore, securityObj, sha256) -end - --- Process the security object -- @param dataStore the datastore object -- @param securityObj security object from nginx conf file -- @param hashFunction a function that will be called to hash the string -- @return apiKey api key for the subscription -function processWithHashFunction(dataStore, securityObj, hashFunction) +local function processWithHashFunction(dataStore, securityObj, hashFunction) local tenant = ngx.var.tenant local gatewayPath = ngx.var.gatewayPath local apiId = dataStore:resourceToApi(utils.concatStrings({'resources:', tenant, ':', gatewayPath})) local scope = securityObj.scope - local name = (securityObj.name == nil) and ((securityObj.header == nil) and 'x-api-key' or securityObj.header) or securityObj.name local queryString = ngx.req.get_uri_args() local location = (securityObj.location == nil) and 'header' or securityObj.location -- backwards compatible with "header" argument for name value. "name" argument takes precedent if both provided local name = (securityObj.name == nil and securityObj.header == nil) and 'x-api-key' or (securityObj.name or securityObj.header) local apiKey = nil + ngx.log(ngx.DEBUG, "Processing API_KEY security policy") + if location == "header" then apiKey = ngx.var[utils.concatStrings({'http_', name}):gsub("-", "_")] end @@ -97,17 +90,10 @@ function processWithHashFunction(dataStore, securityObj, hashFunction) return apiKey end ---- Calculate the sha256 hash of a string --- @param str the string you want to hash --- @return a hashed version of the string -function sha256(str) - local resty_sha256 = require "resty.sha256" - local resty_str = require "resty.string" - local sha = resty_sha256:new() - sha:update(str) - local digest = sha:final() - return resty_str.to_hex(digest) +local function process(dataStore, securityObj) + return processWithHashFunction(dataStore, securityObj, utils.sha256) end + _M.processWithHashFunction = processWithHashFunction _M.process = process diff --git a/scripts/lua/policies/security/clientSecret.lua b/scripts/lua/policies/security/clientSecret.lua index 79f2b82..132c58a 100644 --- a/scripts/lua/policies/security/clientSecret.lua +++ b/scripts/lua/policies/security/clientSecret.lua @@ -19,21 +19,35 @@ -- Check a subscription with a client id and a hashed secret local _M = {} -local dataStore = require "lib/dataStore" local utils = require "lib/utils" local request = require "lib/request" +local logger = require "lib/logger" -local REDIS_HOST = os.getenv("REDIS_HOST") -local REDIS_PORT = os.getenv("REDIS_PORT") -local REDIS_PASS = os.getenv("REDIS_PASS") - ---- Process function main entry point for the security block. --- Takes 2 headers and decides if the request should be allowed based on a hashed secret key ---@param dataStore the datastore object ---@param securityObj the security object loaded from nginx.conf ---@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret -function process(dataStore, securityObj) - local result = processWithHashFunction(dataStore, securityObj, utils.hash) +--- Validate that the subscription exists in the dataStore +-- @param dataStore the datastore object +-- @param tenant the tenantId we are checking for +-- @param gatewayPath the possible resource we are checking for +-- @param apiId if we are checking for an api +-- @param scope which values should we be using to find the location of the secret +-- @param clientId the subscribed client id +-- @param clientSecret the hashed client secret +local function validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, clientSecret) + -- Open connection to redis or use one from connection pool + local k + if scope == "tenant" then + k = utils.concatStrings({"subscriptions:tenant:", tenant}) + elseif scope == "resource" then + k = utils.concatStrings({"subscriptions:tenant:", tenant, ":resource:", gatewayPath}) + elseif scope == "api" then + k = utils.concatStrings({"subscriptions:tenant:", tenant, ":api:", apiId}) + end + -- using the same key location in redis, just using :clientsecret: instead of :key: + k = utils.concatStrings({k, ":clientsecret:", clientId, ":", clientSecret}) + if dataStore:exists(k) == 1 then + return k + else + return nil + end end --- In order to properly test this functionallity, I use this function to do all of the business logic with injected dependencies @@ -41,47 +55,49 @@ end -- @param dataStore the datastore object -- @param securityObj the security configuration for the tenant/resource/api we are verifying -- @param hashFunction the function used to perform the hash of the api secret -function processWithHashFunction(dataStore, securityObj, hashFunction) +local function processWithHashFunction(dataStore, securityObj, hashFunction) -- pull the configuration from nginx local tenant = ngx.var.tenant local gatewayPath = ngx.var.gatewayPath local apiId = ngx.var.apiId local scope = securityObj.scope local queryString = ngx.req.get_uri_args() - local location = (securityObj.location == nil) and 'header' or securityObj.location + local location = (securityObj.location == nil) and "header" or securityObj.location local clientId = nil local clientSecret = nil + ngx.log(ngx.DEBUG, "Processing CLIENT_SECRET security policy") + -- allow support for custom names in query or header - local clientIdName = (securityObj.idFieldName == nil) and 'X-Client-ID' or securityObj.idFieldName + local clientIdName = (securityObj.idFieldName == nil) and "X-Client-ID" or securityObj.idFieldName if location == "header" then - clientId = ngx.var[utils.concatStrings({'http_', clientIdName}):gsub("-", "_")] + clientId = ngx.var[utils.concatStrings({"http_", clientIdName}):gsub("-", "_")] end if location == "query" then clientId = queryString[clientIdName] end --- if they didn't supply whatever name this is configured to require, error out - if clientId == nil or clientId == '' then + -- if they didn't supply whatever name this is configured to require, error out + if clientId == nil or clientId == "" then request.err(401, clientIdName .. " required") return false end --- allow support for custom names in query or header - local clientSecretName = (securityObj.secretFieldName == nil) and 'X-Client-Secret' or securityObj.secretFieldName - _G.clientSecretName = clientSecretName:lower() + -- allow support for custom names in query or header + local clientSecretName = (securityObj.secretFieldName == nil) and "X-Client-Secret" or securityObj.secretFieldName + ngx.ctx.clientSecretName = clientSecretName:lower() if location == "header" then - clientSecret = ngx.var[utils.concatStrings({'http_', clientSecretName}):gsub("-","_")] + clientSecret = ngx.var[utils.concatStrings({"http_", clientSecretName}):gsub("-", "_")] end if location == "query" then clientSecret = queryString[clientSecretName] end --- if they didn't supply whatever name this is configured to require, error out - if clientSecret == nil or clientSecret == '' then + -- if they didn't supply whatever name this is configured to require, error out + if clientSecret == nil or clientSecret == "" then request.err(401, clientSecretName .. " required") return false end --- hash the secret + -- hash the secret local result = validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, hashFunction(clientSecret)) if result == nil then request.err(401, "Secret mismatch or not subscribed to this api.") @@ -90,32 +106,15 @@ function processWithHashFunction(dataStore, securityObj, hashFunction) return result end ---- Validate that the subscription exists in the dataStore --- @param dataStore the datastore object --- @param tenant the tenantId we are checking for --- @param gatewayPath the possible resource we are checking for --- @param apiId if we are checking for an api --- @param scope which values should we be using to find the location of the secret --- @param clientId the subscribed client id --- @param clientSecret the hashed client secret -function validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, clientSecret) --- Open connection to redis or use one from connection pool - local k - if scope == 'tenant' then - k = utils.concatStrings({'subscriptions:tenant:', tenant}) - elseif scope == 'resource' then - k = utils.concatStrings({'subscriptions:tenant:', tenant, ':resource:', gatewayPath}) - elseif scope == 'api' then - k = utils.concatStrings({'subscriptions:tenant:', tenant, ':api:', apiId}) - end - -- using the same key location in redis, just using :clientsecret: instead of :key: - k = utils.concatStrings({k, ':clientsecret:', clientId, ':', clientSecret}) - if dataStore:exists(k) == 1 then - return k - else - return nil - end +--- Process function main entry point for the security block. +-- Takes 2 headers and decides if the request should be allowed based on a hashed secret key +--@param dataStore the datastore object +--@param securityObj the security object loaded from nginx.conf +--@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret +local function process(dataStore, securityObj) + return processWithHashFunction(dataStore, securityObj, utils.hash) end + _M.processWithHashFunction = processWithHashFunction _M.process = process return _M diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua index 6b58bf9..c1492e5 100644 --- a/scripts/lua/policies/security/oauth2.lua +++ b/scripts/lua/policies/security/oauth2.lua @@ -20,20 +20,37 @@ local utils = require "lib/utils" local request = require "lib/request" -local dataStore = require "lib/dataStore" -local cjson = require "cjson" - -local REDIS_HOST = os.getenv("REDIS_HOST") -local REDIS_PORT = os.getenv("REDIS_PORT") -local REDIS_PASS = os.getenv("REDIS_PASS") local _M = {} +--- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf +-- @param dataStore the datastore object +-- @param token the accessToken passed in the authorization header of the routing request +-- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook +-- @return the json object recieved from exchanging tokens with the provider +local function exchange(dataStore, token, provider, securityObj) + -- exchange tokens with the provider + local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider})) + if not loaded then + request.err(500, 'Error loading OAuth provider authentication module') + print("error loading provider:", impl) + return nil + end + + local result = impl.process(dataStore, token, securityObj) + if result == nil then + request.err('401', 'OAuth token didn\'t work or provider doesn\'t support OpenID connect') + end + -- cache the token + return result +end -- Process the security object -- @param securityObj security object from nginx conf file -- @return oauthId oauth identification -function process(dataStore, securityObj) +local function process(dataStore, securityObj) + ngx.log(ngx.DEBUG, "Processing OAUTH2 security policy") + local accessToken = ngx.var['http_Authorization'] if accessToken == nil then request.err(401, "No Authorization header provided") @@ -56,27 +73,5 @@ function process(dataStore, securityObj) return token end ---- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf --- @param dataStore the datastore object --- @param token the accessToken passed in the authorization header of the routing request --- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook --- @return the json object recieved from exchanging tokens with the provider -function exchange(dataStore, token, provider, securityObj) - -- exchange tokens with the provider - local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider})) - if not loaded then - request.err(500, 'Error loading OAuth provider authentication module') - print("error loading provider:", impl) - return nil - end - - local result = impl.process(dataStore, token, securityObj) - if result == nil then - request.err('401', 'OAuth token didn\'t work or provider doesn\'t support OpenID connect') - end - -- cache the token - return result -end - _M.process = process return _M diff --git a/scripts/lua/routing.lua b/scripts/lua/routing.lua index f4d1845..080fca6 100644 --- a/scripts/lua/routing.lua +++ b/scripts/lua/routing.lua @@ -44,6 +44,46 @@ local SNAPSHOTTING = os.getenv('SNAPSHOTTING') local _M = {} +local function setRequestLogs() + local requestHeaders = ngx.req.get_headers() + for k, v in pairs(requestHeaders) do + if k == 'authorization' or k == ngx.ctx.clientSecretName then + requestHeaders[k] = '[redacted]' + end + end + ngx.var.requestHeaders = cjson.encode(requestHeaders) + ngx.req.read_body() + ngx.var.requestBody = ngx.req.get_body_data() +end + +--- Function to read the list of policies and send implementation to the correct backend +-- @param red redis client instance +-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately. +-- @param apiKey optional subscription api key +local function parsePolicies(dataStore, obj, apiKey) + for k, v in pairs (obj) do + if v.type == 'reqMapping' then + mapping.processMap(v.value) + elseif v.type == 'rateLimit' then + rateLimit.limit(dataStore, v.value, apiKey) + elseif v.type == 'backendRouting' then + backendRouting.setDynamicRoute(v.value) + end + end +end + +--- Given a verb, transforms the backend request to use that method +-- @param v Verb to set on the backend request +local function setVerb(v) + local allowedVerbs = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'} + local verb = string.upper(v) + if utils.tableContains(allowedVerbs, verb) then + ngx.req.set_method(ngx[utils.concatStrings({"HTTP_", verb})]) + else + ngx.req.set_method(ngx.HTTP_GET) + end +end + --- Main function that handles parsing of invocation details and carries out implementation function _M.processCall(dataStore) -- Get request headers @@ -154,7 +194,7 @@ function _M.findResource(dataStore, tenant, path) end local resourceKeys = dataStore:getAllResources(tenant) - local result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey) + result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey) if OPTIMIZE > 0 and result ~= nil then dataStore:optimizeLookup(tenant, result, path) @@ -177,6 +217,7 @@ function _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey) return key end end + local cfUrl = ngx.req.get_headers()["x-cf-forwarded-url"] if cfUrl ~= nil and cfUrl ~= "" then return nil end @@ -242,46 +283,6 @@ function _M.pathParamMatch(key, resourceKey) return false end ---- Function to read the list of policies and send implementation to the correct backend --- @param red redis client instance --- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately. --- @param apiKey optional subscription api key -function parsePolicies(dataStore, obj, apiKey) - for k, v in pairs (obj) do - if v.type == 'reqMapping' then - mapping.processMap(v.value) - elseif v.type == 'rateLimit' then - rateLimit.limit(dataStore, v.value, apiKey) - elseif v.type == 'backendRouting' then - backendRouting.setDynamicRoute(v.value) - end - end -end - ---- Given a verb, transforms the backend request to use that method --- @param v Verb to set on the backend request -function setVerb(v) - local allowedVerbs = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'} - local verb = string.upper(v) - if utils.tableContains(allowedVerbs, verb) then - ngx.req.set_method(ngx[utils.concatStrings({"HTTP_", verb})]) - else - ngx.req.set_method(ngx.HTTP_GET) - end -end - -function setRequestLogs() - local requestHeaders = ngx.req.get_headers() - for k, v in pairs(requestHeaders) do - if k == 'authorization' or k == _G.clientSecretName then - requestHeaders[k] = '[redacted]' - end - end - ngx.var.requestHeaders = cjson.encode(requestHeaders) - ngx.req.read_body() - ngx.var.requestBody = ngx.req.get_body_data() -end - function _M.setResponseLogs() ngx.var.responseHeaders = cjson.encode(ngx.resp.get_headers()) local resp_body = ngx.arg[1] diff --git a/tools/lua-releng b/tools/lua-releng new file mode 100755 index 0000000..7560316 --- /dev/null +++ b/tools/lua-releng @@ -0,0 +1,104 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Getopt::Std; + +my (@luas, @tests); + +my %opts; +getopts('Lse', \%opts) or die "Usage: lua-releng [-L] [-s] [-e] [files]\n"; + +my $silent = $opts{s}; +my $stop_on_error = $opts{e}; +my $no_long_line_check = $opts{L}; + +my $check_lua_ver = "luac -v | awk '{print\$2}'| grep 5.1"; +my $output = `$check_lua_ver`; +if ($output eq '') { + die "ERROR: lua-releng ONLY supports Lua 5.1!\n"; +} + +if ($#ARGV != -1) { + @luas = @ARGV; + +} else { + @luas = map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua }; + if (-d 't') { + @tests = map glob, qw{ t/*.t t/*/*.t t/*/*/*.t }; + } +} + +for my $f (sort @luas) { + process_file($f); +} + +for my $t (@tests) { + blank(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $t}); +} +# p: prints a string to STDOUT appending \n +# w: prints a string to STDERR appending \n +# Both respect the $silent value +sub p { print "$_[0]\n" if (!$silent) } +sub w { warn "$_[0]\n" if (!$silent) } + +# blank: runs a command and looks at the output. If the output is not +# blank it is printed (and the program dies if stop_on_error is 1) +sub blank { + my ($command) = @_; + if ($stop_on_error) { + my $output = `$command`; + if ($output ne '') { + die $output; + } + } else { + system($command); + } +} + +my $version; +sub process_file { + my $file = shift; + # Check the sanity of each .lua file + open my $in, $file or + die "ERROR: Can't open $file for reading: $!\n"; + my $found_ver; + while (<$in>) { + my ($ver, $skipping); + if (/(?x) (?:_VERSION|version) \s* = .*? ([\d\.]*\d+) (.*? SKIP)?/) { + my $orig_ver = $ver = $1; + $found_ver = 1; + $skipping = $2; + $ver =~ s{^(\d+)\.(\d{3})(\d{3})$}{join '.', int($1), int($2), int($3)}e; + w("$file: $orig_ver ($ver)"); + last; + + } elsif (/(?x) (?:_VERSION|version) \s* = \s* ([a-zA-Z_]\S*)/) { + w("$file: $1"); + $found_ver = 1; + last; + } + + if ($ver and $version and !$skipping) { + if ($version ne $ver) { + die "$file: $ver != $version\n"; + } + } elsif ($ver and !$version) { + $version = $ver; + } + } + if (!$found_ver) { + w("WARNING: No \"_VERSION\" or \"version\" field found in `$file`."); + } + close $in; + + p("Checking use of Lua global variables in file $file..."); + p("\top no.\tline\tinstruction\targs\t; code"); + blank("luac -p -l $file | grep -E '[GS]ETGLOBAL' | grep -vE '\\<(require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|rawget|rawset|rawlen|select)\\>'"); + unless ($no_long_line_check) { + p("Checking line length exceeding 80..."); + blank("grep -H -n -E --color '.{81}' $file"); + } +} + \ No newline at end of file
