Seperate dev APIs into traits and impliment some app id apis

This commit is contained in:
nab138
2026-01-30 09:36:09 -05:00
parent 6fd088eb65
commit 8f05fecf0f
9 changed files with 372 additions and 259 deletions

View File

@@ -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<Dictionary>,
pub expiration_date: Option<Date>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListAppIdsResponse {
pub app_ids: Vec<AppId>,
pub max_quantity: Option<u64>,
pub available_quantity: Option<u64>,
}
#[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<Option<DeveloperDeviceType>> + Send,
) -> Result<AppId, Report> {
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<Option<DeveloperDeviceType>> + Send,
) -> Result<ListAppIdsResponse, Report> {
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
}
}

View File

@@ -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<String>,
pub certificate_id: Option<String>,
pub serial_number: Option<String>,
pub machine_id: Option<String>,
pub cert_content: Option<ByteBuf>,
}
#[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<Option<DeveloperDeviceType>> + Send,
) -> Result<Vec<DevelopmentCertificate>, Report> {
let body = plist!(dict {
"teamId": &team.team_id,
});
let certs: Vec<DevelopmentCertificate> = 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<Option<DeveloperDeviceType>> + 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<Option<DeveloperDeviceType>> + Send,
) -> Result<CertRequest, Report> {
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
}
}

View File

@@ -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<Vec<DeveloperTeam>, Report> {
let response: Vec<DeveloperTeam> = 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<Option<DeveloperDeviceType>>,
) -> Result<Vec<DeveloperDevice>, Report> {
let body = plist!(dict {
"teamId": &team.team_id,
});
let devices: Vec<DeveloperDevice> = 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<Option<DeveloperDeviceType>>,
) -> Result<DeveloperDevice, Report> {
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<Option<DeveloperDeviceType>>,
) -> Result<Vec<DevelopmentCertificate>, Report> {
let body = plist!(dict {
"teamId": &team.team_id,
});
let certs: Vec<DevelopmentCertificate> = 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<Option<DeveloperDeviceType>>,
) -> 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<Option<DeveloperDeviceType>>,
) -> Result<CertRequest, Report> {
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<ListAppIdsResponse, Report> {
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<Option<DeveloperDeviceType>>) -> String {
format!(
"https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA",
device_type
.into()
.unwrap_or(DeveloperDeviceType::Ios)
.url_segment(),
endpoint,
)
}

View File

@@ -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<Option<DeveloperDeviceType>>) -> String {
format!(
"https://developerservices2.apple.com/services/QH65B2/{}{}.action?clientId=XABBG36SBA",
device_type
.into()
.unwrap_or(DeveloperDeviceType::Ios)
.url_segment(),
endpoint,
)
}

View File

@@ -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<String>,
pub device_id: Option<String>,
pub device_number: String,
pub status: Option<String>,
}
#[async_trait::async_trait]
pub trait DevicesApi {
fn developer_session(&self) -> &DeveloperSession<'_>;
async fn list_devices(
&self,
team: &DeveloperTeam,
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
) -> Result<Vec<DeveloperDevice>, Report> {
let body = plist!(dict {
"teamId": &team.team_id,
});
let devices: Vec<DeveloperDevice> = 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<Option<DeveloperDeviceType>> + Send,
) -> Result<DeveloperDevice, Report> {
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
}
}

View File

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

View File

@@ -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<String>,
pub team_id: String,
pub r#type: Option<String>,
pub status: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ListTeamsResponse {
pub teams: Vec<DeveloperTeam>,
pub result_code: i64,
pub result_string: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DeveloperDevice {
pub name: Option<String>,
pub device_id: Option<String>,
pub device_number: String,
pub status: Option<String>,
}
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DevelopmentCertificate {
pub name: Option<String>,
pub certificate_id: Option<String>,
pub serial_number: Option<String>,
pub machine_id: Option<String>,
pub cert_content: Option<ByteBuf>,
}
// 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<Dictionary>,
pub expiration_date: Option<Date>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListAppIdsResponse {
pub app_ids: Vec<AppId>,
pub max_quantity: Option<u64>,
pub available_quantity: Option<u64>,
}

View File

@@ -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<String>,
pub team_id: String,
pub r#type: Option<String>,
pub status: Option<String>,
}
#[async_trait::async_trait]
pub trait TeamsApi {
fn developer_session(&self) -> &DeveloperSession<'_>;
async fn list_teams(&self) -> Result<Vec<DeveloperTeam>, Report> {
let response: Vec<DeveloperTeam> = 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
}
}