From 41e29caea865842f9039bc82793ce38120145e86 Mon Sep 17 00:00:00 2001 From: nab138 Date: Sun, 1 Feb 2026 01:01:54 -0500 Subject: [PATCH] Add refreshing anisette & Rethink ownership for GrandSlam and anisette to prepare for concurrency --- Cargo.lock | 1 + examples/minimal/src/main.rs | 2 +- isideload/Cargo.toml | 1 + isideload/src/anisette/data.rs | 147 ------------------------ isideload/src/anisette/mod.rs | 67 ++++++++++- isideload/src/anisette/remote_v3/mod.rs | 42 +++++-- isideload/src/auth/apple_account.rs | 80 +++++++------ isideload/src/auth/builder.rs | 26 +++-- isideload/src/auth/grandslam.rs | 92 ++++++++------- isideload/src/dev/app_groups.rs | 12 +- isideload/src/dev/app_ids.rs | 16 +-- isideload/src/dev/certificates.rs | 12 +- isideload/src/dev/developer_session.rs | 37 +++--- isideload/src/dev/devices.rs | 10 +- isideload/src/dev/teams.rs | 8 +- isideload/src/lib.rs | 3 + 16 files changed, 271 insertions(+), 285 deletions(-) delete mode 100644 isideload/src/anisette/data.rs diff --git a/Cargo.lock b/Cargo.lock index b72407b..d23a994 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,6 +824,7 @@ dependencies = [ "serde_json", "sha2", "thiserror 2.0.18", + "tokio", "tokio-tungstenite", "tracing", "uuid", diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index a68aeb3..03f1caa 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -49,7 +49,7 @@ async fn main() { let mut account = account.unwrap(); - let dev_session = DeveloperSession::from_account(&mut account) + let mut dev_session = DeveloperSession::from_account(&mut account) .await .expect("Failed to create developer session"); diff --git a/isideload/Cargo.toml b/isideload/Cargo.toml index 657d926..45726e5 100644 --- a/isideload/Cargo.toml +++ b/isideload/Cargo.toml @@ -38,3 +38,4 @@ hmac = "0.12.1" cbc = { version = "0.1.2", features = ["std"] } aes = "0.8.4" aes-gcm = "0.10.3" +tokio = "1.49.0" diff --git a/isideload/src/anisette/data.rs b/isideload/src/anisette/data.rs deleted file mode 100644 index fbd6f4f..0000000 --- a/isideload/src/anisette/data.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::{ - anisette::{AnisetteProvider, AnisetteProviderConfig, remote_v3::state::AnisetteState}, - auth::grandslam::GrandSlam, -}; -use plist::Dictionary; -use plist_macro::plist; -use reqwest::header::HeaderMap; -use rootcause::prelude::*; -use serde::Deserialize; -use std::{collections::HashMap, time::SystemTime}; - -#[derive(Deserialize, Debug, Clone)] -pub struct AnisetteClientInfo { - pub client_info: String, - pub user_agent: String, -} - -#[derive(Debug, Clone)] -pub struct AnisetteData { - pub routing_info: String, - pub machine_id: String, - pub one_time_password: String, - pub device_description: String, - pub device_unique_identifier: String, - pub local_user_id: String, - pub generated_at: SystemTime, -} - -// Some headers don't seem to be required. I guess not including them is technically more efficient soooo -impl AnisetteData { - pub fn get_headers(&self) -> HashMap { - //let dt: DateTime = Utc::now().round_subsecs(0); - - HashMap::from_iter( - [ - // ( - // "X-Apple-I-Client-Time".to_string(), - // dt.format("%+").to_string().replace("+00:00", "Z"), - // ), - // ("X-Apple-I-SRL-NO".to_string(), serial), - // ("X-Apple-I-TimeZone".to_string(), "UTC".to_string()), - // ("X-Apple-Locale".to_string(), "en_US".to_string()), - // ("X-Apple-I-MD-RINFO".to_string(), self.routing_info.clone()), - // ("X-Apple-I-MD-LU".to_string(), self.local_user_id.clone()), - ( - "X-Mme-Device-Id".to_string(), - self.device_unique_identifier.clone(), - ), - ("X-Apple-I-MD".to_string(), self.one_time_password.clone()), - ("X-Apple-I-MD-M".to_string(), self.machine_id.clone()), - // ( - // "X-Mme-Client-Info".to_string(), - // self.device_description.clone(), - // ), - ] - .into_iter(), - ) - } - - pub fn get_header_map(&self) -> HeaderMap { - let headers_map = self.get_headers(); - let mut header_map = HeaderMap::new(); - - for (key, value) in headers_map { - header_map.insert( - reqwest::header::HeaderName::from_bytes(key.as_bytes()).unwrap(), - reqwest::header::HeaderValue::from_str(&value).unwrap(), - ); - } - - header_map - } - - pub fn get_client_provided_data(&self) -> Dictionary { - let headers = self.get_headers(); - - let mut cpd = plist!(dict { - "bootstrap": "true", - "icscrec": "true", - "loc": "en_US", - "pbe": "false", - "prkgen": "true", - "svct": "iCloud" - }); - - for (key, value) in headers { - cpd.insert(key.to_string(), plist::Value::String(value)); - } - - cpd - } - - pub fn needs_refresh(&self) -> bool { - let elapsed = self.generated_at.elapsed().unwrap(); - elapsed.as_secs() > 60 - } -} - -pub struct RenewableAnisetteData { - config: AnisetteProviderConfig, - anisette_data: Option, - client_info: Option, - state: Option, -} - -impl RenewableAnisetteData { - pub fn new(config: AnisetteProviderConfig) -> Self { - RenewableAnisetteData { - config, - anisette_data: None, - client_info: None, - state: None, - } - } - - pub async fn get_anisette_data(&mut self, gs: &mut GrandSlam) -> Result<&AnisetteData, Report> { - if self - .anisette_data - .as_ref() - .map_or(true, |data| data.needs_refresh()) - { - if self.client_info.is_none() || self.state.is_none() { - let mut provider = self.config.get_provider(self.client_info.clone()); - let client_info = provider.get_client_info().await?; - self.client_info = Some(client_info); - let data = provider.get_anisette_data(gs).await?; - self.anisette_data = Some(data); - } else { - } - } - - Ok(self.anisette_data.as_ref().unwrap()) - } - - pub async fn get_client_info( - &mut self, - gs: &mut GrandSlam, - ) -> Result { - self.get_anisette_data(gs).await?; - - if let Some(client_info) = &self.client_info { - return Ok(client_info.clone()); - } else { - bail!("Anisette client info not available"); - } - } -} diff --git a/isideload/src/anisette/mod.rs b/isideload/src/anisette/mod.rs index 4514634..29c4bd0 100644 --- a/isideload/src/anisette/mod.rs +++ b/isideload/src/anisette/mod.rs @@ -6,7 +6,8 @@ use plist_macro::plist; use reqwest::header::HeaderMap; use rootcause::prelude::*; use serde::Deserialize; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc, time::SystemTime}; +use tokio::sync::RwLock; #[derive(Deserialize, Debug, Clone)] pub struct AnisetteClientInfo { @@ -22,6 +23,7 @@ pub struct AnisetteData { _device_description: String, device_unique_identifier: String, _local_user_id: String, + generated_at: SystemTime, } // Some headers don't seem to be required. I guess not including them is technically more efficient soooo @@ -87,11 +89,72 @@ impl AnisetteData { cpd } + + pub fn needs_refresh(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() > 60 + } } #[async_trait::async_trait] pub trait AnisetteProvider { - async fn get_anisette_data(&mut self, gs: &mut GrandSlam) -> Result; + async fn get_anisette_data(&self) -> Result; async fn get_client_info(&mut self) -> Result; + + async fn provision(&mut self, gs: Arc) -> Result<(), Report>; + + fn needs_provisioning(&self) -> Result; +} + +#[derive(Clone)] +pub struct AnisetteDataGenerator { + provider: Arc>, + data: Option>, +} + +impl AnisetteDataGenerator { + pub fn new(provider: Arc>) -> Self { + AnisetteDataGenerator { + provider, + data: None, + } + } + + pub async fn get_anisette_data( + &mut self, + gs: Arc, + ) -> Result, Report> { + if let Some(data) = &self.data { + if !data.needs_refresh() { + return Ok(data.clone()); + } + } + + // trying to avoid locking as write unless necessary to promote concurrency + let provider = self.provider.read().await; + + if provider.needs_provisioning()? { + drop(provider); + let mut provider_write = self.provider.write().await; + provider_write.provision(gs).await?; + drop(provider_write); + + let provider = self.provider.read().await; + let data = provider.get_anisette_data().await?; + let arc_data = Arc::new(data); + self.data = Some(arc_data.clone()); + Ok(arc_data) + } else { + let data = provider.get_anisette_data().await?; + let arc_data = Arc::new(data); + self.data = Some(arc_data.clone()); + Ok(arc_data) + } + } + + pub async fn get_client_info(&self) -> Result { + let mut provider = self.provider.write().await; + provider.get_client_info().await + } } diff --git a/isideload/src/anisette/remote_v3/mod.rs b/isideload/src/anisette/remote_v3/mod.rs index 8a39140..cee12a5 100644 --- a/isideload/src/anisette/remote_v3/mod.rs +++ b/isideload/src/anisette/remote_v3/mod.rs @@ -2,6 +2,8 @@ mod state; use std::fs; use std::path::PathBuf; +use std::sync::Arc; +use std::time::SystemTime; use base64::prelude::*; use plist_macro::plist; @@ -11,6 +13,7 @@ use serde::Deserialize; use tokio_tungstenite::tungstenite::Message; use tracing::{debug, info}; +use crate::SideloadError; use crate::anisette::remote_v3::state::AnisetteState; use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider}; use crate::auth::grandslam::GrandSlam; @@ -74,13 +77,19 @@ impl Default for RemoteV3AnisetteProvider { #[async_trait::async_trait] impl AnisetteProvider for RemoteV3AnisetteProvider { - async fn get_anisette_data(&mut self, gs: &mut GrandSlam) -> Result { - let state = self.get_state(gs).await?.clone(); + async fn get_anisette_data(&self) -> Result { + let state = self + .state + .as_ref() + .ok_or(SideloadError::AnisetteNotProvisioned)?; let adi_pb = state .adi_pb .as_ref() - .ok_or(report!("Anisette state is not provisioned"))?; - let client_info = self.get_client_info().await?.client_info.clone(); + .ok_or(SideloadError::AnisetteNotProvisioned)?; + let client_info = self + .client_info + .as_ref() + .ok_or(SideloadError::AnisetteNotProvisioned)?; let headers = self .client @@ -109,9 +118,10 @@ impl AnisetteProvider for RemoteV3AnisetteProvider { machine_id, one_time_password, routing_info, - _device_description: client_info, + _device_description: client_info.client_info.clone(), device_unique_identifier: state.get_device_id(), _local_user_id: hex::encode(&state.get_md_lu()), + generated_at: SystemTime::now(), }; Ok(data) @@ -140,10 +150,24 @@ impl AnisetteProvider for RemoteV3AnisetteProvider { Ok(self.client_info.as_ref().unwrap().clone()) } + + fn needs_provisioning(&self) -> Result { + if let Some(state) = &self.state { + Ok(!state.is_provisioned() || self.client_info.is_none()) + } else { + Ok(true) + } + } + + async fn provision(&mut self, gs: Arc) -> Result<(), Report> { + self.get_client_info().await?; + self.get_state(gs).await?; + Ok(()) + } } impl RemoteV3AnisetteProvider { - async fn get_state(&mut self, gs: &mut GrandSlam) -> Result<&mut AnisetteState, Report> { + async fn get_state(&mut self, gs: Arc) -> Result<&mut AnisetteState, Report> { let state_path = self.config_path.join("state.plist"); fs::create_dir_all(&self.config_path)?; if self.state.is_none() { @@ -195,13 +219,13 @@ impl RemoteV3AnisetteProvider { } async fn provision( state: &mut AnisetteState, - gs: &mut GrandSlam, + gs: Arc, url: &str, ) -> Result<(), Report> { debug!("Starting provisioning"); - let start_provisioning = gs.get_url("midStartProvisioning").await?; - let end_provisioning = gs.get_url("midFinishProvisioning").await?; + let start_provisioning = gs.get_url("midStartProvisioning")?; + let end_provisioning = gs.get_url("midFinishProvisioning")?; let websocket_url = format!("{}/v3/provisioning_session", url) .replace("https://", "wss://") diff --git a/isideload/src/auth/apple_account.rs b/isideload/src/auth/apple_account.rs index 5dffd84..d665543 100644 --- a/isideload/src/auth/apple_account.rs +++ b/isideload/src/auth/apple_account.rs @@ -1,7 +1,7 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use crate::{ - anisette::{AnisetteData, AnisetteProvider}, + anisette::{AnisetteData, AnisetteDataGenerator}, auth::{ builder::AppleAccountBuilder, grandslam::{GrandSlam, GrandSlamErrorChecker}, @@ -30,9 +30,8 @@ use tracing::{debug, info, warn}; pub struct AppleAccount { pub email: String, pub spd: Option, - pub anisette_provider: Arc>, - pub anisette_data: AnisetteData, - pub grandslam_client: GrandSlam, + pub anisette_generator: AnisetteDataGenerator, + pub grandslam_client: Arc, login_state: LoginState, debug: bool, } @@ -64,7 +63,7 @@ impl AppleAccount { /// - `debug`: DANGER, If true, accept invalid certificates and enable verbose connection pub async fn new( email: &str, - mut anisette_provider: Arc>, + anisette_generator: AnisetteDataGenerator, debug: bool, ) -> Result { info!("Initializing apple account"); @@ -72,26 +71,18 @@ impl AppleAccount { warn!("Debug mode enabled: this is a security risk!"); } - let client_info = anisette_provider - .lock() - .unwrap() + let client_info = anisette_generator .get_client_info() .await .context("Failed to get anisette client info")?; - let mut grandslam_client = GrandSlam::new(client_info, debug); - - let anisette_data = anisette_provider - .get_anisette_data(&mut grandslam_client) - .await - .context("Failed to get anisette data for login")?; + let grandslam_client = GrandSlam::new(client_info, debug).await?; Ok(AppleAccount { email: email.to_string(), spd: None, - anisette_provider, - anisette_data, - grandslam_client, + anisette_generator, + grandslam_client: Arc::new(grandslam_client), debug, login_state: LoginState::NeedsLogin, }) @@ -197,16 +188,22 @@ impl AppleAccount { two_factor_callback: impl Fn() -> Option, ) -> Result<(), Report> { debug!("Trusted device 2FA required"); + + let anisette_data = self + .anisette_generator + .get_anisette_data(self.grandslam_client.clone()) + .await + .context("Failed to get anisette data for 2FA")?; + let request_code_url = self .grandslam_client - .get_url("trustedDeviceSecondaryAuth") - .await?; + .get_url("trustedDeviceSecondaryAuth")?; - let submit_code_url = self.grandslam_client.get_url("validateCode").await?; + let submit_code_url = self.grandslam_client.get_url("validateCode")?; self.grandslam_client .get(&request_code_url)? - .headers(self.build_2fa_headers().await?) + .headers(self.build_2fa_headers(&anisette_data).await?) .send() .await .context("Failed to request trusted device 2fa")? @@ -221,7 +218,7 @@ impl AppleAccount { let res = self .grandslam_client .get(&submit_code_url)? - .headers(self.build_2fa_headers().await?) + .headers(self.build_2fa_headers(&anisette_data).await?) .header("security-code", code) .send() .await @@ -248,11 +245,17 @@ impl AppleAccount { ) -> Result<(), Report> { debug!("SMS 2FA required"); - let request_code_url = self.grandslam_client.get_url("secondaryAuth").await?; + let anisette_data = self + .anisette_generator + .get_anisette_data(self.grandslam_client.clone()) + .await + .context("Failed to get anisette data for 2FA")?; + + let request_code_url = self.grandslam_client.get_url("secondaryAuth")?; self.grandslam_client .get_sms(&request_code_url)? - .headers(self.build_2fa_headers().await?) + .headers(self.build_2fa_headers(&anisette_data).await?) .send() .await .context("Failed to request SMS 2FA")? @@ -274,7 +277,7 @@ impl AppleAccount { "mode": "sms" }); - let mut headers = self.build_2fa_headers().await?; + let mut headers = self.build_2fa_headers(&anisette_data).await?; headers.insert("Content-Type", HeaderValue::from_static("application/json")); headers.insert( "Accept", @@ -332,8 +335,8 @@ impl AppleAccount { Ok(()) } - async fn build_2fa_headers(&mut self) -> Result { - let mut headers = self.anisette_data.get_header_map(); + async fn build_2fa_headers(&self, anisette_data: &AnisetteData) -> Result { + let mut headers = anisette_data.get_header_map(); let spd = self .spd @@ -354,18 +357,23 @@ impl AppleAccount { ); headers.insert( "X-Apple-I-MD-RINFO", - reqwest::header::HeaderValue::from_str(&self.anisette_data.routing_info)?, + reqwest::header::HeaderValue::from_str(&anisette_data.routing_info)?, ); Ok(headers) } async fn login_inner(&mut self, password: &str) -> Result { - let gs_service_url = self.grandslam_client.get_url("gsService").await?; + let anisette_data = self + .anisette_generator + .get_anisette_data(self.grandslam_client.clone()) + .await + .context("Failed to get anisette data for login")?; + let gs_service_url = self.grandslam_client.get_url("gsService")?; debug!("GrandSlam service URL: {}", gs_service_url); - let cpd = self.anisette_data.get_client_provided_data(); + let cpd = anisette_data.get_client_provided_data(); let srp_client = SrpClient::::new(&G_2048); let a: Vec = (0..32).map(|_| rand::random::()).collect(); @@ -510,6 +518,12 @@ impl AppleAccount { format!("com.apple.gs.{}", app) }; + let anisette_data = self + .anisette_generator + .get_anisette_data(self.grandslam_client.clone()) + .await + .context("Failed to get anisette data for login")?; + let spd = self .spd .as_ref() @@ -531,8 +545,8 @@ impl AppleAccount { .into_bytes() .to_vec(); - let gs_service_url = self.grandslam_client.get_url("gsService").await?; - let cpd = self.anisette_data.get_client_provided_data(); + let gs_service_url = self.grandslam_client.get_url("gsService")?; + let cpd = anisette_data.get_client_provided_data(); let request = plist!(dict { "Header": { diff --git a/isideload/src/auth/builder.rs b/isideload/src/auth/builder.rs index 18bd03e..54fa29e 100644 --- a/isideload/src/auth/builder.rs +++ b/isideload/src/auth/builder.rs @@ -1,14 +1,17 @@ +use std::sync::Arc; + use rootcause::prelude::*; +use tokio::sync::RwLock; use crate::{ - anisette::{AnisetteProvider, remote_v3::RemoteV3AnisetteProvider}, + anisette::{AnisetteDataGenerator, AnisetteProvider, remote_v3::RemoteV3AnisetteProvider}, auth::apple_account::AppleAccount, }; pub struct AppleAccountBuilder { email: String, debug: Option, - anisette_provider: Option>, + anisette_generator: Option, } impl AppleAccountBuilder { @@ -20,7 +23,7 @@ impl AppleAccountBuilder { Self { email: email.to_string(), debug: None, - anisette_provider: None, + anisette_generator: None, } } @@ -33,8 +36,13 @@ impl AppleAccountBuilder { self } - pub fn anisette_provider(mut self, anisette_provider: impl AnisetteProvider + 'static) -> Self { - self.anisette_provider = Some(Box::new(anisette_provider)); + pub fn anisette_provider( + mut self, + anisette_provider: impl AnisetteProvider + Send + Sync + 'static, + ) -> Self { + self.anisette_generator = Some(AnisetteDataGenerator::new(Arc::new(RwLock::new( + anisette_provider, + )))); self } @@ -44,11 +52,11 @@ impl AppleAccountBuilder { /// Returns an error if the reqwest client cannot be built pub async fn build(self) -> Result { let debug = self.debug.unwrap_or(false); - let anisette_provider = self - .anisette_provider - .unwrap_or_else(|| Box::new(RemoteV3AnisetteProvider::default())); + let anisette_generator = self.anisette_generator.unwrap_or_else(|| { + AnisetteDataGenerator::new(Arc::new(RwLock::new(RemoteV3AnisetteProvider::default()))) + }); - AppleAccount::new(&self.email, anisette_provider, debug).await + AppleAccount::new(&self.email, anisette_generator, debug).await } /// Build the AppleAccount and log in diff --git a/isideload/src/auth/grandslam.rs b/isideload/src/auth/grandslam.rs index 44133fa..150adbb 100644 --- a/isideload/src/auth/grandslam.rs +++ b/isideload/src/auth/grandslam.rs @@ -16,7 +16,7 @@ const URL_BAG: &str = "https://gsa.apple.com/grandslam/GsService2/lookup"; pub struct GrandSlam { pub client: reqwest::Client, pub client_info: AnisetteClientInfo, - pub url_bag: Option, + url_bag: Dictionary, } impl GrandSlam { @@ -24,64 +24,75 @@ impl GrandSlam { /// /// # Arguments /// - `client`: The reqwest client to use for requests - pub fn new(client_info: AnisetteClientInfo, debug: bool) -> Self { - Self { - client: Self::build_reqwest_client(debug).unwrap(), + pub async fn new(client_info: AnisetteClientInfo, debug: bool) -> Result { + let client = Self::build_reqwest_client(debug).unwrap(); + let base_headers = Self::base_headers(&client_info, false)?; + let url_bag = Self::fetch_url_bag(&client, base_headers).await?; + Ok(Self { + client, client_info, - url_bag: None, - } + url_bag, + }) } - /// Get the URL bag from GrandSlam - pub async fn get_url_bag(&mut self) -> Result<&Dictionary, Report> { - if self.url_bag.is_none() { - debug!("Fetching URL bag from GrandSlam"); - let resp = self - .client - .get(URL_BAG) - .headers(self.base_headers(false)?) - .send() - .await - .context("Failed to fetch URL Bag")? - .text() - .await - .context("Failed to read URL Bag response text")?; + /// Fetch the URL bag from GrandSlam and cache it + pub async fn fetch_url_bag( + client: &reqwest::Client, + base_headers: HeaderMap, + ) -> Result { + debug!("Fetching URL bag from GrandSlam"); + let resp = client + .get(URL_BAG) + .headers(base_headers) + .send() + .await + .context("Failed to fetch URL Bag")? + .text() + .await + .context("Failed to read URL Bag response text")?; - let dict: Dictionary = - plist::from_bytes(resp.as_bytes()).context("Failed to parse URL Bag plist")?; - let urls = dict - .get("urls") - .and_then(|v| v.as_dictionary()) - .cloned() - .ok_or_else(|| report!("URL Bag plist missing 'urls' dictionary"))?; + let dict: Dictionary = + plist::from_bytes(resp.as_bytes()).context("Failed to parse URL Bag plist")?; + let urls = dict + .get("urls") + .and_then(|v| v.as_dictionary()) + .cloned() + .ok_or_else(|| report!("URL Bag plist missing 'urls' dictionary"))?; - self.url_bag = Some(urls); - } - Ok(self.url_bag.as_ref().unwrap()) + Ok(urls) } - pub async fn get_url(&mut self, key: &str) -> Result { - let url_bag = self.get_url_bag().await?; - let url = url_bag + pub fn get_url(&self, key: &str) -> Result { + let url = self + .url_bag .get_string(key) .context("Unable to find key in URL bag")?; Ok(url) } pub fn get(&self, url: &str) -> Result { - let builder = self.client.get(url).headers(self.base_headers(false)?); + let builder = self + .client + .get(url) + .headers(Self::base_headers(&self.client_info, false)?); Ok(builder) } pub fn get_sms(&self, url: &str) -> Result { - let builder = self.client.get(url).headers(self.base_headers(true)?); + let builder = self + .client + .get(url) + .headers(Self::base_headers(&self.client_info, true)?); Ok(builder) } pub fn post(&self, url: &str) -> Result { - let builder = self.client.post(url).headers(self.base_headers(false)?); + let builder = self + .client + .post(url) + .headers(Self::base_headers(&self.client_info, false)?); Ok(builder) } @@ -121,7 +132,10 @@ impl GrandSlam { Ok(response_plist) } - fn base_headers(&self, sms: bool) -> Result { + fn base_headers( + client_info: &AnisetteClientInfo, + sms: bool, + ) -> Result { let mut headers = reqwest::header::HeaderMap::new(); if !sms { headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist")); @@ -129,11 +143,11 @@ impl GrandSlam { } headers.insert( "X-Mme-Client-Info", - HeaderValue::from_str(&self.client_info.client_info)?, + HeaderValue::from_str(&client_info.client_info)?, ); headers.insert( "User-Agent", - HeaderValue::from_str(&self.client_info.user_agent)?, + HeaderValue::from_str(&client_info.user_agent)?, ); headers.insert("X-Xcode-Version", HeaderValue::from_static("14.2 (14C18)")); headers.insert( diff --git a/isideload/src/dev/app_groups.rs b/isideload/src/dev/app_groups.rs index 2d0f5f9..881153b 100644 --- a/isideload/src/dev/app_groups.rs +++ b/isideload/src/dev/app_groups.rs @@ -18,10 +18,10 @@ pub struct AppGroup { #[async_trait::async_trait] pub trait AppGroupsApi { - fn developer_session(&self) -> &DeveloperSession<'_>; + fn developer_session(&mut self) -> &mut DeveloperSession; async fn list_app_groups( - &self, + &mut self, team: &DeveloperTeam, device_type: impl Into> + Send, ) -> Result, Report> { @@ -43,7 +43,7 @@ pub trait AppGroupsApi { } async fn add_app_group( - &self, + &mut self, team: &DeveloperTeam, name: &str, identifier: &str, @@ -69,7 +69,7 @@ pub trait AppGroupsApi { } async fn assign_app_group( - &self, + &mut self, team: &DeveloperTeam, app_group: &AppGroup, app_id: &AppId, @@ -93,8 +93,8 @@ pub trait AppGroupsApi { } } -impl AppGroupsApi for DeveloperSession<'_> { - fn developer_session(&self) -> &DeveloperSession<'_> { +impl AppGroupsApi for DeveloperSession { + fn developer_session(&mut self) -> &mut DeveloperSession { self } } diff --git a/isideload/src/dev/app_ids.rs b/isideload/src/dev/app_ids.rs index b9a9386..c546438 100644 --- a/isideload/src/dev/app_ids.rs +++ b/isideload/src/dev/app_ids.rs @@ -52,10 +52,10 @@ pub struct Profile { #[async_trait::async_trait] pub trait AppIdsApi { - fn developer_session(&self) -> &DeveloperSession<'_>; + fn developer_session(&mut self) -> &mut DeveloperSession; async fn add_app_id( - &self, + &mut self, team: &DeveloperTeam, name: &str, identifier: &str, @@ -77,7 +77,7 @@ pub trait AppIdsApi { } async fn list_app_ids( - &self, + &mut self, team: &DeveloperTeam, device_type: impl Into> + Send, ) -> Result { @@ -107,7 +107,7 @@ pub trait AppIdsApi { } async fn update_app_id( - &self, + &mut self, team: &DeveloperTeam, app_id: &AppId, features: Dictionary, @@ -130,7 +130,7 @@ pub trait AppIdsApi { } async fn delete_app_id( - &self, + &mut self, team: &DeveloperTeam, app_id: &AppId, device_type: impl Into> + Send, @@ -149,7 +149,7 @@ pub trait AppIdsApi { } async fn download_team_provisioning_profile( - &self, + &mut self, team: &DeveloperTeam, app_id: &AppId, device_type: impl Into> + Send, @@ -173,8 +173,8 @@ pub trait AppIdsApi { } } -impl AppIdsApi for DeveloperSession<'_> { - fn developer_session(&self) -> &DeveloperSession<'_> { +impl AppIdsApi for DeveloperSession { + fn developer_session(&mut self) -> &mut DeveloperSession { self } } diff --git a/isideload/src/dev/certificates.rs b/isideload/src/dev/certificates.rs index 9aaaa86..298da91 100644 --- a/isideload/src/dev/certificates.rs +++ b/isideload/src/dev/certificates.rs @@ -47,10 +47,10 @@ impl std::fmt::Debug for DevelopmentCertificate { #[async_trait::async_trait] pub trait CertificatesApi { - fn developer_session(&self) -> &DeveloperSession<'_>; + fn developer_session(&mut self) -> &mut DeveloperSession; async fn list_all_development_certs( - &self, + &mut self, team: &DeveloperTeam, device_type: impl Into> + Send, ) -> Result, Report> { @@ -72,7 +72,7 @@ pub trait CertificatesApi { } async fn revoke_development_cert( - &self, + &mut self, team: &DeveloperTeam, serial_number: &str, device_type: impl Into> + Send, @@ -94,7 +94,7 @@ pub trait CertificatesApi { } async fn submit_development_csr( - &self, + &mut self, team: &DeveloperTeam, csr_content: String, machine_name: String, @@ -121,8 +121,8 @@ pub trait CertificatesApi { } } -impl CertificatesApi for DeveloperSession<'_> { - fn developer_session(&self) -> &DeveloperSession<'_> { +impl CertificatesApi for DeveloperSession { + fn developer_session(&mut self) -> &mut DeveloperSession { self } } diff --git a/isideload/src/dev/developer_session.rs b/isideload/src/dev/developer_session.rs index 0c80303..2842719 100644 --- a/isideload/src/dev/developer_session.rs +++ b/isideload/src/dev/developer_session.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use plist::Dictionary; use plist_macro::{plist, plist_to_xml_string}; @@ -8,7 +8,7 @@ use tracing::{error, warn}; use uuid::Uuid; use crate::{ - anisette::{AnisetteData, AnisetteProvider}, + anisette::AnisetteDataGenerator, auth::{ apple_account::{AppToken, AppleAccount}, grandslam::GrandSlam, @@ -23,29 +23,29 @@ pub use super::device_type::DeveloperDeviceType; pub use super::devices::*; pub use super::teams::*; -pub struct DeveloperSession<'a> { +pub struct DeveloperSession { token: AppToken, adsid: String, - client: &'a GrandSlam, - anisette_provider: Arc>, + client: Arc, + anisette_generator: AnisetteDataGenerator, } -impl<'a> DeveloperSession<'a> { +impl DeveloperSession { pub fn new( token: AppToken, adsid: String, - client: &'a GrandSlam, - anisette_provider: Arc>, + client: Arc, + anisette_generator: AnisetteDataGenerator, ) -> Self { DeveloperSession { token, adsid, client, - anisette_provider, + anisette_generator, } } - pub async fn from_account(account: &'a mut AppleAccount) -> Result { + pub async fn from_account(account: &mut AppleAccount) -> Result { let token = account .get_app_token("xcode.auth") .await @@ -59,13 +59,13 @@ impl<'a> DeveloperSession<'a> { Ok(DeveloperSession::new( token, spd.get_string("adsid")?, - &account.grandslam_client, - &account.anisette_data, + account.grandslam_client.clone(), + account.anisette_generator.clone(), )) } async fn send_dev_request_internal( - &self, + &mut self, url: &str, body: impl Into>, ) -> Result<(Dictionary, Option), Report> { @@ -86,7 +86,12 @@ impl<'a> DeveloperSession<'a> { .body(plist_to_xml_string(&body)) .header("X-Apple-GS-Token", &self.token.token) .header("X-Apple-I-Identity-Id", &self.adsid) - .headers(self.anisette_data.get_header_map()) + .headers( + self.anisette_generator + .get_anisette_data(self.client.clone()) + .await? + .get_header_map(), + ) .send() .await? .error_for_status() @@ -133,7 +138,7 @@ impl<'a> DeveloperSession<'a> { } pub async fn send_dev_request( - &self, + &mut self, url: &str, body: impl Into>, response_key: &str, @@ -152,7 +157,7 @@ impl<'a> DeveloperSession<'a> { } pub async fn send_dev_request_no_response( - &self, + &mut self, url: &str, body: impl Into>, ) -> Result { diff --git a/isideload/src/dev/devices.rs b/isideload/src/dev/devices.rs index 860f371..6c57559 100644 --- a/isideload/src/dev/devices.rs +++ b/isideload/src/dev/devices.rs @@ -18,10 +18,10 @@ pub struct DeveloperDevice { #[async_trait::async_trait] pub trait DevicesApi { - fn developer_session(&self) -> &DeveloperSession<'_>; + fn developer_session(&mut self) -> &mut DeveloperSession; async fn list_devices( - &self, + &mut self, team: &DeveloperTeam, device_type: impl Into> + Send, ) -> Result, Report> { @@ -39,7 +39,7 @@ pub trait DevicesApi { } async fn add_device( - &self, + &mut self, team: &DeveloperTeam, name: &str, udid: &str, @@ -61,8 +61,8 @@ pub trait DevicesApi { } } -impl DevicesApi for DeveloperSession<'_> { - fn developer_session(&self) -> &DeveloperSession<'_> { +impl DevicesApi for DeveloperSession { + fn developer_session(&mut self) -> &mut DeveloperSession { self } } diff --git a/isideload/src/dev/teams.rs b/isideload/src/dev/teams.rs index 5bf4b98..1a4cb66 100644 --- a/isideload/src/dev/teams.rs +++ b/isideload/src/dev/teams.rs @@ -16,9 +16,9 @@ pub struct DeveloperTeam { #[async_trait::async_trait] pub trait TeamsApi { - fn developer_session(&self) -> &DeveloperSession<'_>; + fn developer_session(&mut self) -> &mut DeveloperSession; - async fn list_teams(&self) -> Result, Report> { + async fn list_teams(&mut self) -> Result, Report> { let response: Vec = self .developer_session() .send_dev_request(&dev_url("listTeams", Any), None, "teams") @@ -29,8 +29,8 @@ pub trait TeamsApi { } } -impl TeamsApi for DeveloperSession<'_> { - fn developer_session(&self) -> &DeveloperSession<'_> { +impl TeamsApi for DeveloperSession { + fn developer_session(&mut self) -> &mut DeveloperSession { self } } diff --git a/isideload/src/lib.rs b/isideload/src/lib.rs index 9fc2232..14a1c66 100644 --- a/isideload/src/lib.rs +++ b/isideload/src/lib.rs @@ -15,6 +15,9 @@ pub enum SideloadError { #[error("Plist parse error: {0}")] PlistParseError(String), + + #[error("Failed to get anisette data, anisette not provisioned")] + AnisetteNotProvisioned, } struct ReqwestErrorFormatter;