Fixes #25809 - JWT auth for external users#6549
Conversation
|
Issues: #25809 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
timogoebel
left a comment
There was a problem hiding this comment.
@rahulbajaj0509: I checked out the code. I still believe you should not hook into the SSO::JWT workflow as this is unrelated to what you want to achieve. It both uses JWTs, that's the only similarity.
You do need money to buy a tree or a space station. But both a tree and a space station are totally unrelated. The JWT is just the technique used to transfer payload. I'd suggest to create a separate SSO implementation for Open Id Connect and try to use a gem that offers a proper implementation.
| session[:user] = user.id | ||
| session[:api_authenticated_session] = true | ||
| if jwt_data[:sso_method] == "SSO::Jwt" | ||
| add_jwt_sesssion_values(jwt_data) |
There was a problem hiding this comment.
How about session.merge!(jwt_data)
There was a problem hiding this comment.
Doesn't this store data in the session that was read from the session a couple of lines before? 😕
There was a problem hiding this comment.
It should have, but since we have the reset_session on line no.79, it resets all the values and therefore I had to use this method to restore the value again.
| def set_activity_time | ||
| session[:expires_at] = Setting[:idle_timeout].minutes.from_now.to_i | ||
| unless session[:sso_method] == 'SSO::Jwt' | ||
| session[:expires_at] = Setting[:idle_timeout].minutes.from_now.to_i |
There was a problem hiding this comment.
Can we ask the SSO if it sets the expiry time instead of hardcoding stuff for JWT here?
def set_activity_time
return if available_sso.sets_expiry_time?
session[:expires_at] = Setting[:idle_timeout].minutes.from_now.to_i
endThere was a problem hiding this comment.
Yes, I think I can achieve this, thanks!
There was a problem hiding this comment.
@timogoebel I tried using available_sso.sets_expiry_time?.
It works fine when we first authenticate the user.
However, when we use sessions in subsequent calls the method fails.
That is if I run hammer auth login basic --username admin --password changeme it works correctly.
Immediately after when I run hammer os list (which will use session since we are already autheticated in the previous command), it fails because all the SSO methods (Basic, JWT, openIDConnect etc) return a false for {SSO::Method}.available?..
Hence we get available_sso value as nil when using session
There was a problem hiding this comment.
You should be able to use get_sso_method to get the SSO object stored in the session.
|
@timogoebel let me explain a bit about what I am trying to achieve here, let me know if I am thinking in the correct direction here. So at the hammer-cli-foreman side, I authenticate the user by keycloak using the OAuth out of bound implementation. In return to that, the Keycloak returns me with a JWT that has the authenticated users authorization information which I send to Foreman(here) and create a user in Foreman using that JWT. This is the flow that I am trying to achieve. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ares
left a comment
There was a problem hiding this comment.
@rahulbajaj0509 could you please point us to respective part that actually issues the JWT token? I'm a bit confused here about the whole flow. I suppose something creates the JWT which is added to requests to Foreman. If that's the case, we download the public key from some URL and decode the token (which does the token signature verification) and then read information out of that token.
|
|
||
| def generate_jwks_uri | ||
| response = RestClient::Request.execute( | ||
| :url => "https://ssoo1.usersys.redhat.com/auth/realms/hammer-cli/protocol/openid-connect/certs", |
There was a problem hiding this comment.
what is this url? it does not resolve, IIUC we download the keycloak public key from here and then use it for signature verification, should this URL be part of JWT token?
There was a problem hiding this comment.
I have added this to the settings, this URL is different for each user, so it may differ. To obtain the URL user can read the description of the field as seen here
| :url => "https://ssoo1.usersys.redhat.com/auth/realms/hammer-cli/protocol/openid-connect/certs", | ||
| :method => :get, | ||
| :headers => { content_type: 'application/x-www-form-urlencoded'}, | ||
| :verify_ssl => false |
There was a problem hiding this comment.
this is not a good idea, we need to trust the side that provides the public key, otherwise an attacker could give us wrong public key for verification
There was a problem hiding this comment.
@ares thanks, makes sense, I have change the setting to :verify_ssl to true.
| key.set_key(base64_to_long(modulus), base64_to_long(exponent), nil) | ||
| end | ||
|
|
||
| def base64_to_long(data) |
There was a problem hiding this comment.
was this code taken from some library? if not, can you describe what's going on in here, e.g. what format we're trying to build here, we don't use unpack('C*') every day :-)
There was a problem hiding this comment.
yes, it was but as @tbrisker mentioned, it is not good to handle the encryption by ourself, so I am using the JWT gem to do all this for us.
| end | ||
|
|
||
| def to_hex(int) | ||
| int < 16 ? '0' + int.to_s(16) : int.to_s(16) |
There was a problem hiding this comment.
1.to_s(16).rjust(2, '0') is more straightforward
| return nil if secret.blank? | ||
|
|
||
| payload = JWT.decode(token, secret.token) | ||
| if secret.blank? |
There was a problem hiding this comment.
is there a better way to detect what type of token this is? Can we get that information out of decoded_payload?
There was a problem hiding this comment.
We should never rely on information in decoded_payload as it hasn't been verified (the signature wasn't validated).
There was a problem hiding this comment.
We verify the signature later, but we need to decide what is the purpose of this token here. We can decode and if that's for SSO, we proceed with verification. If it's not and we just decode like we did before.
I don't like this too much, hence I'm asking for a better way of differentiation. Is it possible to use other header or that breaks jwt conventions? Can we somehow detect the signature is present, meaning this is SSO token? If we detect there's signature, then perform verification (which JWT.decode does for us if we pass public key I believe)
There was a problem hiding this comment.
We can use the Auth-Header. The RFC states to use two parts:
TYPE SECRET. The type can be an arbitrary string, e.g. Basic. I would suggest using Bearer for OIDC and JWT for JWT's issued by Foreman.
|
@ares thanks for reviewing the pull request :) We receive the token from the hammer-cli-foreman which can be seen here. Hammer uses the rest-client gem to authenticate the user from Keycloak and if the authentication is successful it receives a JWT token in return. Now we send this JWT here in Foreman and then create a user with external login. When we send the JWT token in Foreman, we need to validate it and make sure it the same token as we had sent from Hammer. There are two ways of validating a JWT token on the foreman side: Method 1: We need to verify a few claims and check the signature:
Method 2: we can also invoke the token introspection endpoint on Keycloak, but that's then a remote call to Keycloak to validate the token.
|
This comment has been minimized.
This comment has been minimized.
|
@tbrisker @timogoebel initially I thought it might be something inside jwt gem that loops/waits/sleeps for a long time under certain conditions but i was wrong. I made a blunder with a condition and failing of the tests justified it! Although now the tests are finally green 👍 |
tbrisker
left a comment
There was a problem hiding this comment.
Do you have any idea when a new version of JWT will be released with the fix? iiuc right now this doesn't work on ruby 2.3, i'd be fine with that as an initial step but if we won't be able to fix that in the near future we'll need to have some other way of handling it (such as carrying a patched gem or using the github version)
| end | ||
|
|
||
| def valid_jwt? | ||
| return false if Setting['oidc_algorithm'].nil? || !OpenSSL::PKey::RSA.new.respond_to?(:set_key) |
There was a problem hiding this comment.
Does this mean jwt auth won't work with ruby 2.3, or at least until jwt releases a new version? should the condition be more explicit so we know when we can remove it?
There was a problem hiding this comment.
Yes, I have done the fix here: jwt/ruby-jwt#333. The gem releases every 3 months but this time they have identified few issues with the gem therefore they might do the release in few weeks.
Also, I have an eye on the gem as I am working on few issues of the gem. Once, we have the new version of the gem, then I will update this condition.
What do you think?
There was a problem hiding this comment.
@tbrisker to be safe I have added a comment that addresses our concern. Does that make sense?
There was a problem hiding this comment.
I would prefer to raise or at least log an error instead of failing silently in this case - otherwise users have no way of telling why this failed.
| test 'if valid jwk json is passed' do | ||
| stub_request(:get, Setting['oidc_jwks_url']) | ||
| .to_return(body: {"keys": [@jwk.export]}.to_json) | ||
| if OpenSSL::PKey::RSA.new.respond_to?(:set_key) |
There was a problem hiding this comment.
IIUC this essentially makes all of these tests do nothing other than setup. Why not use skip instead? or wrap the whole context in a condition?
| # OpenSSL#set_key method for all ruby version. We must remove this condition once new version | ||
| # of the JWT(2.2.2) is released. | ||
| unless OpenSSL::PKey::RSA.new.respond_to?(:set_key) | ||
| Foreman::Logging.logger('notifications').error "SSO feature is not available for Ruby < 2.4.0" |
There was a problem hiding this comment.
Added error logging in case of Ruby < 2.4.0. Here is an example log:
2019-10-04T15:41:12 [E|not|267ad15b] SSO feature is not available for Ruby < 2.4.0
2019-10-04T15:41:12 [E|app|267ad15b] JWT SSO: Failed to decode JWT.
2019-10-04T15:41:12 [W|app|267ad15b] SSO failed
| SSO::OpenidConnect.new api_controller({:authorization => "Bearer #{token}"}) | ||
| end | ||
|
|
||
| test "It returns nil for Ruby < 2.4.0" do |
There was a problem hiding this comment.
Added a test for checking failure of authentication in case of Ruby < 2.4.0
There was a problem hiding this comment.
This should test that the authentication fails and logs the correct message but only run on 2.3 (i.e. skip if RUBY_VERSION >= '2.4') instead of stubbing the method
ekohl
left a comment
There was a problem hiding this comment.
The use of jwt_token is redundant, right? JSON Web Token Token.
| :method => :get, | ||
| :verify_ssl => true | ||
| ) | ||
| jwks_keys = JSON.parse(response)['keys'] |
There was a problem hiding this comment.
JSON.parse(response) may not be a hash in which case it can fail. I don't think you're catching that exception.
There was a problem hiding this comment.
Since you asked the question on IRC:
For this comment, when can a JSON not be a key-value pair?
Never assume content that comes from the network is in the shape you want. It's always good to think about failure cases. The user might have configured the URL incorrect and it's actually pointing to a different service that does return JSON for example.
| class OidcJwtToken < JwtToken | ||
| def decode | ||
| return if token.blank? | ||
| raise JWT::DecodeError unless OidcJwtTokenValidate.new(token).valid_jwt? |
There was a problem hiding this comment.
In valid_jwt? you catch the JWT::DecodeError and return false. Here you raise another. That feels a bit odd to me. You're also decoding twice: once in the validate and if it's present. Then you discard that. Am I missing something or should these be combined?
| # OpenSSL#set_key method does not support ruby version < 2.4.0, apparently the JWT gem uses | ||
| # OpenSSL#set_key method for all ruby version. We must remove this condition once new version | ||
| # of the JWT(2.2.2) is released. | ||
| unless OpenSSL::PKey::RSA.new.respond_to?(:set_key) |
There was a problem hiding this comment.
Perhaps this should be in a supported? helper. I'd even consider raising a NotImplementedError
| class OidcJwtTokenValidateTest < ActiveSupport::TestCase | ||
| context '#valid_jwt?' do | ||
| def setup | ||
| skip "SSO feature is not available for Ruby < 2.4.0" unless |
There was a problem hiding this comment.
This is the same code but it can easily hide problems. I'd actually use RUBY_VERSION here so you know it's tested on newer rubies.
| def setup | ||
| skip "SSO feature is not available for Ruby < 2.4.0" unless | ||
| OpenSSL::PKey::RSA.new.respond_to?(:set_key) | ||
| @jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)) |
There was a problem hiding this comment.
Generating private keys is slow, especially when entropy is low (common on VMs). This can easily blow up the test runtime. I'd consider a fixture.
|
@ekohl i have worked on some of your comments(naming and #6549 (comment)) and will work on the others shortly. |
| # OpenSSL#set_key method for all ruby version. We must remove this condition once new version | ||
| # of the JWT(2.2.2) is released. | ||
| unless OpenSSL::PKey::RSA.new.respond_to?(:set_key) | ||
| Foreman::Logging.logger('notifications').error "SSO feature is not available for Ruby < 2.4.0" |
There was a problem hiding this comment.
This shouldn't be in the notifications logger, all auth related messages are logged in the app logger.
| def decode | ||
| return if token.blank? | ||
| raise JWT::DecodeError unless OidcJwtValidate.new(token).valid_jwt? | ||
| payload = JWT.decode(token, nil, false) |
There was a problem hiding this comment.
| payload = JWT.decode(token, nil, false) | |
| decoded_payload |
| Foreman::Logging.logger('notifications').error "SSO feature is not available for Ruby < 2.4.0" | ||
| return false | ||
| end | ||
| return false if Setting['oidc_algorithm'].nil? |
There was a problem hiding this comment.
Should we also check the audience, jwks and issuer settings are set? Also, would it make sense to log here that the authentication failed due to missing setting?
There was a problem hiding this comment.
@tbrisker:
Thanks for bringing this to notice! I double checked and found that if audience, jwks, issuer or algorithm are not set, its already caught as a JWT::DecodeError when we run JWT.decode(). Here are some sample logs
- When issuer is missing:
'Failed to decode JWT' error (JWT::InvalidIssuerError): Invalid issuer. Expected , received https://keycloak.journalctl.org/auth/realms/hammer-cli- When algorithm is missing:
'Failed to decode JWT' error (JWT::IncorrectAlgorithm): Expected a different algorithmI am hence, also removing this redundant check for algorithm from here.
| SSO::OpenidConnect.new api_controller({:authorization => "Bearer #{token}"}) | ||
| end | ||
|
|
||
| test "It returns nil for Ruby < 2.4.0" do |
There was a problem hiding this comment.
This should test that the authentication fails and logs the correct message but only run on 2.3 (i.e. skip if RUBY_VERSION >= '2.4') instead of stubbing the method
| context '#valid_jwt?' do | ||
| def setup | ||
| skip "SSO feature is not available for Ruby < 2.4.0" unless | ||
| OpenSSL::PKey::RSA.new.respond_to?(:set_key) |
There was a problem hiding this comment.
| OpenSSL::PKey::RSA.new.respond_to?(:set_key) | |
| RUBY_VERSION >= '2.4' |
|
|
||
| private | ||
|
|
||
| def jwks_loader |
There was a problem hiding this comment.
This method should accept an options parameter or an :invalidate keyword arg that is used to invalidate cache (and should cache the response otherwise to avoid repeated calls to the server) - see https://github.com/jwt/ruby-jwt#json-web-key-jwk and https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/jwk/key_finder.rb#L30
There was a problem hiding this comment.
Something like this:
def jwks_loader(options)
@cached_keys = nil if options[:invalidate]
return @cached_keys if @cached_keys.present?
response = RestClient::Request.execute(
:url => Setting['oidc_jwks_url'],
:method => :get,
:verify_ssl => true
)
json_response = JSON.parse(response)
if json_response.is_a?(Hash)
jwks_keys = json_response['keys']
@cached_keys = { keys: jwks_keys.map(&:symbolize_keys) }
else
raise JSON::ParserError.new('Invalid JWKS response')
end
rescue RestClient::Exception, SocketError, JSON::ParserError => e
Foreman::Logging.exception('Failed to load the JWKS', e)
@cached_keys = {}
end| ).first | ||
| rescue JWT::DecodeError => e | ||
| Foreman::Logging.exception('Failed to decode JWT', e) | ||
| return nil |
There was a problem hiding this comment.
Style/RedundantReturn: Redundant return detected.
| self.set('idle_timeout', N_("Log out idle users after a certain number of minutes"), 60, N_('Idle timeout')), | ||
| self.set('bcrypt_cost', N_("Cost value of bcrypt password hash function for internal auth-sources (4-30). Higher value is safer but verification is slower particularly for stateless API calls and UI logins. Password change needed to take effect."), 4, N_('BCrypt password cost')), | ||
| self.set('bmc_credentials_accessible', N_("Permits access to BMC interface passwords through ENC YAML output and in templates"), true, N_('BMC credentials access')), | ||
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. For example if you are using Keycloak this url(https://<your keycloak server>/auth/realms/<realm name>/protocol/openid-connect/certs) would be found as `jwk_uri` at https://<your keycloak server>/auth/realms/<realm name>/.well-known/openid-configuration."), nil, N_('OIDC JWKs URL')), |
There was a problem hiding this comment.
Isn't the whole idea behind .well-known that software can rely on it and humans never need to care? As a user I don't know which of the two I should enter. If only the well-known one should be specified, I'd write something like:
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. For example if you are using Keycloak this url(https://<your keycloak server>/auth/realms/<realm name>/protocol/openid-connect/certs) would be found as `jwk_uri` at https://<your keycloak server>/auth/realms/<realm name>/.well-known/openid-configuration."), nil, N_('OIDC JWKs URL')), | |
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. Typically https://keycloak.example.com/auth/realms/<realm name>/.well-known/openid-configuration."), nil, N_('OIDC JWKs URL')), |
| self.set('bmc_credentials_accessible', N_("Permits access to BMC interface passwords through ENC YAML output and in templates"), true, N_('BMC credentials access')), | ||
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. For example if you are using Keycloak this url(https://<your keycloak server>/auth/realms/<realm name>/protocol/openid-connect/certs) would be found as `jwk_uri` at https://<your keycloak server>/auth/realms/<realm name>/.well-known/openid-configuration."), nil, N_('OIDC JWKs URL')), | ||
| self.set('oidc_audience', N_("Name of the OpenID Connect Audience that is being used for Authentication. For example in case of Keycloak this is the Client ID."), nil, N_('OIDC Audience')), | ||
| self.set('oidc_issuer', N_("The iss(issuer) claim identifies the principal that issued the JWT, which exists at a `/.well-known/openid-configuration` in case of most of the IDP's."), nil, N_('OIDC Issuer')), |
There was a problem hiding this comment.
Is iss(issuer) a typo? Maybe IDP should also be written out since most users don't know what it stands for.
There was a problem hiding this comment.
iss(issuer) is a very term in the JWT world. It is one of the most important claims and this line is directly taken from the spec(https://tools.ietf.org/html/rfc7519#section-4.1.1). I would like to stick to it, if you don't mind?
There was a problem hiding this comment.
I would add a space in between like the RFC did.
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. For example if you are using Keycloak this url(https://<your keycloak server>/auth/realms/<realm name>/protocol/openid-connect/certs) would be found as `jwk_uri` at https://<your keycloak server>/auth/realms/<realm name>/.well-known/openid-configuration."), nil, N_('OIDC JWKs URL')), | ||
| self.set('oidc_audience', N_("Name of the OpenID Connect Audience that is being used for Authentication. For example in case of Keycloak this is the Client ID."), nil, N_('OIDC Audience')), | ||
| self.set('oidc_issuer', N_("The iss(issuer) claim identifies the principal that issued the JWT, which exists at a `/.well-known/openid-configuration` in case of most of the IDP's."), nil, N_('OIDC Issuer')), | ||
| self.set('oidc_algorithm', N_("The algorithm with which JWT was encoded in the IDP."), nil, N_('OIDC Algorithm')), |
There was a problem hiding this comment.
| self.set('oidc_algorithm', N_("The algorithm with which JWT was encoded in the IDP."), nil, N_('OIDC Algorithm')), | |
| self.set('oidc_algorithm', N_("The algorithm used to encode the JWT in the IDP."), nil, N_('OIDC Algorithm')), |
| if json_response.is_a?(Hash) | ||
| jwks_keys = json_response['keys'] | ||
| @cached_keys = nil if options[:invalidate] # need to reload the keys | ||
| @cached_keys ||= { keys: jwks_keys.map(&:symbolize_keys) } |
There was a problem hiding this comment.
If there are cached keys, does that mean you don't need to fetch a response in the first place and the method could return early?
There was a problem hiding this comment.
As @tbrisker explained here: #6549 (comment).
| context '#decoded_payload?' do | ||
| def setup | ||
| skip "SSO feature is not available for Ruby < 2.4.0" unless RUBY_VERSION >= '2.4' | ||
| @jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)) |
There was a problem hiding this comment.
Is setup ran for every test or only for the class? The test would be a lot faster if you could reuse the key.
| jwks_keys = json_response['keys'] | ||
| { keys: jwks_keys.map(&:symbolize_keys) } | ||
| else | ||
| raise JSON::ParserError.new('Invalid JWKS response') |
There was a problem hiding this comment.
instead of raising here you can log and return an empty hash (but still keep the rescue below in case JSON.parse fails)
| attribute88: | ||
| name: oidc_jwks_url | ||
| category: Setting::Auth | ||
| default: 127.0.0.1 |
There was a problem hiding this comment.
This default is very different from the description since it's not a URL. If there is no sane default, should it be empty?
| name: oidc_audience | ||
| category: Setting::Auth | ||
| default: 'rest-client' | ||
| description: 'Name of the OpenID Connect Audience that is being used for Authentication. For exmaple in case of Keycloak this is the Client ID.' |
There was a problem hiding this comment.
| description: 'Name of the OpenID Connect Audience that is being used for Authentication. For exmaple in case of Keycloak this is the Client ID.' | |
| description: 'Name of the OpenID Connect Audience that is being used for Authentication. For example in case of Keycloak this is the Client ID.' |
| name: oidc_jwks_url | ||
| category: Setting::Auth | ||
| default: 127.0.0.1 | ||
| description: 'OpenID Connect JSON Web Key Set(JWKS) URL. For example if you are using Keycloak this url(https://<your keycloak server>/auth/realms/<realm name>/protocol/openid-connect/certs) would be found as `jwk_uri` at https://<your keycloak server>/auth/realms/<realm name>/.well-known/openid-configuration.' |
There was a problem hiding this comment.
Should this be synced to the Ruby code?
timogoebel
left a comment
There was a problem hiding this comment.
Code looks good to me. I'm good with merging this.
@tbrisker: Feel free to merge at your own discretion. Note, we still need the packaging to be done.
ekohl
left a comment
There was a problem hiding this comment.
Overall 👍 besides some small textual nits
| self.set('idle_timeout', N_("Log out idle users after a certain number of minutes"), 60, N_('Idle timeout')), | ||
| self.set('bcrypt_cost', N_("Cost value of bcrypt password hash function for internal auth-sources (4-30). Higher value is safer but verification is slower particularly for stateless API calls and UI logins. Password change needed to take effect."), 4, N_('BCrypt password cost')), | ||
| self.set('bmc_credentials_accessible', N_("Permits access to BMC interface passwords through ENC YAML output and in templates"), true, N_('BMC credentials access')), | ||
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. Typically https://keycloak.example.com/auth/realms/<realm name>/protocol/openid-connect/certs if you are using Keycloak as an IDP"), nil, N_('OIDC JWKs URL')), |
There was a problem hiding this comment.
Nit: I think it's possible to avoid the personal pronoun which generally the preferred style. Maybe a native speaker has a better suggestion
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. Typically https://keycloak.example.com/auth/realms/<realm name>/protocol/openid-connect/certs if you are using Keycloak as an IDP"), nil, N_('OIDC JWKs URL')), | |
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. Typically https://keycloak.example.com/auth/realms/<realm name>/protocol/openid-connect/certs when using Keycloak as an IDP"), nil, N_('OIDC JWKs URL')), |
| self.set('bcrypt_cost', N_("Cost value of bcrypt password hash function for internal auth-sources (4-30). Higher value is safer but verification is slower particularly for stateless API calls and UI logins. Password change needed to take effect."), 4, N_('BCrypt password cost')), | ||
| self.set('bmc_credentials_accessible', N_("Permits access to BMC interface passwords through ENC YAML output and in templates"), true, N_('BMC credentials access')), | ||
| self.set('oidc_jwks_url', N_("OpenID Connect JSON Web Key Set(JWKS) URL. Typically https://keycloak.example.com/auth/realms/<realm name>/protocol/openid-connect/certs if you are using Keycloak as an IDP"), nil, N_('OIDC JWKs URL')), | ||
| self.set('oidc_audience', N_("Name of the OpenID Connect Audience that is being used for Authentication. For example in case of Keycloak this is the Client ID."), nil, N_('OIDC Audience')), |
There was a problem hiding this comment.
Nit: phrasing could be a bit more compact. For example and in case are redundant.
| self.set('oidc_audience', N_("Name of the OpenID Connect Audience that is being used for Authentication. For example in case of Keycloak this is the Client ID."), nil, N_('OIDC Audience')), | |
| self.set('oidc_audience', N_("Name of the OpenID Connect Audience used for Authentication. In case of Keycloak this is the Client ID."), nil, N_('OIDC Audience')), |
| assert_nil expected, actual | ||
| end | ||
|
|
||
| test 'if audiance is not valid' do |
There was a problem hiding this comment.
| test 'if audiance is not valid' do | |
| test 'if audience is not valid' do |
ekohl
left a comment
There was a problem hiding this comment.
👍 from a packaging perspective.
|
|
||
| private | ||
|
|
||
| def jwt_token |
There was a problem hiding this comment.
I'd still like to avoid this naming (https://en.wikipedia.org/wiki/RAS_syndrome) but I won't block on it.
There was a problem hiding this comment.
I understand but we're already using that in multiple places and it seems pretty common in general (e.g. https://www.google.com/search?q=%22jwt+token%22).
tbrisker
left a comment
There was a problem hiding this comment.
Thanks @rahulbajaj0509 and @timogoebel, @ekohl and everyone else who took part in reviewing!
|
Please add this in the headline features for 1.24 and where it makes sense in the manual (e.g. after https://theforeman.org/manuals/nightly/index.html#5.1.9UsingOAuth or https://theforeman.org/manuals/nightly/index.html#5.7ExternalAuthentication) |
|
Thanks a lot everyone! I think I made many mistakes and without your help I would have never learnt so many things. Few points I want to add:
|
|
Building a feature that touches the system at a deep level does expose you to a lot of details :) |
This pr enables Foreman to act as a consumer of the JWT token. It makes sure that an external user, authenticated via a third party application(in my case keycloak), can be created using the JWT token(without the need of a password).