Skip to content

Commit

Permalink
feat: Make on connect script toggle only + disable remote dash by def…
Browse files Browse the repository at this point in the history
…ault (#2621)
  • Loading branch information
The-personified-devil authored and zmerp committed Jan 15, 2025
1 parent 651273e commit 1fd80cf
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 23 deletions.
18 changes: 15 additions & 3 deletions alvr/dashboard/src/data_sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use std::{
thread::{self, JoinHandle},
time::{Duration, Instant},
};
use tungstenite::http::Uri;
use tungstenite::{
client::IntoClientRequest,
http::{HeaderValue, Uri},
};

const REQUEST_TIMEOUT: Duration = Duration::from_millis(200);

Expand Down Expand Up @@ -190,7 +193,11 @@ impl DataSources {
}
}
} else {
request_agent.get(&uri).send_json(&request).ok();
request_agent
.get(&uri)
.set("X-ALVR", "true")
.send_json(&request)
.ok();
}
}

Expand Down Expand Up @@ -224,7 +231,11 @@ impl DataSources {
continue;
};

let mut ws = if let Ok((ws, _)) = tungstenite::client(uri, socket) {
let mut req = uri.into_client_request().unwrap();
req.headers_mut()
.insert("X-ALVR", HeaderValue::from_str("true").unwrap());

let mut ws = if let Ok((ws, _)) = tungstenite::client(req, socket) {
ws
} else {
thread::sleep(Duration::from_millis(500));
Expand Down Expand Up @@ -281,6 +292,7 @@ impl DataSources {
loop {
let maybe_server_version = request_agent
.get(&uri)
.set("X-ALVR", "true")
.call()
.ok()
.and_then(|r| Version::from_str(&r.into_string().ok()?).ok());
Expand Down
4 changes: 4 additions & 0 deletions alvr/dashboard/src/data_sources_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ impl DataSources {
let context = self.context.clone();
wasm_bindgen_futures::spawn_local(async move {
Request::post("/api/dashboard-request")
.header("X-ALVR", "true")
.body(serde_json::to_string(&request).unwrap())
.send()
.await
Expand All @@ -33,6 +34,9 @@ impl DataSources {
pub fn poll_event(&mut self) -> Option<Event> {
if self.ws_receiver.is_none() {
let host = web_sys::window().unwrap().location().host().unwrap();
// TODO: Set X-ALVR
//let mut options = ewebsock::Options::default();
//options.additional_headers = vec!(("X-ALVR", "true"));
let Ok((_, receiver)) = ewebsock::connect(format!("ws://{host}/api/events")) else {
return None;
};
Expand Down
16 changes: 16 additions & 0 deletions alvr/filesystem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,22 @@ impl Layout {
}
}

pub fn connect_script(&self) -> PathBuf {
self.config_dir.join(if cfg!(windows) {
"on_connect.bat"
} else {
"on_connect.sh"
})
}

pub fn disconnect_script(&self) -> PathBuf {
self.config_dir.join(if cfg!(windows) {
"on_disconnect.bat"
} else {
"on_disconnect.sh"
})
}

pub fn crash_log(&self) -> PathBuf {
self.log_dir.join("crash_log.txt")
}
Expand Down
26 changes: 17 additions & 9 deletions alvr/server_core/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,10 +1361,12 @@ fn connection_pipeline(
});

{
let on_connect_script = initial_settings.connection.on_connect_script;

if !on_connect_script.is_empty() {
info!("Running on connect script (connect): {on_connect_script}");
if initial_settings.connection.enable_on_connect_script {
let on_connect_script = FILESYSTEM_LAYOUT.get().map(|l| l.connect_script()).unwrap();
info!(
"Running on connect script (connect): {}",
on_connect_script.display()
);
if let Err(e) = Command::new(&on_connect_script)
.env("ACTION", "connect")
.spawn()
Expand Down Expand Up @@ -1403,13 +1405,19 @@ fn connection_pipeline(
ClientListAction::SetConnectionState(ConnectionState::Disconnecting),
);

let on_disconnect_script = session_manager_lock
let enable_on_disconnect_script = session_manager_lock
.settings()
.connection
.on_disconnect_script
.clone();
if !on_disconnect_script.is_empty() {
info!("Running on disconnect script (disconnect): {on_disconnect_script}");
.enable_on_disconnect_script;
if enable_on_disconnect_script {
let on_disconnect_script = FILESYSTEM_LAYOUT
.get()
.map(|l| l.disconnect_script())
.unwrap();
info!(
"Running on disconnect script (disconnect): {}",
on_disconnect_script.display()
);
if let Err(e) = Command::new(&on_disconnect_script)
.env("ACTION", "disconnect")
.spawn()
Expand Down
98 changes: 95 additions & 3 deletions alvr/server_core/src/web_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ use alvr_events::{ButtonEvent, EventType};
use alvr_packets::{ButtonEntry, ClientListAction, ServerRequest};
use bytes::Buf;
use futures::SinkExt;
use headers::HeaderMapExt;
use headers::{
AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlRequestHeaders,
AccessControlRequestMethod, HeaderMapExt,
};
use hyper::{
header::{self, HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL},
service, Body, Request, Response, StatusCode,
header::{
self, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL, CONTENT_TYPE,
},
service, Body, Method, Request, Response, StatusCode,
};
use serde::de::DeserializeOwned;
use serde_json as json;
Expand Down Expand Up @@ -88,6 +93,93 @@ async fn http_api(
connection_context: &ConnectionContext,
request: Request<Body>,
) -> Result<Response<Body>> {
let allow_untrusted_http = SESSION_MANAGER
.read()
.session()
.session_settings
.connection
.allow_untrusted_http;

const X_ALVR: &str = "X-ALVR";

// A browser is asking for CORS info
if request.method() == Method::OPTIONS {
let bad_request: Response<Body> = Response::builder()
.status(StatusCode::FORBIDDEN)
.body("".into())?;

if !allow_untrusted_http {
return Ok(bad_request);
}

if let Some(requested_method) = request.headers().typed_get::<AccessControlRequestMethod>()
{
if requested_method != Method::GET.into() && requested_method != Method::POST.into() {
return Ok(bad_request);
}
} else {
return Ok(bad_request);
}

if let Some(requested_headers) =
request.headers().typed_get::<AccessControlRequestHeaders>()
{
let mut found_x_alvr = false;
for header in requested_headers.iter() {
if header == HeaderName::from_static(X_ALVR) {
found_x_alvr = true;
} else if header != CONTENT_TYPE {
return Ok(bad_request);
}
}

// Ensure it actually requested the X-ALVR header, because we don't want to allow it
// if it never got asked for
if !found_x_alvr {
return Ok(bad_request);
}
} else {
return Ok(bad_request);
}

let allowed_methods = [Method::GET, Method::POST, Method::OPTIONS]
.into_iter()
.collect::<AccessControlAllowMethods>();
let allowed_headers = [CONTENT_TYPE, HeaderName::from_static(X_ALVR)]
.into_iter()
.collect::<AccessControlAllowHeaders>();

let mut response: Response<Body> = Response::builder()
.status(StatusCode::OK)
.header(CACHE_CONTROL, "no-cache, no-store, must-revalidate")
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.body("".into())?;

let headers = response.headers_mut();
headers.typed_insert(allowed_methods);
headers.typed_insert(allowed_headers);

return Ok(response);
}

if request.method() != Method::POST && request.method() != Method::GET {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body("invalid method".into())?);
}

// This is the actual core part of cors
// We require the X-ALVR header, but the browser forces a cors preflight
// if the site tries to send a request with it set since it's not-whitelisted
//
// The dashboard can just set the header and be allowed through without the preflight
// thus not getting blocked by allow_untrusted_http being disabled
if request.headers().get(X_ALVR) != Some(&HeaderValue::from_static("true")) {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body("missing X-ALVR header".into())?);
}

let mut response = match request.uri().path() {
// New unified requests
"/api/dashboard-request" => {
Expand Down
42 changes: 34 additions & 8 deletions alvr/session/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ pub enum SocketProtocol {

#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
pub struct DiscoveryConfig {
#[cfg_attr(target_os = "linux", schema(flag = "hidden"))]
#[schema(strings(
help = "Allow untrusted clients to connect without confirmation. This is not recommended for security reasons."
))]
Expand Down Expand Up @@ -1149,16 +1150,40 @@ TCP: Slower than UDP, but more stable. Pick this if you experience video or audi
))]
pub wired_client_autolaunch: bool,

#[schema(strings(
help = "This script will be ran when the headset connects. Env var ACTION will be set to `connect`."
))]
pub on_connect_script: String,
#[cfg_attr(
windows,
schema(strings(
help = "If on_connect.bat exists alongside session.json, it will be run on headset connect. Env var ACTION will be set to `connect`."
))
)]
#[cfg_attr(
not(windows),
schema(strings(
help = "If on_connect.sh exists alongside session.json, it will be run on headset connect. Env var ACTION will be set to `connect`."
))
)]
pub enable_on_connect_script: bool,

#[cfg_attr(
windows,
schema(strings(
help = "If on_disconnect.bat exists alongside session.json, it will be run on headset disconnect. Env var ACTION will be set to `disconnect`."
))
)]
#[cfg_attr(
not(windows),
schema(strings(
help = "If on_disconnect.sh exists alongside session.json, it will be run on headset disconnect. Env var ACTION will be set to `disconnect`."
))
)]
#[schema(flag = "real-time")]
pub enable_on_disconnect_script: bool,

#[schema(strings(
help = "This script will be ran when the headset disconnects, or when SteamVR shuts down. Env var ACTION will be set to `disconnect`."
help = "Allow cross-origin browser requests to control ALVR settings remotely."
))]
#[schema(flag = "real-time")]
pub on_disconnect_script: String,
pub allow_untrusted_http: bool,

#[schema(strings(
help = r#"If the client, server or the network discarded one packet, discard packets until a IDR packet is found.
Expand Down Expand Up @@ -1834,8 +1859,9 @@ pub fn session_settings_default() -> SettingsDefault {
max_queued_server_video_frames: 1024,
avoid_video_glitching: false,
minimum_idr_interval_ms: 100,
on_connect_script: "".into(),
on_disconnect_script: "".into(),
enable_on_connect_script: false,
enable_on_disconnect_script: false,
allow_untrusted_http: false,
packet_size: 1400,
statistics_history_size: 256,
},
Expand Down

0 comments on commit 1fd80cf

Please sign in to comment.