Closed col-panic closed 1 month ago
I need that as well. The only thing I'm missing is role mapping after login.
What OAuth provider do you use? In Azure it should be possible in general. If you specify groupMembershipClaims, then we should get the access token with a list of groups. If you have the option to set the groupMembershipClaims in your application, I can create a branch where I log the access token to see whether some groups are present and in in format. I can't test it myself as I have no direct access to Azure administration. https://stackoverflow.com/questions/54868974/getting-security-groups-in-jwt-access-token
We have a Keycloak Realm with multiple users. Now I only want a subset of this users to be able to login to Redmine. Keyloak is an Authentication tool, while the task of authorization is done by the Resource Provider (in this case Redmine).
At the moment redmine_oauth
does only require a user to successfully authenticate against a realm to be able to login as user. This already leaves projects marked as "public" open to this user.
(There exists an extension to block a user from getting a client token in https://github.com/sventorben/keycloak-restrict-client-auth - but as mentioned, this is considered to be a task of the resource provider)
The basic approach would be, that a client role user
is introduced, and if the token represented by the user does not contain a role like resource_access.redmine.user
then access is not granted.
In the following IDToken I added myself to a newly required client role user
{
"exp": 1724998286,
"iat": 1724997986,
"jti": "fb1d6fb5-1734-4c16-ab98-4e008b9ffefd",
"iss": "https://***/realms/***",
"aud": "redmine",
"sub": "8e82f27a-39cf-4540-a308-ddd8c94e6a5d",
"typ": "ID",
"azp": "redmine",
"sid": "dddeb1de-ebd8-4c6f-913e-290ae5d5f8ac",
"acr": "1",
"resource_access": {
"redmine": {
"roles": [
"user"
]
}
},
"email_verified": true,
"name": "Marco Descher",
"preferred_username": "mdescher",
"given_name": "Marco",
"family_name": "Descher",
"email": "***@***"
}
the mapping to resource_access.redmine.roles
is keycloaks default to map a users client.role. The location
should however be configurable. This role could also be mapped to the access token. I asumme, however, that
you are reading the IDToken.
So the following extensions could be realize:
user
to be authorized to login to redmine. (This feature could be optional. If someone has a setup where having a valid user on the openid server is already enough, then we would not need to check this)"resource_access": {
"redmine": {
"roles": [
"user", "admin"
]
}
}
then the user could already be granted the Administrator privileges. Some way round - if admin
is not part of the token anymore, then the admin rights should be removed.
groups
which could then contain memberships to groups as used within redmine. But I don't elaborate on this here. If we come to this point, I'll create a separate issue explaining in detail.Could you test roles branch with your Keycloak configuration?
@picman thank you for your response, I added some comments to https://github.com/kontron/redmine_oauth/commit/e1f6a6808e5366e5f84c05c20e94388270c94b27
Updated accordingly.
Validate user roles = resource_access.mis.roles
If none of the roles is mapped, then this happens:
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
mis-echo-1 | E, [2024-09-05T13:23:35.291165 #1] ERROR -- : undefined method `[]' for nil:NilClass
it is correct not to let me in - but there should be a better message :)
if user
role is mapped the same happens for the access token
{
"exp": 1725543182,
"iat": 1725542882,
"jti": "433d0ae4-3821-499c-9eee-06f061685acd",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "Bearer",
"azp": "mis",
"sid": "b9957d40-8c9e-4881-ba03-92eb636de2eb",
"acr": "1",
"allowed-origins": [
"/*"
],
"resource_access": {
"mis": {
"roles": [
"user"
]
}
},
"scope": "openid profile email",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@tester.at"
}
id_token
currently it seems to select the access_token
id_token
looks like this
{
"exp": 1725547546,
"iat": 1725547246,
"jti": "1e939b4e-9df2-412d-89a7-89b77e5e9363",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"aud": "mis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "ID",
"azp": "mis",
"sid": "a83e1b4e-c707-4a8b-b14a-ee806bf35708",
"acr": "1",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@tester.at"
}
the access-token
like this
{
"exp": 1725547546,
"iat": 1725547246,
"jti": "c201968e-4c78-4124-a244-5b37e1ea6064",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "Bearer",
"azp": "mis",
"sid": "7efbb610-e786-43c4-aaee-9cc02dc1f02a",
"acr": "1",
"allowed-origins": [
"/*"
],
"scope": "openid profile email",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@medevit.at"
}
user
granted for the client, the id_token
looks like this
{
"exp": 1725547853,
"iat": 1725547553,
"jti": "99f42891-d424-4bc7-b403-3a41967ef884",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"aud": "mis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "ID",
"azp": "mis",
"sid": "ae6b6e47-a4f3-4d35-841b-27bdc395119b",
"acr": "1",
"resource_access": {
"mis": {
"roles": [
"user"
]
}
},
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@tester.at"
}
the access_token
like this
{
"exp": 1725547853,
"iat": 1725547553,
"jti": "107e0d71-7bb8-4de0-b759-85245ace0623",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "Bearer",
"azp": "mis",
"sid": "c6377cd2-2303-4984-b2b3-86585d1eaec7",
"acr": "1",
"allowed-origins": [
"/*"
],
"resource_access": {
"mis": {
"roles": [
"user"
]
}
},
"scope": "openid profile email",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@tester.at"
}
for all these combinations I receive
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
mis-echo-1 | E, [2024-09-05T14:47:40.218602 #1] ERROR -- : undefined method `[]' for nil:NilClass
Fixed. (The error "contained more than one 'token' key" comes from an external library. I can't change it in my code.)
thanks - will test on Monday!
If I set resource_access.mis.roles
then it won't let me login at all.
Both with and without the role set, I will see
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
mis-echo-1 | I, [2024-09-09T06:41:45.064722 #1] INFO -- : Authentication failed due to a missing role in the token
mis-echo-1 | W, [2024-09-09T06:45:17.847620 #1] WARN -- : Failed login for 'tester@whatever.at' from 80.108.2.151 at 2024-09-09 06:45:17 UTC
mis-echo-1 | E, [2024-09-09T06:45:17.847757 #1] ERROR -- : Benutzer oder Passwort ist ungültig.
But Keycloak tells me that the content of the access token should be
{
"exp": 1725864532,
"iat": 1725864232,
"jti": "a165eff7-17f6-4f42-87bd-618cc093d013",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "Bearer",
"azp": "mis",
"sid": "1173cc58-9397-41b9-b9c5-c1e2406b5376",
"acr": "1",
"allowed-origins": [
"/*"
],
"resource_access": {
"mis": {
"roles": [
"user"
]
}
},
"scope": "openid profile email",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@whatever.at"
}
Is there a means to show what access token redmine_oauth effectively gets? Maybe there's a problem in parsing the correct entry?
I've added some debug log messages. Could you switch your log level to debug and post your output?
(config.log_level
= :debug in _config/additionalenvironment.rb)
In my case I see:
1) Validate user roles set to 'resource_access.mis.roles' nad roles present in the access_token:
DEBUG -- : Setting.validate_user_roles = 'resource_access.mis.roles'
DEBUG -- : key: resource_access
DEBUG -- : key: mis
DEBUG -- : key: roles
DEBUG -- : Roles: user
DEBUG -- : admin = false
DEBUG -- : try_to_log_in Karel.Picman@kontron.com
DEBUG -- : {:exp=>1725864532, :iat=>1725864232, :jti=>"a165eff7-17f6-4f42-87bd-618cc093d013", :iss=>"https://keycloak.medelexis.ch/realms/Medelexis", :sub=>"c64712b8-a25b-4368-82e0-a73d5fb7c944", :typ=>"Bearer", :azp=>"mis", :sid=>"1173cc58-9397-41b9-b9c5-c1e2406b5376", :acr=>"1", :"allowed-origins"=>["/*"], :resource_access=>{:mis=>{:roles=>["user"]}}, :scope=>"openid profile email", :email_verified=>true, :name=>"Test flawil", :preferred_username=>"tester", :locale=>"de", :given_name=>"Test", :family_name=>"flawil", :email=>"tester@whatever.at"}
INFO -- : Successful authentication for 'kpicman'
2) Roles are not present in the access_token:
DEBUG -- : Setting.validate_user_roles = 'resource_access.mis.roles'
DEBUG -- : key: resource_access
DEBUG -- : Key not found => access denied
DEBUG -- : Roles:
DEBUG -- : user role not found => access denied
INFO -- : Authentication failed due to a missing role in the token
WARN -- : Failed login for 'Karel.Picman@kontron.com'
I don't get it - on the left hand side you see the Evaluate Client Scope entry for access token,
which clearly states that the resp. resource_access
entry is part of it. Yet it seems not to
pick it up?!
The interesting bit seems
mis-echo-1 | D, [2024-09-11T13:59:12.396448 #1] DEBUG -- : Setting.validate_user_roles = 'resource_access.mis.roles'
mis-echo-1 | D, [2024-09-11T13:59:12.396493 #1] DEBUG -- : key: resource_access
mis-echo-1 | D, [2024-09-11T13:59:12.396507 #1] DEBUG -- : Key not found => access denied
it's not even finding the base element?!
(Nevermind the No filters fit
line .. thats a separate plugin. I removed it also to test, and it didn't change the behavior)
Thats the client mapper setting for client roles
Maybe the problem is located in this line
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
the info I transport is either part of access_token
or id_token
but these are encoded as JWT. I'm not sure you are analyzing this. Could you output the response you get at this point?
After studying documentation and source codes I came to a conclusion that the roles information are a part of _idtoken not _accesstoken. Despite your option Add to access token. Problem is that if both tokens are present in the response, the OAuth2 library takes the first one. In your case it is _accesstoken that doesn't contain roles. From oauth2 gem:
TOKEN_KEYS_STR = %w[access_token id_token token accessToken idToken].freeze
TOKEN_KEYS_SYM = %i[access_token id_token token accessToken idToken].freeze
TOKEN_KEY_LOOKUP = TOKEN_KEYS_STR + TOKEN_KEYS_SYM
supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
key = supported_keys[0]
So they simply takes the first available token and there is no option to change it. Can't you somehow set in your configuration to the response contains _idtoken only and not _accesstoken?
After checking your calls, I don't seem there is a way for me to intervene in the response.
Lets fix the calls that happen (from my live debugging)
GET "/oauth2callback?state=92hNDQ7yPNGPt%2F%2FD%2BLazs5xZx6g6tUn2Tbx66TJrlfM%3D&session_state=c72ab908-32b4-41a0-a774-f1145e0af2fd&iss=https%3A%2F%2Fkeycloak.medelexis.ch%2Frealms%2FMedelexis&code=2a630c94-47e8-44c0-b7fa-52bba6372e81.c72ab908-32b4-41a0-a774-f1145e0af2fd.b7d66e22-37d0-4915-b3cc-13b21c65f233" for 80.108.2.151 at 2024-09-12 07:50:13 +0000
1) Is an "Authentication Request" (see https://www.amazon.com/Keycloak-Management-Applications-protocols-applications/dp/1800562497 page 44) which creates an authorization code which is returned to the application in 2
2) The code
entry is the authorization code which the application uses to obtain the ID token and the refresh token.
3) This code you would use to connect to the token endpoint to get the actual token (I'm confused, because I don't see this request to https://keycloak.medelexis.ch/realms/Medelexis/protocol/openid-connect/token happen in the redmine log) which would look like 4)
4) see book right bottom
This token response is not meant to contain the respective info, but the id_token is (afaik the access token is only in keycloak a JWT, it is not required to be of any known kind to other oauth2 idps) - hence the content of it should be analyzed.
But I guess another approach would be to use the authorization code access token to query the userinfo endpoint.
I've added a new option into the plugin's settings. You can now chose a preferable token type. Please give it a try.
removed (content was clearly wrong)
I added the following line Rails.logger.debug { "Decoded token: #{user_info.to_json}" }
after https://github.com/kontron/redmine_oauth/blob/bc7e4e40c85680943b8761dae95f50cbd74f1ab7/app/controllers/redmine_oauth_controller.rb#L103 which now leads to the following ouput
-echo-1 | I, [2024-09-13T10:54:43.718583 #1] INFO -- : Current user: anonymous
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "id_token".
mis-echo-1 | D, [2024-09-13T10:54:43.763172 #1] DEBUG -- : Decoded token: {"exp":1726225183,"iat":1726224883,"auth_time":1726223264,"jti":"6ee9e5f5-fca1-4b2e-a882-7bd83102c1c7","iss":"https://keycloak.medelexis.ch/realms/Medelexis","aud":"mis","sub":"c64712b8-a25b-4368-82e0-a73d5fb7c944","typ":"ID","azp":"mis","sid":"81765aa5-87b8-4666-8c55-cf7f8cdf657e","at_hash":"piggBPy814R-XJvOE36EIg","acr":"0","email_verified":true,"name":"Test flawil","preferred_username":"tester","user_roles":["user"],"locale":"de","given_name":"Test","family_name":"flawil","email":"tester@medevit.at"}
mis-echo-1 | D, [2024-09-13T10:54:43.763218 #1] DEBUG -- : Setting.validate_user_roles = '"user_roles"'
mis-echo-1 | D, [2024-09-13T10:54:43.763241 #1] DEBUG -- : key: "user_roles"
mis-echo-1 | D, [2024-09-13T10:54:43.763285 #1] DEBUG -- : Key not found => access denied
mis-echo-1 | D, [2024-09-13T10:54:43.763312 #1] DEBUG -- : Roles:
mis-echo-1 | D, [2024-09-13T10:54:43.763325 #1] DEBUG -- : user role not found => access denied
mis-echo-1 | I, [2024-09-13T10:54:43.763338 #1] INFO -- : Authentication failed due to a missing role in the token
mis-echo-1 | W, [2024-09-13T10:54:43.763392 #1] WARN -- : Failed login for 'tester@medevit.at' from 80.108.2.151 at 2024-09-13 10:54:43 UTC
For easier analysis i formatted the decoded token:
{
"exp": 1726225183,
"iat": 1726224883,
"auth_time": 1726223264,
"jti": "6ee9e5f5-fca1-4b2e-a882-7bd83102c1c7",
"iss": "https://keycloak.medelexis.ch/realms/Medelexis",
"aud": "mis",
"sub": "c64712b8-a25b-4368-82e0-a73d5fb7c944",
"typ": "ID",
"azp": "mis",
"sid": "81765aa5-87b8-4666-8c55-cf7f8cdf657e",
"at_hash": "piggBPy814R-XJvOE36EIg",
"acr": "0",
"email_verified": true,
"name": "Test flawil",
"preferred_username": "tester",
"user_roles": [
"user"
],
"locale": "de",
"given_name": "Test",
"family_name": "flawil",
"email": "tester@medevit.at"
}
clearly the value is in there - what is wrong here??
Here seems to be a problem:
my environment: DEBUG -- : Setting.validate_user_roles = 'user_roles'
your environment: DEBUG -- : Setting.validate_user_roles = '"user_roles"'
Notice the double quotation marks around _userroles. Do you happen to enter user_roles with double quotation marks in your plugin's settings?
you are right, I removed the quotation marks, yet the problem persists:
is-echo-1 | I, [2024-09-13T11:52:36.748956 #1] INFO -- : Current user: anonymous
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "access_token".
mis-echo-1 | D, [2024-09-13T11:52:36.799044 #1] DEBUG -- : Setting.validate_user_roles = 'user_roles'
mis-echo-1 | D, [2024-09-13T11:52:36.799201 #1] DEBUG -- : key: user_roles
mis-echo-1 | D, [2024-09-13T11:52:36.799428 #1] DEBUG -- : Key not found => access denied
mis-echo-1 | D, [2024-09-13T11:52:36.799514 #1] DEBUG -- : Roles:
mis-echo-1 | D, [2024-09-13T11:52:36.799730 #1] DEBUG -- : user role not found => access denied
mis-echo-1 | I, [2024-09-13T11:52:36.799774 #1] INFO -- : Authentication failed due to a missing role in the token
I've added a debug message with _userinfo content. git pull roles branch and post your log again, please.
mis-echo-1 | OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (["access_token", "id_token"]); using "id_token".
mis-echo-1 | D, [2024-09-13T12:03:15.060896 #1] DEBUG -- : Setting.validate_user_roles = 'user_roles.'
mis-echo-1 | D, [2024-09-13T12:03:15.060943 #1] DEBUG -- : {"exp"=>1726229295, "iat"=>1726228995, "auth_time"=>1726227416, "jti"=>"b62a791b-5c6c-47cc-880b-7b2fa7e4204d", "iss"=>"https://keycloak.medelexis.ch/realms/Medelexis", "aud"=>"mis", "sub"=>"c64712b8-a25b-4368-82e0-a73d5fb7c944", "typ"=>"ID", "azp"=>"mis", "sid"=>"1f8783d5-a7db-4861-b37f-f65f72bc9bad", "at_hash"=>"vpCP80GKDS7qrmO95Rgr-g", "acr"=>"0", "email_verified"=>true, "name"=>"Test flawil", "preferred_username"=>"tester", "user_roles"=>["user"], "locale"=>"de", "given_name"=>"Test", "family_name"=>"flawil", "email"=>"tester@medevit.at", "login"=>"tester"}
mis-echo-1 | D, [2024-09-13T12:03:15.061015 #1] DEBUG -- : key: user_roles
mis-echo-1 | D, [2024-09-13T12:03:15.061047 #1] DEBUG -- : Key not found => access denied
mis-echo-1 | D, [2024-09-13T12:03:15.061064 #1] DEBUG -- : Roles:
mis-echo-1 | D, [2024-09-13T12:03:15.061076 #1] DEBUG -- : user role not found => access denied
mis-echo-1 | I, [2024-09-13T12:03:15.061103 #1] INFO -- : Authentication failed due to a missing role in the token
Once again, please.
This looks very good now @picman
It worked with the id_token
and the key user_roles
- but as this was an adaptation, i fixed the defaults of keycloak.
So it should directly work with keycloak if:
The keycloak instance creates a client and adds the following client roles:
Now the user
role has to be set for a user. But the roles are only transported by default with the access_token
,
if the scope roles
is either requested by the client (which is NOT the case for redmine_oauth
at the moment) or set to Default
in keycloak (in which case the info will be transmitted without requesting the scope)
The keycloak scope roles
will then automatically add the info to the access_token
(!NOT! the id_token) by the key resource_access.${client_id}.roles
which can be analysed as follows (my client is called mis)
I also tested dynamically assigning the administrator - and it worked as expected!
Thank you very much! I'm not sure about the requirement of selecting the token you patched in https://github.com/kontron/redmine_oauth/commit/fddd607753f1b8d4f05bba78f958d27423e5c735 - the default behaviour for keycloak should be to select the access_token
as described!
All right. I reverted the patch. Can you test it with _roles_withoutpatch branch, please?
The branch roles_without_patch
works like a charm, and the log info OAuth2::AccessToken.from_hash:
hashcontained more than one 'token' key (["access_token", "id_token"]); using "access_token".
is just correct for the Keycloak setting.
Thank you very much for your work - maybe we should somehow link the info in this issue to the documentation! 👍
Merged into devel.
Hi,
Tested also with Zitadel (devel branch), works well ! One thing, even if that's exactly what the text says below the field, but when an account has only the "admin" role without the "user" role, it denies the auth. Maybe "admin" role should be enough to log in ?
Thanks
It makes sense.
Do I get your propsal right @Aurel004 ? There is only user
for default access OR admin
for access with admin rights anything else will deny access? What if oauth2 despite that delivers roles [ user, admin ]
how should the plugin react?
I'd say: user => access granted user, admin => access + admin rights granted admin => access + admin rights granted
I'd say:
user => access granted
user, admin => access + admin rights granted
admin => access + admin rights granted
Exactly, and atm only ["user"] and ["user","admin"] give access
Optionally require a certain role or group membership to allow login. (e.g. the user must have the client role redmine-user to be allowed login in the system)