| -- |
| -- Licensed to the Apache Software Foundation (ASF) under one or more |
| -- contributor license agreements. See the NOTICE file distributed with |
| -- this work for additional information regarding copyright ownership. |
| -- The ASF licenses this file to You under the Apache License, Version 2.0 |
| -- (the "License"); you may not use this file except in compliance with |
| -- the License. You may obtain a copy of the License at |
| -- |
| -- http://www.apache.org/licenses/LICENSE-2.0 |
| -- |
| -- Unless required by applicable law or agreed to in writing, software |
| -- distributed under the License is distributed on an "AS IS" BASIS, |
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| -- See the License for the specific language governing permissions and |
| -- limitations under the License. |
| -- |
| local request = require 'lib/request' |
| local cjson = require 'cjson' |
| local utils = require 'lib/utils' |
| local APPID_PKURL = os.getenv("APPID_PKURL") |
| local _M = {} |
| local http = require 'resty.http' |
| local cjose = require 'resty.cjose' |
| |
| local function inject_req_headers(token_obj) |
| ngx.header['X-OIDC-Email'] = token_obj['email'] |
| ngx.header['X-OIDC-Sub'] = token_obj['sub'] |
| end |
| |
| local function fetchJWKs(tenantId) |
| local httpc = http.new() |
| local keyUrl = utils.concatStrings({APPID_PKURL, tenantId, '/publickeys'}) |
| local request_options = { |
| headers = { |
| ["Accept"] = "application/json" |
| }, |
| ssl_verify = true |
| } |
| return httpc:request_uri(keyUrl, request_options) |
| end |
| |
| function _M.process(dataStore, token, securityObj) |
| local cache_key = 'appid_' .. securityObj.tenantId |
| local result = dataStore:getOAuthToken(cache_key, token) |
| local token_obj |
| |
| -- Was the token in the cache? |
| if result ~= ngx.null then |
| token_obj = cjson.decode(result) |
| inject_req_headers(token_obj) |
| return token_obj |
| end |
| |
| -- Cache miss. Proceed to validate the token |
| local res, err = fetchJWKs(securityObj.tenantId) |
| if err ~= nil or not res or res.status ~= 200 then |
| request.err(500, 'An error occurred while fetching the App ID JWK configuration: ' .. err or res.body) |
| return nil |
| end |
| |
| local key |
| local keys = cjson.decode(res.body).keys |
| for _, v in ipairs(keys) do |
| key = v |
| end |
| result = cjose.validateJWS(token, cjson.encode(key)) |
| if not result then |
| request.err(401, 'The token signature did not match any known JWK.') |
| return nil |
| end |
| |
| token_obj = cjson.decode(cjose.getJWSInfo(token)) |
| local expireTime = token_obj['exp'] |
| if expireTime < os.time() then |
| request.err(401, 'The access token has expired.') |
| return nil |
| end |
| |
| -- Add token metadata to the request headers |
| inject_req_headers(token_obj) |
| |
| -- keep token in cache until it expires |
| local ttl = expireTime - os.time() |
| local encodedToken = cjson.encode(token_obj) |
| dataStore:saveOAuthToken(cache_key, token, encodedToken, ttl) |
| return token_obj |
| end |
| |
| return _M |
| |
| |