Open reggiepierce opened 8 years ago
Took a stab at a feature update. First time using Lua, so probably inefficient.
I added two new functionalities:
See the code below:
local jwt = require('resty.jwt')
local lrucache = require('resty.lrucache')
local validators = require('resty.jwt-validators')
local stormpathApplicationHref = os.getenv('STORMPATH_APPLICATION_HREF')
local stormpathApiKeyId = os.getenv('STORMPATH_CLIENT_APIKEY_ID')
local stormpathApiKeySecret = os.getenv('STORMPATH_CLIENT_APIKEY_SECRET')
local groupMembershipCacheExpireSeconds = os.getenv('STORMPATH_GROUP_MEMBERSHIP_CACHE_EXPIRE_SECONDS')
local jwtCookieName = os.getenv('STORMPATH_JWT_COOKIE_NAME')
local cache = lrucache.new(200)
local M = {}
local Helpers = {}
function M.getAccount()
getAccount(false)
end
function M.requireAccount()
getAccount(true)
end
function M.requireAccountWithGroupMembership(groupHref)
local jwt=getAccount(true)
checkGroupMembership(jwt, groupHref)
end
function getAccount(required)
local jwtString = Helpers.getBearerToken()
if isEmpty(jwtString) then
jwtString = getJwtCookie()
end
local jwt=getJwt(jwtString)
if jwt==nil then
return Helpers.exit(required)
end
ngx.req.set_header('x-stormpath-application-href', jwt.payload.iss)
ngx.req.set_header('x-stormpath-account-href', jwt.payload.sub)
return jwt
end
local http = require('resty.http')
local cjson = require('cjson')
-- custom methods for group stuff
function getJwtCookie()
local cookie_name = jwtCookieName
if isEmpty(cookie_name) then
cookie_name='account'
end
cookie_name='cookie_' .. cookie_name
return ngx.var[cookie_name]
end
function getJwt(jwtString)
if isEmpty(jwtString) then
return nil
end
local claimSpec = {
exp = validators.required(validators.opt_is_not_expired()),
}
local jwt = jwt:verify(stormpathApiKeySecret, jwtString, claimSpec)
if not (jwt.verified and jwt.header.stt == 'access' and jwt.header.alg == 'HS256') then
return nil
end
return jwt
end
function isEmpty(s)
return s == nil or s == ''
end
function splitStr(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=0
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
i = i + 1
t[i] = str
end
local size=i+1
return t, size
end
function getAccountId(accountHref)
local t, size =splitStr(accountHref,'/')
return t[size-1]
end
function checkGroupMembership(jwt, groupHref)
-- prereq check
if jwt==nil then
ngx.log(ngx.STDERR, 'no jwt found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
if isEmpty(groupHref) then
ngx.log(ngx.STDERR, 'group href is blank or nil')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountHref=jwt.payload.sub
if isEmpty(accountHref) then
ngx.log(ngx.STDERR, 'no account href found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountId=getAccountId(accountHref)
if isEmpty(accountId) then
ngx.log(ngx.STDERR, 'could not parse account id: ' .. accountHref)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- end prereq check
-- cache check
local cacheKey= accountId .. groupHref
local cachedSuccess = cache:get(cacheKey)
if cachedSuccess ~= nil and cachedSuccess then
return nil
end
if cachedSuccess ~= nil and not cachedSuccess then
ngx.log(ngx.STDERR, 'account is not member of group: ' .. accountHref .. ' - ' .. groupHref)
return Helpers.exit(required)
end
-- done cache check
-- api check
local httpc = http.new()
ngx.req.read_body()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = ngx.var.request_method,
body = ngx.req.get_body_data(),
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- For client credentials requests, we need to transform basic auth to post body parameters
local apiKeyId, apiKeySecret = Helpers.getBasicAuthCredentials()
if apiKeyId and apiKeySecret then
request.body = (request.body or '') .. '&apiKeyId=' .. ngx.escape_uri(apiKeyId) ..
'&apiKeySecret=' .. ngx.escape_uri(apiKeySecret)
end
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request to get memberships
local membershipUrl='https://api.stormpath.com/v1/accounts/' .. accountId .. '/groupMemberships'
local res, err = httpc:request_uri(membershipUrl , request)
--check status
if not res or res.status ~=200 then
ngx.log(ngx.STDERR, 'group membership lookup failed: ' .. membershipUrl)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local found=false
for i,item in pairs(json.items) do
if item.group.href == groupHref then
found=true
break
end
end
-- done api check
-- cache val
local cacheSeconds=10;
if not isEmpty(groupMembershipCacheExpireSeconds) and toNumber(groupMembershipCacheExpireSeconds) > 0 then
cacheSeconds=toNumber(groupMembershipCacheExpireSeconds)
end
cache:set(cacheKey,found,cacheSeconds)
if not found then
ngx.log(ngx.STDERR, 'account is not member of group: ' .. accountHref .. ' - ' .. groupHref)
return Helpers.exit(required)
end
-- done cache val
end
-- end group stuff
function M.oauthTokenEndpoint(applicationHref)
applicationHref = applicationHref or stormpathApplicationHref
oauthTokenEndpoint(applicationHref)
end
function oauthTokenEndpoint(applicationHref)
local httpc = http.new()
ngx.req.read_body()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = ngx.var.request_method,
body = ngx.req.get_body_data(),
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- For client credentials requests, we need to transform basic auth to post body parameters
local apiKeyId, apiKeySecret = Helpers.getBasicAuthCredentials()
if apiKeyId and apiKeySecret then
request.body = (request.body or '') .. '&apiKeyId=' .. ngx.escape_uri(apiKeyId) ..
'&apiKeySecret=' .. ngx.escape_uri(apiKeySecret)
end
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request
local res, err = httpc:request_uri(applicationHref .. '/oauth/token' , request)
if not res or res.status >= 500 then
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local response = {}
-- Respond with a stripped token response or error
if res.status == 200 then
response = {
access_token = json.access_token,
refresh_token = json.refresh_token,
token_type = json.token_type,
expires_in = json.expires_in
}
else
response = {
error = json.error,
message = json.message
}
end
ngx.status = res.status
ngx.header.content_type = res.headers['Content-Type']
ngx.header.cache_control = 'no-store'
ngx.header.pragma = 'no-cache'
ngx.say(cjson.encode(response))
ngx.exit(ngx.HTTP_OK)
end
function Helpers.exit(required)
if required then
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
else
return ngx.exit(ngx.OK)
end
end
function Helpers.getBearerToken()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Bearer ') then
return nil
else
return authorizationHeader:sub(8)
end
end
function Helpers.getBasicAuthCredentials()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Basic ') then
return nil
else
local decodedHeader = ngx.decode_base64(authorizationHeader:sub(7))
local position = decodedHeader:find(':')
local username = decodedHeader:sub(1,position-1)
local password = decodedHeader:sub(position+1)
return username, password
end
end
function string:startsWith(partialString)
local partialStringLength = partialString:len()
return self:len() >= partialStringLength and self:sub(1, partialStringLength) == partialString
end
function Helpers.copy(headers)
local result = {}
for k,v in pairs(headers) do
result[k] = v
end
return result
end
return M
I think I messed up the caching before, and so I took another stab. See below:
local jwt = require('resty.jwt')
local validators = require('resty.jwt-validators')
local stormpathApplicationHref = os.getenv('STORMPATH_APPLICATION_HREF')
local stormpathApiKeyId = os.getenv('STORMPATH_CLIENT_APIKEY_ID')
local stormpathApiKeySecret = os.getenv('STORMPATH_CLIENT_APIKEY_SECRET')
local groupMembershipCacheExpireSeconds = os.getenv('STORMPATH_GROUP_MEMBERSHIP_CACHE_EXPIRE_SECONDS')
local jwtCookieName = os.getenv('STORMPATH_JWT_COOKIE_NAME')
local M = {}
local Helpers = {}
function M.getAccount()
getAccount(false)
end
function M.requireAccount()
getAccount(true)
end
function M.requireAccountWithGroupMembership(groupHref)
local jwt=getAccount(true)
checkGroupMembership(true, jwt, groupHref)
end
function getAccount(required)
local jwtString = Helpers.getBearerToken()
if Helpers.isEmpty(jwtString) then
jwtString = Helpers.getJwtCookie()
end
local jwt= Helpers.getAndVeriftyJwt(jwtString)
if jwt==nil then
return Helpers.exit(required)
end
ngx.req.set_header('x-stormpath-application-href', jwt.payload.iss)
ngx.req.set_header('x-stormpath-account-href', jwt.payload.sub)
return jwt
end
local http = require('resty.http')
local cjson = require('cjson')
-- custom methods for group stuff
function checkGroupMembership(required, jwt, groupHref)
-- prereq check
if jwt==nil then
ngx.log(ngx.STDERR, 'no jwt found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
if Helpers.isEmpty(groupHref) then
ngx.log(ngx.STDERR, 'group href is blank or nil')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountHref=jwt.payload.sub
if Helpers.isEmpty(accountHref) then
ngx.log(ngx.STDERR, 'no account href found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountId=Helpers.getAccountId(accountHref)
if Helpers.isEmpty(accountId) then
ngx.log(ngx.STDERR, 'could not parse account id: ' .. accountHref)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- end prereq check
-- cache check
local cacheKey= accountId .. groupHref
local cacheValue = Helpers.cache():get(cacheKey)
if not (cacheValue == nil) then
if cacheValue.success then
return nil
else
return Helpers.exit(required)
end
end
-- done cache check
-- api check
local httpc = http.new()
ngx.req.read_body()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = ngx.var.request_method,
body = ngx.req.get_body_data(),
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- For client credentials requests, we need to transform basic auth to post body parameters
local apiKeyId, apiKeySecret = Helpers.getBasicAuthCredentials()
if apiKeyId and apiKeySecret then
request.body = (request.body or '') .. '&apiKeyId=' .. ngx.escape_uri(apiKeyId) ..
'&apiKeySecret=' .. ngx.escape_uri(apiKeySecret)
end
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request to get memberships
local membershipUrl='https://api.stormpath.com/v1/accounts/' .. accountId .. '/groupMemberships'
local res, err = httpc:request_uri(membershipUrl , request)
--check status
if not res or res.status ~=200 then
ngx.log(ngx.STDERR, 'group membership lookup failed: ' .. membershipUrl)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local found=false
for i,item in pairs(json.items) do
if item.group.href == groupHref then
found=true
break
end
end
-- done api check
-- cache val
local cacheSeconds=10
if not Helpers.isEmpty(groupMembershipCacheExpireSeconds) then
local envCacheSeconds=toNumber(groupMembershipCacheExpireSeconds)
if envCacheSeconds > 0 then
cacheSeconds=envCacheSeconds
end
end
Helpers.cache():set(cacheKey,{ success = found },cacheSeconds)
if not found then
return Helpers.exit(required)
end
-- done cache val
end
-- end group stuff
function M.oauthTokenEndpoint(applicationHref)
applicationHref = applicationHref or stormpathApplicationHref
oauthTokenEndpoint(applicationHref)
end
function oauthTokenEndpoint(applicationHref)
local httpc = http.new()
ngx.req.read_body()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = ngx.var.request_method,
body = ngx.req.get_body_data(),
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- For client credentials requests, we need to transform basic auth to post body parameters
local apiKeyId, apiKeySecret = Helpers.getBasicAuthCredentials()
if apiKeyId and apiKeySecret then
request.body = (request.body or '') .. '&apiKeyId=' .. ngx.escape_uri(apiKeyId) ..
'&apiKeySecret=' .. ngx.escape_uri(apiKeySecret)
end
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request
local res, err = httpc:request_uri(applicationHref .. '/oauth/token' , request)
if not res or res.status >= 500 then
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local response = {}
-- Respond with a stripped token response or error
if res.status == 200 then
response = {
access_token = json.access_token,
refresh_token = json.refresh_token,
token_type = json.token_type,
expires_in = json.expires_in
}
else
response = {
error = json.error,
message = json.message
}
end
ngx.status = res.status
ngx.header.content_type = res.headers['Content-Type']
ngx.header.cache_control = 'no-store'
ngx.header.pragma = 'no-cache'
ngx.say(cjson.encode(response))
ngx.exit(ngx.HTTP_OK)
end
function Helpers.exit(required)
if required then
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
else
return ngx.exit(ngx.OK)
end
end
function Helpers.getBearerToken()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Bearer ') then
return nil
else
return authorizationHeader:sub(8)
end
end
function Helpers.getBasicAuthCredentials()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Basic ') then
return nil
else
local decodedHeader = ngx.decode_base64(authorizationHeader:sub(7))
local position = decodedHeader:find(':')
local username = decodedHeader:sub(1,position-1)
local password = decodedHeader:sub(position+1)
return username, password
end
end
function string:startsWith(partialString)
local partialStringLength = partialString:len()
return self:len() >= partialStringLength and self:sub(1, partialStringLength) == partialString
end
function Helpers.copy(headers)
local result = {}
for k,v in pairs(headers) do
result[k] = v
end
return result
end
--new helpers
function Helpers.isEmpty(s)
return s == nil or s == ''
end
function Helpers.splitString(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=0
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
i = i + 1
t[i] = str
end
local size=i+1
return t, size
end
function Helpers.getAccountId(accountHref)
local t, size =Helpers.splitString(accountHref,'/')
return t[size-1]
end
function Helpers.getAndVeriftyJwt(jwtString)
if Helpers.isEmpty(jwtString) then
return nil
end
local claimSpec = {
exp = validators.required(validators.opt_is_not_expired()),
}
local jwt = jwt:verify(stormpathApiKeySecret, jwtString, claimSpec)
if not (jwt.verified and jwt.header.stt == 'access' and jwt.header.alg == 'HS256') then
return nil
end
return jwt
end
function Helpers.getJwtCookie()
local cookie_name = jwtCookieName
if Helpers.isEmpty(cookie_name) then
cookie_name='account'
end
cookie_name='cookie_' .. cookie_name
return ngx.var[cookie_name]
end
function Helpers.cache()
local cache=_G.groupMembershipCache
if cache == nil then
local lrucache = require('resty.lrucache')
_G.groupMembershipCache=lrucache.new(100 * 1000);
cache=_G.groupMembershipCache
end
return cache
end
return M
Final update. Simplified the API call to stormpath and added some debug logging. Let me know if anyone has feedback.
local jwt = require('resty.jwt')
local validators = require('resty.jwt-validators')
local stormpathApplicationHref = os.getenv('STORMPATH_APPLICATION_HREF')
local stormpathApiKeyId = os.getenv('STORMPATH_CLIENT_APIKEY_ID')
local stormpathApiKeySecret = os.getenv('STORMPATH_CLIENT_APIKEY_SECRET')
local groupMembershipCacheExpireSeconds = os.getenv('STORMPATH_GROUP_MEMBERSHIP_CACHE_EXPIRE_SECONDS')
local jwtCookieName = os.getenv('STORMPATH_JWT_COOKIE_NAME')
local M = {}
local Helpers = {}
function M.getAccount()
getAccount(false)
end
function M.requireAccount()
getAccount(true)
end
function M.requireAccountWithGroupMembership(groupHref)
local jwt=getAccount(true)
checkGroupMembership(true, jwt, groupHref)
end
function getAccount(required)
local jwtString = Helpers.getBearerToken()
if Helpers.isEmpty(jwtString) then
jwtString = Helpers.getJwtCookie()
end
local jwt= Helpers.getAndVeriftyJwt(jwtString)
if jwt==nil then
return Helpers.exit(required)
end
ngx.req.set_header('x-stormpath-application-href', jwt.payload.iss)
ngx.req.set_header('x-stormpath-account-href', jwt.payload.sub)
return jwt
end
local http = require('resty.http')
local cjson = require('cjson')
-- custom methods for group stuff
function checkGroupMembership(required, jwt, groupHref)
-- prereq check
if jwt==nil then
ngx.log(ngx.STDERR, 'no jwt found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
if Helpers.isEmpty(groupHref) then
ngx.log(ngx.STDERR, 'group href is blank or nil')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountHref=jwt.payload.sub
if Helpers.isEmpty(accountHref) then
ngx.log(ngx.STDERR, 'no account href found')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local accountId=Helpers.getAccountId(accountHref)
if Helpers.isEmpty(accountId) then
ngx.log(ngx.STDERR, 'could not parse account id: ' .. accountHref)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- end prereq check
-- cache check
local cacheKey= accountId .. groupHref
local cacheValue = Helpers.cache():get(cacheKey)
if not (cacheValue == nil) then
if cacheValue.success then
ngx.log(ngx.DEBUG, 'cache: membership found - ' .. accountId .. ' - ' .. groupHref)
return nil
else
ngx.log(ngx.DEBUG, 'cache: membership not found - ' .. accountId .. ' - ' .. groupHref)
return Helpers.exit(required)
end
end
-- done cache check
-- api check
local httpc = http.new()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = "GET",
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request to get memberships
local membershipUrl='https://api.stormpath.com/v1/accounts/' .. accountId .. '/groupMemberships'
local res, err = httpc:request_uri(membershipUrl , request)
--check status
if not res or res.status ~=200 then
ngx.log(ngx.STDERR, 'group membership lookup failed: ' .. membershipUrl)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local found=false
for i,item in pairs(json.items) do
if item.group.href == groupHref then
found=true
break
end
end
-- done api check
-- cache val
local cacheSeconds=10
if not Helpers.isEmpty(groupMembershipCacheExpireSeconds) then
local envCacheSeconds=toNumber(groupMembershipCacheExpireSeconds)
if envCacheSeconds > 0 then
cacheSeconds=envCacheSeconds
end
end
Helpers.cache():set(cacheKey,{ success = found },cacheSeconds)
if not found then
ngx.log(ngx.DEBUG, 'api: membership not found - ' .. accountId .. ' - ' .. groupHref)
return Helpers.exit(required)
end
ngx.log(ngx.DEBUG, 'api: membership found - ' .. accountId .. ' - ' .. groupHref)
-- done cache val
end
-- end group stuff
function M.oauthTokenEndpoint(applicationHref)
applicationHref = applicationHref or stormpathApplicationHref
oauthTokenEndpoint(applicationHref)
end
function oauthTokenEndpoint(applicationHref)
local httpc = http.new()
ngx.req.read_body()
local headers = ngx.req.get_headers()
-- Proxy these certain parameters to the Stormpath API
local request = {
method = ngx.var.request_method,
body = ngx.req.get_body_data(),
headers = {
authorization = 'Basic ' .. ngx.encode_base64(stormpathApiKeyId .. ':' .. stormpathApiKeySecret),
['content-type'] = headers['content-type'],
accept = 'application/json',
['user-agent'] = 'stormpath-nginx/1.0.1 nginx/' .. ngx.var.nginx_version
}
}
-- For client credentials requests, we need to transform basic auth to post body parameters
local apiKeyId, apiKeySecret = Helpers.getBasicAuthCredentials()
if apiKeyId and apiKeySecret then
request.body = (request.body or '') .. '&apiKeyId=' .. ngx.escape_uri(apiKeyId) ..
'&apiKeySecret=' .. ngx.escape_uri(apiKeySecret)
end
-- We also need to pass the X-Stormpath-Agent if present
if headers['x-stormpath-agent'] then
request.headers['user-agent'] = headers['x-stormpath-agent'] .. ' ' .. request.headers['user-agent']
end
-- Make the request
local res, err = httpc:request_uri(applicationHref .. '/oauth/token' , request)
if not res or res.status >= 500 then
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local json = cjson.decode(res.body)
local response = {}
-- Respond with a stripped token response or error
if res.status == 200 then
response = {
access_token = json.access_token,
refresh_token = json.refresh_token,
token_type = json.token_type,
expires_in = json.expires_in
}
else
response = {
error = json.error,
message = json.message
}
end
ngx.status = res.status
ngx.header.content_type = res.headers['Content-Type']
ngx.header.cache_control = 'no-store'
ngx.header.pragma = 'no-cache'
ngx.say(cjson.encode(response))
ngx.exit(ngx.HTTP_OK)
end
function Helpers.exit(required)
if required then
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
else
return ngx.exit(ngx.OK)
end
end
function Helpers.getBearerToken()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Bearer ') then
return nil
else
return authorizationHeader:sub(8)
end
end
function Helpers.getBasicAuthCredentials()
local authorizationHeader = ngx.var.http_authorization
if not authorizationHeader or not authorizationHeader:startsWith('Basic ') then
return nil
else
local decodedHeader = ngx.decode_base64(authorizationHeader:sub(7))
local position = decodedHeader:find(':')
local username = decodedHeader:sub(1,position-1)
local password = decodedHeader:sub(position+1)
return username, password
end
end
function string:startsWith(partialString)
local partialStringLength = partialString:len()
return self:len() >= partialStringLength and self:sub(1, partialStringLength) == partialString
end
function Helpers.copy(headers)
local result = {}
for k,v in pairs(headers) do
result[k] = v
end
return result
end
--new helpers
function Helpers.isEmpty(s)
return s == nil or s == ''
end
function Helpers.splitString(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=0
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
i = i + 1
t[i] = str
end
local size=i+1
return t, size
end
function Helpers.getAccountId(accountHref)
local t, size =Helpers.splitString(accountHref,'/')
return t[size-1]
end
function Helpers.getAndVeriftyJwt(jwtString)
if Helpers.isEmpty(jwtString) then
return nil
end
local claimSpec = {
exp = validators.required(validators.opt_is_not_expired()),
}
local jwt = jwt:verify(stormpathApiKeySecret, jwtString, claimSpec)
if not (jwt.verified and jwt.header.stt == 'access' and jwt.header.alg == 'HS256') then
return nil
end
return jwt
end
function Helpers.getJwtCookie()
local cookie_name = jwtCookieName
if Helpers.isEmpty(cookie_name) then
cookie_name='account'
end
cookie_name='cookie_' .. cookie_name
return ngx.var[cookie_name]
end
function Helpers.cache()
local cache=_G.groupMembershipCache
if cache == nil then
local lrucache = require('resty.lrucache')
_G.groupMembershipCache=lrucache.new(100 * 1000);
cache=_G.groupMembershipCache
end
return cache
end
return M
Hey @reggiepierce, this is a great idea -- thanks so much for sharing! This is actually really interesting for me, because of two reasons:
Let's set up some time to chat more about how you're using the nginx plugin, and how we can better support it =] If it's supporting more web based use cases, we can figure out how we can put this in the design. I'll email you with some good times.
(Also, it might be easier to show this code by making a fork of this repo!)
Hi @edjiang,
I really should have just created a fork. However, I just kept noticing bugs and so I kept updating. Sorry for the clutter.
As per custom data, I can see that there may be potential use cases, but at this time they don't apply to my team. (Maybe requiring information from 3rd party providers like Facebook)
@edjiang: My company runs many internal services that we would love to access through the internet but we don't wont to make them publicly accessible. We also don't want to manage individual accounts for all this services.
Group level permission would allow us grant access to different parts of our infrastructure to members of my organization very easily.
Any chance stormpath can get @reggiepierce modifications merged?
Hey @javierbq, are you currently using the nginx plugin, or just looking for a good solution for your needs? I'm happy to discuss how the current integrations we have could potentially make this work.
I discussed this with Reggie on the phone, and while his contributions are great, it'll definitely take more work to make this useful on a more general level, and document it, etc =]
It would be great to have the ability to require group membership. Unfortunately, I'm not very experienced with LUA, and this may already be possible for all I know.
Something like this would be great: