diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index eb74f6f..fc6bdc9 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -40,7 +40,7 @@ async fn main() { match &account { Ok(a) => println!("Logged in. {}", a), - Err(e) => eprintln!("Failed to log in to Apple ID: {:?}", e), + Err(e) => panic!("Failed to log in to Apple ID: {:?}", e), } let mut account = account.unwrap(); @@ -59,7 +59,7 @@ async fn main() { .expect("No developer teams available for this account"); let res = dev_session - .list_devices(team, None) + .revoke_development_cert(team, "2655CFC31A258B1B4D7D9FC22E23AEC3", None) .await .expect("Failed to list developer devices"); diff --git a/isideload/src/dev/developer_session.rs b/isideload/src/dev/developer_session.rs index 4670b27..d9a613b 100644 --- a/isideload/src/dev/developer_session.rs +++ b/isideload/src/dev/developer_session.rs @@ -1,4 +1,4 @@ -use plist::Dictionary; +use plist::{Dictionary, Value}; use plist_macro::{plist, plist_to_xml_string}; use rootcause::prelude::*; use serde::de::DeserializeOwned; @@ -15,7 +15,7 @@ use crate::{ DeveloperDeviceType::{self, *}, *, }, - util::plist::PlistDataExtract, + util::plist::{PlistDataExtract, SensitivePlistAttachment}, }; pub struct DeveloperSession<'a> { @@ -59,12 +59,11 @@ impl<'a> DeveloperSession<'a> { )) } - pub async fn send_developer_request( + async fn send_dev_request_internal( &self, url: &str, - result_key: &str, body: impl Into>, - ) -> Result { + ) -> Result<(Dictionary, Option), Report> { let body = body.into().unwrap_or_else(|| Dictionary::new()); let base = plist!(dict { @@ -102,23 +101,41 @@ impl<'a> DeveloperSession<'a> { let mut server_error: Option = None; if let Some(code) = response_code { if code != 0 { - server_error = Some(format!( - "{} Code {}: {}", - dict.get("userString") - .and_then(|v| v.as_string()) - .unwrap_or("Developer request failed."), - code, - dict.get("resultString") - .and_then(|v| v.as_string()) - .unwrap_or("No error message given.") - )); + let user_string = dict + .get("userString") + .and_then(|v| v.as_string()) + .unwrap_or("Developer request failed."); + + let result_string = dict + .get("resultString") + .and_then(|v| v.as_string()) + .unwrap_or("No error message given."); + + // if user and result string match, only show one + if user_string == result_string { + server_error = Some(format!("{} Code: {}", user_string, code)); + } else { + server_error = + Some(format!("{} Code: {}; {}", user_string, code, result_string)); + } error!(server_error); } } else { warn!("No resultCode in developer request response"); } - let result: Result = dict.get_struct(result_key); + Ok((dict, server_error)) + } + + pub async fn send_dev_request( + &self, + url: &str, + body: impl Into>, + response_key: &str, + ) -> Result { + let (dict, server_error) = self.send_dev_request_internal(url, body).await?; + + let result: Result = dict.get_struct(response_key); if let Err(_) = &result { if let Some(err) = server_error { @@ -129,9 +146,23 @@ impl<'a> DeveloperSession<'a> { Ok(result.context("Failed to extract developer request result")?) } + pub async fn send_dev_request_no_response( + &self, + url: &str, + body: impl Into>, + ) -> Result { + let (dict, server_error) = self.send_dev_request_internal(url, body).await?; + + if let Some(err) = server_error { + bail!(err); + } + + Ok(dict) + } + pub async fn list_teams(&self) -> Result, Report> { let response: Vec = self - .send_developer_request(&dev_url("listTeams", Any), "teams", None) + .send_dev_request(&dev_url("listTeams", Any), None, "teams") .await .context("Failed to list developer teams")?; @@ -148,7 +179,7 @@ impl<'a> DeveloperSession<'a> { }); let devices: Vec = self - .send_developer_request(&dev_url("listDevices", device_type), "devices", body) + .send_dev_request(&dev_url("listDevices", device_type), body, "devices") .await .context("Failed to list developer devices")?; @@ -169,7 +200,7 @@ impl<'a> DeveloperSession<'a> { }); let device: DeveloperDevice = self - .send_developer_request(&dev_url("addDevice", device_type), "device", body) + .send_dev_request(&dev_url("addDevice", device_type), body, "device") .await .context("Failed to add developer device")?; @@ -186,16 +217,83 @@ impl<'a> DeveloperSession<'a> { }); let certs: Vec = self - .send_developer_request( + .send_dev_request( &dev_url("listAllDevelopmentCerts", device_type), - "certificates", body, + "certificates", ) .await .context("Failed to list development certificates")?; Ok(certs) } + + pub async fn revoke_development_cert( + &self, + team: &DeveloperTeam, + serial_number: &str, + device_type: impl Into>, + ) -> Result<(), Report> { + let body = plist!(dict { + "teamId": &team.team_id, + "serialNumber": serial_number, + }); + + self.send_dev_request_no_response( + &dev_url("revokeDevelopmentCert", device_type), + Some(body), + ) + .await + .context("Failed to revoke development certificate")?; + + Ok(()) + } + + pub async fn submit_development_csr( + &self, + team: &DeveloperTeam, + csr_content: String, + machine_name: String, + device_type: impl Into>, + ) -> Result { + let body = plist!(dict { + "teamId": &team.team_id, + "csrContent": csr_content, + "machineName": machine_name, + "machineId": Uuid::new_v4().to_string().to_uppercase(), + }); + + let cert: CertRequest = self + .send_dev_request( + &dev_url("submitDevelopmentCSR", device_type), + body, + "certRequest", + ) + .await + .context("Failed to submit development CSR")?; + + Ok(cert) + } + + pub async fn list_app_ids(&self, team: &DeveloperTeam) -> Result { + let body = plist!(dict { + "teamId": &team.team_id, + }); + + let response: Value = self + .send_dev_request_no_response(&dev_url("listAppIds", Any), body) + .await + .context("Failed to list developer app IDs")? + .into(); + + let app_ids: ListAppIdsResponse = plist::from_value(&response).map_err(|e| { + report!("Failed to deserialize app id response: {:?}", e).attach( + SensitivePlistAttachment::new(response.as_dictionary().clone().unwrap_or_default()), + ) + })?; + + Ok(app_ids) + } } fn dev_url(endpoint: &str, device_type: impl Into>) -> String { diff --git a/isideload/src/dev/structures.rs b/isideload/src/dev/structures.rs index 5631f18..fa7703d 100644 --- a/isideload/src/dev/structures.rs +++ b/isideload/src/dev/structures.rs @@ -1,3 +1,4 @@ +use plist::{Date, Dictionary}; use serde::Deserialize; use serde_bytes::ByteBuf; @@ -40,8 +41,8 @@ pub struct ListTeamsResponse { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct DeveloperDevice { - pub name: String, - pub device_id: String, + pub name: Option, + pub device_id: Option, pub device_number: String, pub status: Option, } @@ -49,8 +50,8 @@ pub struct DeveloperDevice { #[derive(Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct DevelopmentCertificate { - pub name: String, - pub certificate_id: String, + pub name: Option, + pub certificate_id: Option, pub serial_number: Option, pub machine_id: Option, pub cert_content: Option, @@ -75,3 +76,27 @@ impl std::fmt::Debug for DevelopmentCertificate { .finish() } } + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CertRequest { + pub cert_request_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AppId { + pub app_id_id: String, + pub identifier: String, + pub name: String, + pub features: Option, + pub expiration_date: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ListAppIdsResponse { + pub app_ids: Vec, + pub max_quantity: Option, + pub available_quantity: Option, +}