App (#237)

* jwk validation

* appid

* move introspection into c

* move c code into a module
diff --git a/Dockerfile b/Dockerfile
index a30f2f2..6ac7126 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -148,11 +148,12 @@
 RUN opm get openresty/lua-resty-string=${LUA_RESTY_STRING_VERSION}
 ENV LUA_RESTY_LRUCACHE_VERSION 0.04
 RUN opm get openresty/lua-resty-lrucache=${LUA_RESTY_LRUCACHE_VERSION}
-ENV LUA_RESTY_JWT_VERSION 0.1.10
-RUN opm get SkyLothar/lua-resty-jwt=${LUA_RESTY_JWT_VERSION}
-ENV NETURL_LUA_VERSION 0.9-1
+ENV LUA_RESTY_CJOSE_VERSION 0.3
+RUN opm get taylorking/lua-resty-cjose=${LUA_RESTY_CJOSE_VERSION}
 RUN opm get taylorking/lua-resty-rate-limit
 
+
+ENV NETURL_LUA_VERSION 0.9-1
 RUN echo " ... installing neturl.lua ... " \
     && mkdir -p /tmp/api-gateway \
     && curl -k -L https://github.com/golgote/neturl/archive/${NETURL_LUA_VERSION}.tar.gz -o /tmp/api-gateway/neturl.lua-${NETURL_LUA_VERSION}.tar.gz \
@@ -162,6 +163,18 @@
     && cp lib/net/url.lua ${LUA_LIB_DIR} \
     && rm -rf /tmp/api-gateway
 
+ENV CJOSE_VERSION 0.5.1
+RUN echo " ... installing cjose ... " \
+    && apk update && apk add automake autoconf git gcc make jansson jansson-dev \
+    && mkdir -p /tmp/api-gateway \
+    && curl -L -k https://github.com/cisco/cjose/archive/${CJOSE_VERSION}.tar.gz -o /tmp/api-gateway/cjose-${CJOSE_VERSION}.tar.gz \
+    && tar -xf /tmp/api-gateway/cjose-${CJOSE_VERSION}.tar.gz -C /tmp/api-gateway/ \
+    && cd /tmp/api-gateway/cjose-${CJOSE_VERSION} \
+    && sh configure \
+    && make && make install
+RUN mkdir -p /tmp/api-gateway
+
+
 RUN \
     curl -L -k -s -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \
     && apk update \
@@ -176,7 +189,6 @@
 
 COPY init.sh /etc/init-container.sh
 ONBUILD COPY init.sh /etc/init-container.sh
-
 # add the default configuration for the Gateway
 COPY . /etc/api-gateway
 RUN adduser -S nginx-api-gateway \
@@ -185,6 +197,7 @@
 
 EXPOSE 80 8080 8423 9000
 
+ENV LD_LIBRARY_PATH /usr/local/lib
 
 ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
 CMD ["/etc/init-container.sh"]
diff --git a/Makefile b/Makefile
index 209eea1..ee90289 100644
--- a/Makefile
+++ b/Makefile
@@ -44,6 +44,8 @@
 		-e TOKEN_GOOGLE_URL=https://www.googleapis.com/oauth2/v3/tokeninfo \
 	 	-e TOKEN_FACEBOOK_URL=https://graph.facebook.com/debug_token \
 		-e TOKEN_GITHUB_URL=https://api.github.com/user \
+		-e APPID_PKURL=https://appid-oauth.ng.bluemix.net/oauth/v3/ \
+		-e LD_LIBRARY_PATH=/usr/local/lib \
 		openwhisk/apigateway:latest
 
 .PHONY: docker-debug
diff --git a/api-gateway.conf b/api-gateway.conf
index 3aea095..ecf7b22 100644
--- a/api-gateway.conf
+++ b/api-gateway.conf
@@ -35,6 +35,8 @@
 env PORT;
 
 env OPTIMIZE;
+env APPID_PKURL;
+
 env REDIS_RETRY_COUNT;
 env SNAPSHOTTING;
 env CACHING_ENABLED;
diff --git a/scripts/lua/oauth/appid.lua b/scripts/lua/oauth/appid.lua
new file mode 100644
index 0000000..b400df4
--- /dev/null
+++ b/scripts/lua/oauth/appid.lua
@@ -0,0 +1,51 @@
+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'
+
+function _M.process(dataStore, token, securityObj)
+  local result = dataStore:getOAuthToken('appId', token)
+  local httpc = http.new()
+  local json_resp
+  if result ~= ngx.null then
+    json_resp = cjson.decode(result)
+    ngx.header['X-OIDC-Email'] = json_resp['email']
+    ngx.header['X-OIDC-Sub'] = json_resp['sub']
+    return json_resp
+  end
+  local keyUrl = utils.concatStrings({APPID_PKURL, securityObj.tenantId, '/publickeys'})
+  local request_options = {
+    headers = {
+      ["Accept"] = "application/json"
+    },
+    ssl_verify = false
+  }
+  local res, err = httpc:request_uri(keyUrl, request_options)
+  if err then
+    request.err(500, 'error getting app id key: ' .. err)
+  end
+  
+  local key
+  local keys = cjson.decode(res.body).keys
+  for _, v in ipairs(keys) do 
+    key = v
+  end
+  local result = cjose.validateJWS(token, cjson.encode(key))
+  if not result then
+    request.err(401, 'AppId key signature verification failed.')
+    return nil
+  end 
+  jwt_obj = cjson.decode(cjose.getJWSInfo(token))
+  ngx.header['X-OIDC-Email'] = jwt_obj['email']
+  ngx.header['X-OIDC-Sub'] = jwt_obj['sub']
+  dataStore:saveOAuthToken('appId', token, cjson.encode(jwt_obj), jwt_obj['exp'])
+  return jwt_obj
+end
+
+
+return _M
+
+
diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua
index 778e7b4..be3ef2a 100644
--- a/scripts/lua/policies/security/oauth2.lua
+++ b/scripts/lua/policies/security/oauth2.lua
@@ -48,7 +48,7 @@
   end
   accessToken = string.gsub(accessToken, '^Bearer%s', '')
 
-  local token = exchange(dataStore, accessToken, securityObj.provider)
+  local token = exchange(dataStore, accessToken, securityObj.provider, securityObj)
   if token == nil then
     request.err(401, 'Token didn\'t work or provider doesn\'t support OpenID connect. ')
     return nil
@@ -67,7 +67,7 @@
 -- @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)
+function exchange(dataStore, token, provider, securityObj)
     -- exchange tokens with the provider
     local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider}))
     if not loaded then
@@ -76,7 +76,7 @@
       return nil
     end
 
-    local result = impl.process(dataStore, token)
+    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