-
Notifications
You must be signed in to change notification settings - Fork 143
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
Always challenge a user with no/bad credentials #359
Always challenge a user with no/bad credentials #359
Conversation
According to RFC 2617: > The 401 (Unauthorized) response message is used by an origin server to challenge the authorization of a user agent. This response MUST include a WWW-Authenticate header field containing at least one challenge applicable to the requested resource. We currently only issue a challenge when no credentials are provided - if a user fails authentication they are not challenged again. This broken implementation can cause some frustration for instance when using the web browser, which will cache a mis-typed user/password combination and not challenge the user again. This is somewhat problematic as we only use the `Authorization` header for Basic Authentication. It seems like a safe compromise for the user to be challenged to use Basic Authentication if authentication fails for whatever reason.
rescue MiqException::MiqEVMLoginError => e | ||
raise AuthenticationError, e.message | ||
end | ||
end or raise AuthenticationError |
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.
why the or
? that's generally a no-no
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 just a Perl-ism - || raise
isn't valid Ruby, so the alternative is to make this (perhaps) less readable by assigning to a variable that's only used to check that this is non-nil
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.
honestly, I find the variable more readable...or use tap (I do find tap less readable).
See also https://github.com/bbatsov/ruby-style-guide#no-and-or-or
||
is valid Ruby, so not sure why you think it's not...maybe you are using it without parenthesis?
[1] pry(main)> false || raise("blah")
RuntimeError: blah
from (pry):20:in `<main>'
[2] pry(main)> false || raise "blah"
SyntaxError: unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
false || raise "blah"
^
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.
Ah yes, I must have misinterpreted that error, but I find having to know that that raise
requires parentheses perhaps a bit arcane. I'll change to use the variable.
FWIW I prefer Avdi's take on the no or
/and
rule 😉
rescue MiqException::MiqEVMLoginError => e | ||
raise AuthenticationError, e.message | ||
end | ||
end or raise AuthenticationError |
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.
since the "guts" of the other legs of the if clause are methods, can we make this a method as well? Maybe authenticate_with_basic_auth
?
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.
Yeah I considered this, but then it's not clear to me at a glance how that method (or any similarly-named method following the scheme above) differs crucially from the one from Rails' Authentication library (authenticate_with_http_basic
) so I felt that might be better dealt with in a follow up?
rescue MiqException::MiqEVMLoginError => e | ||
raise AuthenticationError, e.message | ||
end | ||
end or raise AuthenticationError | ||
end | ||
log_api_auth |
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.
Strangely this only logged successful logins, but do we log unsuccessful logins anywhere?
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 have never logged an unsuccessful basic authentication login....it looks like we might lose some logging around the other cases. I'll try to rectify that
This also restores the existing logging that was present for failures in the other strategies.
rescue AuthenticationError => e | ||
api_log_error("AuthenticationError: #{e.message}") | ||
response.headers["Content-Type"] = "application/json" | ||
request_http_basic_authentication("Application", ErrorSerializer.new(:unauthorized, e).serialize.to_json) |
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're missing the logging of the 401,
before this PR, we get the following:
[----] I, [2018-04-16T17:48:16.502716 #26595:3fdeb52eb958] INFO -- : MIQ(Api::ApiController.log_request_initiated)
[----] I, [2018-04-16T17:48:16.502900 #26595:3fdeb52eb958] INFO -- : MIQ(Api::ApiController.log_request) API Request: {:requested_at=>"2018-04-16 21:48:16 UTC", :method=>"GET", :url=>"http://localhost:3000/api"}
[----] E, [2018-04-16T17:48:16.571327 #26595:3fdeb52eb958] ERROR -- : MIQ(Api::ApiController.api_error) API Error
[----] E, [2018-04-16T17:48:16.571381 #26595:3fdeb52eb958] ERROR -- : MIQ(Api::ApiController.api_error) MiqException::MiqEVMLoginError: Authentication failed
[----] I, [2018-04-16T17:48:16.571986 #26595:3fdeb52eb958] INFO -- : MIQ(Api::ApiController.log_request) Response: {:completed_at=>"2018-04-16 21:48:16 UTC", :size=>"0.108 KBytes", :time_taken=>"0.069 Seconds", :status=>401}
The last Response: entry is missing. This stuff is handled by api_error, so instead of adding more of that logic here, maybe the above can be as follows:
rescue AuthenticationError => e
api_error(:unauthorized, e)
request_http_basic_authentication("Application", ErrorSerializer.new(:unauthorized, e).serialize.to_json)
end
Or possibly get api_error to return to the response generated
rescue AuthenticationError => e
request_http_basic_authentication("Application", api_error(:unauthorized, e).to_json)
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.
Interesting. It looks like request_http_basic_authentication
is short-circuiting the after_action
that invokes this - it's not specific to api_error
. Will look into 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.
Addressed in 9fe4247
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 @imtayadeway for the update.
@@ -37,8 +37,6 @@ class BaseController < ActionController::API | |||
rescue_from(ActiveRecord::StatementInvalid) { |e| api_error(:bad_request, e) } | |||
rescue_from(JSON::ParserError) { |e| api_error(:bad_request, e) } | |||
rescue_from(MultiJson::LoadError) { |e| api_error(:bad_request, e) } | |||
rescue_from(MiqException::MiqEVMLoginError) { |e| api_error(:unauthorized, e) } | |||
rescue_from(AuthenticationError) { |e| api_error(:unauthorized, e) } |
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 AuthenticationError raised from within this file so that's ok to remove. I'm not sure about MiqEVMLoginError in the other 2 token based auth. Should be verified. If that error can be raised, we'd need to rescue MiqException::MiqEVMLoginError for those cases too.
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 this is OK because:
- I see tests that cover the happy/unhappy paths around authenticating with tokens
- System token authentication does a catch-all rescue, and that specific error inherits from
StandardError
- User token authentication doesn't do any actual logging-in as such, it just talks to the token manager
Checked commits imtayadeway/manageiq-api@4ebe167~...9fe4247 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0 |
LGTM!! Thanks @imtayadeway for fixing this. 👍 |
(summarizing gitter ☝️ April 24, 2018 3:24 PM) Turns out the change in Before, it would not prevent the user from logging in, but now that it is being run inside a Turns out logging in as a newly created user now fails with |
Looks like this is a problem...
There seems to be no way of disabling the popup on the browser side, so this may need to be undone. |
Login dialog popup problem addressed in #488 |
According to RFC 2617:
We currently only issue a challenge when no credentials are provided -
if a user fails authentication they are not challenged again. This
broken implementation can cause some frustration for instance when
using the web browser, which will cache a mis-typed user/password
combination and not challenge the user again.
This is somewhat problematic as we only use the
Authorization
headerfor Basic Authentication. It seems like a safe compromise for the user
to be challenged to use Basic Authentication if authentication fails
for whatever reason.