Add the requesting URL to the 401 prompt. Only show the 401 prompt when the WWW-Authenticate header is present.

This commit is contained in:
Josh Matthews 2016-04-29 14:36:50 -04:00
parent 224bcd7057
commit cb63ad6c87
2 changed files with 52 additions and 14 deletions

View file

@ -768,18 +768,18 @@ pub fn obtain_response<A>(request_factory: &HttpRequestFactory<R=A>,
} }
pub trait UIProvider { pub trait UIProvider {
fn input_username_and_password(&self) -> (Option<String>, Option<String>); fn input_username_and_password(&self, prompt: &str) -> (Option<String>, Option<String>);
} }
impl UIProvider for TFDProvider { impl UIProvider for TFDProvider {
#[cfg(any(target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "macos", target_os = "linux"))]
fn input_username_and_password(&self) -> (Option<String>, Option<String>) { fn input_username_and_password(&self, prompt: &str) -> (Option<String>, Option<String>) {
(tinyfiledialogs::input_box("Enter username", "Username:", ""), (tinyfiledialogs::input_box(prompt, "Username:", ""),
tinyfiledialogs::input_box("Enter password", "Password:", "")) tinyfiledialogs::input_box(prompt, "Password:", ""))
} }
#[cfg(not(any(target_os = "macos", target_os = "linux")))] #[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn input_username_and_password(&self) -> (Option<String>, Option<String>) { fn input_username_and_password(&self, _prompt: &str) -> (Option<String>, Option<String>) {
(None, None) (None, None)
} }
} }
@ -870,8 +870,10 @@ pub fn load<A, B>(load_data: &LoadData,
process_response_headers(&response, &doc_url, &http_state.cookie_jar, &http_state.hsts_list, &load_data); process_response_headers(&response, &doc_url, &http_state.cookie_jar, &http_state.hsts_list, &load_data);
//if response status is unauthorized then prompt user for username and password //if response status is unauthorized then prompt user for username and password
if response.status() == StatusCode::Unauthorized { if response.status() == StatusCode::Unauthorized &&
let (username_option, password_option) = ui_provider.input_username_and_password(); response.headers().get_raw("WWW-Authenticate").is_some() {
let (username_option, password_option) =
ui_provider.input_username_and_password(doc_url.as_str());
match username_option { match username_option {
Some(name) => { Some(name) => {

View file

@ -109,15 +109,15 @@ impl TestProvider {
} }
} }
impl UIProvider for TestProvider { impl UIProvider for TestProvider {
fn input_username_and_password(&self) -> (Option<String>, Option<String>) { fn input_username_and_password(&self, _prompt: &str) -> (Option<String>, Option<String>) {
(Some(self.username.to_owned()), (Some(self.username.to_owned()),
Some(self.password.to_owned())) Some(self.password.to_owned()))
} }
} }
fn basic_auth() -> MockResponse { fn basic_auth(headers: Headers) -> MockResponse {
MockResponse::new( MockResponse::new(
Headers::new(), headers,
StatusCode::Unauthorized, StatusCode::Unauthorized,
RawStatus(401, Cow::Borrowed("Unauthorized")), RawStatus(401, Cow::Borrowed("Unauthorized")),
b"".to_vec() b"".to_vec()
@ -140,7 +140,7 @@ enum ResponseType {
RedirectWithHeaders(String, Headers), RedirectWithHeaders(String, Headers),
Text(Vec<u8>), Text(Vec<u8>),
WithHeaders(Vec<u8>, Headers), WithHeaders(Vec<u8>, Headers),
NeedsAuth, NeedsAuth(Headers),
} }
struct MockRequest { struct MockRequest {
@ -167,8 +167,8 @@ fn response_for_request_type(t: ResponseType) -> Result<MockResponse, LoadError>
ResponseType::WithHeaders(b, h) => { ResponseType::WithHeaders(b, h) => {
Ok(respond_with_headers(b, h)) Ok(respond_with_headers(b, h))
}, },
ResponseType::NeedsAuth => { ResponseType::NeedsAuth(h) => {
Ok(basic_auth()) Ok(basic_auth(h))
} }
} }
} }
@ -208,7 +208,9 @@ impl HttpRequestFactory for AssertAuthHeaderRequestFactory {
assert_headers_included(&self.expected_headers, &headers); assert_headers_included(&self.expected_headers, &headers);
MockRequest::new(ResponseType::Text(self.body.clone())) MockRequest::new(ResponseType::Text(self.body.clone()))
} else { } else {
MockRequest::new(ResponseType::NeedsAuth) let mut headers = Headers::new();
headers.set_raw("WWW-Authenticate", vec![b"Basic realm=\"Test realm\"".to_vec()]);
MockRequest::new(ResponseType::NeedsAuth(headers))
}; };
Ok(request) Ok(request)
@ -1442,6 +1444,40 @@ fn test_auth_ui_sets_header_on_401() {
} }
} }
#[test]
fn test_auth_ui_needs_www_auth() {
let url = Url::parse("http://mozilla.com").unwrap();
let http_state = HttpState::new();
struct AuthProvider;
impl UIProvider for AuthProvider {
fn input_username_and_password(&self, _prompt: &str) -> (Option<String>, Option<String>) {
panic!("shouldn't be invoked")
}
}
struct Factory;
impl HttpRequestFactory for Factory {
type R = MockRequest;
fn create(&self, _: Url, _: Method, _: Headers) -> Result<MockRequest, LoadError> {
Ok(MockRequest::new(ResponseType::NeedsAuth(Headers::new())))
}
}
let load_data = LoadData::new(LoadContext::Browsing, url, None, None, None);
let response = load(&load_data, &AuthProvider, &http_state,
None, &Factory, DEFAULT_USER_AGENT.to_owned(),
&CancellationListener::new(None));
match response {
Err(e) => panic!("response contained error {:?}", e),
Ok(response) => {
assert_eq!(response.metadata.status, Some(RawStatus(401, Cow::Borrowed("Unauthorized"))));
}
}
}
fn assert_referer_header_matches(request_url: &str, fn assert_referer_header_matches(request_url: &str,
referrer_url: &str, referrer_url: &str,
referrer_policy: Option<ReferrerPolicy>, referrer_policy: Option<ReferrerPolicy>,