From 3d1e27b77d18c80eb9489cc094db17d261cc408e Mon Sep 17 00:00:00 2001 From: azilentech Date: Sat, 12 Mar 2022 11:02:48 +0530 Subject: [PATCH] Refactored code. Better variable names. Documentation updated as per guideline. --- apisix/plugins/authz-keycloak.lua | 141 ++++++++++++----------- docs/en/latest/plugins/authz-keycloak.md | 24 ++-- 2 files changed, 85 insertions(+), 80 deletions(-) diff --git a/apisix/plugins/authz-keycloak.lua b/apisix/plugins/authz-keycloak.lua index 8bc3d66720b8f..29175b2f8467b 100644 --- a/apisix/plugins/authz-keycloak.lua +++ b/apisix/plugins/authz-keycloak.lua @@ -67,7 +67,7 @@ local schema = { access_token_expires_leeway = {type = "integer", minimum = 0, default = 0}, refresh_token_expires_in = {type = "integer", minimum = 1, default = 3600}, refresh_token_expires_leeway = {type = "integer", minimum = 0, default = 0}, - token_generation_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + password_grant_token_generation_incoming_uri = {type = "string", minLength = 1, maxLength = 4096}, }, allOf = { -- Require discovery or token endpoint. @@ -697,102 +697,105 @@ local function fetch_jwt_token(ctx) end -- This function is used to split data from array by respective delimitter and return array in result local function stringsplit(s, delimiter) - local result = {}; + local result = {} for match in (s..delimiter):gmatch("(.-)"..delimiter) do - table.insert(result, match); + table.insert(result, match) end - return result; + return result end -- To get new access token by calling get token api -local function generate_token(conf,ctx) - log.warn("------------Generate Access Token Function Called---------------") +local function generate_token_using_password_grant(conf,ctx) + log.warn("------------generate_token_using_password_grant Function Called---------------") --Read Body - ngx.req.read_body(); --To read requestbody first + ngx.req.read_body() --Get Body Data - local RequestBody=ngx.req.get_body_data(); - local UserName = ""; local Password = ""; + local request_body=ngx.req.get_body_data() + local username = nil + local password = nil --split by & - local strBodyArr = stringsplit(RequestBody, "&"); + local parameters_array = stringsplit(request_body, "&") if strBodyArr then - for k, strBodyValue in ipairs(strBodyArr) do - if string.find(strBodyValue, "username") then + for k, parameter in ipairs(parameters_array) do + if string.find(parameter, "username") then --split by = - local usr = stringsplit(strBodyValue, "="); - UserName = usr[2]; + local username_value_array = stringsplit(parameter, "=") + username = username_value_array[2] end - if string.find(strBodyValue, "password") then - local psw = stringsplit(strBodyValue, "="); - Password = psw[2]; + if string.find(parameter, "password") then + --split by = + local password_value_array = stringsplit(parameter, "=") + password = password_value_array[2] end end end + if not username then + err = "username is missing" + log.error(err) + return 422, err + end + if not password then + err = "password is missing" + log.error(err) + return 422, err + end + local client_id = authz_keycloak_get_client_id(conf) - + local token_endpoint = authz_keycloak_get_token_endpoint(conf) - + if not token_endpoint then - log.error("Unable to determine token endpoint.") - return 500, "Unable to determine token endpoint." + err = "Unable to determine token endpoint." + log.error(err) + return 500, err end - local httpc = authz_keycloak_get_http_client(conf) + local httpc = authz_keycloak_get_http_client(conf) - local params = { - method = "POST", - body = ngx.encode_args({ - grant_type = "password", - client_id = client_id, - client_secret = conf.client_secret, - username = UserName, - password = Password - }), - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded" - } + local params = { + method = "POST", + body = ngx.encode_args({ + grant_type = "password", + client_id = client_id, + client_secret = conf.client_secret, + username = username, + password = password + }), + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded" } + } - params = authz_keycloak_configure_params(params, conf) + params = authz_keycloak_configure_params(params, conf) - local res, err = httpc:request_uri(token_endpoint, params) + local res, err = httpc:request_uri(token_endpoint, params) - if not res then - err = "Accessing token endpoint URL (" .. token_endpoint - .. ") failed: " .. err - log.error(err) - return 401, {message = err} - end - - log.debug("Response data: " .. res.body) - local json, err = authz_keycloak_parse_json_response(res) + if not res then + err = "Accessing token endpoint URL (" .. token_endpoint + .. ") failed: " .. err + log.error(err) + return 401, {message = err} + end + + log.debug("Response data: " .. res.body) + local json, err = authz_keycloak_parse_json_response(res) + + if not json then + err = "Could not decode JSON from response" + .. (err and (": " .. err) or '.') + log.error(err) + return 401, {message = err} + end - log.warn(core.json.encode(json)) - if not json then - err = "Could not decode JSON from token endpoint" - .. (err and (": " .. err) or '.') - log.error(err) - return 401, {message = err} - end - - return res.status, res.body; + return res.status, res.body end function _M.access(conf, ctx) - -- Get Requested URI - local RequestURI = string.upper(ngx.var.request_uri); - - if conf.token_generation_endpoint then - -- Get Token generation end point of key cloak which we have mession in keyclock plugin config - local token_generation_endpoint = string.upper(conf.token_generation_endpoint); - local curr_req_method = string.upper(ctx.curr_req_matched["_method"]); - --Call Generate Access Token function if "\Token" found in URI based on configuration - if RequestURI == token_generation_endpoint then - if curr_req_method ~= "POST" then - log.error("Invalid request type") - return 400, {message = "Request method must be POST only.!"} - end - - return generate_token(conf,ctx); + + if conf.password_grant_token_generation_incoming_uri then + if string.upper(ngx.var.request_uri) == string.upper(conf.password_grant_token_generation_incoming_uri) then + if string.upper(ctx.curr_req_matched["_method"]) == "POST" then + return generate_token_using_password_grant(conf,ctx) end end log.debug("hit keycloak-auth access") diff --git a/docs/en/latest/plugins/authz-keycloak.md b/docs/en/latest/plugins/authz-keycloak.md index 7e5c7f20252a9..3039c3b4ccbde 100644 --- a/docs/en/latest/plugins/authz-keycloak.md +++ b/docs/en/latest/plugins/authz-keycloak.md @@ -55,7 +55,7 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ | keepalive_timeout | integer | optional | 60000 | positive integer >= 1000 | Idle timeout after which established HTTP connections will be closed. | | keepalive_pool | integer | optional | 5 | positive integer >= 1 | Maximum number of connections in the connection pool. | | access_denied_redirect_uri | string | optional | | [1, 2048] | Redirect unauthorized user with the given uri like "http://127.0.0.1/test", instead of returning `"error_description":"not_authorized"`. | -| token_generation_endpoint | string | optional | | | Endpoint path to identify URL pattern based on Path configuration. So that we can generate token if Request Uri match with this path. | +| password_grant_token_generation_incoming_uri | string | optional | | /api/token | You can set this uri value to generate token using password grant type. Plugin will compare incoming request uri with this value. | ### Discovery and Endpoints @@ -123,24 +123,26 @@ of the same name. The scope is then added to every permission to check. If `lazy_load_paths` is `false`, the plugin adds the mapped scope to any of the static permissions configured in the `permissions` attribute, even if they contain one or more scopes already. -### Token generation endpoint -If user wants to generate a token based on user name and password with the support of grant type `password`. +### Password Grant Token Generation Incoming URI -The user have to configure URI path (E.g. `/api/Token`) in `token_generation_endpoint` which will match with incomming Request URI path and it will generate a new token with using `token_endpoint`. +If you want to generate a token using `password` grant, you can set value of `password_grant_token_generation_incoming_uri`. -And for other route config, if will check token and redirect to the resource which are allocated to users. +Incoming request URI will be matched with this value and if matched, it will generate token using `Token Endpoint`. +It will also check, if REST method is `POST`. -The user must have to pass Content-Type header as `application/x-www-form-urlencoded` and `username & password` in body part of request. +You need to pass `application/x-www-form-urlencoded` as `Content-Type` header and `username`, `password` as parameters. -## Token generation example +**Sample request** -```cURL Code -curl --location --request POST 'http://127.0.0.1:9080/api/Token' \ +If value of `password_grant_token_generation_incoming_uri` is `/api/token`, you can use following curl request. + +```shell +curl --location --request POST 'http://127.0.0.1:9080/api/token' \ --header 'Accept: application/json, text/plain, */*' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'username=User Name' \ ---data-urlencode 'password=Password' +--data-urlencode 'username=' \ +--data-urlencode 'password=' ``` ## How To Enable