Browse Source

fix(updater): Run elevated task only if server tell us (#2357)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
david 4 years ago
parent
commit
c576119013
2 changed files with 127 additions and 37 deletions
  1. 6 0
      .changes/tauri-updater-windows.md
  2. 121 37
      core/tauri/src/updater/core.rs

+ 6 - 0
.changes/tauri-updater-windows.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch
+---
+
+- Do not run the updater with UAC task if server don't tell us. (Allow toggling server-side)
+- The updater expect a field named `with_elevated_task` with a `boolean` and will not run if the task is not installed first. (windows only)

+ 121 - 37
core/tauri/src/updater/core.rs

@@ -43,6 +43,9 @@ pub struct RemoteRelease {
   pub body: Option<String>,
   /// Optional signature for the current platform
   pub signature: Option<String>,
+  #[cfg(target_os = "windows")]
+  /// Optional: Windows only try to use elevated task
+  pub with_elevated_task: bool,
 }
 
 impl RemoteRelease {
@@ -70,10 +73,11 @@ impl RemoteRelease {
     };
 
     // pub_date is required default is: `N/A` if not provided by the remote JSON
-    let date = match release.get("pub_date") {
-      Some(pub_date) => pub_date.as_str().unwrap_or("N/A").to_string(),
-      None => "N/A".to_string(),
-    };
+    let date = release
+      .get("pub_date")
+      .and_then(|v| v.as_str())
+      .unwrap_or("N/A")
+      .to_string();
 
     // body is optional to build our update
     let body = release
@@ -86,6 +90,8 @@ impl RemoteRelease {
       .map(|signature| signature.as_str().unwrap_or("").to_string());
 
     let download_url;
+    #[cfg(target_os = "windows")]
+    let mut with_elevated_task = false;
 
     match release.get("platforms") {
       //
@@ -118,6 +124,13 @@ impl RemoteRelease {
               Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
             })?
             .to_string();
+          #[cfg(target_os = "windows")]
+          {
+            with_elevated_task = current_target_data
+              .get("with_elevated_task")
+              .and_then(|v| v.as_bool())
+              .unwrap_or_default();
+          }
         } else {
           // make sure we have an available platform from the static
           return Err(Error::RemoteMetadata("Platform not available".into()));
@@ -134,6 +147,13 @@ impl RemoteRelease {
             Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
           })?
           .to_string();
+        #[cfg(target_os = "windows")]
+        {
+          with_elevated_task = match release.get("with_elevated_task") {
+            Some(with_elevated_task) => with_elevated_task.as_bool().unwrap_or(false),
+            None => false,
+          };
+        }
       }
     }
     // Return our formatted release
@@ -143,6 +163,8 @@ impl RemoteRelease {
       download_url,
       body,
       signature,
+      #[cfg(target_os = "windows")]
+      with_elevated_task,
     })
   }
 }
@@ -343,6 +365,8 @@ impl<'a> UpdateBuilder<'a> {
       download_url: final_release.download_url,
       body: final_release.body,
       signature: final_release.signature,
+      #[cfg(target_os = "windows")]
+      with_elevated_task: final_release.with_elevated_task,
     })
   }
 }
@@ -371,6 +395,10 @@ pub struct Update {
   download_url: String,
   /// Signature announced
   signature: Option<String>,
+  #[cfg(target_os = "windows")]
+  /// Optional: Windows only try to use elevated task
+  /// Default to false
+  with_elevated_task: bool,
 }
 
 impl Update {
@@ -465,6 +493,9 @@ impl Update {
     // we copy the files depending of the operating system
     // we run the setup, appimage re-install or overwrite the
     // macos .app
+    #[cfg(target_os = "windows")]
+    copy_files_and_run(tmp_dir, extract_path, self.with_elevated_task)?;
+    #[cfg(not(target_os = "windows"))]
     copy_files_and_run(tmp_dir, extract_path)?;
     // We are done!
     Ok(())
@@ -525,7 +556,11 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, extract_path: PathBuf) -> Resu
 // Update server can provide a custom EXE (installer) who can run any task.
 #[cfg(target_os = "windows")]
 #[allow(clippy::unnecessary_wraps)]
-fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Result {
+fn copy_files_and_run(
+  tmp_dir: tempfile::TempDir,
+  _extract_path: PathBuf,
+  with_elevated_task: bool,
+) -> Result {
   use crate::api::file::Move;
 
   let paths = read_dir(&tmp_dir)?;
@@ -544,41 +579,42 @@ fn copy_files_and_run(tmp_dir: tempfile::TempDir, _extract_path: PathBuf) -> Res
 
       exit(0);
     } else if found_path.extension() == Some(OsStr::new("msi")) {
-      if let Some(bin_name) = std::env::current_exe()
-        .ok()
-        .and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
-        .and_then(|s| s.into_string().ok())
-      {
-        let product_name = bin_name.replace(".exe", "");
-
-        // Check if there is a task that enables the updater to skip the UAC prompt
-        let update_task_name = format!("Update {} - Skip UAC", product_name);
-        if let Ok(status) = Command::new("schtasks")
-          .arg("/QUERY")
-          .arg("/TN")
-          .arg(update_task_name.clone())
-          .status()
+      if with_elevated_task {
+        if let Some(bin_name) = std::env::current_exe()
+          .ok()
+          .and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
+          .and_then(|s| s.into_string().ok())
         {
-          if status.success() {
-            // Rename the MSI to the match file name the Skip UAC task is expecting it to be
-            let temp_msi = tmp_path.with_file_name(bin_name).with_extension("msi");
-            Move::from_source(&found_path)
-              .to_dest(&temp_msi)
-              .expect("Unable to move update MSI");
-            let exit_status = Command::new("schtasks")
-              .arg("/RUN")
-              .arg("/TN")
-              .arg(update_task_name)
-              .status()
-              .expect("failed to start updater task");
-
-            if exit_status.success() {
-              // Successfully launched task that skips the UAC prompt
-              exit(0);
+          let product_name = bin_name.replace(".exe", "");
+
+          // Check if there is a task that enables the updater to skip the UAC prompt
+          let update_task_name = format!("Update {} - Skip UAC", product_name);
+          if let Ok(status) = Command::new("schtasks")
+            .arg("/QUERY")
+            .arg("/TN")
+            .arg(update_task_name.clone())
+            .status()
+          {
+            if status.success() {
+              // Rename the MSI to the match file name the Skip UAC task is expecting it to be
+              let temp_msi = tmp_path.with_file_name(bin_name).with_extension("msi");
+              Move::from_source(&found_path)
+                .to_dest(&temp_msi)
+                .expect("Unable to move update MSI");
+              let exit_status = Command::new("schtasks")
+                .arg("/RUN")
+                .arg("/TN")
+                .arg(update_task_name)
+                .status()
+                .expect("failed to start updater task");
+
+              if exit_status.success() {
+                // Successfully launched task that skips the UAC prompt
+                exit(0);
+              }
             }
+            // Failed to run update task. Following UAC Path
           }
-
-          // Failed to run update task. Following UAC Path
         }
       }
 
@@ -834,6 +870,27 @@ mod test {
     )
   }
 
+  fn generate_sample_with_elevated_task_platform_json(
+    version: &str,
+    public_signature: &str,
+    download_url: &str,
+    with_elevated_task: bool,
+  ) -> String {
+    format!(
+      r#"
+        {{
+          "name": "v{}",
+          "notes": "This is the latest version! Once updated you shouldn't see this prompt.",
+          "pub_date": "2020-06-25T14:14:19Z",
+          "signature": "{}",
+          "url": "{}",
+          "with_elevated_task": "{}"
+        }}
+      "#,
+      version, public_signature, download_url, with_elevated_task
+    )
+  }
+
   fn generate_sample_bad_json() -> String {
     r#"{
       "version": "v0.0.3",
@@ -963,6 +1020,33 @@ mod test {
     assert!(updater.should_update);
   }
 
+  #[test]
+  fn simple_http_updater_with_elevated_task() {
+    let _m = mockito::mock("GET", "/win64/1.0.0")
+      .with_status(200)
+      .with_header("content-type", "application/json")
+      .with_body(generate_sample_with_elevated_task_platform_json(
+        "2.0.0",
+        "SampleTauriKey",
+        "https://tauri.studio",
+        true,
+      ))
+      .create();
+
+    let check_update = block!(builder()
+      .current_version("1.0.0")
+      .url(format!(
+        "{}/win64/{{{{current_version}}}}",
+        mockito::server_url()
+      ))
+      .build());
+
+    assert!(check_update.is_ok());
+    let updater = check_update.expect("Can't check update");
+
+    assert!(updater.should_update);
+  }
+
   #[test]
   fn http_updater_uptodate() {
     let _m = mockito::mock("GET", "/darwin/10.0.0")