diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index fc6bdc9..dd7be2a 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -1,8 +1,9 @@ use std::env; use isideload::{ - anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccount, - dev::developer_session::DeveloperSession, + anisette::remote_v3::RemoteV3AnisetteProvider, + auth::apple_account::AppleAccount, + dev::developer_session::{AppIdsApi, DeveloperSession, TeamsApi}, }; use tracing::Level; @@ -59,9 +60,9 @@ async fn main() { .expect("No developer teams available for this account"); let res = dev_session - .revoke_development_cert(team, "2655CFC31A258B1B4D7D9FC22E23AEC3", None) + .list_app_ids(team, None) .await - .expect("Failed to list developer devices"); + .expect("Failed to add appid"); println!("{:?}", res); } diff --git a/isideload/src/dev/app_ids.rs b/isideload/src/dev/app_ids.rs new file mode 100644 index 0000000..bc0ee0e --- /dev/null +++ b/isideload/src/dev/app_ids.rs @@ -0,0 +1,93 @@ +use crate::{ + dev::{ + developer_session::DeveloperSession, + device_type::{DeveloperDeviceType, dev_url}, + teams::DeveloperTeam, + }, + util::plist::SensitivePlistAttachment, +}; +use plist::{Date, Dictionary, Value}; +use plist_macro::plist; +use rootcause::prelude::*; +use serde::Deserialize; + +#[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, +} + +#[async_trait::async_trait] +pub trait AppIdsApi { + fn developer_session(&self) -> &DeveloperSession<'_>; + + async fn add_app_id( + &self, + team: &DeveloperTeam, + name: &str, + identifier: &str, + device_type: impl Into> + Send, + ) -> Result { + let body = plist!(dict { + "teamId": &team.team_id, + "identifier": identifier, + "name": name, + }); + + let app_id: AppId = self + .developer_session() + .send_dev_request(&dev_url("addAppId", device_type), body, "appId") + .await + .context("Failed to add developer app ID")?; + + Ok(app_id) + } + + async fn list_app_ids( + &self, + team: &DeveloperTeam, + device_type: impl Into> + Send, + ) -> Result { + let body = plist!(dict { + "teamId": &team.team_id, + }); + + let response: Value = self + .developer_session() + .send_dev_request_no_response(&dev_url("listAppIds", device_type), 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() + .unwrap_or(&Dictionary::new()) + .clone(), + ), + ) + })?; + + Ok(app_ids) + } +} + +impl AppIdsApi for DeveloperSession<'_> { + fn developer_session(&self) -> &DeveloperSession<'_> { + self + } +} diff --git a/isideload/src/dev/certificates.rs b/isideload/src/dev/certificates.rs new file mode 100644 index 0000000..bc434e5 --- /dev/null +++ b/isideload/src/dev/certificates.rs @@ -0,0 +1,128 @@ +use crate::dev::{ + developer_session::DeveloperSession, + device_type::{DeveloperDeviceType, dev_url}, + teams::DeveloperTeam, +}; +use plist_macro::plist; +use rootcause::prelude::*; +use serde::Deserialize; +use serde_bytes::ByteBuf; +use uuid::Uuid; + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DevelopmentCertificate { + pub name: Option, + pub certificate_id: Option, + pub serial_number: Option, + pub machine_id: Option, + pub cert_content: Option, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CertRequest { + pub cert_request_id: String, +} + +// the automatic debug implementation spams the console with the cert content bytes +impl std::fmt::Debug for DevelopmentCertificate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DevelopmentCertificate") + .field("name", &self.name) + .field("certificate_id", &self.certificate_id) + .field("serial_number", &self.serial_number) + .field("machine_id", &self.machine_id) + .field( + "cert_content", + &self + .cert_content + .as_ref() + .map(|c| format!("Some([{} bytes])", c.len())) + .unwrap_or("None".to_string()), + ) + .finish() + } +} + +#[async_trait::async_trait] +pub trait CertificatesApi { + fn developer_session(&self) -> &DeveloperSession<'_>; + + async fn list_all_development_certs( + &self, + team: &DeveloperTeam, + device_type: impl Into> + Send, + ) -> Result, Report> { + let body = plist!(dict { + "teamId": &team.team_id, + }); + + let certs: Vec = self + .developer_session() + .send_dev_request( + &dev_url("listAllDevelopmentCerts", device_type), + body, + "certificates", + ) + .await + .context("Failed to list development certificates")?; + + Ok(certs) + } + + async fn revoke_development_cert( + &self, + team: &DeveloperTeam, + serial_number: &str, + device_type: impl Into> + Send, + ) -> Result<(), Report> { + let body = plist!(dict { + "teamId": &team.team_id, + "serialNumber": serial_number, + }); + + self.developer_session() + .send_dev_request_no_response( + &dev_url("revokeDevelopmentCert", device_type), + Some(body), + ) + .await + .context("Failed to revoke development certificate")?; + + Ok(()) + } + + async fn submit_development_csr( + &self, + team: &DeveloperTeam, + csr_content: String, + machine_name: String, + device_type: impl Into> + Send, + ) -> 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 + .developer_session() + .send_dev_request( + &dev_url("submitDevelopmentCSR", device_type), + body, + "certRequest", + ) + .await + .context("Failed to submit development CSR")?; + + Ok(cert) + } +} + +impl CertificatesApi for DeveloperSession<'_> { + fn developer_session(&self) -> &DeveloperSession<'_> { + self + } +} diff --git a/isideload/src/dev/developer_session.rs b/isideload/src/dev/developer_session.rs index d9a613b..f279791 100644 --- a/isideload/src/dev/developer_session.rs +++ b/isideload/src/dev/developer_session.rs @@ -1,4 +1,4 @@ -use plist::{Dictionary, Value}; +use plist::Dictionary; use plist_macro::{plist, plist_to_xml_string}; use rootcause::prelude::*; use serde::de::DeserializeOwned; @@ -11,13 +11,15 @@ use crate::{ apple_account::{AppToken, AppleAccount}, grandslam::GrandSlam, }, - dev::structures::{ - DeveloperDeviceType::{self, *}, - *, - }, - util::plist::{PlistDataExtract, SensitivePlistAttachment}, + util::plist::PlistDataExtract, }; +pub use super::app_ids::*; +pub use super::certificates::*; +pub use super::device_type::DeveloperDeviceType; +pub use super::devices::*; +pub use super::teams::*; + pub struct DeveloperSession<'a> { token: AppToken, adsid: String, @@ -159,150 +161,4 @@ impl<'a> DeveloperSession<'a> { Ok(dict) } - - pub async fn list_teams(&self) -> Result, Report> { - let response: Vec = self - .send_dev_request(&dev_url("listTeams", Any), None, "teams") - .await - .context("Failed to list developer teams")?; - - Ok(response) - } - - pub async fn list_devices( - &self, - team: &DeveloperTeam, - device_type: impl Into>, - ) -> Result, Report> { - let body = plist!(dict { - "teamId": &team.team_id, - }); - - let devices: Vec = self - .send_dev_request(&dev_url("listDevices", device_type), body, "devices") - .await - .context("Failed to list developer devices")?; - - Ok(devices) - } - - pub async fn add_device( - &self, - team: &DeveloperTeam, - name: &str, - udid: &str, - device_type: impl Into>, - ) -> Result { - let body = plist!(dict { - "teamId": &team.team_id, - "name": name, - "deviceNumber": udid, - }); - - let device: DeveloperDevice = self - .send_dev_request(&dev_url("addDevice", device_type), body, "device") - .await - .context("Failed to add developer device")?; - - Ok(device) - } - - pub async fn list_all_development_certs( - &self, - team: &DeveloperTeam, - device_type: impl Into>, - ) -> Result, Report> { - let body = plist!(dict { - "teamId": &team.team_id, - }); - - let certs: Vec = self - .send_dev_request( - &dev_url("listAllDevelopmentCerts", device_type), - 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 { - format!( - "https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA", - device_type - .into() - .unwrap_or(DeveloperDeviceType::Ios) - .url_segment(), - endpoint, - ) } diff --git a/isideload/src/dev/device_type.rs b/isideload/src/dev/device_type.rs new file mode 100644 index 0000000..d2a1001 --- /dev/null +++ b/isideload/src/dev/device_type.rs @@ -0,0 +1,29 @@ +#[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(endpoint: &str, device_type: impl Into>) -> String { + format!( + "https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA", + device_type + .into() + .unwrap_or(DeveloperDeviceType::Ios) + .url_segment(), + endpoint, + ) +} diff --git a/isideload/src/dev/devices.rs b/isideload/src/dev/devices.rs new file mode 100644 index 0000000..860f371 --- /dev/null +++ b/isideload/src/dev/devices.rs @@ -0,0 +1,68 @@ +use crate::dev::{ + developer_session::DeveloperSession, + device_type::{DeveloperDeviceType, dev_url}, + teams::DeveloperTeam, +}; +use plist_macro::plist; +use rootcause::prelude::*; +use serde::Deserialize; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DeveloperDevice { + pub name: Option, + pub device_id: Option, + pub device_number: String, + pub status: Option, +} + +#[async_trait::async_trait] +pub trait DevicesApi { + fn developer_session(&self) -> &DeveloperSession<'_>; + + async fn list_devices( + &self, + team: &DeveloperTeam, + device_type: impl Into> + Send, + ) -> Result, Report> { + let body = plist!(dict { + "teamId": &team.team_id, + }); + + let devices: Vec = self + .developer_session() + .send_dev_request(&dev_url("listDevices", device_type), body, "devices") + .await + .context("Failed to list developer devices")?; + + Ok(devices) + } + + async fn add_device( + &self, + team: &DeveloperTeam, + name: &str, + udid: &str, + device_type: impl Into> + Send, + ) -> Result { + let body = plist!(dict { + "teamId": &team.team_id, + "name": name, + "deviceNumber": udid, + }); + + let device: DeveloperDevice = self + .developer_session() + .send_dev_request(&dev_url("addDevice", device_type), body, "device") + .await + .context("Failed to add developer device")?; + + Ok(device) + } +} + +impl DevicesApi for DeveloperSession<'_> { + fn developer_session(&self) -> &DeveloperSession<'_> { + self + } +} diff --git a/isideload/src/dev/mod.rs b/isideload/src/dev/mod.rs index 1a66810..1f8b80a 100644 --- a/isideload/src/dev/mod.rs +++ b/isideload/src/dev/mod.rs @@ -1,2 +1,6 @@ +pub mod app_ids; +pub mod certificates; pub mod developer_session; -pub mod structures; +pub mod device_type; +pub mod devices; +pub mod teams; diff --git a/isideload/src/dev/structures.rs b/isideload/src/dev/structures.rs deleted file mode 100644 index fa7703d..0000000 --- a/isideload/src/dev/structures.rs +++ /dev/null @@ -1,102 +0,0 @@ -use plist::{Date, Dictionary}; -use serde::Deserialize; -use serde_bytes::ByteBuf; - -#[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/", - } - } -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DeveloperTeam { - pub name: Option, - pub team_id: String, - pub r#type: Option, - pub status: Option, -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ListTeamsResponse { - pub teams: Vec, - pub result_code: i64, - pub result_string: Option, -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DeveloperDevice { - pub name: Option, - pub device_id: Option, - pub device_number: String, - pub status: Option, -} - -#[derive(Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DevelopmentCertificate { - pub name: Option, - pub certificate_id: Option, - pub serial_number: Option, - pub machine_id: Option, - pub cert_content: Option, -} - -// the automatic debug implementation spams the console with the cert content bytes -impl std::fmt::Debug for DevelopmentCertificate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DevelopmentCertificate") - .field("name", &self.name) - .field("certificate_id", &self.certificate_id) - .field("serial_number", &self.serial_number) - .field("machine_id", &self.machine_id) - .field( - "cert_content", - &self - .cert_content - .as_ref() - .map(|c| format!("Some([{} bytes])", c.len())) - .unwrap_or("None".to_string()), - ) - .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, -} diff --git a/isideload/src/dev/teams.rs b/isideload/src/dev/teams.rs new file mode 100644 index 0000000..5bf4b98 --- /dev/null +++ b/isideload/src/dev/teams.rs @@ -0,0 +1,36 @@ +use crate::dev::{ + developer_session::DeveloperSession, + device_type::{DeveloperDeviceType::*, dev_url}, +}; +use rootcause::prelude::*; +use serde::Deserialize; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DeveloperTeam { + pub name: Option, + pub team_id: String, + pub r#type: Option, + pub status: Option, +} + +#[async_trait::async_trait] +pub trait TeamsApi { + fn developer_session(&self) -> &DeveloperSession<'_>; + + async fn list_teams(&self) -> Result, Report> { + let response: Vec = self + .developer_session() + .send_dev_request(&dev_url("listTeams", Any), None, "teams") + .await + .context("Failed to list developer teams")?; + + Ok(response) + } +} + +impl TeamsApi for DeveloperSession<'_> { + fn developer_session(&self) -> &DeveloperSession<'_> { + self + } +}