-
Notifications
You must be signed in to change notification settings - Fork 993
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
Fixes #36972 - Make hammer accept unlimited as JWT life time #9951
Conversation
Can one of the admins verify this patch? |
2 similar comments
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
As a person of interest, I wanted to ask something. Right now the Is there an alternative way to generate the token for unlimited time using some numeric\interger inputs instead of using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @sayan3296 mentioned, going with :number
only would be probably better and safer way to go.
Plus we could (should?) unify the behavior with UI controller as well.
@@ -15,7 +15,7 @@ class RegistrationCommandsController < V2::BaseController | |||
param :smart_proxy_id, :number, desc: N_("ID of the Smart Proxy. This Proxy must have enabled both the 'Templates' and 'Registration' features") | |||
param :setup_insights, :bool, desc: N_("Set 'host_registration_insights' parameter for the host. If it is set to true, insights client will be installed and registered on Red Hat family operating systems") | |||
param :setup_remote_execution, :bool, desc: N_("Set 'host_registration_remote_execution' parameter for the host. If it is set to true, SSH keys will be installed on the host") | |||
param :jwt_expiration, :number, desc: N_("Expiration of the authorization token (in hours)") | |||
param :jwt_expiration, String, desc: N_("Expiration of the authorization token (in hours)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should allow both: number and string as well. But I think we should go only with :number
only, with following logic:
x < 0
raise an error, invalid valuex = 0
unlimited expirationx > 0
expiration in hours.
This should be also described in the documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot about the default value, if not set , default value should be 4 (same as in the UI)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense @stejskalleos , thank you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about leaving negative numbers as valid, but treat them as unlimited expiration
? I mean, 0
can be used for test purposes to simulate expired tokens without a need to actually wait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, perhaps
x < -1 raises an error, invalid value
x = 0 expired
x = -1 Unlimited token
x > 0 expiration in hours.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed with @ares on our 1:1, it's a good idea to verify with the QEs if they actually use 0 for testing purposes (from UI) , can double check for api as well, accordingly we can either use '-1' or '0' and incline UI in the similar manner. I will also verify the convention with the reporter and update the PR.
I get the following when testing this patch:
|
That's weird. It could be because the API is modified to accept strings as well. |
@@ -14,7 +14,7 @@ import LabelIcon from '../../../../../components/common/LabelIcon'; | |||
import { sprintf, translate as __ } from '../../../../../common/I18n'; | |||
|
|||
const TokenLifeTime = ({ value, onChange, handleInvalidField, isLoading }) => { | |||
const minValue = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the use-case for it? Why would the user want to create a registration command with an expired token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so too, I spoke with QAs, they do not use it for testing either. I think it would be safe to use '0' to represent 'unlimited'
@pablomh what is your opinion on using 0 for unlimited instead of -1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @stejskalleos and I think that we should avoid accepting an "expired token option" and settle on:
- x < 0 raises an error, invalid value
- x = 0 Unlimited token
- x > 0 expiration in hours
@sayan3296, opinions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I apologize for the late reply and that sounds fine to me as long as everyone else agrees.
@@ -33,11 +33,13 @@ def command_headers | |||
} | |||
|
|||
if registration_params['jwt_expiration'].present? | |||
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i if registration_params['jwt_expiration'] != 'unlimited' | |||
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i if registration_params['jwt_expiration'] != 'unlimited' && registration_params['jwt_expiration'].to_i != -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not readable much, maybe the case
statement here would be better.
Also why to have the condition for the unlimited
, that should be changed to -1
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value of jwt_args[:expiration]
must be nil when the token value is 'unlimited' (from the UI) or -1 from the api or hammer so this line is basically not assigning anything to jwt_args[:expiration]
when the either of them is the case.
We can also convert 'unlimited' (coming from the UI) to -1 and then send nil to jwt_args[:expiration]
when the case is -1 but that's just an extra step. Whatever works.
I agree with the case statements. I can try that
@@ -15,7 +15,7 @@ class RegistrationCommandsController < V2::BaseController | |||
param :smart_proxy_id, :number, desc: N_("ID of the Smart Proxy. This Proxy must have enabled both the 'Templates' and 'Registration' features") | |||
param :setup_insights, :bool, desc: N_("Set 'host_registration_insights' parameter for the host. If it is set to true, insights client will be installed and registered on Red Hat family operating systems") | |||
param :setup_remote_execution, :bool, desc: N_("Set 'host_registration_remote_execution' parameter for the host. If it is set to true, SSH keys will be installed on the host") | |||
param :jwt_expiration, :number, desc: N_("Expiration of the authorization token (in hours)") | |||
param :jwt_expiration, :number, desc: N_("Expiration of the authorization token (in hours), -1 means 'unlimited'.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we explicitly mention all the possibilities? -2..,-1,0, 1, max value(999999)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To align it with UI, its a good idea to set the max value to 999999.
Current patch works in my testing environment. |
if is_token_valid | ||
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i unless unlimited | ||
else | ||
raise ArgumentError, _("Error: Option --jwt-expiration: The value must be between 0 to 999999 hours. 0 means 'unlimited'.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nitpicks:
- The API has no option
--jwt-expiration
, that's a hammer thing. I think here we should talk about thejwt_expiration
parameter. - I think it's more correct to raise a
Foreman::Exception
like this:raise ::Foreman::Exception.new(N_("the text"))
(see https://projects.theforeman.org/projects/foreman/wiki/Translating#Exceptions) - Why 999999? This seems like an arbitrary number without any meaning? It translates to roughly 114 years which seems rather long. We either should have a "sensible" limit, like a year or two. Or no limit at all (my preference).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much @evgeni , I'll update the exception, Foreman exception is definitely more valid.
I used 999999 because that's the limit set from the UI validations I think when are conventionally accepting unlimited, then 999999 hours(which is still limited) might make sense. I could remove the limit all together but that would not incline with the UI.
@@ -33,7 +44,11 @@ def command_headers | |||
} | |||
|
|||
if registration_params['jwt_expiration'].present? | |||
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i if registration_params['jwt_expiration'] != 'unlimited' | |||
if expiration_valid? | |||
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i unless expiration_unlimited? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we extract the registration_params['jwt_expiration']
into a method, to make the jwt_expiration
constant appear once?
Something like this:
def jwt_expiration_param
# use require if the parameter is mandatory or permit if it's optional.
param = registration_params.require('jwt_expiration') || 4
@jwt_expiration_param ||= begin
if param == 'unlimited'
0
else
Integer(param)
end
end
Then the expiration_unlimited
would not have to handle with string values at all:
def def expiration_unlimited?
jwt_expiration_param == 0
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Shim. This was more readable.
A few points:
- permit method returns the value as a Parameter object. So I'm unable to use to_i or Integer on it
- I have used
param.to_i
instead ofInteger(param)
because it would break the code if we input a decimal value from the UI (which currently is converted to greatest integer with the to_i method). Also integer validation is automatically happening for hammer and api here with the number validator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, we do not enable Apipie validations in Foreman (for 11 years now: 5d78633 -- this is why the UI can pass unlimited
, even if the api says it's a number):
foreman/config/initializers/apipie.rb
Lines 13 to 14 in a1142ab
# TODO enable? | |
config.validate = false |
Some clients (e.g. FAM) still use the values in the APIDoc to enforce validation, but that's up to the clients.
if param == 'unlimited' | ||
0 | ||
else | ||
param.to_i |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation will not fail validation of strings other than unlimited
.
Since you have to accept decimal values, you can still have it validated:
param.to_i | |
Float(param).to_i |
This will accept 11.5
, but will fail for unknown words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two nits
@@ -27,15 +30,33 @@ def registration_url(proxy = nil) | |||
"#{url}/register" | |||
end | |||
|
|||
def jwt_expiration_param | |||
param = registration_params['jwt_expiration'] || 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have constants for MIN_VALUE
and MAX_VALUE
, shouldn't we have also a constant for the default value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MIN_VALUE
and MAX_VALUE
are used twice (in expiration_valid?
and in the error message)
We're using default value only once
if param == 'unlimited' | ||
0 | ||
else | ||
Float(param).to_i |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should catch an error when user sends invalid data directly to the API:
curl -X POST "http://localhost:3000/api/registration_commands" \
-H "Content-Type: application/json" \
--user admin:changeme \
-d '{"registration_command": {"jwt_expiration": "unlimiteded"}}'
=>
{
"error": {"message":"invalid value for Float(): \"unlimiteded\""}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more? I tested this and as far as I know , we are already catching the error. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well yes, but the error is kinda programmatic, not user-friendly.
Instead of invalid value for Float(): \"unlimiteded\"
we can raise Invalid value %s for jwt_expiration. The value must be between %s and %s. 0 means 'unlimited'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validation is done in the following order :
- Float() is to invalidate Strings except 'unlimited'
Invalid value %s for jwt_expiration. The value must be between 0 and 999999. 0 means 'unlimited'.
is basically to invalidate negative numbers
I think it makes sense to have 2 different errors for 2 different types of validations. I could however try to update it but that could mean changing the order(which is a lot of modification) or I could pass -1 as a value for strings, if it's absolutely required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably talk about two different things, but my main concern is the message itself.
As a user, I don't want to get an invalid value for Float()
error, I want to get easy to follow, descriptive error message, like The value must be between 0 and 999999. 0 means 'unlimited'
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
A single and easily understandable error message is always preferable when you consider the user-experience for our product.
The value must be between 0 and 999999. Here 0 represents 'unlimited'.
--> This sounds great to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @stejskalleos, @sayan3296 , I was unaware that we can set Float exceptions to false. Updated it :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the code, LGTM, just lacks the test coverage. Can you add the following tests:
- With expiration value below 0 (-1)
- With expiration value above max value
- Trying to generate command with nonsense string
With this test coverage, we should be safe.
Nice catch @stejskalleos , updated it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍏 LGTM
Works as expected, nice test coverage, let's get this in.
Thanks @girijaasoni and everyone involved!
@@ -27,17 +31,40 @@ def registration_url(proxy = nil) | |||
"#{url}/register" | |||
end | |||
|
|||
def invalid_expiration_error | |||
raise ::Foreman::Exception.new(N_("Invalid value '%s' for jwt_expiration. The value must be between %s and %s. 0 means 'unlimited'."), registration_params['jwt_expiration'], MIN_VALUE, MAX_VALUE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This broke translations:
MALFORMED: "Invalid value '%s' for jwt_expiration. The value must be between %s and %s. 0 "
I think this is because you can only use %s
once. More than that and it becomes ambiguous.
No description provided.