diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index b929c9d36..05900ed62 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ API Umbrella is an open source API management platform for exposing web service Binary packages are available for [download](https://apiumbrella.io/install/). Follow the quick setup instructions on the download page to begin running API Umbrella. +## Additional Features + +In this repo is hosted one additional feature of API-Umbrella for providing validation using external IdP's. +The documentation about how to use is available here: [Documentation](./docs/idp-doc.md) + ## Getting Started Once you have API Umbrella up and running, there are a variety of things you can do to start using the platform. For a quick tutorial, see [getting started](https://api-umbrella.readthedocs.org/en/latest/getting-started.html). diff --git a/Vagrantfile b/Vagrantfile index 507117289..60b6a9d73 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -62,6 +62,7 @@ Vagrant.configure("2") do |config| end config.vm.synced_folder "src/api-umbrella/admin-ui", "/vagrant-admin-ui", + :nfs => options[:nfs], :type => "rsync", :rsync__verbose => true, :rsync__exclude => [ diff --git a/bin/api-umbrella b/bin/api-umbrella old mode 100755 new mode 100644 diff --git a/bin/api-umbrella-cli b/bin/api-umbrella-cli old mode 100755 new mode 100644 diff --git a/bin/api-umbrella-exec b/bin/api-umbrella-exec old mode 100755 new mode 100644 diff --git a/bin/api-umbrella-geoip-auto-updater b/bin/api-umbrella-geoip-auto-updater old mode 100755 new mode 100644 diff --git a/bin/api-umbrella-nginx-reloader b/bin/api-umbrella-nginx-reloader old mode 100755 new mode 100644 diff --git a/bin/docker-compose/make b/bin/docker-compose/make old mode 100755 new mode 100644 diff --git a/bin/docker-compose/rake b/bin/docker-compose/rake old mode 100755 new mode 100644 diff --git a/build/package/build_package b/build/package/build_package old mode 100755 new mode 100644 diff --git a/build/package/docker_run b/build/package/docker_run old mode 100755 new mode 100644 diff --git a/build/package/docker_script b/build/package/docker_script old mode 100755 new mode 100644 diff --git a/build/package/files/etc/init.d/api-umbrella b/build/package/files/etc/init.d/api-umbrella old mode 100755 new mode 100644 diff --git a/build/package/parse_version b/build/package/parse_version old mode 100755 new mode 100644 diff --git a/build/package/publish b/build/package/publish old mode 100755 new mode 100644 diff --git a/build/package/scripts/after-install b/build/package/scripts/after-install old mode 100755 new mode 100644 diff --git a/build/package/scripts/after-remove b/build/package/scripts/after-remove old mode 100755 new mode 100644 diff --git a/build/package/scripts/before-remove b/build/package/scripts/before-remove old mode 100755 new mode 100644 diff --git a/build/package/verify/docker_run b/build/package/verify/docker_run old mode 100755 new mode 100644 diff --git a/build/package/verify/docker_script b/build/package/verify/docker_script old mode 100755 new mode 100644 diff --git a/build/package/verify/download_previous_packages b/build/package/verify/download_previous_packages old mode 100755 new mode 100644 diff --git a/build/scripts/distclean b/build/scripts/distclean old mode 100755 new mode 100644 diff --git a/build/scripts/download_cmake b/build/scripts/download_cmake old mode 100755 new mode 100644 diff --git a/build/scripts/install_build_dependencies b/build/scripts/install_build_dependencies old mode 100755 new mode 100644 diff --git a/config/default.yml b/config/default.yml index 21486ff2f..34e5fe1fb 100644 --- a/config/default.yml +++ b/config/default.yml @@ -58,7 +58,17 @@ gatekeeper: - header - getParam - basicAuthUsername - api_key_cache: true + api_key_cache: false + positive_ttl: 120 + negative_ttl: 60 + idp_providers: + - fiware-oauth2 + - github-oauth2 + - facebook-oauth2 + - google-oauth2 + default_idp: + backend_name: fiware-oauth2 + host: http://138.4.7.8:8000 trafficserver: host: 127.0.0.1 port: 14009 @@ -283,12 +293,12 @@ apiSettings: message: The requested URL was not found on this server. api_key_missing: status_code: 403 - code: API_KEY_MISSING - message: No api_key was supplied. Get one at {{signup_url}} + code: API_KEY_OR_TOKEN_MISSING + message: No api_key or token was supplied. Get one at {{signup_url}} api_key_invalid: status_code: 403 - code: API_KEY_INVALID - message: An invalid api_key was supplied. Get one at {{signup_url}} + code: API_KEY_OR_TOKEN_INVALID + message: An invalid api_key or token was supplied. Get one at {{signup_url}} api_key_disabled: status_code: 403 code: API_KEY_DISABLED @@ -299,8 +309,12 @@ apiSettings: message: The api_key supplied has not been verified yet. Please check your e-mail to verify the API key. Contact us at {{contact_url}} for assistance api_key_unauthorized: status_code: 403 - code: API_KEY_UNAUTHORIZED - message: The api_key supplied is not authorized to access the given service. Contact us at {{contact_url}} for assistance + code: API_KEY_OR_TOKEN_UNAUTHORIZED + message: The api_key or token supplied is not authorized to access the given service. Contact us at {{contact_url}} for assistance + not_trusted_app: + status_code: 403 + code: NOT_TRUSTED_APPLICATION + message: The application trying to access the API_BACKEND is not trusted over_rate_limit: status_code: 429 code: OVER_RATE_LIMIT diff --git a/configure b/configure old mode 100755 new mode 100644 diff --git a/docker-ging/Dockerfile b/docker-ging/Dockerfile new file mode 100644 index 000000000..27a0ccaf8 --- /dev/null +++ b/docker-ging/Dockerfile @@ -0,0 +1,23 @@ +FROM centos:centos6 + +ENV API_UMBRELLA_VERSION 0.14.4-1~centos + +# Install API Umbrella +RUN yum -y update; yum clean all +RUN yum -y install git +RUN groupadd -r api-umbrella && \ +useradd -r -g api-umbrella -s /sbin/nologin -d /opt/api-umbrella -c "API Umbrella user" api-umbrella +RUN git clone https://github.com/ging/api-umbrella.git +RUN chmod 775 -R ./api-umbrella +RUN cd ./api-umbrella && ./build/scripts/install_build_dependencies && ./configure && make && make install + + +# Define mountable directories +VOLUME ["/etc/api-umbrella", "/opt/api-umbrella/var/db", "/opt/api-umbrella/var/log"] + +# Expose HTTP and HTTPS ports +EXPOSE 80 443 + +# Run the API Umbrella service +CMD ["api-umbrella", "run"] + diff --git a/docker/dev/docker-start b/docker/dev/docker-start old mode 100755 new mode 100644 diff --git a/docs/idp-doc.md b/docs/idp-doc.md new file mode 100644 index 000000000..7cec0db15 --- /dev/null +++ b/docs/idp-doc.md @@ -0,0 +1,221 @@ +## External IdP Validation + +This new feature allows to the API-umbrella users +the possibility of make request to a registered API + backend, using an API key or an OAuth2 token. + If the user uses a token in the request, this token is verified using an external IdP, once the token is validated the user information is retrieved and redirect to the API backend following the usual workflow. + The list of IdP's + included in this development are: + +* Fiware +* Google +* Facebook +* GitHub + +Hence, after the inclusion of this feature, +the gatekeeper architecture is modified +as is showed in the next figure: + +![API-Umbrella architecture](./images/idp-arch.png) + +## Getting Started + +### Installation +For using API-Umbrella with External IdP validation, you have to clone +this repo and install API-Umbrella from source code. The instructions fordoing this are: + +Note: This installation was tested with centos 6 and 7 with other Linux +distribution should work too but, is recommended to use centos + +First, clone the repo and install: + +``` +$ git clone https://github.com/ging/api-umbrella.git +$ chmod 775 -R ./api-umbrella +$ cd api-umbrella +$ sudo ./build/scripts/install_build_dependencies +$ ./configure +$ make +$ sudo make install +``` +This process could take some minutes while it download and install the dependencies. + +Second, start API-Umbrella + +``` +sudo /etc/init.d/api-umbrella start +``` +### Docker API-Umbrella + +Also, you have the option of use a docker container instead of install +API-Umbrella. The instructions for creating and running the docker +container with this version of API umbrella are: + +You can run directly the container pulling the image from the docker hub: + +``` +$ docker run -d --name=api-umbrella -p 80:80 -p 443:443 martel/api-umbrella +``` + +Or build the API-Umbrella image and run the container: + +``` +$ git clone https://github.com/ging/api-umbrella.git +$ cd api-umbrella/docker-ging +$ docker build -t umbrella:0.14.4 . +``` + +Once you have your api-umbrella image, you can run the container and use it + +``` +$ docker run -d --name=api-umbrella -p 80:80 -p 443:443 umbrella:0.14.4 +``` + +### Admin and User guide + +At this point, you can perform the same operations described in +[getting started](https://api-umbrella.readthedocs.org/en/latest/getting-started.html). but Also +in this section is included the guide of how to create API backends for processing +the requests using Oauth2 token and external IdP's + +#### Registering and API-Backend uding API REST + +The procedure of how you can create a request using an REST call is available in the official documentation but, +for using the external validation feature you need to include in you JSON body request the field ** require_idp: ** where IdP value could be one of this: fiware-oauth2, google-oauth2, facebook-oauth2, github-oauth2. + +An request example for registring a API-backend with a generic parameters is: + +``` +curl -k -X POST "https:///api-umbrella/v1/apis" -H "X-Api-Key: " -H "X-Admin-Auth-Token: " -H "Accept: application/json" -H "Content-Type: application/json" -d @- </?token=" + +``` +An example of using the google maps API backend registered in API-Umbrella: + +``` +curl -k "https://127.0.0.1/distance1/maps/api/distancematrix/json?units=imperial&origins=Washington,DC&destinations=New+York+City,NY&token=XXXXXXXXXX" +``` +Output: +``` +{ + "destination_addresses" : [ "New York, NY, USA" ], + "origin_addresses" : [ "Washington, DC, USA" ], + "rows" : [ + { + "elements" : [ + { + "distance" : { + "text" : "225 mi", + "value" : 361940 + }, + "duration" : { + "text" : "3 hours 51 mins", + "value" : 13839 + }, + "status" : "OK" + } + ] + } + ], + "status" : "OK" +} + +``` + +For sending the token via header: + +``` +curl -k -H "X-Auth-Token:" "https:///" +``` + +An example using the same API backend of google maps. + +``` +curl -k -H "X-Auth-Token:XXXXXX" "https://127.0.0.1/distance/maps/api/distancematrix/json?units=imperial&origins=Washington,DC&destinations=New+York+City,NY" +``` +Output: +``` +{ + "destination_addresses" : [ "New York, NY, USA" ], + "origin_addresses" : [ "Washington, DC, USA" ], + "rows" : [ + { + "elements" : [ + { + "distance" : { + "text" : "225 mi", + "value" : 361940 + }, + "duration" : { + "text" : "3 hours 51 mins", + "value" : 13839 + }, + "status" : "OK" + } + ] + } + ], + "status" : "OK" +} + +``` +For any additional questions or support please send an email to: + +* + diff --git a/docs/images/create-backend-ex.png b/docs/images/create-backend-ex.png new file mode 100644 index 000000000..9e9144ec5 Binary files /dev/null and b/docs/images/create-backend-ex.png differ diff --git a/docs/images/create-backend.png b/docs/images/create-backend.png new file mode 100644 index 000000000..95b7bd570 Binary files /dev/null and b/docs/images/create-backend.png differ diff --git a/docs/images/idp-arch.png b/docs/images/idp-arch.png new file mode 100644 index 000000000..4a521dd0a Binary files /dev/null and b/docs/images/idp-arch.png differ diff --git a/scripts/fix-log-geoip/fix b/scripts/fix-log-geoip/fix old mode 100755 new mode 100644 diff --git a/scripts/generate_user_agent_data b/scripts/generate_user_agent_data old mode 100755 new mode 100644 diff --git a/scripts/import_access_logs/import b/scripts/import_access_logs/import old mode 100755 new mode 100644 diff --git a/scripts/replay_logs b/scripts/replay_logs old mode 100755 new mode 100644 diff --git a/src/api-umbrella/admin-ui/app/components/apis/settings/common-fields.js b/src/api-umbrella/admin-ui/app/components/apis/settings/common-fields.js index a221eb23f..ff4e60d0f 100644 --- a/src/api-umbrella/admin-ui/app/components/apis/settings/common-fields.js +++ b/src/api-umbrella/admin-ui/app/components/apis/settings/common-fields.js @@ -17,14 +17,20 @@ export default Component.extend({ { id: false, name: I18n.t('admin.api.settings.disable_api_key_options.required') }, { id: true, name: I18n.t('admin.api.settings.disable_api_key_options.disabled') }, ]; - + this.requireIdpOptions: [ + { id: null, name: I18n.t('admin.api.settings.require_idp_options.inherit') }, + { id: 'none', name: I18n.t('admin.api.settings.require_idp_options.none') }, + { id: 'fiware-oauth2', name: "FIWARE" }, + { id: 'github-oauth2', name: "GitHub" }, + { id: 'facebook-oauth2', name: "Facebook" }, + { id: 'google-oauth2', name: "Google" }, + ]; this.apiKeyVerificationLevelOptions = [ { id: null, name: I18n.t('admin.api.settings.api_key_verification_level_options.inherit') }, { id: 'none', name: I18n.t('admin.api.settings.api_key_verification_level_options.none') }, { id: 'transition_email', name: I18n.t('admin.api.settings.api_key_verification_level_options.transition_email') }, { id: 'required_email', name: I18n.t('admin.api.settings.api_key_verification_level_options.required_email') }, ]; - this.passApiKeyOptions = [ { id: 'header', name: I18n.t('admin.api.settings.pass_api_key_header') }, { id: 'param', name: I18n.t('admin.api.settings.pass_api_key_param') }, diff --git a/src/api-umbrella/admin-ui/app/models/api/settings.js b/src/api-umbrella/admin-ui/app/models/api/settings.js index 9dffe8510..9765c68f3 100644 --- a/src/api-umbrella/admin-ui/app/models/api/settings.js +++ b/src/api-umbrella/admin-ui/app/models/api/settings.js @@ -10,6 +10,7 @@ export default DS.Model.extend({ requireHttps: DS.attr(), disableApiKey: DS.attr(), apiKeyVerificationLevel: DS.attr(), + requireIdp: DS.attr(), requiredRoles: DS.attr(), requiredRolesOverride: DS.attr(), allowedIps: DS.attr(), diff --git a/src/api-umbrella/admin-ui/app/routes/apis/form.js b/src/api-umbrella/admin-ui/app/routes/apis/form.js index 3573dd1d3..e85c6355a 100644 --- a/src/api-umbrella/admin-ui/app/routes/apis/form.js +++ b/src/api-umbrella/admin-ui/app/routes/apis/form.js @@ -6,7 +6,9 @@ import { hash } from 'rsvp'; export default Base.extend(Confirmation, UncachedModel, { // Return a promise for loading multiple models all together. fetchModels(record) { - return hash({ + + //TODO: add external IDPs config here + return Ember.RSVP.hash({ record: record, roleOptions: this.get('store').findAll('api-user-role', { reload: true }), }); diff --git a/src/api-umbrella/admin-ui/app/templates/components/apis/settings/common-fields.hbs b/src/api-umbrella/admin-ui/app/templates/components/apis/settings/common-fields.hbs index a03e3faec..ec5978edd 100644 --- a/src/api-umbrella/admin-ui/app/templates/components/apis/settings/common-fields.hbs +++ b/src/api-umbrella/admin-ui/app/templates/components/apis/settings/common-fields.hbs @@ -2,6 +2,7 @@ {{f.select-field "requireHttps" label=(t "admin.api.settings.require_https") tooltip=(t "admin.api.settings.require_https_tooltip_markdown") options=requireHttpsOptions}} {{f.select-field "disableApiKey" label=(t "admin.api.settings.disable_api_key") options=disableApiKeyOptions}} {{f.select-field "apiKeyVerificationLevel" label=(t "admin.api.settings.api_key_verification_level") options=apiKeyVerificationLevelOptions}} + {{f.select-field "requireIdp" label=(t "admin.api.settings.require_idp") options=requireIdpOptions}} {{f.selectize-field "requiredRolesString" label=(t "admin.api.settings.required_roles") tooltip=(t "admin.api.settings.required_roles_tooltip_markdown") options=roleOptions}} {{#if isSubSettings}} {{f.checkbox-field "requiredRolesOverride" label=(t "admin.api.settings.required_roles_override") tooltip=(t "admin.api.settings.required_roles_override_tooltip_markdown")}} diff --git a/src/api-umbrella/proxy/hooks/rewrite.lua b/src/api-umbrella/proxy/hooks/rewrite.lua index c81c9e87f..2072e5386 100644 --- a/src/api-umbrella/proxy/hooks/rewrite.lua +++ b/src/api-umbrella/proxy/hooks/rewrite.lua @@ -17,6 +17,7 @@ wait_for_setup() -- ngx.var lookups are apparently somewhat expensive. ngx.ctx.args = ngx_var.args ngx.ctx.arg_api_key = ngx_var.arg_api_key +ngx.ctx.arg_token = ngx_var.arg_token if(config["router"]["match_x_forwarded_host"]) then ngx.ctx.host = ngx_var.http_x_forwarded_host or ngx_var.http_host or ngx_var.host else @@ -24,6 +25,7 @@ else end ngx.ctx.host_normalized = host_normalize(ngx.ctx.host) ngx.ctx.http_x_api_key = ngx_var.http_x_api_key +ngx.ctx.http_x_auth_token = ngx_var.http_x_auth_token ngx.ctx.port = ngx_var.real_port ngx.ctx.protocol = ngx_var.real_scheme ngx.ctx.remote_addr = ngx_var.remote_addr @@ -72,4 +74,4 @@ else else error_handler(api_err) end -end +end \ No newline at end of file diff --git a/src/api-umbrella/proxy/middleware/api_key_validator.lua b/src/api-umbrella/proxy/middleware/api_key_validator.lua index ab1447b32..128c93c05 100644 --- a/src/api-umbrella/proxy/middleware/api_key_validator.lua +++ b/src/api-umbrella/proxy/middleware/api_key_validator.lua @@ -6,32 +6,56 @@ local is_empty = types.is_empty local function resolve_api_key() local api_key_methods = config["gatekeeper"]["api_key_methods"] - local api_key + local key = {key_value="", key_type="", idp=nil, trusted_apps=nil} + -- The api_key variable is a dictionary compose by three elements, the key_value which stores + -- the api_key value or the user token value, the key_type field in where is stored + -- the type of key that was provided by the user, it value could be an api_key or a token, Finally + -- the idp field indicates, if the api-backend have an IdP registred for the token validation + -- The validation process is made for all the api_key_methods (except basicAuthUsername) + -- declared in the configuration file, checking if the user sends an api_key or token + -- Only the header and get_param methods are supported by the token validation. for _, method in ipairs(api_key_methods) do - if method == "header" then - api_key = ngx.ctx.http_x_api_key - elseif method == "getParam" then - api_key = ngx.ctx.arg_api_key - elseif method == "basicAuthUsername" then - api_key = ngx.ctx.remote_user + if method == "header" and ngx.ctx.http_x_api_key then + key.key_value = ngx.ctx.http_x_api_key + key.key_type = "api_key" + elseif ngx.ctx.http_x_auth_token then + key.key_value = ngx.ctx.http_x_auth_token + key.key_type = "token" + elseif method == "getParam" and ngx.ctx.arg_api_key then + key.key_value = ngx.ctx.arg_api_key + key.key_type = "api_key" + elseif ngx.ctx.arg_token then + key.key_value = ngx.ctx.arg_token + key.key_type = "token" + elseif method == "basicAuthUsername" and ngx.ctx.remote_user then + key.key_value = ngx.ctx.remote_user + key.key_type = "api_key" end - if not is_empty(api_key) then + if not is_empty(key["key_value"]) then break end end -- Store the api key for logging. - ngx.ctx.api_key = api_key + ngx.ctx.api_key = key["key_value"] - return api_key + return key end return function(settings) -- Find the API key in the header, query string, or HTTP auth. local api_key = resolve_api_key() - if is_empty(api_key) then + -- Find if and IdP was set + if settings["require_idp"] then + api_key.idp=config["gatekeeper"]["default_idp"] + api_key.idp=settings["require_idp"] + api_key.trusted_apps = settings["trusted_apps"] + api_key.app_id = settings["idp_app_id"] + api_key.mode = settings["idp_mode"] + end + if is_empty(api_key["key_value"]) then if settings and settings["disable_api_key"] then return nil else @@ -44,10 +68,14 @@ return function(settings) if not user then return nil, "api_key_invalid" end + -- Verify if the app that makes the request is in the trusted app list of the API Backend + if api_key["key_type"]== "token" and user["trusted_app"]== "not_trusted" then + return nil, "not_trusted_app" + end -- Store the api key on the user object for easier access (the user object -- doesn't contain it directly, to save memory storage in the lookup table). - user["api_key"] = api_key + user["api_key"] = api_key["key_value"] -- Store user details for logging. ngx.ctx.user_id = user["id"] @@ -76,4 +104,4 @@ return function(settings) end return user -end +end \ No newline at end of file diff --git a/src/api-umbrella/proxy/user_store.lua b/src/api-umbrella/proxy/user_store.lua index 6ca87e4e2..5413c0102 100644 --- a/src/api-umbrella/proxy/user_store.lua +++ b/src/api-umbrella/proxy/user_store.lua @@ -8,19 +8,35 @@ local mongo = require "api-umbrella.utils.mongo" local shcache = require "shcache" local types = require "pl.types" local utils = require "api-umbrella.proxy.utils" +local idp = require "api-umbrella.utils.idp" +local trusted_app = require "api-umbrella.utils.trusted_app_validation" local cache_computed_settings = utils.cache_computed_settings local is_empty = types.is_empty local function lookup_user(api_key) - local raw_user, err = mongo.first("api_users", { - query = { - api_key = api_key, - }, - }) - - if err then - ngx.log(ngx.ERR, "failed to fetch user from mongodb: ", err) + local raw_user + local db_err + local idp_err + + -- Checking the field of api_key ["key_type"], if the key_type is api_key + -- the api_key value is checked in the database and retrieve the user information + -- else if the key_type is token, the token is checked using the corresponding IdP + -- registred in the api-backend and the user information is retrieved + + if not api_key["key_type"] or api_key["key_type"] == "api_key" then + raw_user, db_err = mongo.first("api_users", { + query = { + api_key = api_key["key_value"], + }, + }) + elseif api_key["key_type"] == "token" and api_key["idp"]then + raw_user, idp_err = idp.first(api_key) + end + if idp_err then + ngx.log(ngx.ERR, "failed to autenticate , status code:", idp_err) + elseif db_err then + ngx.log(ngx.ERR, "failed to fetch user from mongodb", db_err) elseif raw_user then local user = utils.pick_where_present(raw_user, { "created_at", @@ -32,19 +48,31 @@ local function lookup_user(api_key) "settings", "throttle_by_ip", }) - -- Ensure IDs get stored as strings, even if Mongo ObjectIds are in use. - if raw_user["_id"] and raw_user["_id"]["$oid"] then + if api_key["key_type"]=="api_key" and raw_user["_id"] and raw_user["_id"]["$oid"] then user["id"] = raw_user["_id"]["$oid"] else user["id"] = raw_user["_id"] end + if api_key["idp"] and api_key["key_type"]=="token" and api_key["idp"]== "fiware-oauth2" then + user["id"] = raw_user.id + user["email"] = raw_user.email + user["trusted_app"]= trusted_app.trusted_app_validation(raw_user.app_id,api_key["trusted_apps"]) + elseif api_key["idp"] and api_key["key_type"]=="token" and api_key["idp"]~= "fiware-oauth2" then + user["id"] = raw_user.name + user["email"] = raw_user.email + end + -- Invert the array of roles into a hashy table for more optimized -- lookups (so we can just check if the key exists, rather than -- looping over each value). + -- Moreover, in case that the user information have been retrieved using a token validation, + -- the roles associated with the token are stored in user ["roles"] if user["roles"] then user["roles"] = invert_table(user["roles"]) + elseif api_key["idp"] and api_key["key_type"]=="token" and api_key["idp"]== "fiware-oauth2" and raw_user.Roles then + user["roles"] = invert_table(raw_user.Roles) end if user["created_at"] and user["created_at"]["$date"] then @@ -103,14 +131,14 @@ function _M.get(api_key) return nil end - user = shared_cache:load(api_key) + user = shared_cache:load(api_key["key_value"]) if user then - local_cache:set(api_key, user, 2) + local_cache:set(api_key["key_value"], user, 2) else - local_cache:set(api_key, EMPTY_DATA, 2) + local_cache:set(api_key["key_value"], EMPTY_DATA, 2) end return user end -return _M +return _M \ No newline at end of file diff --git a/src/api-umbrella/utils/idp.lua b/src/api-umbrella/utils/idp.lua new file mode 100644 index 000000000..b992269bf --- /dev/null +++ b/src/api-umbrella/utils/idp.lua @@ -0,0 +1,64 @@ +local http = require "resty.http" +local cjson = require "cjson" +local _M = {} + +-- Function to connect with an IdP service (Google, Facebook, Fiware, Github) for checking +-- if a token is valid and retrieve the user properties. The function takes +-- the token provided by the user and the IdP provider registered in the api-backend +-- for checking if the token is valid making a validation request to the corresponding IdP. +-- If the token is valid, the user information stored in the IdP is retrieved. + +function _M.first(dict) + local idp_back_name = dict["idp"] + local token = dict["key_value"] + local idp_host, result, res, err, rpath,resource, method + local ssl=false + local app_id = dict["app_id"] + local mode = dict["mode"] + local httpc = http.new() + httpc:set_timeout(45000) + + if config["nginx"]["lua_ssl_trusted_certificate"] then + ssl=true + end + local rquery = "access_token="..token + if idp_back_name == "google-oauth2" then + rpath = "/oauth2/v3/userinfo" + idp_host="https://www.googleapis.com" + elseif idp_back_name == "fiware-oauth2" and mode == "authorization" then + rpath = "/user" + idp_host=dict["idp"]["host"] + resource = ngx.ctx.uri + method = ngx.ctx.request_method + rquery = "access_token="..token.."&app_id="..app_id.."&resource="..resource.."&action="..method + elseif idp_back_name == "fiware-oauth2" and mode == "authentication" then + rpath = "/user" + idp_host=dict["idp"]["host"] + rquery = "access_token="..token.."&app_id="..app_id + elseif idp_back_name == "facebook-oauth2" then + rpath = "/me" + idp_host="https://graph.facebook.com" + rquery = "fields=id,name,email&access_token="..token + elseif idp_back_name == "github-oauth2" then + rpath = "/user" + idp_host="https://api.github.com" + end + + res, err = httpc:request_uri(idp_host..rpath,{ + method = "GET", + query = rquery, + ssl_verify = ssl, + }) + + if res and res.status == 200 then + local body= res.body + if not body then + return nil + end + result = cjson.decode(body) + end + + return result, err +end + +return _M \ No newline at end of file diff --git a/src/api-umbrella/utils/invert_table.lua b/src/api-umbrella/utils/invert_table.lua index 5d0000f22..4b5a1b8f9 100644 --- a/src/api-umbrella/utils/invert_table.lua +++ b/src/api-umbrella/utils/invert_table.lua @@ -1,8 +1,18 @@ return function(table) + local numItems = 0 local inverted = {} for key, value in pairs(table) do - inverted[value] = key + if type(value)=="string" then + inverted[value] = key + else + for k,v in pairs(value) do + numItems = numItems + 1 + end + if numItems > 1 then + value = value["name"] + end + inverted[value] = key + end end - return inverted end diff --git a/src/api-umbrella/utils/trusted_app_validation.lua b/src/api-umbrella/utils/trusted_app_validation.lua new file mode 100644 index 000000000..1b3058dcd --- /dev/null +++ b/src/api-umbrella/utils/trusted_app_validation.lua @@ -0,0 +1,17 @@ +-- +-- Created by: anmunoz +-- Date: 3/16/18 +-- Time: 2:03 PM +-- +local _output_value = {} +function _output_value.trusted_app_validation(app_id_scope,trusted_app_list) + for _, trusted_app_id in ipairs(trusted_app_list) do + if app_id_scope == trusted_app_id.id then + _output_value="trusted" + return _output_value + end + end + _output_value="not_trusted" + return _output_value +end +return _output_value \ No newline at end of file diff --git a/src/api-umbrella/web-app/app/controllers/api/v1/apis_controller.rb b/src/api-umbrella/web-app/app/controllers/api/v1/apis_controller.rb index 38550bc96..462a7c50e 100644 --- a/src/api-umbrella/web-app/app/controllers/api/v1/apis_controller.rb +++ b/src/api-umbrella/web-app/app/controllers/api/v1/apis_controller.rb @@ -94,6 +94,7 @@ def api_params :disable_api_key, :api_key_verification_level, :api_key_verification_transition_start_at, + :require_idp, :rate_limit_mode, :anonymous_rate_limit_behavior, :authenticated_rate_limit_behavior, diff --git a/src/api-umbrella/web-app/app/models/api/settings.rb b/src/api-umbrella/web-app/app/models/api/settings.rb index 3d8e11aa0..3fcda9bad 100644 --- a/src/api-umbrella/web-app/app/models/api/settings.rb +++ b/src/api-umbrella/web-app/app/models/api/settings.rb @@ -10,6 +10,7 @@ class Api::Settings field :disable_api_key, :type => Boolean field :api_key_verification_level, :type => String field :api_key_verification_transition_start_at, :type => Time + field :require_idp, :type => String field :required_roles, :type => Array field :required_roles_override, :type => Boolean field :allowed_ips, :type => Array diff --git a/src/api-umbrella/web-app/bin/bundle b/src/api-umbrella/web-app/bin/bundle old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/bin/delayed_job b/src/api-umbrella/web-app/bin/delayed_job old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/bin/rails b/src/api-umbrella/web-app/bin/rails old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/bin/rake b/src/api-umbrella/web-app/bin/rake old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/bin/setup b/src/api-umbrella/web-app/bin/setup old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/bin/spring b/src/api-umbrella/web-app/bin/spring old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/config/locales/en.yml b/src/api-umbrella/web-app/config/locales/en.yml index 6186b1afa..12bb353a4 100644 --- a/src/api-umbrella/web-app/config/locales/en.yml +++ b/src/api-umbrella/web-app/config/locales/en.yml @@ -139,6 +139,10 @@ en: none: None - API keys can be used without any verification transition_email: E-mail verification transition - Existing API keys will continue to work, new API keys will only work if verified required_email: E-mail verification required - Existing API keys will break, only new API keys will work if verified + require_idp: External Identity Provider + require_idp_options: + inherit: Inherit (default - none) + none: None required_roles: Required Roles required_roles_tooltip_markdown: |- Define roles that API keys must have in order to access this API. If multiple roles are set, then the API key must have all of the roles. diff --git a/src/api-umbrella/web-app/script/migrate_logs b/src/api-umbrella/web-app/script/migrate_logs old mode 100755 new mode 100644 diff --git a/src/api-umbrella/web-app/script/migrate_users b/src/api-umbrella/web-app/script/migrate_users old mode 100755 new mode 100644 diff --git a/templates/etc/perp/.boot/rc.log b/templates/etc/perp/.boot/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/.boot/rc.perp.mustache b/templates/etc/perp/.boot/rc.perp.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/dev-env-ember-server/rc.log b/templates/etc/perp/dev-env-ember-server/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/dev-env-ember-server/rc.main.mustache b/templates/etc/perp/dev-env-ember-server/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/elasticsearch/rc.log b/templates/etc/perp/elasticsearch/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/elasticsearch/rc.main.mustache b/templates/etc/perp/elasticsearch/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/flume/rc.log b/templates/etc/perp/flume/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/flume/rc.main.mustache b/templates/etc/perp/flume/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/geoip-auto-updater/rc.log b/templates/etc/perp/geoip-auto-updater/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/geoip-auto-updater/rc.main.mustache b/templates/etc/perp/geoip-auto-updater/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/kylin/rc.log b/templates/etc/perp/kylin/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/kylin/rc.main.mustache b/templates/etc/perp/kylin/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/kylin/rc.run.mustache b/templates/etc/perp/kylin/rc.run.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/mongod/rc.log b/templates/etc/perp/mongod/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/mongod/rc.main.mustache b/templates/etc/perp/mongod/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/mora/rc.log b/templates/etc/perp/mora/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/mora/rc.main.mustache b/templates/etc/perp/mora/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/nginx-reloader/rc.log b/templates/etc/perp/nginx-reloader/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/nginx-reloader/rc.main.mustache b/templates/etc/perp/nginx-reloader/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/nginx/rc.log b/templates/etc/perp/nginx/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/nginx/rc.main.mustache b/templates/etc/perp/nginx/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/presto/rc.log b/templates/etc/perp/presto/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/presto/rc.main.mustache b/templates/etc/perp/presto/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/rc.log.mustache b/templates/etc/perp/rc.log.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/rsyslog/rc.log b/templates/etc/perp/rsyslog/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/rsyslog/rc.main.mustache b/templates/etc/perp/rsyslog/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-mailhog/rc.log b/templates/etc/perp/test-env-mailhog/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-mailhog/rc.main.mustache b/templates/etc/perp/test-env-mailhog/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-mongo-orchestration/rc.log b/templates/etc/perp/test-env-mongo-orchestration/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-mongo-orchestration/rc.main.mustache b/templates/etc/perp/test-env-mongo-orchestration/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-nginx/rc.log b/templates/etc/perp/test-env-nginx/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-nginx/rc.main.mustache b/templates/etc/perp/test-env-nginx/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-openldap/rc.log b/templates/etc/perp/test-env-openldap/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-openldap/rc.main.mustache b/templates/etc/perp/test-env-openldap/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-unbound/rc.log b/templates/etc/perp/test-env-unbound/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/test-env-unbound/rc.main.mustache b/templates/etc/perp/test-env-unbound/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/trafficserver/rc.log b/templates/etc/perp/trafficserver/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/trafficserver/rc.main.mustache b/templates/etc/perp/trafficserver/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/web-delayed-job/rc.log b/templates/etc/perp/web-delayed-job/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/web-delayed-job/rc.main.mustache b/templates/etc/perp/web-delayed-job/rc.main.mustache old mode 100755 new mode 100644 diff --git a/templates/etc/perp/web-puma/rc.log b/templates/etc/perp/web-puma/rc.log old mode 100755 new mode 100644 diff --git a/templates/etc/perp/web-puma/rc.main.mustache b/templates/etc/perp/web-puma/rc.main.mustache old mode 100755 new mode 100644 diff --git a/test/scripts/circle-ci b/test/scripts/circle-ci old mode 100755 new mode 100644