mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Prompt user for credentials when http request needs it (#34620)
* prompt user to get their credentials Signed-off-by: Lloyd Massiah artmis9@protonmail.com move credential prompt to a function Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * add prompt for step 15.4 Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * add new prompt definition for user credentials Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * remove default implementation for HttpState which allowed making the embedder_proxy non-optional - default implementation was only used in tests so created an alternative create_http_state function Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> add credentials to authentication cache Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * add tests that are successful for the happy path Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * add test for user cancels prompt and user inputs incorrect credentials, and refactor shared code between tests Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * handle error when setting username and password in Url and ran formatting Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> renaming test functions Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * change authentication flag to false for proxy authentication. The spec doesn't specify that the flag should be true, and the flag is by default false Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * clean up test code a bit Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * add skeleton implementation to support open harmony and android Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * update warning message to include Android Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * fix build error for OH os and Android Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> * remove unused import to fix warning Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> --------- Signed-off-by: Lloyd Massiah <artmis9@protonmail.com> Co-authored-by: lazypassion <25536767+lazypassion@users.noreply.github.com>
This commit is contained in:
parent
a9539d8b03
commit
aa40b8f820
8 changed files with 379 additions and 51 deletions
|
@ -16,6 +16,9 @@ use devtools_traits::{
|
||||||
ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest,
|
ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest,
|
||||||
HttpResponse as DevtoolsHttpResponse, NetworkEvent,
|
HttpResponse as DevtoolsHttpResponse, NetworkEvent,
|
||||||
};
|
};
|
||||||
|
use embedder_traits::{
|
||||||
|
EmbedderMsg, EmbedderProxy, PromptCredentialsInput, PromptDefinition, PromptOrigin,
|
||||||
|
};
|
||||||
use futures::{future, StreamExt, TryFutureExt, TryStreamExt};
|
use futures::{future, StreamExt, TryFutureExt, TryStreamExt};
|
||||||
use headers::authorization::Basic;
|
use headers::authorization::Basic;
|
||||||
use headers::{
|
use headers::{
|
||||||
|
@ -60,10 +63,7 @@ use tokio::sync::mpsc::{
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
|
|
||||||
use crate::async_runtime::HANDLE;
|
use crate::async_runtime::HANDLE;
|
||||||
use crate::connector::{
|
use crate::connector::{CertificateErrorOverrideManager, Connector};
|
||||||
create_http_client, create_tls_config, CACertificates, CertificateErrorOverrideManager,
|
|
||||||
Connector,
|
|
||||||
};
|
|
||||||
use crate::cookie::ServoCookie;
|
use crate::cookie::ServoCookie;
|
||||||
use crate::cookie_storage::CookieStorage;
|
use crate::cookie_storage::CookieStorage;
|
||||||
use crate::decoder::Decoder;
|
use crate::decoder::Decoder;
|
||||||
|
@ -72,7 +72,7 @@ use crate::fetch::headers::{SecFetchDest, SecFetchMode, SecFetchSite, SecFetchUs
|
||||||
use crate::fetch::methods::{main_fetch, Data, DoneChannel, FetchContext, Target};
|
use crate::fetch::methods::{main_fetch, Data, DoneChannel, FetchContext, Target};
|
||||||
use crate::hsts::HstsList;
|
use crate::hsts::HstsList;
|
||||||
use crate::http_cache::{CacheKey, HttpCache};
|
use crate::http_cache::{CacheKey, HttpCache};
|
||||||
use crate::resource_thread::AuthCache;
|
use crate::resource_thread::{AuthCache, AuthCacheEntry};
|
||||||
|
|
||||||
/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
|
/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
|
||||||
pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
|
pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
|
||||||
|
@ -103,26 +103,7 @@ pub struct HttpState {
|
||||||
pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>,
|
pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>,
|
||||||
pub client: Client<Connector, Body>,
|
pub client: Client<Connector, Body>,
|
||||||
pub override_manager: CertificateErrorOverrideManager,
|
pub override_manager: CertificateErrorOverrideManager,
|
||||||
}
|
pub embedder_proxy: Mutex<EmbedderProxy>,
|
||||||
|
|
||||||
impl Default for HttpState {
|
|
||||||
fn default() -> Self {
|
|
||||||
let override_manager = CertificateErrorOverrideManager::new();
|
|
||||||
Self {
|
|
||||||
hsts_list: RwLock::new(HstsList::default()),
|
|
||||||
cookie_jar: RwLock::new(CookieStorage::new(150)),
|
|
||||||
auth_cache: RwLock::new(AuthCache::default()),
|
|
||||||
history_states: RwLock::new(HashMap::new()),
|
|
||||||
http_cache: RwLock::new(HttpCache::default()),
|
|
||||||
http_cache_state: Mutex::new(HashMap::new()),
|
|
||||||
client: create_http_client(create_tls_config(
|
|
||||||
CACertificates::Default,
|
|
||||||
false, /* ignore_certificate_errors */
|
|
||||||
override_manager.clone(),
|
|
||||||
)),
|
|
||||||
override_manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step 13 of <https://fetch.spec.whatwg.org/#concept-fetch>.
|
/// Step 13 of <https://fetch.spec.whatwg.org/#concept-fetch>.
|
||||||
|
@ -1590,12 +1571,26 @@ async fn http_network_or_cache_fetch(
|
||||||
|
|
||||||
// Step 14.3 If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:
|
// Step 14.3 If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:
|
||||||
if !http_request.use_url_credentials || authentication_fetch_flag {
|
if !http_request.use_url_credentials || authentication_fetch_flag {
|
||||||
// TODO(#33616, #27439): Prompt the user for username and password from the window
|
let Some(credentials) = prompt_user_for_credentials(&context.state.embedder_proxy)
|
||||||
|
else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
let Some(username) = credentials.username else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
let Some(password) = credentials.password else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
// Wrong, but will have to do until we are able to prompt the user
|
if let Err(err) = http_request.current_url_mut().set_username(&username) {
|
||||||
// otherwise this creates an infinite loop
|
error!("error setting username for url: {:?}", err);
|
||||||
// We basically pretend that the user declined to enter credentials (#33616)
|
return response;
|
||||||
return response;
|
};
|
||||||
|
|
||||||
|
if let Err(err) = http_request.current_url_mut().set_password(Some(&password)) {
|
||||||
|
error!("error setting password for url: {:?}", err);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure this is set to None,
|
// Make sure this is set to None,
|
||||||
|
@ -1627,15 +1622,43 @@ async fn http_network_or_cache_fetch(
|
||||||
|
|
||||||
// TODO(#33616): Step 15.3 If fetchParams is canceled, then return
|
// TODO(#33616): Step 15.3 If fetchParams is canceled, then return
|
||||||
// the appropriate network error for fetchParams.
|
// the appropriate network error for fetchParams.
|
||||||
// TODO(#33616): Step 15.4 Prompt the end user as appropriate in request’s window and store the
|
|
||||||
// result as a proxy-authentication entry.
|
// Step 15.4 Prompt the end user as appropriate in request’s window
|
||||||
|
// window and store the result as a proxy-authentication entry.
|
||||||
|
let Some(credentials) = prompt_user_for_credentials(&context.state.embedder_proxy) else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
let Some(user_name) = credentials.username else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
let Some(password) = credentials.password else {
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
// store the credentials as a proxy-authentication entry.
|
||||||
|
let entry = AuthCacheEntry {
|
||||||
|
user_name,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let mut auth_cache = context.state.auth_cache.write().unwrap();
|
||||||
|
let key = http_request.current_url().origin().ascii_serialization();
|
||||||
|
auth_cache.entries.insert(key, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this is set to None,
|
||||||
|
// since we're about to start a new `http_network_or_cache_fetch`.
|
||||||
|
*done_chan = None;
|
||||||
|
|
||||||
// Step 15.5 Set response to the result of running HTTP-network-or-cache fetch given fetchParams.
|
// Step 15.5 Set response to the result of running HTTP-network-or-cache fetch given fetchParams.
|
||||||
|
response = http_network_or_cache_fetch(
|
||||||
// Wrong, but will have to do until we are able to prompt the user
|
http_request,
|
||||||
// otherwise this creates an infinite loop
|
false, /* authentication flag */
|
||||||
// We basically pretend that the user declined to enter credentials (#33616)
|
cors_flag,
|
||||||
return response;
|
done_chan,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(#33616): Step 16. If all of the following are true:
|
// TODO(#33616): Step 16. If all of the following are true:
|
||||||
|
@ -1737,6 +1760,29 @@ impl Drop for ResponseEndTimer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_user_for_credentials(
|
||||||
|
embedder_proxy: &Mutex<EmbedderProxy>,
|
||||||
|
) -> Option<PromptCredentialsInput> {
|
||||||
|
let proxy = embedder_proxy.lock().unwrap();
|
||||||
|
|
||||||
|
let (ipc_sender, ipc_receiver) = ipc::channel().unwrap();
|
||||||
|
|
||||||
|
proxy.send((
|
||||||
|
None,
|
||||||
|
EmbedderMsg::Prompt(
|
||||||
|
PromptDefinition::Credentials(ipc_sender),
|
||||||
|
PromptOrigin::Trusted,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
let Ok(credentials) = ipc_receiver.recv() else {
|
||||||
|
warn!("error getting user credentials");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(credentials)
|
||||||
|
}
|
||||||
|
|
||||||
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
|
/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
|
||||||
async fn http_network_fetch(
|
async fn http_network_fetch(
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
|
|
|
@ -132,7 +132,7 @@ pub fn new_core_resource_thread(
|
||||||
user_agent,
|
user_agent,
|
||||||
devtools_sender,
|
devtools_sender,
|
||||||
time_profiler_chan,
|
time_profiler_chan,
|
||||||
embedder_proxy,
|
embedder_proxy.clone(),
|
||||||
ca_certificates.clone(),
|
ca_certificates.clone(),
|
||||||
ignore_certificate_errors,
|
ignore_certificate_errors,
|
||||||
);
|
);
|
||||||
|
@ -151,6 +151,7 @@ pub fn new_core_resource_thread(
|
||||||
private_setup_port,
|
private_setup_port,
|
||||||
report_port,
|
report_port,
|
||||||
protocols,
|
protocols,
|
||||||
|
embedder_proxy,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
String::from("network-cache-reporter"),
|
String::from("network-cache-reporter"),
|
||||||
|
@ -173,6 +174,7 @@ fn create_http_states(
|
||||||
config_dir: Option<&Path>,
|
config_dir: Option<&Path>,
|
||||||
ca_certificates: CACertificates,
|
ca_certificates: CACertificates,
|
||||||
ignore_certificate_errors: bool,
|
ignore_certificate_errors: bool,
|
||||||
|
embedder_proxy: EmbedderProxy,
|
||||||
) -> (Arc<HttpState>, Arc<HttpState>) {
|
) -> (Arc<HttpState>, Arc<HttpState>) {
|
||||||
let mut hsts_list = HstsList::from_servo_preload();
|
let mut hsts_list = HstsList::from_servo_preload();
|
||||||
let mut auth_cache = AuthCache::default();
|
let mut auth_cache = AuthCache::default();
|
||||||
|
@ -198,6 +200,7 @@ fn create_http_states(
|
||||||
override_manager.clone(),
|
override_manager.clone(),
|
||||||
)),
|
)),
|
||||||
override_manager,
|
override_manager,
|
||||||
|
embedder_proxy: Mutex::new(embedder_proxy.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let override_manager = CertificateErrorOverrideManager::new();
|
let override_manager = CertificateErrorOverrideManager::new();
|
||||||
|
@ -214,6 +217,7 @@ fn create_http_states(
|
||||||
override_manager.clone(),
|
override_manager.clone(),
|
||||||
)),
|
)),
|
||||||
override_manager,
|
override_manager,
|
||||||
|
embedder_proxy: Mutex::new(embedder_proxy),
|
||||||
};
|
};
|
||||||
|
|
||||||
(Arc::new(http_state), Arc::new(private_http_state))
|
(Arc::new(http_state), Arc::new(private_http_state))
|
||||||
|
@ -227,11 +231,13 @@ impl ResourceChannelManager {
|
||||||
private_receiver: IpcReceiver<CoreResourceMsg>,
|
private_receiver: IpcReceiver<CoreResourceMsg>,
|
||||||
memory_reporter: IpcReceiver<ReportsChan>,
|
memory_reporter: IpcReceiver<ReportsChan>,
|
||||||
protocols: Arc<ProtocolRegistry>,
|
protocols: Arc<ProtocolRegistry>,
|
||||||
|
embedder_proxy: EmbedderProxy,
|
||||||
) {
|
) {
|
||||||
let (public_http_state, private_http_state) = create_http_states(
|
let (public_http_state, private_http_state) = create_http_states(
|
||||||
self.config_dir.as_deref(),
|
self.config_dir.as_deref(),
|
||||||
self.ca_certificates.clone(),
|
self.ca_certificates.clone(),
|
||||||
self.ignore_certificate_errors,
|
self.ignore_certificate_errors,
|
||||||
|
embedder_proxy,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut rx_set = IpcReceiverSet::new().unwrap();
|
let mut rx_set = IpcReceiverSet::new().unwrap();
|
||||||
|
|
|
@ -29,7 +29,6 @@ use net::filemanager_thread::FileManager;
|
||||||
use net::hsts::HstsEntry;
|
use net::hsts::HstsEntry;
|
||||||
use net::protocols::ProtocolRegistry;
|
use net::protocols::ProtocolRegistry;
|
||||||
use net::resource_thread::CoreResourceThreadPool;
|
use net::resource_thread::CoreResourceThreadPool;
|
||||||
use net::test::HttpState;
|
|
||||||
use net_traits::filemanager_thread::FileTokenCheck;
|
use net_traits::filemanager_thread::FileTokenCheck;
|
||||||
use net_traits::http_status::HttpStatus;
|
use net_traits::http_status::HttpStatus;
|
||||||
use net_traits::request::{
|
use net_traits::request::{
|
||||||
|
@ -47,8 +46,8 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::http_loader::{expect_devtools_http_request, expect_devtools_http_response};
|
use crate::http_loader::{expect_devtools_http_request, expect_devtools_http_response};
|
||||||
use crate::{
|
use crate::{
|
||||||
create_embedder_proxy, fetch, fetch_with_context, fetch_with_cors_cache, make_server,
|
create_embedder_proxy, create_http_state, fetch, fetch_with_context, fetch_with_cors_cache,
|
||||||
make_ssl_server, new_fetch_context, DEFAULT_USER_AGENT,
|
make_server, make_ssl_server, new_fetch_context, DEFAULT_USER_AGENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO write a struct that impls Handler for storing test values
|
// TODO write a struct that impls Handler for storing test values
|
||||||
|
@ -669,7 +668,7 @@ fn test_fetch_with_hsts() {
|
||||||
let (server, url) = make_ssl_server(handler);
|
let (server, url) = make_ssl_server(handler);
|
||||||
|
|
||||||
let mut context = FetchContext {
|
let mut context = FetchContext {
|
||||||
state: Arc::new(HttpState::default()),
|
state: Arc::new(create_http_state(None)),
|
||||||
user_agent: DEFAULT_USER_AGENT.into(),
|
user_agent: DEFAULT_USER_AGENT.into(),
|
||||||
devtools_chan: None,
|
devtools_chan: None,
|
||||||
filemanager: Arc::new(Mutex::new(FileManager::new(
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
||||||
|
@ -724,7 +723,7 @@ fn test_load_adds_host_to_hsts_list_when_url_is_https() {
|
||||||
url.as_mut_url().set_scheme("https").unwrap();
|
url.as_mut_url().set_scheme("https").unwrap();
|
||||||
|
|
||||||
let mut context = FetchContext {
|
let mut context = FetchContext {
|
||||||
state: Arc::new(HttpState::default()),
|
state: Arc::new(create_http_state(None)),
|
||||||
user_agent: DEFAULT_USER_AGENT.into(),
|
user_agent: DEFAULT_USER_AGENT.into(),
|
||||||
devtools_chan: None,
|
devtools_chan: None,
|
||||||
filemanager: Arc::new(Mutex::new(FileManager::new(
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
||||||
|
@ -781,7 +780,7 @@ fn test_fetch_self_signed() {
|
||||||
url.as_mut_url().set_scheme("https").unwrap();
|
url.as_mut_url().set_scheme("https").unwrap();
|
||||||
|
|
||||||
let mut context = FetchContext {
|
let mut context = FetchContext {
|
||||||
state: Arc::new(HttpState::default()),
|
state: Arc::new(create_http_state(None)),
|
||||||
user_agent: DEFAULT_USER_AGENT.into(),
|
user_agent: DEFAULT_USER_AGENT.into(),
|
||||||
devtools_chan: None,
|
devtools_chan: None,
|
||||||
filemanager: Arc::new(Mutex::new(FileManager::new(
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -47,7 +46,10 @@ use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
use tokio_test::block_on;
|
use tokio_test::block_on;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{fetch, fetch_with_context, make_server, new_fetch_context};
|
use crate::{
|
||||||
|
create_embedder_proxy_and_receiver, fetch, fetch_with_context, make_server, new_fetch_context,
|
||||||
|
receive_credential_prompt_msgs,
|
||||||
|
};
|
||||||
|
|
||||||
fn mock_origin() -> ImmutableOrigin {
|
fn mock_origin() -> ImmutableOrigin {
|
||||||
ServoUrl::parse("http://servo.org").unwrap().origin()
|
ServoUrl::parse("http://servo.org").unwrap().origin()
|
||||||
|
@ -1479,3 +1481,180 @@ fn test_origin_serialization_compatability() {
|
||||||
|
|
||||||
ensure_serialiations_match("data:,dataurltexta");
|
ensure_serialiations_match("data:,dataurltexta");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_credentials_prompt_when_proxy_authentication_is_required() {
|
||||||
|
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||||
|
let expected = Authorization::basic("username", "test");
|
||||||
|
if let Some(credentials) = request.headers().typed_get::<Authorization<Basic>>() {
|
||||||
|
if credentials == expected {
|
||||||
|
*response.status_mut() = StatusCode::OK;
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::PROXY_AUTHENTICATION_REQUIRED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let mut request = RequestBuilder::new(url.clone(), Referrer::NoReferrer)
|
||||||
|
.method(Method::GET)
|
||||||
|
.body(None)
|
||||||
|
.destination(Destination::Document)
|
||||||
|
.origin(mock_origin())
|
||||||
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||||
|
.credentials_mode(CredentialsMode::Include)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let (embedder_proxy, embedder_receiver) = create_embedder_proxy_and_receiver();
|
||||||
|
let _ = receive_credential_prompt_msgs(
|
||||||
|
embedder_receiver,
|
||||||
|
Some("username".to_string()),
|
||||||
|
Some("test".to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut context = new_fetch_context(None, Some(embedder_proxy), None);
|
||||||
|
|
||||||
|
let response = fetch_with_context(&mut request, &mut context);
|
||||||
|
|
||||||
|
let _ = server.close();
|
||||||
|
|
||||||
|
assert!(response
|
||||||
|
.internal_response
|
||||||
|
.unwrap()
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prompt_credentials_when_client_receives_unauthorized_response() {
|
||||||
|
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||||
|
let expected = Authorization::basic("username", "test");
|
||||||
|
if let Some(credentials) = request.headers().typed_get::<Authorization<Basic>>() {
|
||||||
|
if credentials == expected {
|
||||||
|
*response.status_mut() = StatusCode::OK;
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let mut request = RequestBuilder::new(url.clone(), Referrer::NoReferrer)
|
||||||
|
.method(Method::GET)
|
||||||
|
.body(None)
|
||||||
|
.destination(Destination::Document)
|
||||||
|
.origin(mock_origin())
|
||||||
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||||
|
.credentials_mode(CredentialsMode::Include)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let (embedder_proxy, embedder_receiver) = create_embedder_proxy_and_receiver();
|
||||||
|
let _ = receive_credential_prompt_msgs(
|
||||||
|
embedder_receiver,
|
||||||
|
Some("username".to_string()),
|
||||||
|
Some("test".to_string()),
|
||||||
|
);
|
||||||
|
let mut context = new_fetch_context(None, Some(embedder_proxy), None);
|
||||||
|
|
||||||
|
let response = fetch_with_context(&mut request, &mut context);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
assert!(response
|
||||||
|
.internal_response
|
||||||
|
.unwrap()
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prompt_credentials_user_cancels_dialog_input() {
|
||||||
|
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||||
|
let expected = Authorization::basic("username", "test");
|
||||||
|
if let Some(credentials) = request.headers().typed_get::<Authorization<Basic>>() {
|
||||||
|
if credentials == expected {
|
||||||
|
*response.status_mut() = StatusCode::OK;
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let mut request = RequestBuilder::new(url.clone(), Referrer::NoReferrer)
|
||||||
|
.method(Method::GET)
|
||||||
|
.body(None)
|
||||||
|
.destination(Destination::Document)
|
||||||
|
.origin(mock_origin())
|
||||||
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||||
|
.credentials_mode(CredentialsMode::Include)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let (embedder_proxy, embedder_receiver) = create_embedder_proxy_and_receiver();
|
||||||
|
let _ = receive_credential_prompt_msgs(embedder_receiver, None, None);
|
||||||
|
let mut context = new_fetch_context(None, Some(embedder_proxy), None);
|
||||||
|
|
||||||
|
let response = fetch_with_context(&mut request, &mut context);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
assert!(response
|
||||||
|
.internal_response
|
||||||
|
.unwrap()
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.is_client_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prompt_credentials_user_input_incorrect_credentials() {
|
||||||
|
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||||
|
let expected = Authorization::basic("username", "test");
|
||||||
|
if let Some(credentials) = request.headers().typed_get::<Authorization<Basic>>() {
|
||||||
|
if credentials == expected {
|
||||||
|
*response.status_mut() = StatusCode::OK;
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*response.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let mut request = RequestBuilder::new(url.clone(), Referrer::NoReferrer)
|
||||||
|
.method(Method::GET)
|
||||||
|
.body(None)
|
||||||
|
.destination(Destination::Document)
|
||||||
|
.origin(mock_origin())
|
||||||
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||||
|
.credentials_mode(CredentialsMode::Include)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let (embedder_proxy, embedder_receiver) = create_embedder_proxy_and_receiver();
|
||||||
|
let _ = receive_credential_prompt_msgs(
|
||||||
|
embedder_receiver,
|
||||||
|
Some("test".to_string()),
|
||||||
|
Some("test".to_string()),
|
||||||
|
);
|
||||||
|
let mut context = new_fetch_context(None, Some(embedder_proxy), None);
|
||||||
|
|
||||||
|
let response = fetch_with_context(&mut request, &mut context);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
assert!(response
|
||||||
|
.internal_response
|
||||||
|
.unwrap()
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.is_client_error());
|
||||||
|
}
|
||||||
|
|
|
@ -19,21 +19,23 @@ mod resource_thread;
|
||||||
mod subresource_integrity;
|
mod subresource_integrity;
|
||||||
|
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
use std::net::TcpListener as StdTcpListener;
|
use std::net::TcpListener as StdTcpListener;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, LazyLock, Mutex, Weak};
|
use std::sync::{Arc, LazyLock, Mutex, RwLock, Weak};
|
||||||
|
|
||||||
use crossbeam_channel::{unbounded, Sender};
|
use crossbeam_channel::{unbounded, Sender};
|
||||||
use devtools_traits::DevtoolsControlMsg;
|
use devtools_traits::DevtoolsControlMsg;
|
||||||
use embedder_traits::{EmbedderProxy, EventLoopWaker};
|
use embedder_traits::{EmbedderProxy, EmbedderReceiver, EventLoopWaker};
|
||||||
use futures::future::ready;
|
use futures::future::ready;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use hyper::server::conn::Http;
|
use hyper::server::conn::Http;
|
||||||
use hyper::server::Server as HyperServer;
|
use hyper::server::Server as HyperServer;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::{Body, Request as HyperRequest, Response as HyperResponse};
|
use hyper::{Body, Request as HyperRequest, Response as HyperResponse};
|
||||||
|
use net::connector::{create_http_client, create_tls_config};
|
||||||
use net::fetch::cors_cache::CorsCache;
|
use net::fetch::cors_cache::CorsCache;
|
||||||
use net::fetch::methods::{self, CancellationListener, FetchContext};
|
use net::fetch::methods::{self, CancellationListener, FetchContext};
|
||||||
use net::filemanager_thread::FileManager;
|
use net::filemanager_thread::FileManager;
|
||||||
|
@ -95,6 +97,76 @@ fn create_embedder_proxy() -> EmbedderProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_embedder_proxy_and_receiver() -> (EmbedderProxy, EmbedderReceiver) {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
let event_loop_waker = || {
|
||||||
|
struct DummyEventLoopWaker {}
|
||||||
|
impl DummyEventLoopWaker {
|
||||||
|
fn new() -> DummyEventLoopWaker {
|
||||||
|
DummyEventLoopWaker {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl embedder_traits::EventLoopWaker for DummyEventLoopWaker {
|
||||||
|
fn wake(&self) {}
|
||||||
|
fn clone_box(&self) -> Box<dyn embedder_traits::EventLoopWaker> {
|
||||||
|
Box::new(DummyEventLoopWaker {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::new(DummyEventLoopWaker::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
let embedder_proxy = embedder_traits::EmbedderProxy {
|
||||||
|
sender: sender.clone(),
|
||||||
|
event_loop_waker: event_loop_waker(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let embedder_receiver = EmbedderReceiver { receiver };
|
||||||
|
(embedder_proxy, embedder_receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_credential_prompt_msgs(
|
||||||
|
mut embedder_receiver: EmbedderReceiver,
|
||||||
|
username: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> std::thread::JoinHandle<()> {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let (_browser_context_id, embedder_msg) = embedder_receiver.recv_embedder_msg();
|
||||||
|
match embedder_msg {
|
||||||
|
embedder_traits::EmbedderMsg::Prompt(prompt_definition, _prompt_origin) => {
|
||||||
|
match prompt_definition {
|
||||||
|
embedder_traits::PromptDefinition::Credentials(ipc_sender) => {
|
||||||
|
ipc_sender
|
||||||
|
.send(embedder_traits::PromptCredentialsInput { username, password })
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_http_state(fc: Option<EmbedderProxy>) -> HttpState {
|
||||||
|
let override_manager = net::connector::CertificateErrorOverrideManager::new();
|
||||||
|
HttpState {
|
||||||
|
hsts_list: RwLock::new(net::hsts::HstsList::default()),
|
||||||
|
cookie_jar: RwLock::new(net::cookie_storage::CookieStorage::new(150)),
|
||||||
|
auth_cache: RwLock::new(net::resource_thread::AuthCache::default()),
|
||||||
|
history_states: RwLock::new(HashMap::new()),
|
||||||
|
http_cache: RwLock::new(net::http_cache::HttpCache::default()),
|
||||||
|
http_cache_state: Mutex::new(HashMap::new()),
|
||||||
|
client: create_http_client(create_tls_config(
|
||||||
|
net::connector::CACertificates::Default,
|
||||||
|
false, /* ignore_certificate_errors */
|
||||||
|
override_manager.clone(),
|
||||||
|
)),
|
||||||
|
override_manager,
|
||||||
|
embedder_proxy: Mutex::new(fc.unwrap_or_else(|| create_embedder_proxy())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_fetch_context(
|
fn new_fetch_context(
|
||||||
dc: Option<Sender<DevtoolsControlMsg>>,
|
dc: Option<Sender<DevtoolsControlMsg>>,
|
||||||
fc: Option<EmbedderProxy>,
|
fc: Option<EmbedderProxy>,
|
||||||
|
@ -103,7 +175,7 @@ fn new_fetch_context(
|
||||||
let sender = fc.unwrap_or_else(|| create_embedder_proxy());
|
let sender = fc.unwrap_or_else(|| create_embedder_proxy());
|
||||||
|
|
||||||
FetchContext {
|
FetchContext {
|
||||||
state: Arc::new(HttpState::default()),
|
state: Arc::new(create_http_state(Some(sender.clone()))),
|
||||||
user_agent: DEFAULT_USER_AGENT.into(),
|
user_agent: DEFAULT_USER_AGENT.into(),
|
||||||
devtools_chan: dc.map(|dc| Arc::new(Mutex::new(dc))),
|
devtools_chan: dc.map(|dc| Arc::new(Mutex::new(dc))),
|
||||||
filemanager: Arc::new(Mutex::new(FileManager::new(
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
||||||
|
|
|
@ -131,6 +131,16 @@ pub enum PromptDefinition {
|
||||||
YesNo(String, IpcSender<PromptResult>),
|
YesNo(String, IpcSender<PromptResult>),
|
||||||
/// Ask the user to enter text.
|
/// Ask the user to enter text.
|
||||||
Input(String, String, IpcSender<Option<String>>),
|
Input(String, String, IpcSender<Option<String>>),
|
||||||
|
/// Ask user to enter their username and password
|
||||||
|
Credentials(IpcSender<PromptCredentialsInput>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PromptCredentialsInput {
|
||||||
|
/// Username for http request authentication
|
||||||
|
pub username: Option<String>,
|
||||||
|
/// Password for http request authentication
|
||||||
|
pub password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Serialize)]
|
#[derive(Deserialize, PartialEq, Serialize)]
|
||||||
|
|
|
@ -21,8 +21,8 @@ use servo::base::id::TopLevelBrowsingContextId as WebViewId;
|
||||||
use servo::compositing::windowing::{EmbedderEvent, WebRenderDebugOption};
|
use servo::compositing::windowing::{EmbedderEvent, WebRenderDebugOption};
|
||||||
use servo::embedder_traits::{
|
use servo::embedder_traits::{
|
||||||
CompositorEventVariant, ContextMenuResult, DualRumbleEffectParams, EmbedderMsg, FilterPattern,
|
CompositorEventVariant, ContextMenuResult, DualRumbleEffectParams, EmbedderMsg, FilterPattern,
|
||||||
GamepadHapticEffectType, PermissionPrompt, PermissionRequest, PromptDefinition, PromptOrigin,
|
GamepadHapticEffectType, PermissionPrompt, PermissionRequest, PromptCredentialsInput,
|
||||||
PromptResult,
|
PromptDefinition, PromptOrigin, PromptResult,
|
||||||
};
|
};
|
||||||
use servo::ipc_channel::ipc::IpcSender;
|
use servo::ipc_channel::ipc::IpcSender;
|
||||||
use servo::script_traits::{
|
use servo::script_traits::{
|
||||||
|
@ -697,6 +697,12 @@ where
|
||||||
PromptDefinition::Input(_message, default, sender) => {
|
PromptDefinition::Input(_message, default, sender) => {
|
||||||
sender.send(Some(default.to_owned()))
|
sender.send(Some(default.to_owned()))
|
||||||
},
|
},
|
||||||
|
PromptDefinition::Credentials(sender) => {
|
||||||
|
sender.send(PromptCredentialsInput {
|
||||||
|
username: None,
|
||||||
|
password: None,
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
|
@ -751,6 +757,12 @@ where
|
||||||
let result = tinyfiledialogs::input_box("", &message, &default);
|
let result = tinyfiledialogs::input_box("", &message, &default);
|
||||||
sender.send(result)
|
sender.send(result)
|
||||||
},
|
},
|
||||||
|
PromptDefinition::Credentials(sender) => {
|
||||||
|
// TODO: figure out how to make the message a localized string
|
||||||
|
let username = tinyfiledialogs::input_box("", "username", "");
|
||||||
|
let password = tinyfiledialogs::input_box("", "password", "");
|
||||||
|
sender.send(PromptCredentialsInput { username, password })
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join()
|
.join()
|
||||||
|
|
|
@ -512,6 +512,10 @@ impl ServoGlue {
|
||||||
PromptDefinition::Input(message, default, sender) => {
|
PromptDefinition::Input(message, default, sender) => {
|
||||||
sender.send(cb.prompt_input(message, default, trusted))
|
sender.send(cb.prompt_input(message, default, trusted))
|
||||||
},
|
},
|
||||||
|
PromptDefinition::Credentials(_) => {
|
||||||
|
warn!("implement credentials prompt for OpenHarmony OS and Android");
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
let reason = format!("Failed to send Prompt response: {}", e);
|
let reason = format!("Failed to send Prompt response: {}", e);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue