mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 14:36:16 +01:00
Add refreshing anisette & Rethink ownership for GrandSlam and anisette to prepare for concurrency
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -824,6 +824,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ async fn main() {
|
|||||||
|
|
||||||
let mut account = account.unwrap();
|
let mut account = account.unwrap();
|
||||||
|
|
||||||
let dev_session = DeveloperSession::from_account(&mut account)
|
let mut dev_session = DeveloperSession::from_account(&mut account)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create developer session");
|
.expect("Failed to create developer session");
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ hmac = "0.12.1"
|
|||||||
cbc = { version = "0.1.2", features = ["std"] }
|
cbc = { version = "0.1.2", features = ["std"] }
|
||||||
aes = "0.8.4"
|
aes = "0.8.4"
|
||||||
aes-gcm = "0.10.3"
|
aes-gcm = "0.10.3"
|
||||||
|
tokio = "1.49.0"
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,8 @@ use plist_macro::plist;
|
|||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
use rootcause::prelude::*;
|
use rootcause::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc, time::SystemTime};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct AnisetteClientInfo {
|
pub struct AnisetteClientInfo {
|
||||||
@@ -22,6 +23,7 @@ pub struct AnisetteData {
|
|||||||
_device_description: String,
|
_device_description: String,
|
||||||
device_unique_identifier: String,
|
device_unique_identifier: String,
|
||||||
_local_user_id: 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
|
// 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
|
cpd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn needs_refresh(&self) -> bool {
|
||||||
|
let elapsed = self.generated_at.elapsed().unwrap();
|
||||||
|
elapsed.as_secs() > 60
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AnisetteProvider {
|
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 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ mod state;
|
|||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use plist_macro::plist;
|
use plist_macro::plist;
|
||||||
@@ -11,6 +13,7 @@ use serde::Deserialize;
|
|||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
use crate::SideloadError;
|
||||||
use crate::anisette::remote_v3::state::AnisetteState;
|
use crate::anisette::remote_v3::state::AnisetteState;
|
||||||
use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider};
|
use crate::anisette::{AnisetteClientInfo, AnisetteData, AnisetteProvider};
|
||||||
use crate::auth::grandslam::GrandSlam;
|
use crate::auth::grandslam::GrandSlam;
|
||||||
@@ -74,13 +77,19 @@ impl Default for RemoteV3AnisetteProvider {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl AnisetteProvider for RemoteV3AnisetteProvider {
|
impl AnisetteProvider for RemoteV3AnisetteProvider {
|
||||||
async fn get_anisette_data(&mut self, gs: &mut GrandSlam) -> Result<AnisetteData, Report> {
|
async fn get_anisette_data(&self) -> Result<AnisetteData, Report> {
|
||||||
let state = self.get_state(gs).await?.clone();
|
let state = self
|
||||||
|
.state
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SideloadError::AnisetteNotProvisioned)?;
|
||||||
let adi_pb = state
|
let adi_pb = state
|
||||||
.adi_pb
|
.adi_pb
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(report!("Anisette state is not provisioned"))?;
|
.ok_or(SideloadError::AnisetteNotProvisioned)?;
|
||||||
let client_info = self.get_client_info().await?.client_info.clone();
|
let client_info = self
|
||||||
|
.client_info
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SideloadError::AnisetteNotProvisioned)?;
|
||||||
|
|
||||||
let headers = self
|
let headers = self
|
||||||
.client
|
.client
|
||||||
@@ -109,9 +118,10 @@ impl AnisetteProvider for RemoteV3AnisetteProvider {
|
|||||||
machine_id,
|
machine_id,
|
||||||
one_time_password,
|
one_time_password,
|
||||||
routing_info,
|
routing_info,
|
||||||
_device_description: client_info,
|
_device_description: client_info.client_info.clone(),
|
||||||
device_unique_identifier: state.get_device_id(),
|
device_unique_identifier: state.get_device_id(),
|
||||||
_local_user_id: hex::encode(&state.get_md_lu()),
|
_local_user_id: hex::encode(&state.get_md_lu()),
|
||||||
|
generated_at: SystemTime::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
@@ -140,10 +150,24 @@ impl AnisetteProvider for RemoteV3AnisetteProvider {
|
|||||||
|
|
||||||
Ok(self.client_info.as_ref().unwrap().clone())
|
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 {
|
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");
|
let state_path = self.config_path.join("state.plist");
|
||||||
fs::create_dir_all(&self.config_path)?;
|
fs::create_dir_all(&self.config_path)?;
|
||||||
if self.state.is_none() {
|
if self.state.is_none() {
|
||||||
@@ -195,13 +219,13 @@ impl RemoteV3AnisetteProvider {
|
|||||||
}
|
}
|
||||||
async fn provision(
|
async fn provision(
|
||||||
state: &mut AnisetteState,
|
state: &mut AnisetteState,
|
||||||
gs: &mut GrandSlam,
|
gs: Arc<GrandSlam>,
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> Result<(), Report> {
|
) -> Result<(), Report> {
|
||||||
debug!("Starting provisioning");
|
debug!("Starting provisioning");
|
||||||
|
|
||||||
let start_provisioning = gs.get_url("midStartProvisioning").await?;
|
let start_provisioning = gs.get_url("midStartProvisioning")?;
|
||||||
let end_provisioning = gs.get_url("midFinishProvisioning").await?;
|
let end_provisioning = gs.get_url("midFinishProvisioning")?;
|
||||||
|
|
||||||
let websocket_url = format!("{}/v3/provisioning_session", url)
|
let websocket_url = format!("{}/v3/provisioning_session", url)
|
||||||
.replace("https://", "wss://")
|
.replace("https://", "wss://")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
anisette::{AnisetteData, AnisetteProvider},
|
anisette::{AnisetteData, AnisetteDataGenerator},
|
||||||
auth::{
|
auth::{
|
||||||
builder::AppleAccountBuilder,
|
builder::AppleAccountBuilder,
|
||||||
grandslam::{GrandSlam, GrandSlamErrorChecker},
|
grandslam::{GrandSlam, GrandSlamErrorChecker},
|
||||||
@@ -30,9 +30,8 @@ use tracing::{debug, info, warn};
|
|||||||
pub struct AppleAccount {
|
pub struct AppleAccount {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub spd: Option<plist::Dictionary>,
|
pub spd: Option<plist::Dictionary>,
|
||||||
pub anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
|
pub anisette_generator: AnisetteDataGenerator,
|
||||||
pub anisette_data: AnisetteData,
|
pub grandslam_client: Arc<GrandSlam>,
|
||||||
pub grandslam_client: GrandSlam,
|
|
||||||
login_state: LoginState,
|
login_state: LoginState,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,7 @@ impl AppleAccount {
|
|||||||
/// - `debug`: DANGER, If true, accept invalid certificates and enable verbose connection
|
/// - `debug`: DANGER, If true, accept invalid certificates and enable verbose connection
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
email: &str,
|
email: &str,
|
||||||
mut anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
|
anisette_generator: AnisetteDataGenerator,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Result<Self, Report> {
|
) -> Result<Self, Report> {
|
||||||
info!("Initializing apple account");
|
info!("Initializing apple account");
|
||||||
@@ -72,26 +71,18 @@ impl AppleAccount {
|
|||||||
warn!("Debug mode enabled: this is a security risk!");
|
warn!("Debug mode enabled: this is a security risk!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_info = anisette_provider
|
let client_info = anisette_generator
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.get_client_info()
|
.get_client_info()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get anisette client info")?;
|
.context("Failed to get anisette client info")?;
|
||||||
|
|
||||||
let mut grandslam_client = GrandSlam::new(client_info, debug);
|
let grandslam_client = GrandSlam::new(client_info, debug).await?;
|
||||||
|
|
||||||
let anisette_data = anisette_provider
|
|
||||||
.get_anisette_data(&mut grandslam_client)
|
|
||||||
.await
|
|
||||||
.context("Failed to get anisette data for login")?;
|
|
||||||
|
|
||||||
Ok(AppleAccount {
|
Ok(AppleAccount {
|
||||||
email: email.to_string(),
|
email: email.to_string(),
|
||||||
spd: None,
|
spd: None,
|
||||||
anisette_provider,
|
anisette_generator,
|
||||||
anisette_data,
|
grandslam_client: Arc::new(grandslam_client),
|
||||||
grandslam_client,
|
|
||||||
debug,
|
debug,
|
||||||
login_state: LoginState::NeedsLogin,
|
login_state: LoginState::NeedsLogin,
|
||||||
})
|
})
|
||||||
@@ -197,16 +188,22 @@ impl AppleAccount {
|
|||||||
two_factor_callback: impl Fn() -> Option<String>,
|
two_factor_callback: impl Fn() -> Option<String>,
|
||||||
) -> Result<(), Report> {
|
) -> Result<(), Report> {
|
||||||
debug!("Trusted device 2FA required");
|
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
|
let request_code_url = self
|
||||||
.grandslam_client
|
.grandslam_client
|
||||||
.get_url("trustedDeviceSecondaryAuth")
|
.get_url("trustedDeviceSecondaryAuth")?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let submit_code_url = self.grandslam_client.get_url("validateCode").await?;
|
let submit_code_url = self.grandslam_client.get_url("validateCode")?;
|
||||||
|
|
||||||
self.grandslam_client
|
self.grandslam_client
|
||||||
.get(&request_code_url)?
|
.get(&request_code_url)?
|
||||||
.headers(self.build_2fa_headers().await?)
|
.headers(self.build_2fa_headers(&anisette_data).await?)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.context("Failed to request trusted device 2fa")?
|
.context("Failed to request trusted device 2fa")?
|
||||||
@@ -221,7 +218,7 @@ impl AppleAccount {
|
|||||||
let res = self
|
let res = self
|
||||||
.grandslam_client
|
.grandslam_client
|
||||||
.get(&submit_code_url)?
|
.get(&submit_code_url)?
|
||||||
.headers(self.build_2fa_headers().await?)
|
.headers(self.build_2fa_headers(&anisette_data).await?)
|
||||||
.header("security-code", code)
|
.header("security-code", code)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
@@ -248,11 +245,17 @@ impl AppleAccount {
|
|||||||
) -> Result<(), Report> {
|
) -> Result<(), Report> {
|
||||||
debug!("SMS 2FA required");
|
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
|
self.grandslam_client
|
||||||
.get_sms(&request_code_url)?
|
.get_sms(&request_code_url)?
|
||||||
.headers(self.build_2fa_headers().await?)
|
.headers(self.build_2fa_headers(&anisette_data).await?)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.context("Failed to request SMS 2FA")?
|
.context("Failed to request SMS 2FA")?
|
||||||
@@ -274,7 +277,7 @@ impl AppleAccount {
|
|||||||
"mode": "sms"
|
"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("Content-Type", HeaderValue::from_static("application/json"));
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"Accept",
|
"Accept",
|
||||||
@@ -332,8 +335,8 @@ impl AppleAccount {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn build_2fa_headers(&mut self) -> Result<HeaderMap, Report> {
|
async fn build_2fa_headers(&self, anisette_data: &AnisetteData) -> Result<HeaderMap, Report> {
|
||||||
let mut headers = self.anisette_data.get_header_map();
|
let mut headers = anisette_data.get_header_map();
|
||||||
|
|
||||||
let spd = self
|
let spd = self
|
||||||
.spd
|
.spd
|
||||||
@@ -354,18 +357,23 @@ impl AppleAccount {
|
|||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"X-Apple-I-MD-RINFO",
|
"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)
|
Ok(headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login_inner(&mut self, password: &str) -> Result<LoginState, Report> {
|
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);
|
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 srp_client = SrpClient::<Sha256>::new(&G_2048);
|
||||||
let a: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
|
let a: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
|
||||||
@@ -510,6 +518,12 @@ impl AppleAccount {
|
|||||||
format!("com.apple.gs.{}", app)
|
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
|
let spd = self
|
||||||
.spd
|
.spd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -531,8 +545,8 @@ impl AppleAccount {
|
|||||||
.into_bytes()
|
.into_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let gs_service_url = self.grandslam_client.get_url("gsService").await?;
|
let gs_service_url = self.grandslam_client.get_url("gsService")?;
|
||||||
let cpd = self.anisette_data.get_client_provided_data();
|
let cpd = anisette_data.get_client_provided_data();
|
||||||
|
|
||||||
let request = plist!(dict {
|
let request = plist!(dict {
|
||||||
"Header": {
|
"Header": {
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rootcause::prelude::*;
|
use rootcause::prelude::*;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
anisette::{AnisetteProvider, remote_v3::RemoteV3AnisetteProvider},
|
anisette::{AnisetteDataGenerator, AnisetteProvider, remote_v3::RemoteV3AnisetteProvider},
|
||||||
auth::apple_account::AppleAccount,
|
auth::apple_account::AppleAccount,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppleAccountBuilder {
|
pub struct AppleAccountBuilder {
|
||||||
email: String,
|
email: String,
|
||||||
debug: Option<bool>,
|
debug: Option<bool>,
|
||||||
anisette_provider: Option<Box<dyn AnisetteProvider>>,
|
anisette_generator: Option<AnisetteDataGenerator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppleAccountBuilder {
|
impl AppleAccountBuilder {
|
||||||
@@ -20,7 +23,7 @@ impl AppleAccountBuilder {
|
|||||||
Self {
|
Self {
|
||||||
email: email.to_string(),
|
email: email.to_string(),
|
||||||
debug: None,
|
debug: None,
|
||||||
anisette_provider: None,
|
anisette_generator: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +36,13 @@ impl AppleAccountBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn anisette_provider(mut self, anisette_provider: impl AnisetteProvider + 'static) -> Self {
|
pub fn anisette_provider(
|
||||||
self.anisette_provider = Some(Box::new(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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +52,11 @@ impl AppleAccountBuilder {
|
|||||||
/// Returns an error if the reqwest client cannot be built
|
/// Returns an error if the reqwest client cannot be built
|
||||||
pub async fn build(self) -> Result<AppleAccount, Report> {
|
pub async fn build(self) -> Result<AppleAccount, Report> {
|
||||||
let debug = self.debug.unwrap_or(false);
|
let debug = self.debug.unwrap_or(false);
|
||||||
let anisette_provider = self
|
let anisette_generator = self.anisette_generator.unwrap_or_else(|| {
|
||||||
.anisette_provider
|
AnisetteDataGenerator::new(Arc::new(RwLock::new(RemoteV3AnisetteProvider::default())))
|
||||||
.unwrap_or_else(|| Box::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
|
/// Build the AppleAccount and log in
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const URL_BAG: &str = "https://gsa.apple.com/grandslam/GsService2/lookup";
|
|||||||
pub struct GrandSlam {
|
pub struct GrandSlam {
|
||||||
pub client: reqwest::Client,
|
pub client: reqwest::Client,
|
||||||
pub client_info: AnisetteClientInfo,
|
pub client_info: AnisetteClientInfo,
|
||||||
pub url_bag: Option<Dictionary>,
|
url_bag: Dictionary,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GrandSlam {
|
impl GrandSlam {
|
||||||
@@ -24,22 +24,26 @@ impl GrandSlam {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// - `client`: The reqwest client to use for requests
|
/// - `client`: The reqwest client to use for requests
|
||||||
pub fn new(client_info: AnisetteClientInfo, debug: bool) -> Self {
|
pub async fn new(client_info: AnisetteClientInfo, debug: bool) -> Result<Self, Report> {
|
||||||
Self {
|
let client = Self::build_reqwest_client(debug).unwrap();
|
||||||
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,
|
client_info,
|
||||||
url_bag: None,
|
url_bag,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the URL bag from GrandSlam
|
/// Fetch the URL bag from GrandSlam and cache it
|
||||||
pub async fn get_url_bag(&mut self) -> Result<&Dictionary, Report> {
|
pub async fn fetch_url_bag(
|
||||||
if self.url_bag.is_none() {
|
client: &reqwest::Client,
|
||||||
|
base_headers: HeaderMap,
|
||||||
|
) -> Result<Dictionary, Report> {
|
||||||
debug!("Fetching URL bag from GrandSlam");
|
debug!("Fetching URL bag from GrandSlam");
|
||||||
let resp = self
|
let resp = client
|
||||||
.client
|
|
||||||
.get(URL_BAG)
|
.get(URL_BAG)
|
||||||
.headers(self.base_headers(false)?)
|
.headers(base_headers)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.context("Failed to fetch URL Bag")?
|
.context("Failed to fetch URL Bag")?
|
||||||
@@ -55,33 +59,40 @@ impl GrandSlam {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| report!("URL Bag plist missing 'urls' dictionary"))?;
|
.ok_or_else(|| report!("URL Bag plist missing 'urls' dictionary"))?;
|
||||||
|
|
||||||
self.url_bag = Some(urls);
|
Ok(urls)
|
||||||
}
|
|
||||||
Ok(self.url_bag.as_ref().unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_url(&mut self, key: &str) -> Result<String, Report> {
|
pub fn get_url(&self, key: &str) -> Result<String, Report> {
|
||||||
let url_bag = self.get_url_bag().await?;
|
let url = self
|
||||||
let url = url_bag
|
.url_bag
|
||||||
.get_string(key)
|
.get_string(key)
|
||||||
.context("Unable to find key in URL bag")?;
|
.context("Unable to find key in URL bag")?;
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> {
|
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)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sms(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> {
|
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)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> {
|
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)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
@@ -121,7 +132,10 @@ impl GrandSlam {
|
|||||||
Ok(response_plist)
|
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();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
if !sms {
|
if !sms {
|
||||||
headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist"));
|
headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist"));
|
||||||
@@ -129,11 +143,11 @@ impl GrandSlam {
|
|||||||
}
|
}
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"X-Mme-Client-Info",
|
"X-Mme-Client-Info",
|
||||||
HeaderValue::from_str(&self.client_info.client_info)?,
|
HeaderValue::from_str(&client_info.client_info)?,
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"User-Agent",
|
"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("X-Xcode-Version", HeaderValue::from_static("14.2 (14C18)"));
|
||||||
headers.insert(
|
headers.insert(
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ pub struct AppGroup {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AppGroupsApi {
|
pub trait AppGroupsApi {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_>;
|
fn developer_session(&mut self) -> &mut DeveloperSession;
|
||||||
|
|
||||||
async fn list_app_groups(
|
async fn list_app_groups(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
) -> Result<Vec<AppGroup>, Report> {
|
) -> Result<Vec<AppGroup>, Report> {
|
||||||
@@ -43,7 +43,7 @@ pub trait AppGroupsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_app_group(
|
async fn add_app_group(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
name: &str,
|
name: &str,
|
||||||
identifier: &str,
|
identifier: &str,
|
||||||
@@ -69,7 +69,7 @@ pub trait AppGroupsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn assign_app_group(
|
async fn assign_app_group(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
app_group: &AppGroup,
|
app_group: &AppGroup,
|
||||||
app_id: &AppId,
|
app_id: &AppId,
|
||||||
@@ -93,8 +93,8 @@ pub trait AppGroupsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppGroupsApi for DeveloperSession<'_> {
|
impl AppGroupsApi for DeveloperSession {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_> {
|
fn developer_session(&mut self) -> &mut DeveloperSession {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ pub struct Profile {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait AppIdsApi {
|
pub trait AppIdsApi {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_>;
|
fn developer_session(&mut self) -> &mut DeveloperSession;
|
||||||
|
|
||||||
async fn add_app_id(
|
async fn add_app_id(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
name: &str,
|
name: &str,
|
||||||
identifier: &str,
|
identifier: &str,
|
||||||
@@ -77,7 +77,7 @@ pub trait AppIdsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn list_app_ids(
|
async fn list_app_ids(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
) -> Result<ListAppIdsResponse, Report> {
|
) -> Result<ListAppIdsResponse, Report> {
|
||||||
@@ -107,7 +107,7 @@ pub trait AppIdsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn update_app_id(
|
async fn update_app_id(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
app_id: &AppId,
|
app_id: &AppId,
|
||||||
features: Dictionary,
|
features: Dictionary,
|
||||||
@@ -130,7 +130,7 @@ pub trait AppIdsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_app_id(
|
async fn delete_app_id(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
app_id: &AppId,
|
app_id: &AppId,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
@@ -149,7 +149,7 @@ pub trait AppIdsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn download_team_provisioning_profile(
|
async fn download_team_provisioning_profile(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
app_id: &AppId,
|
app_id: &AppId,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
@@ -173,8 +173,8 @@ pub trait AppIdsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppIdsApi for DeveloperSession<'_> {
|
impl AppIdsApi for DeveloperSession {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_> {
|
fn developer_session(&mut self) -> &mut DeveloperSession {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ impl std::fmt::Debug for DevelopmentCertificate {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait CertificatesApi {
|
pub trait CertificatesApi {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_>;
|
fn developer_session(&mut self) -> &mut DeveloperSession;
|
||||||
|
|
||||||
async fn list_all_development_certs(
|
async fn list_all_development_certs(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
) -> Result<Vec<DevelopmentCertificate>, Report> {
|
) -> Result<Vec<DevelopmentCertificate>, Report> {
|
||||||
@@ -72,7 +72,7 @@ pub trait CertificatesApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn revoke_development_cert(
|
async fn revoke_development_cert(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
serial_number: &str,
|
serial_number: &str,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
@@ -94,7 +94,7 @@ pub trait CertificatesApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn submit_development_csr(
|
async fn submit_development_csr(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
csr_content: String,
|
csr_content: String,
|
||||||
machine_name: String,
|
machine_name: String,
|
||||||
@@ -121,8 +121,8 @@ pub trait CertificatesApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CertificatesApi for DeveloperSession<'_> {
|
impl CertificatesApi for DeveloperSession {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_> {
|
fn developer_session(&mut self) -> &mut DeveloperSession {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use plist::Dictionary;
|
use plist::Dictionary;
|
||||||
use plist_macro::{plist, plist_to_xml_string};
|
use plist_macro::{plist, plist_to_xml_string};
|
||||||
@@ -8,7 +8,7 @@ use tracing::{error, warn};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
anisette::{AnisetteData, AnisetteProvider},
|
anisette::AnisetteDataGenerator,
|
||||||
auth::{
|
auth::{
|
||||||
apple_account::{AppToken, AppleAccount},
|
apple_account::{AppToken, AppleAccount},
|
||||||
grandslam::GrandSlam,
|
grandslam::GrandSlam,
|
||||||
@@ -23,29 +23,29 @@ pub use super::device_type::DeveloperDeviceType;
|
|||||||
pub use super::devices::*;
|
pub use super::devices::*;
|
||||||
pub use super::teams::*;
|
pub use super::teams::*;
|
||||||
|
|
||||||
pub struct DeveloperSession<'a> {
|
pub struct DeveloperSession {
|
||||||
token: AppToken,
|
token: AppToken,
|
||||||
adsid: String,
|
adsid: String,
|
||||||
client: &'a GrandSlam,
|
client: Arc<GrandSlam>,
|
||||||
anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
|
anisette_generator: AnisetteDataGenerator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DeveloperSession<'a> {
|
impl DeveloperSession {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
token: AppToken,
|
token: AppToken,
|
||||||
adsid: String,
|
adsid: String,
|
||||||
client: &'a GrandSlam,
|
client: Arc<GrandSlam>,
|
||||||
anisette_provider: Arc<Mutex<dyn AnisetteProvider + Send>>,
|
anisette_generator: AnisetteDataGenerator,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
DeveloperSession {
|
DeveloperSession {
|
||||||
token,
|
token,
|
||||||
adsid,
|
adsid,
|
||||||
client,
|
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
|
let token = account
|
||||||
.get_app_token("xcode.auth")
|
.get_app_token("xcode.auth")
|
||||||
.await
|
.await
|
||||||
@@ -59,13 +59,13 @@ impl<'a> DeveloperSession<'a> {
|
|||||||
Ok(DeveloperSession::new(
|
Ok(DeveloperSession::new(
|
||||||
token,
|
token,
|
||||||
spd.get_string("adsid")?,
|
spd.get_string("adsid")?,
|
||||||
&account.grandslam_client,
|
account.grandslam_client.clone(),
|
||||||
&account.anisette_data,
|
account.anisette_generator.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_dev_request_internal(
|
async fn send_dev_request_internal(
|
||||||
&self,
|
&mut self,
|
||||||
url: &str,
|
url: &str,
|
||||||
body: impl Into<Option<Dictionary>>,
|
body: impl Into<Option<Dictionary>>,
|
||||||
) -> Result<(Dictionary, Option<String>), Report> {
|
) -> Result<(Dictionary, Option<String>), Report> {
|
||||||
@@ -86,7 +86,12 @@ impl<'a> DeveloperSession<'a> {
|
|||||||
.body(plist_to_xml_string(&body))
|
.body(plist_to_xml_string(&body))
|
||||||
.header("X-Apple-GS-Token", &self.token.token)
|
.header("X-Apple-GS-Token", &self.token.token)
|
||||||
.header("X-Apple-I-Identity-Id", &self.adsid)
|
.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()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()
|
.error_for_status()
|
||||||
@@ -133,7 +138,7 @@ impl<'a> DeveloperSession<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_dev_request<T: DeserializeOwned>(
|
pub async fn send_dev_request<T: DeserializeOwned>(
|
||||||
&self,
|
&mut self,
|
||||||
url: &str,
|
url: &str,
|
||||||
body: impl Into<Option<Dictionary>>,
|
body: impl Into<Option<Dictionary>>,
|
||||||
response_key: &str,
|
response_key: &str,
|
||||||
@@ -152,7 +157,7 @@ impl<'a> DeveloperSession<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_dev_request_no_response(
|
pub async fn send_dev_request_no_response(
|
||||||
&self,
|
&mut self,
|
||||||
url: &str,
|
url: &str,
|
||||||
body: impl Into<Option<Dictionary>>,
|
body: impl Into<Option<Dictionary>>,
|
||||||
) -> Result<Dictionary, Report> {
|
) -> Result<Dictionary, Report> {
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ pub struct DeveloperDevice {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait DevicesApi {
|
pub trait DevicesApi {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_>;
|
fn developer_session(&mut self) -> &mut DeveloperSession;
|
||||||
|
|
||||||
async fn list_devices(
|
async fn list_devices(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
|
||||||
) -> Result<Vec<DeveloperDevice>, Report> {
|
) -> Result<Vec<DeveloperDevice>, Report> {
|
||||||
@@ -39,7 +39,7 @@ pub trait DevicesApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn add_device(
|
async fn add_device(
|
||||||
&self,
|
&mut self,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
name: &str,
|
name: &str,
|
||||||
udid: &str,
|
udid: &str,
|
||||||
@@ -61,8 +61,8 @@ pub trait DevicesApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DevicesApi for DeveloperSession<'_> {
|
impl DevicesApi for DeveloperSession {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_> {
|
fn developer_session(&mut self) -> &mut DeveloperSession {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ pub struct DeveloperTeam {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait TeamsApi {
|
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
|
let response: Vec<DeveloperTeam> = self
|
||||||
.developer_session()
|
.developer_session()
|
||||||
.send_dev_request(&dev_url("listTeams", Any), None, "teams")
|
.send_dev_request(&dev_url("listTeams", Any), None, "teams")
|
||||||
@@ -29,8 +29,8 @@ pub trait TeamsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TeamsApi for DeveloperSession<'_> {
|
impl TeamsApi for DeveloperSession {
|
||||||
fn developer_session(&self) -> &DeveloperSession<'_> {
|
fn developer_session(&mut self) -> &mut DeveloperSession {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ pub enum SideloadError {
|
|||||||
|
|
||||||
#[error("Plist parse error: {0}")]
|
#[error("Plist parse error: {0}")]
|
||||||
PlistParseError(String),
|
PlistParseError(String),
|
||||||
|
|
||||||
|
#[error("Failed to get anisette data, anisette not provisioned")]
|
||||||
|
AnisetteNotProvisioned,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReqwestErrorFormatter;
|
struct ReqwestErrorFormatter;
|
||||||
|
|||||||
Reference in New Issue
Block a user