diff --git a/CHANGELOG.md b/CHANGELOG.md index f71860468..446ac8db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Ability to use Redis DB and password via `REDIS_URL` [PR #303](https://github.com/3scale/apicast/pull/303) - Ability to Authenticate against API using RHSSO and OpenID Connect [PR #283](https://github.com/3scale/apicast/pull/283) +### Fixed +- `http_ng` client supports auth passsed in the url, and default client options if the request options are missing for methods with body (POST, PUT, etc.) [PR #310](https://github.com/3scale/apicast/pull/310) + ### Removed - Removed support for sending Request logs [PR #296](https://github.com/3scale/apicast/pull/296) diff --git a/apicast/src/configuration_loader.lua b/apicast/src/configuration_loader.lua index 53af7a6cf..65907b4cc 100644 --- a/apicast/src/configuration_loader.lua +++ b/apicast/src/configuration_loader.lua @@ -107,10 +107,16 @@ function boot.init(configuration) os.exit(0) end - local keycloak_config = _M.run_external_command("keycloak") - - if keycloak_config then - configuration.keycloak = cjson.decode(keycloak_config) + if keycloak.enabled() then + local keycloak_config + keycloak_config, err, code = _M.run_external_command("keycloak") + if keycloak_config then + ngx.log(ngx.DEBUG, 'downloaded keycloak configuration: ', keycloak_config) + configuration.keycloak = cjson.decode(keycloak_config) + else + -- TODO: consider exiting if there is problem with keycloak configuration + ngx.log(ngx.WARN, 'failed to load keycloak configuration, exiting (code ', code, ')\n', err) + end end end @@ -128,8 +134,6 @@ end function boot.init_worker(configuration) local interval = ttl() or 0 - configuration.keycloak = keycloak.load_configuration() - local function schedule(...) local ok, err = ngx.timer.at(...) diff --git a/apicast/src/oauth/keycloak.lua b/apicast/src/oauth/keycloak.lua index bdd59162e..e06ec6d86 100644 --- a/apicast/src/oauth/keycloak.lua +++ b/apicast/src/oauth/keycloak.lua @@ -57,6 +57,10 @@ local function get_public_key(http_client, endpoint) return format_public_key(key) end +function _M.enabled() + return env.get('RHSSO_ENDPOINT') +end + function _M.load_configuration(client) local endpoint = env.get('RHSSO_ENDPOINT') diff --git a/apicast/src/proxy.lua b/apicast/src/proxy.lua index e5ab6c899..d87d699fc 100644 --- a/apicast/src/proxy.lua +++ b/apicast/src/proxy.lua @@ -266,38 +266,15 @@ function _M:set_backend_upstream(service) ngx.var.backend_host = backend.host or server or ngx.var.backend_host end - -local function auth_oauth(proxy, service, usage, auth) - local credentials, err = proxy.oauth:transform_credentials(auth) - - if err then - return error_authorization_failed(service) - end - - credentials = encode_args(credentials) - - return proxy:authorize(service, usage, credentials) -end - -local function auth_key(proxy, service, usage, auth) - local credentials = encode_args(auth) - - return proxy:authorize(service, usage, credentials) -end - function _M:call(host) host = host or ngx.var.host local service = ngx.ctx.service or self:set_service(host) self:set_backend_upstream(service) - local authorize = auth_key - if service.backend_version == 'oauth' then local o = oauth.new(self.configuration) local f, params = oauth.call(o, service) - - authorize = auth_oauth self.oauth = o if f then @@ -308,11 +285,11 @@ function _M:call(host) return function() -- call access phase - return self:access(service, authorize) + return self:access(service) end end -function _M:access(service, authorize) +function _M:access(service) local request = ngx.var.request -- NYI: return to lower frame ngx.var.secret_token = service.secret_token @@ -328,8 +305,7 @@ function _M:access(service, authorize) insert(credentials, 1, service.id) - local _, matched_patterns, params = service:extract_usage(request) - local usage = encode_args(params) + local _, matched_patterns, usage_params = service:extract_usage(request) ngx.var.cached_key = concat(credentials, ':') @@ -342,11 +318,22 @@ function _M:access(service, authorize) local ctx = ngx.ctx -- save those tables in context so they can be used in the backend client - ctx.usage = params + ctx.usage = usage_params ctx.credentials = credentials ctx.matched_patterns = matched_patterns - return authorize(self, service, usage, credentials) + if self.oauth then + credentials, err = self.oauth:transform_credentials(credentials) + + if err then + return error_authorization_failed(service) + end + end + + credentials = encode_args(credentials) + local usage = encode_args(usage_params) + + return self:authorize(service, usage, credentials) end diff --git a/apicast/src/resty/http_ng.lua b/apicast/src/resty/http_ng.lua index cdf11d4d4..84b259692 100644 --- a/apicast/src/resty/http_ng.lua +++ b/apicast/src/resty/http_ng.lua @@ -52,6 +52,26 @@ local function merge(...) return res end +local function get_request_params(method, client, url, options) + local opts = {} + local scheme, user, pass, host, port, path = unpack(assert(resty_url.split(url))) + if port then host = concat({host, port}, ':') end + + opts.headers = { ['Host'] = host } + + if user or pass then + opts.headers.Authorization = "Basic " .. ngx.encode_base64(concat({ user or '', pass or '' }, ':')) + end + + return { + url = concat({ scheme, '://', host, path or DEFAULT_PATH }, ''), + method = method, + options = merge(opts, rawget(client, 'options'), options), + client = client, + serializer = client.serializer or http.serializers.default + } +end + http.method = function(method, client) assert(method) assert(client) @@ -64,23 +84,8 @@ http.method = function(method, client) assert(url, 'url as first parameter is required') - local opts = {} - local scheme, user, pass, host, port, path = unpack(assert(resty_url.split(url))) - if port then host = concat({host, port}, ':') end - - opts.headers = { ['Host'] = host } - - if user or pass then - opts.headers.Authorization = "Basic " .. ngx.encode_base64(concat({ user or '', pass or '' }, ':')) - end - - local req = http.request.new({ - url = concat({ scheme, '://', host, path or DEFAULT_PATH }, ''), - method = method, - options = merge(opts, rawget(client, 'options'), options), - client = client, - serializer = client.serializer or http.serializers.default - }) + local req_params = get_request_params(method, client, url, options) + local req = http.request.new(req_params) return client.backend.send(req) end @@ -99,9 +104,10 @@ http.method_with_body = function(method, client) assert(url, 'url as first parameter is required') assert(body, 'body as second parameter is required') - local req = http.request.new{ url = url, method = method, body = body, - options = options, client = client, - serializer = client.serializer or http.serializers.default } + local req_params = get_request_params(method, client, url, options) + req_params.body = body + local req = http.request.new(req_params) + return client.backend.send(req) end end diff --git a/spec/keycloak_spec.lua b/spec/keycloak_spec.lua index 93b344293..803e33318 100644 --- a/spec/keycloak_spec.lua +++ b/spec/keycloak_spec.lua @@ -154,4 +154,14 @@ describe('Keycloak', function() assert.spy(_M.respond_with_error).was.called_with(401, 'invalid_client') end) end) + + describe('.enabled', function() + it('is falsy if there is no RHSSO_ENDPOINT enviroment varialbe set', function() + assert.is.falsy(_M.enabled()) + end) + it('if truty a non-nil value if RHSSO_ENDPOINT enviroment varialbe set', function() + env.set('RHSSO_ENDPOINT', 'http://www.example.com:80/auth/realms/test') + assert.is.truthy(_M.enabled()) + end) + end) end) \ No newline at end of file diff --git a/spec/resty/http_ng_spec.lua b/spec/resty/http_ng_spec.lua index 5af7ad5df..09af6e864 100755 --- a/spec/resty/http_ng_spec.lua +++ b/spec/resty/http_ng_spec.lua @@ -88,6 +88,14 @@ describe('http_ng', function() http.get('http://example.com') local last_request = assert(backend.last_request) + assert.equal(false, last_request.options.ssl.verify) + end) + it('can turn off ssl validation for methods with body', function() + http = http_ng.new{backend = backend, options = { ssl = { verify = false } } } + + http.post('http://example.com', {}) + local last_request = assert(backend.last_request) + assert.equal(false, last_request.options.ssl.verify) end) end)