mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
Developer API implimentation
This commit is contained in:
88
Cargo.lock
generated
88
Cargo.lock
generated
@@ -43,15 +43,6 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.37"
|
||||
@@ -190,19 +181,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -661,30 +639,6 @@ dependencies = [
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
@@ -849,7 +803,6 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cbc",
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac",
|
||||
@@ -965,6 +918,7 @@ name = "minimal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"isideload",
|
||||
"plist",
|
||||
"plist-macro",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2018,10 +1972,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -2187,41 +2142,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
isideload = { path = "../../isideload" }
|
||||
plist = "1.8.0"
|
||||
plist-macro = "0.1.3"
|
||||
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] }
|
||||
tracing = "0.1.44"
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
use std::env;
|
||||
|
||||
use isideload::{
|
||||
anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccountBuilder,
|
||||
anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccount,
|
||||
dev::developer_session::DeveloperSession,
|
||||
};
|
||||
use tracing::Level;
|
||||
|
||||
use plist_macro::pretty_print_dictionary;
|
||||
use tracing::{Level, debug};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
isideload::init().expect("Failed to initialize error reporting");
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::DEBUG)
|
||||
.with_max_level(Level::INFO)
|
||||
.finish();
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
@@ -31,7 +34,7 @@ async fn main() {
|
||||
Some(code.trim().to_string())
|
||||
};
|
||||
|
||||
let account = AppleAccountBuilder::new(apple_id)
|
||||
let account = AppleAccount::builder(apple_id)
|
||||
.anisette_provider(RemoteV3AnisetteProvider::default().set_serial_number("2".to_string()))
|
||||
.login(apple_password, get_2fa_code)
|
||||
.await;
|
||||
@@ -41,10 +44,16 @@ async fn main() {
|
||||
Err(e) => eprintln!("Failed to log in to Apple ID: {:?}", e),
|
||||
}
|
||||
|
||||
let app_token = account.unwrap().get_app_token("xcode.auth").await;
|
||||
let mut account = account.unwrap();
|
||||
|
||||
match app_token {
|
||||
Ok(t) => println!("App token acquired"),
|
||||
Err(e) => eprintln!("Failed to get app token: {:?}", e),
|
||||
}
|
||||
let dev_session = DeveloperSession::from_account(&mut account)
|
||||
.await
|
||||
.expect("Failed to create developer session");
|
||||
|
||||
let res = dev_session
|
||||
.list_teams()
|
||||
.await
|
||||
.expect("Failed to list teams");
|
||||
|
||||
println!("{}", pretty_print_dictionary(&res));
|
||||
}
|
||||
|
||||
@@ -20,11 +20,10 @@ plist = "1.8"
|
||||
plist-macro = "0.1.3"
|
||||
reqwest = { version = "0.13.1", features = ["json", "gzip"] }
|
||||
thiserror = "2.0.17"
|
||||
chrono = "0.4.43"
|
||||
async-trait = "0.1.89"
|
||||
serde = "1.0.228"
|
||||
rand = "0.9.2"
|
||||
uuid = "1.19.0"
|
||||
uuid = {version = "1.20.0", features = ["v4"] }
|
||||
sha2 = "0.10.9"
|
||||
tracing = "0.1.44"
|
||||
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-webpki-roots"] }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod remote_v3;
|
||||
|
||||
use crate::auth::grandslam::GrandSlam;
|
||||
use chrono::{DateTime, SubsecRound, Utc};
|
||||
use plist::Dictionary;
|
||||
use plist_macro::plist;
|
||||
use reqwest::header::HeaderMap;
|
||||
@@ -20,13 +19,14 @@ pub struct AnisetteData {
|
||||
machine_id: String,
|
||||
one_time_password: String,
|
||||
pub routing_info: String,
|
||||
device_description: String,
|
||||
_device_description: String,
|
||||
device_unique_identifier: String,
|
||||
local_user_id: String,
|
||||
_local_user_id: String,
|
||||
}
|
||||
|
||||
// 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, serial: String) -> HashMap<String, String> {
|
||||
pub fn get_headers(&self) -> HashMap<String, String> {
|
||||
//let dt: DateTime<Utc> = Utc::now().round_subsecs(0);
|
||||
|
||||
HashMap::from_iter(
|
||||
@@ -55,8 +55,8 @@ impl AnisetteData {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_header_map(&self, serial: String) -> HeaderMap {
|
||||
let headers_map = self.get_headers(serial);
|
||||
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 {
|
||||
@@ -69,8 +69,8 @@ impl AnisetteData {
|
||||
header_map
|
||||
}
|
||||
|
||||
pub fn get_client_provided_data(&self, serial: String) -> Dictionary {
|
||||
let headers = self.get_headers(serial);
|
||||
pub fn get_client_provided_data(&self) -> Dictionary {
|
||||
let headers = self.get_headers();
|
||||
|
||||
let mut cpd = plist!(dict {
|
||||
"bootstrap": "true",
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use base64::prelude::*;
|
||||
// use chrono::{SubsecRound, Utc};
|
||||
use plist_macro::plist;
|
||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||
use rootcause::prelude::*;
|
||||
@@ -110,9 +109,9 @@ impl AnisetteProvider for RemoteV3AnisetteProvider {
|
||||
machine_id,
|
||||
one_time_password,
|
||||
routing_info,
|
||||
device_description: client_info,
|
||||
_device_description: client_info,
|
||||
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()),
|
||||
};
|
||||
|
||||
Ok(data)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
anisette::{AnisetteData, AnisetteProvider, remote_v3::RemoteV3AnisetteProvider},
|
||||
auth::grandslam::{GrandSlam, GrandSlamErrorChecker},
|
||||
anisette::{AnisetteData, AnisetteProvider},
|
||||
auth::{
|
||||
builder::AppleAccountBuilder,
|
||||
grandslam::{GrandSlam, GrandSlamErrorChecker},
|
||||
},
|
||||
util::plist::PlistDataExtract,
|
||||
};
|
||||
use aes::{
|
||||
@@ -22,8 +25,6 @@ use srp::{
|
||||
};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
const SERIAL_NUMBER: &str = "2";
|
||||
|
||||
pub struct AppleAccount {
|
||||
pub email: String,
|
||||
pub spd: Option<plist::Dictionary>,
|
||||
@@ -34,12 +35,6 @@ pub struct AppleAccount {
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
pub struct AppleAccountBuilder {
|
||||
email: String,
|
||||
debug: Option<bool>,
|
||||
anisette_provider: Option<Box<dyn AnisetteProvider>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoginState {
|
||||
LoggedIn,
|
||||
@@ -49,67 +44,6 @@ pub enum LoginState {
|
||||
NeedsLogin,
|
||||
}
|
||||
|
||||
impl AppleAccountBuilder {
|
||||
/// Create a new AppleAccountBuilder with the given email
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `email`: The Apple ID email address
|
||||
pub fn new(email: &str) -> Self {
|
||||
Self {
|
||||
email: email.to_string(),
|
||||
debug: None,
|
||||
anisette_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// DANGER Set whether to enable debug mode
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `debug`: If true, accept invalid certificates and enable verbose connection logging
|
||||
pub fn danger_debug(mut self, debug: bool) -> Self {
|
||||
self.debug = Some(debug);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn anisette_provider(mut self, anisette_provider: impl AnisetteProvider + 'static) -> Self {
|
||||
self.anisette_provider = Some(Box::new(anisette_provider));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the AppleAccount without logging in
|
||||
///
|
||||
/// # Errors
|
||||
/// 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()));
|
||||
|
||||
AppleAccount::new(&self.email, anisette_provider, debug).await
|
||||
}
|
||||
|
||||
/// Build the AppleAccount and log in
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `password`: The Apple ID password
|
||||
/// - `two_factor_callback`: A callback function that returns the two-factor authentication code
|
||||
/// # Errors
|
||||
/// Returns an error if the reqwest client cannot be built
|
||||
pub async fn login<F>(
|
||||
self,
|
||||
password: &str,
|
||||
two_factor_callback: F,
|
||||
) -> Result<AppleAccount, Report>
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let mut account = self.build().await?;
|
||||
account.login(password, two_factor_callback).await?;
|
||||
Ok(account)
|
||||
}
|
||||
}
|
||||
|
||||
impl AppleAccount {
|
||||
/// Create a new AppleAccountBuilder with the given email
|
||||
///
|
||||
@@ -395,7 +329,7 @@ impl AppleAccount {
|
||||
}
|
||||
|
||||
async fn build_2fa_headers(&mut self) -> Result<HeaderMap, Report> {
|
||||
let mut headers = self.anisette_data.get_header_map(SERIAL_NUMBER.to_string());
|
||||
let mut headers = self.anisette_data.get_header_map();
|
||||
|
||||
let spd = self
|
||||
.spd
|
||||
@@ -427,9 +361,7 @@ impl AppleAccount {
|
||||
|
||||
debug!("GrandSlam service URL: {}", gs_service_url);
|
||||
|
||||
let cpd = self
|
||||
.anisette_data
|
||||
.get_client_provided_data(SERIAL_NUMBER.to_string());
|
||||
let cpd = self.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();
|
||||
@@ -567,7 +499,7 @@ impl AppleAccount {
|
||||
Ok(LoginState::LoggedIn)
|
||||
}
|
||||
|
||||
pub async fn get_app_token(&mut self, app: &str) -> Result<Dictionary, Report> {
|
||||
pub async fn get_app_token(&mut self, app: &str) -> Result<AppToken, Report> {
|
||||
let app = if app.contains("com.apple.gs.") {
|
||||
app.to_string()
|
||||
} else {
|
||||
@@ -596,16 +528,14 @@ impl AppleAccount {
|
||||
.to_vec();
|
||||
|
||||
let gs_service_url = self.grandslam_client.get_url("gsService").await?;
|
||||
let cpd = self
|
||||
.anisette_data
|
||||
.get_client_provided_data(SERIAL_NUMBER.to_string());
|
||||
let cpd = self.anisette_data.get_client_provided_data();
|
||||
|
||||
let request = plist!(dict {
|
||||
"Header": {
|
||||
"Version": "1.0.1"
|
||||
},
|
||||
"Request": {
|
||||
"app": [app],
|
||||
"app": [app.clone()],
|
||||
"c": c,
|
||||
"checksum": checksum,
|
||||
"cpd": cpd,
|
||||
@@ -642,8 +572,24 @@ impl AppleAccount {
|
||||
let token_dict = token
|
||||
.get_dict("t")
|
||||
.context("Failed to get token dictionary from app token")?;
|
||||
let app_token = token_dict
|
||||
.get_dict(&app)
|
||||
.context("Failed to get app token string")?;
|
||||
|
||||
Ok(token_dict.clone())
|
||||
let app_token = AppToken {
|
||||
token: app_token
|
||||
.get_str("token")
|
||||
.context("Failed to get app token string")?
|
||||
.to_string(),
|
||||
duration: app_token
|
||||
.get_signed_integer("duration")
|
||||
.context("Failed to get app token duration")? as u64,
|
||||
expiry: app_token
|
||||
.get_signed_integer("expiry")
|
||||
.context("Failed to get app token expiry")? as u64,
|
||||
};
|
||||
|
||||
Ok(app_token)
|
||||
}
|
||||
|
||||
fn create_session_key(usr: &SrpClientVerifier<Sha256>, name: &str) -> Result<Vec<u8>, Report> {
|
||||
@@ -715,3 +661,10 @@ impl std::fmt::Display for AppleAccount {
|
||||
write!(f, "{} ({:?})", self.email, self.login_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppToken {
|
||||
pub token: String,
|
||||
pub duration: u64,
|
||||
pub expiry: u64,
|
||||
}
|
||||
|
||||
73
isideload/src/auth/builder.rs
Normal file
73
isideload/src/auth/builder.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use rootcause::prelude::*;
|
||||
|
||||
use crate::{
|
||||
anisette::{AnisetteProvider, remote_v3::RemoteV3AnisetteProvider},
|
||||
auth::apple_account::AppleAccount,
|
||||
};
|
||||
|
||||
pub struct AppleAccountBuilder {
|
||||
email: String,
|
||||
debug: Option<bool>,
|
||||
anisette_provider: Option<Box<dyn AnisetteProvider>>,
|
||||
}
|
||||
|
||||
impl AppleAccountBuilder {
|
||||
/// Create a new AppleAccountBuilder with the given email
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `email`: The Apple ID email address
|
||||
pub fn new(email: &str) -> Self {
|
||||
Self {
|
||||
email: email.to_string(),
|
||||
debug: None,
|
||||
anisette_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// DANGER Set whether to enable debug mode
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `debug`: If true, accept invalid certificates and enable verbose connection logging
|
||||
pub fn danger_debug(mut self, debug: bool) -> Self {
|
||||
self.debug = Some(debug);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn anisette_provider(mut self, anisette_provider: impl AnisetteProvider + 'static) -> Self {
|
||||
self.anisette_provider = Some(Box::new(anisette_provider));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the AppleAccount without logging in
|
||||
///
|
||||
/// # Errors
|
||||
/// 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()));
|
||||
|
||||
AppleAccount::new(&self.email, anisette_provider, debug).await
|
||||
}
|
||||
|
||||
/// Build the AppleAccount and log in
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `password`: The Apple ID password
|
||||
/// - `two_factor_callback`: A callback function that returns the two-factor authentication code
|
||||
/// # Errors
|
||||
/// Returns an error if the reqwest client cannot be built
|
||||
pub async fn login<F>(
|
||||
self,
|
||||
password: &str,
|
||||
two_factor_callback: F,
|
||||
) -> Result<AppleAccount, Report>
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let mut account = self.build().await?;
|
||||
account.login(password, two_factor_callback).await?;
|
||||
Ok(account)
|
||||
}
|
||||
}
|
||||
@@ -134,15 +134,15 @@ impl GrandSlam {
|
||||
"X-Mme-Client-Info",
|
||||
HeaderValue::from_str(&self.client_info.client_info)?,
|
||||
);
|
||||
// headers.insert(
|
||||
// "User-Agent",
|
||||
// HeaderValue::from_str(&self.client_info.user_agent)?,
|
||||
// );
|
||||
// headers.insert("X-Xcode-Version", HeaderValue::from_static("14.2 (14C18)"));
|
||||
// headers.insert(
|
||||
// "X-Apple-App-Info",
|
||||
// HeaderValue::from_static("com.apple.gs.xcode.auth"),
|
||||
// );
|
||||
headers.insert(
|
||||
"User-Agent",
|
||||
HeaderValue::from_str(&self.client_info.user_agent)?,
|
||||
);
|
||||
headers.insert("X-Xcode-Version", HeaderValue::from_static("14.2 (14C18)"));
|
||||
headers.insert(
|
||||
"X-Apple-App-Info",
|
||||
HeaderValue::from_static("com.apple.gs.xcode.auth"),
|
||||
);
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod apple_account;
|
||||
pub mod builder;
|
||||
pub mod grandslam;
|
||||
|
||||
@@ -1,13 +1,98 @@
|
||||
use std::sync::Arc;
|
||||
use plist::Dictionary;
|
||||
use plist_macro::plist;
|
||||
use rootcause::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::auth::apple_account::AppleAccount;
|
||||
use crate::{
|
||||
anisette::AnisetteData,
|
||||
auth::{
|
||||
apple_account::{AppToken, AppleAccount},
|
||||
grandslam::GrandSlam,
|
||||
},
|
||||
dev::device_type::DeveloperDeviceType,
|
||||
util::plist::{PlistDataExtract, plist_to_xml_string},
|
||||
};
|
||||
|
||||
struct DeveloperSession {
|
||||
apple_account: Arc<AppleAccount>,
|
||||
pub struct DeveloperSession<'a> {
|
||||
token: AppToken,
|
||||
adsid: String,
|
||||
client: &'a GrandSlam,
|
||||
anisette_data: &'a AnisetteData,
|
||||
}
|
||||
|
||||
impl DeveloperSession {
|
||||
pub fn new(apple_account: Arc<AppleAccount>) -> Self {
|
||||
DeveloperSession { apple_account }
|
||||
impl<'a> DeveloperSession<'a> {
|
||||
pub fn new(
|
||||
token: AppToken,
|
||||
adsid: String,
|
||||
client: &'a GrandSlam,
|
||||
anisette_data: &'a AnisetteData,
|
||||
) -> Self {
|
||||
DeveloperSession {
|
||||
token,
|
||||
adsid,
|
||||
client,
|
||||
anisette_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_account(account: &'a mut AppleAccount) -> Result<Self, Report> {
|
||||
let token = account
|
||||
.get_app_token("xcode.auth")
|
||||
.await
|
||||
.context("Failed to get xcode token from Apple account")?;
|
||||
|
||||
let spd = account
|
||||
.spd
|
||||
.as_ref()
|
||||
.ok_or_else(|| report!("SPD not available, cannot get adsid"))?;
|
||||
|
||||
Ok(DeveloperSession::new(
|
||||
token,
|
||||
spd.get_string("adsid")?,
|
||||
&account.grandslam_client,
|
||||
&account.anisette_data,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn send_developer_request(
|
||||
&self,
|
||||
url: &str,
|
||||
body: Option<Dictionary>,
|
||||
) -> Result<Dictionary, Report> {
|
||||
let body = body.unwrap_or_else(|| Dictionary::new());
|
||||
|
||||
let base = plist!(dict {
|
||||
"clientId": "XABBG36SBA",
|
||||
"protocolVersion": "QH65B2",
|
||||
"requestId": Uuid::new_v4().to_string().to_uppercase(),
|
||||
"userLocale": ["en_US"],
|
||||
});
|
||||
|
||||
let body = base.into_iter().chain(body.into_iter()).collect();
|
||||
|
||||
let text = self
|
||||
.client
|
||||
.post(url)?
|
||||
.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())
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()
|
||||
.context("Developer request failed")?
|
||||
.text()
|
||||
.await
|
||||
.context("Failed to read developer request response text")?;
|
||||
|
||||
let dict: Dictionary = plist::from_bytes(text.as_bytes())
|
||||
.context("Failed to parse developer request plist")?;
|
||||
|
||||
Ok(dict)
|
||||
}
|
||||
|
||||
pub async fn list_teams(&self) -> Result<Dictionary, Report> {
|
||||
self.send_developer_request(&DeveloperDeviceType::Any.dev_url("listTeams"), None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
26
isideload/src/dev/device_type.rs
Normal file
26
isideload/src/dev/device_type.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeveloperDeviceType {
|
||||
Any,
|
||||
Ios,
|
||||
Tvos,
|
||||
Watchos,
|
||||
}
|
||||
|
||||
impl DeveloperDeviceType {
|
||||
pub fn url_segment(&self) -> &'static str {
|
||||
match self {
|
||||
DeveloperDeviceType::Any => "",
|
||||
DeveloperDeviceType::Ios => "ios/",
|
||||
DeveloperDeviceType::Tvos => "tvos/",
|
||||
DeveloperDeviceType::Watchos => "watchos/",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dev_url(&self, endpoint: &str) -> String {
|
||||
format!(
|
||||
"https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA",
|
||||
self.url_segment(),
|
||||
endpoint,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod developer_session;
|
||||
pub mod device_type;
|
||||
|
||||
Reference in New Issue
Block a user