Add refreshing anisette & Rethink ownership for GrandSlam and anisette to prepare for concurrency

This commit is contained in:
nab138
2026-02-01 01:01:54 -05:00
parent 0073ab2568
commit 41e29caea8
16 changed files with 271 additions and 285 deletions

1
Cargo.lock generated
View File

@@ -824,6 +824,7 @@ dependencies = [
"serde_json",
"sha2",
"thiserror 2.0.18",
"tokio",
"tokio-tungstenite",
"tracing",
"uuid",

View File

@@ -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");

View File

@@ -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"

View File

@@ -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<String, String> {
//let dt: DateTime<Utc> = 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<AnisetteData>,
client_info: Option<AnisetteClientInfo>,
state: Option<AnisetteState>,
}
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<AnisetteClientInfo, Report> {
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");
}
}
}

View File

@@ -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<AnisetteData, Report>;
async fn get_anisette_data(&self) -> Result<AnisetteData, Report>;
async fn get_client_info(&mut self) -> Result<AnisetteClientInfo, Report>;
async fn provision(&mut self, gs: Arc<GrandSlam>) -> Result<(), Report>;
fn needs_provisioning(&self) -> Result<bool, Report>;
}
#[derive(Clone)]
pub struct AnisetteDataGenerator {
provider: Arc<RwLock<dyn AnisetteProvider + Send + Sync>>,
data: Option<Arc<AnisetteData>>,
}
impl AnisetteDataGenerator {
pub fn new(provider: Arc<RwLock<dyn AnisetteProvider + Send + Sync>>) -> Self {
AnisetteDataGenerator {
provider,
data: None,
}
}
pub async fn get_anisette_data(
&mut self,
gs: Arc<GrandSlam>,
) -> Result<Arc<AnisetteData>, 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<AnisetteClientInfo, Report> {
let mut provider = self.provider.write().await;
provider.get_client_info().await
}
}

View File

@@ -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<AnisetteData, Report> {
let state = self.get_state(gs).await?.clone();
async fn get_anisette_data(&self) -> Result<AnisetteData, Report> {
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<bool, Report> {
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<GrandSlam>) -> 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<GrandSlam>) -> 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<GrandSlam>,
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://")

View File

@@ -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<plist::Dictionary>,
pub anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
pub anisette_data: AnisetteData,
pub grandslam_client: GrandSlam,
pub anisette_generator: AnisetteDataGenerator,
pub grandslam_client: Arc<GrandSlam>,
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<Mutex<dyn AnisetteProvider + Send>>,
anisette_generator: AnisetteDataGenerator,
debug: bool,
) -> Result<Self, Report> {
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<String>,
) -> 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<HeaderMap, Report> {
let mut headers = self.anisette_data.get_header_map();
async fn build_2fa_headers(&self, anisette_data: &AnisetteData) -> Result<HeaderMap, Report> {
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<LoginState, Report> {
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::<Sha256>::new(&G_2048);
let a: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).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": {

View File

@@ -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<bool>,
anisette_provider: Option<Box<dyn AnisetteProvider>>,
anisette_generator: Option<AnisetteDataGenerator>,
}
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<AppleAccount, Report> {
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

View File

@@ -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<Dictionary>,
url_bag: Dictionary,
}
impl GrandSlam {
@@ -24,22 +24,26 @@ 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<Self, Report> {
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() {
/// Fetch the URL bag from GrandSlam and cache it
pub async fn fetch_url_bag(
client: &reqwest::Client,
base_headers: HeaderMap,
) -> Result<Dictionary, Report> {
debug!("Fetching URL bag from GrandSlam");
let resp = self
.client
let resp = client
.get(URL_BAG)
.headers(self.base_headers(false)?)
.headers(base_headers)
.send()
.await
.context("Failed to fetch URL Bag")?
@@ -55,33 +59,40 @@ impl GrandSlam {
.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<String, Report> {
let url_bag = self.get_url_bag().await?;
let url = url_bag
pub fn get_url(&self, key: &str) -> Result<String, Report> {
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<reqwest::RequestBuilder, Report> {
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<reqwest::RequestBuilder, Report> {
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<reqwest::RequestBuilder, Report> {
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<reqwest::header::HeaderMap, Report> {
fn base_headers(
client_info: &AnisetteClientInfo,
sms: bool,
) -> Result<reqwest::header::HeaderMap, Report> {
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(

View File

@@ -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<Option<DeveloperDeviceType>> + Send,
) -> Result<Vec<AppGroup>, 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
}
}

View File

@@ -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<Option<DeveloperDeviceType>> + Send,
) -> Result<ListAppIdsResponse, Report> {
@@ -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<Option<DeveloperDeviceType>> + 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<Option<DeveloperDeviceType>> + 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
}
}

View File

@@ -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<Option<DeveloperDeviceType>> + Send,
) -> Result<Vec<DevelopmentCertificate>, 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<Option<DeveloperDeviceType>> + 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
}
}

View File

@@ -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<Mutex<dyn AnisetteProvider + Send>>,
client: Arc<GrandSlam>,
anisette_generator: AnisetteDataGenerator,
}
impl<'a> DeveloperSession<'a> {
impl DeveloperSession {
pub fn new(
token: AppToken,
adsid: String,
client: &'a GrandSlam,
anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
client: Arc<GrandSlam>,
anisette_generator: AnisetteDataGenerator,
) -> Self {
DeveloperSession {
token,
adsid,
client,
anisette_provider,
anisette_generator,
}
}
pub async fn from_account(account: &'a mut AppleAccount) -> Result<Self, Report> {
pub async fn from_account(account: &mut AppleAccount) -> Result<Self, Report> {
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<Option<Dictionary>>,
) -> Result<(Dictionary, Option<String>), 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<T: DeserializeOwned>(
&self,
&mut self,
url: &str,
body: impl Into<Option<Dictionary>>,
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<Option<Dictionary>>,
) -> Result<Dictionary, Report> {

View File

@@ -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<Option<DeveloperDeviceType>> + Send,
) -> Result<Vec<DeveloperDevice>, 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
}
}

View File

@@ -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<Vec<DeveloperTeam>, Report> {
async fn list_teams(&mut self) -> Result<Vec<DeveloperTeam>, Report> {
let response: Vec<DeveloperTeam> = 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
}
}

View File

@@ -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;