-
Notifications
You must be signed in to change notification settings - Fork 141
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
Deny standalone service template ordering when product setting is enabled #476
Deny standalone service template ordering when product setting is enabled #476
Conversation
bce5997
to
32b7f04
Compare
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 general, this feels like a model validation issue more than an api problem
@@ -17,6 +19,14 @@ def order_service_template(id, data, scheduled_time = nil) | |||
def service_template_ident(st) | |||
"Service Template id:#{st.id} name:'#{st.name}'" | |||
end | |||
|
|||
def api_request_allowed? | |||
standalone_api_request? ? !Settings.product.deny_api_service_ordering : true |
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 the Setting have a positive name?
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 only issue I have with changing it to a positive name is then the users who want it to work the way it works after this PR would be to set the value to "false", which feels weird but I don't know if we have a precedent for this or not.
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.
No, but I think the default should be in here anyway.
let(:product_settings) { double(:deny_api_service_ordering => true) } | ||
|
||
before do | ||
allow(Settings).to receive(:product).and_return(product_settings) |
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.
Prefer stub_settings_merge
end | ||
|
||
def standalone_api_request? | ||
request.authorization.present? && request.authorization.to_s.split(" ", 2).first.downcase == "basic" |
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.
request.authorization.try(:starts_with?, "basic")
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.
Also, this will break if we allow other authentication types to the api
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.
Agreed, but do you know of another way to distinguish standalone API calls versus API calls made from 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.
I can't think of anything at the moment
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.
maybe we can leverage the token manager namespace, api, ws, (might need to add param or separate api/ui as 2 namespaces).
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.
Also, I pulled the "basic" check from here: https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/actionpack/lib/action_controller/metal/http_authentication.rb#L104.
I suppose we could do .try(:downcase).try(:starts_with?, "basic")
? Not sure how else to handle the fact that it could be "Basic", "BASIC", or any other various capitalizations.
32b7f04
to
663ecc8
Compare
@bdunne I agree, but fixing the validation to take care of ensuring that the values passed in through the API versus actual acceptable values is much more involved because of dynamic dialogs and how we don't want to call into automate when we shouldn't have to (and the potential sequencing of those calls depending on which fields are dependent on which for refreshes). If the dialog has some expensive automate calls, submitting will take way longer than it should since for the majority of the cases the API call will have proper values already. A PR could build on this one so that instead of just flat out denying the request a product setting is no longer needed and it delegates to the model to verify that the values passed in are acceptable values. |
663ecc8
to
4218444
Compare
Not sure what labels to put on this now that we've transitioned into Hammer. @miq-bot add_label bug |
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 see where @bdunne is coming from and would agree in an ideal world, but if validating the incoming values properly means double automate calls then I think this is a better stop-gap solution.
Generally though, I would like to see something less fragile than checking the auth type to determine if the request came from our UI or not. Given that this bug is of medium priority and not a blocker, I think it would be good to do this for the first pass. Maybe we could implement a special header value to denote API calls originating in the UI and make this information available across the API for all requests. Thoughts @bdunne @abellotti ?
end | ||
|
||
def standalone_api_request? | ||
request.authorization.present? && request.authorization.try(:starts_with?, "basic") |
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're doing the .present?
check, why do we need the .try
? Do we expect request.authorization
not to respond to starts_with?
?
Also, is the premise here valid @abellotti @jvlcek? Does external auth not work with the API? Or am I misunderstanding this check?
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.
Good question about external auth, I'm not sure. I mentioned before when @bdunne suggested using the try
that I (originally) pulled this line from https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/actionpack/lib/action_controller/metal/http_authentication.rb#L104, and when I went to change to the try I forgot to take off the .present?
.
standalone_api_request? ? Settings.product.allow_api_service_ordering : true | ||
end | ||
|
||
def standalone_api_request? |
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 name this better? I don't understand what it means for an API request to be "standalone", maybe reverse the logic and call it request_from_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's fair, I was struggling to come up with a good name, but reversing the logic and naming it request_from_ui?
sounds better to me
@carbonin I agree, ultimately the auth type was the only thing I could think of to determine the difference between UI API calls and API calls originating from |
08779c9
to
f6c1828
Compare
I think it's best if we use/extend our UI assigned tokens for this. Since API calls coming in from the UI use their tokens, we already have that information (or easy to add) for differentiation. This way this will work for basic as well as ext-auth requests from the UI. |
09185ff
to
8e470f1
Compare
@abellotti Is this more what you had in mind? I originally started down this path but couldn't figure out how to authenticate it. Thanks for the push in the right direction with the |
@eclarizio Isn't this a validation problem, say if you had two drop down items in a service dialog When the caller sends in state = NY, fruit = banana, county = bergen this is invalid because Bergen is not a county in NY. When the user is in the UI we will never present Bergen as an option since we would have made the dynamic call If during validation of the dialog if we fetched the list of the dynamic components we could have rejected that invalid county. I think the validation is making an assumption that if the field is a drop down the user has selected a valid item so even in the static fruit list if someone passed in kiwi we would still let it thru. |
We could, you're right, but that (to me) is an unnecessary automate call, because we should have already done that when the user selected NY from the UI, which (correct me if you think I'm wrong here) will be a large majority of the time. Maybe in your example case, the automate call isn't very expensive, but if the dialog has multiple expensive automate calls, I don't think the trade off of always calling automate to validate the values is worth it compared to how often it would actually be useful since again, the majority of the order submit calls will be going through the UIs. Unless someone purposely mucks with data, it is impossible to actually send through values that don't exist. Even in the event people use the API to POST order submits, in theory they should be doing a GET request on the dialog first, then POST-ing to the refresh endpoint when their data changes so they can get the correct values on the dialog fields, and then finally POST-ing to the order submit endpoint. |
@eclarizio
The UI today mostly focuses on (1) but its easy to do (2) by doing some of the same things that were done in (1). That code is already there. It just needs to be used during the ordering phase. (2) cannot be done in Automate, we cannot have Automate load up a dialog and for each field call all the Automate Methods to create dynamic content, all of that has to be done from the dialog side since there is already code doing this validation. Granted "Validation" might take a long time but if there is an option that says "Strict Dialog Validation" and this can be done after the MiqRequest is created it would be ideal. We wont he holding the Request API call while we do the validation. If we dont do this validation all the REST API calls are going in without any validation and we run into the issues reported in this BZ. |
This pull request is not mergeable. Please rebase and repush. |
@eclarizio is this will be able to be backported, can you add the hammer/yes and gaprindashvili/yes labels |
@miq-bot add_label blocker |
be62161
to
3045610
Compare
def request_from_ui? | ||
return false if request.headers["x-auth-token"].blank? | ||
token_info.present? | ||
!token_info.empty? |
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.
Aren't these the same?
@@ -15,19 +17,24 @@ def order_service_template(id, data, scheduled_time = nil) | |||
|
|||
private | |||
|
|||
def service_template_ident(st) |
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 don't think this move should be in the diff
@@ -375,6 +375,13 @@ def sc_template_url(id, st_id = nil) | |||
request_headers["x-auth-token"] = test_token | |||
end | |||
|
|||
before do | |||
stub_settings_merge(:product => double(:allow_api_service_ordering => true)) |
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.
You shouldn't need a double here, it can be a nested hash
@@ -466,6 +466,15 @@ | |||
|
|||
describe "Service Templates order" do | |||
let(:service_template) { FactoryGirl.create(:service_template, :with_provision_resource_action_and_dialog, :orderable) } | |||
let(:product_settings) { double(:allow_api_service_ordering => allow_api_service_ordering) } |
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.
Same here
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.
Also, it doesn't feel like this should be in a let
3045610
to
417873f
Compare
@bdunne Thanks for the catches, was from the rebase and I was just concerned about fixing the conflicts, haha. |
@miq-bot add_label gaprindashvili/yes, hammer/yes |
417873f
to
364b585
Compare
364b585
to
86d0986
Compare
Checked commits eclarizio/manageiq-api@b46e4c0~...86d0986 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0 |
Deny standalone service template ordering when product setting is enabled (cherry picked from commit 7343ad7) https://bugzilla.redhat.com/show_bug.cgi?id=1632416
Hammer backport details:
|
Deny standalone service template ordering when product setting is enabled (cherry picked from commit 7343ad7) https://bugzilla.redhat.com/show_bug.cgi?id=1646435
Gaprindashvili backport details:
|
The issue this PR fixes is that currently, you can simply POST to the service_template order endpoint with whatever data you want (ideally, there is sort of a flow, i.e. you request the dialog, you may request any dialog field refreshes, and then you can POST to the service_template order endpoint with the data you've gathered from the previous requests). With any random data, it won't really work, but for example if a dropdown only contains the options "A" or "B", but the API request is submitted with "C", it is accepted.
This change, along with a product setting called
allow_api_service_ordering
, that the users must set tofalse
first (as it is defaulted totrue
via this PR), should ensure that only the API calls that come from the UIs will be accepted. Otherwise everything will behave as it does now. This is mostly just a quick fix solution until we can potentially implement RBAC for API users, or add code to the model to validate the values against expected values, but those options are both a lot more involved./cc @tinaafitz
https://bugzilla.redhat.com/show_bug.cgi?id=1632416