Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for password grant in authz-keycloak plugin #6586

Conversation

azilentech
Copy link
Contributor

@azilentech azilentech commented Mar 11, 2022

Support of password grant type for token generation (#6574)

Committing changes after adding new feature for generating token based on user id/password and also taking support of grant type password

feat: #6574

Pre-submission checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

Squashed commit:

[99ff9f4] refactor: Removing unnecessary properties (apache#6574)

Removing unnecessary properties and test codes. And also improve document for Token generation feature.

feat: apache#6574

[22c8d8e] feat: Support of password grant type for token generation (apache#6574)

Committing changes after adding new feature for generating token based on `user id/password` and also taking support of grant type `password`

feat: apache#6574
@azilentech azilentech changed the title feat-6574-keycloak-plugin-password-grant-support feat:6574 - keycloak plugin password grant support Mar 11, 2022
@azilentech azilentech changed the title feat:6574 - keycloak plugin password grant support feat - keycloak plugin password grant support Mar 11, 2022
@azilentech azilentech changed the title feat - keycloak plugin password grant support feat: keycloak plugin password grant support Mar 11, 2022
@azilentech azilentech changed the title feat: keycloak plugin password grant support feat: add support for password grant in keycloak plugin Mar 11, 2022
@soulbird
Copy link
Contributor

you should sing the CLA first

@soulbird
Copy link
Contributor

Missing unit tests

@azilentech azilentech force-pushed the feat-6574-keycloak-plugin-password-grant-support branch from e555a00 to 3d1e27b Compare March 12, 2022 05:43
Better variable names.
Documentation updated as per guideline.
@azilentech azilentech force-pushed the feat-6574-keycloak-plugin-password-grant-support branch from 3d1e27b to fc96102 Compare March 12, 2022 12:37
@azilentech
Copy link
Contributor Author

Hi,

I will need little guidance for the CLA signing process from your side.

In PR, I have made the following changes.

  • Added test cases. As these test cases require keycloak installation, I tested with my local environment by adjusting parameters and configuration settings. Please let me know If I need to perform further steps.
  • I updated code by using better configuration setting "password_grant_token_generation_incoming_uri" instead of using "token_generation_endpoint" which can be confusing with "token_endpoint".
  • I updated the documentation by following contribution guidelines.
  • I also ran "make lint" and followed all the suggestions.

Please let me know the next actions for me.

…using password_grant_token_generation_incoming_uri.

I have removed some more errors reported by git workflow build process.
@spacewander
Copy link
Member

I will need little guidance for the CLA signing process from your side.

There is no CLA file for APISIX :)

Added test cases. As these test cases require keycloak installation, I tested with my local environment by adjusting parameters and configuration settings. Please let me know If I need to perform further steps.

As the keycloak environment is set up by @sshniro,
@sshniro
Could you provide some help for @azilentech to test this feature? Thanks!

@niketrk
Copy link

niketrk commented Mar 13, 2022

@sshniro

I checked the logs of above test runs.
error is: "status=401, body={"error":"invalid_grant","error_description":"Invalid user credentials"}"

I have used following settings in my test scenario:
"client_id": "course_management"
"client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5"
username = "teacher@gmail.com"
password = "123456"
grant_type = "password"

I took the above settings from other Keycloak test cases.
I am doubting that these settings are not proper for the test Keycloak instance. In my local environment, I put my Keycloak settings and then run test scenario via
TEST_NGINX_BINARY=/usr/local/bin/openresty prove -Itest-nginx/lib -r t/plugin/authz-keycloak.t

And it worked properly.


Another issue is reported as: "ERROR: apisix/plugins/authz-keycloak.lua: line 719: getting the Lua global "string""

For which I made fix "local str_find = string.find", so it should be fixed now.

@@ -695,8 +703,107 @@ local function fetch_jwt_token(ctx)
return token
end

-- To get new access token by calling get token api
local function generate_token_using_password_grant(conf,ctx)
log.warn("generate_token_using_password_grant Function Called")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should we use warn level ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree... it should be debug level log. I changed it.

--Read Body
ngx.req.read_body()
--Get Body Data
local request_body=ngx.req.get_body_data()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use core.request.get_body() instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I changed it.

return 401, {message = err}
end

return res.status, res.body
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return res.status, res.body
return res.status, res.body

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


if conf.password_grant_token_generation_incoming_uri then
if ngx.var.request_uri:upper()
== conf.password_grant_token_generation_incoming_uri:upper() then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use and would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Suggestion and changed it to use and for all three conditions.

local username = nil
local password = nil
--split by &
local parameters_array = util.split(request_body, "&")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the username, password in the request body and use & to concat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As url encoded parameters were coming, they were automatically appended using '&'. Having said that, your comment helped me to utilize "ngx.decode_args" which makes code much cleaner. Thanks!

Used ngx.decode_args for input arguments.
@azilentech azilentech requested a review from starsz March 15, 2022 04:45
@sshniro
Copy link
Member

sshniro commented Mar 15, 2022

scenario

Hi, are you getting the same error when you run the docker image instance (sshniro/keycloak-apisix:latest) locally?

@azilentech
Copy link
Contributor Author

@sshniro
I didn't test that way earlier, but just now, I did that and it is working. Also when I see the above checks, that test case is starting to pass now.

I have got the idea what things might cause that. Earlier I was using '&' to split the incoming parameters. but now I am using "ngx.decode_args" ( #6586 (comment) ).

Because of that earlier, username (which is email) wouldn't parse properly.
That should be the root cause of the same.

if conf.password_grant_token_generation_incoming_uri and
ngx.var.request_uri:upper() ==
conf.password_grant_token_generation_incoming_uri:upper() and
ctx.curr_req_matched["_method"]:upper() == "POST" then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.curr_req_matched["_method"]:upper() looks strange.
I think you can use core.req.get_method instead.


function _M.access(conf, ctx)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant line ?

@@ -122,6 +123,27 @@ 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.

### Password Grant Token Generation Incoming URI

If you want to generate a token using `password` grant, you can set value of `password_grant_token_generation_incoming_uri`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you want to generate a token using `password` grant, you can set value of `password_grant_token_generation_incoming_uri`.
If you want to generate a token using `password` grant, you can set the value of `password_grant_token_generation_incoming_uri`.


If you want to generate a token using `password` grant, you can set value of `password_grant_token_generation_incoming_uri`.

Incoming request URI will be matched with this value and if matched, it will generate token using `Token Endpoint`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Incoming request URI will be matched with this value and if matched, it will generate token using `Token Endpoint`.
Incoming request URI will be matched with this value and if matched, it will generate a token using `Token Endpoint`.

If you want to generate a token using `password` grant, you can set value of `password_grant_token_generation_incoming_uri`.

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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It will also check, if REST method is `POST`.
It will also check if the request method is `POST`.

starsz
starsz previously approved these changes Mar 16, 2022

local params = {
method = "POST",
body = ngx.encode_args({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
body = ngx.encode_args({
body = ngx.encode_args({

Copy link
Contributor Author

@azilentech azilentech Mar 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done and also incorporated this in the same file at other places.

if not token_endpoint then
local err = "Unable to determine token endpoint."
log.error(err)
return 500, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return 500, err
return 503, err

New feature is encouraged to avoid 500

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point. I updated it. I didn't update at other places because there can be backward compatibility issues and there should be separate PR for the same.

if conf.password_grant_token_generation_incoming_uri and
ngx.var.request_uri:upper() ==
conf.password_grant_token_generation_incoming_uri:upper() and
core.request.get_method() == "POST" then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check the "Content-Type"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. it was missed. I introduced that now.


function _M.access(conf, ctx)
if conf.password_grant_token_generation_incoming_uri and
ngx.var.request_uri:upper() ==
conf.password_grant_token_generation_incoming_uri:upper() and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it required to compare case-insensitive?
Also, the var.request_uri will contain ?argument=part, is it desirable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL should be case sensitive so removed that "upper" from code.
For the part of request_uri, I believe, as we are checking uri, method, and header, all three parts, this should be fine.
Basically for the operation of token generation, exact uri matching will be helpful.
Your thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request_uri will contain the query string part of the uri. For example,
With request /path?query=string
The ngx.var.request_uri will return /path?query=string, and ctx.var.uri will return /path. By looking at the doc of password_grant_token_generation_incoming_uri, I guess we can use ctx.var.uri so both /path?query=string and /path can match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spacewander I understood your idea. I am curious, as to why we should treat both strings (/path?query=string or /path) the same way and proceed with token generation.

Token generation is a very specific use case, and shouldn't we say if and only if a specific URL will be submitted then we will generate token and submit result back.

if the request is made with /path?query=string, it shouldn't be assumed for token generation and it should be treated the same as any other request. (e.g. proceed to forward to upstream for further processing).

Please share your view. Accordingly, I can make changes, if needed.

},
"uri": "/api/token"
}]],
[[{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to remove the echo response data as #6545

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. I removed it from this new test case.

spacewander
spacewander previously approved these changes Mar 18, 2022
Copy link
Member

@spacewander spacewander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved except #6586 (comment)

@azilentech azilentech force-pushed the feat-6574-keycloak-plugin-password-grant-support branch from 6f04d1a to c1d1c8d Compare March 18, 2022 12:58
spacewander
spacewander previously approved these changes Mar 20, 2022

function _M.access(conf, ctx)
local headers = core.request.headers(ctx)
local need_grant_token = conf.password_grant_token_generation_incoming_uri and
ngx.var.request_uri == conf.password_grant_token_generation_incoming_uri and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ngx.var.request_uri == conf.password_grant_token_generation_incoming_uri and
ctx.var.request_uri == conf.password_grant_token_generation_incoming_uri and

would be better, which provides a cache of the var.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Made the change.

@lijing-21
Copy link
Contributor

Hi @azilentech , thank you for your contribution!

Here is the Contributor T-shirt form[1], if you're interested, kindly take a look :)

[1] https://github.com/apache/apisix/blob/master/CONTRIBUTING.md#contributor-t-shirt

@kayx23 kayx23 changed the title feat: add support for password grant in keycloak plugin feat: add support for password grant in authz-keycloak plugin Dec 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants