mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
register app ids
This commit is contained in:
11
README.md
11
README.md
@@ -8,9 +8,14 @@ This branch is home to isideload-next, the next major version of isideload. It f
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Signing apps
|
||||
- [ ] Installing apps
|
||||
- [ ] Remove dependency on ring
|
||||
Things left todo before the rewrite is considered finished
|
||||
|
||||
- Download provisioning profiles
|
||||
- Signing apps
|
||||
- Installing apps
|
||||
(will superceed the original isideload at this point)
|
||||
- Remove dependency on ring
|
||||
- More parallelism/cachng for better performance
|
||||
|
||||
## Licensing
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
device_type::{DeveloperDeviceType, dev_url},
|
||||
teams::DeveloperTeam,
|
||||
},
|
||||
util::plist::SensitivePlistAttachment,
|
||||
util::plist::{PlistDataExtract, SensitivePlistAttachment},
|
||||
};
|
||||
use plist::{Data, Date, Dictionary, Value};
|
||||
use plist_macro::plist;
|
||||
@@ -17,7 +17,7 @@ pub struct AppId {
|
||||
pub app_id_id: String,
|
||||
pub identifier: String,
|
||||
pub name: String,
|
||||
pub features: Option<Dictionary>,
|
||||
pub features: Dictionary,
|
||||
pub expiration_date: Option<Date>,
|
||||
}
|
||||
|
||||
@@ -178,3 +178,26 @@ impl AppIdsApi for DeveloperSession {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AppId {
|
||||
pub async fn ensure_group_feature(
|
||||
&mut self,
|
||||
dev_session: &mut DeveloperSession,
|
||||
team: &DeveloperTeam,
|
||||
) -> Result<(), Report> {
|
||||
let app_group_feature_enabled = self.features.get_bool("APG3427HIY")?;
|
||||
|
||||
if !app_group_feature_enabled {
|
||||
let body = plist!(dict {
|
||||
"APG3427HIY": true,
|
||||
});
|
||||
let new_features = dev_session
|
||||
.update_app_id(team, self, body, None)
|
||||
.await?
|
||||
.features;
|
||||
self.features = new_features;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
// I'm planning on redoing this later to better handle entitlements, extensions, etc, but it will do for now
|
||||
|
||||
use crate::SideloadError;
|
||||
use crate::dev::app_ids::{AppId, AppIdsApi};
|
||||
use crate::dev::developer_session::DeveloperSession;
|
||||
use crate::dev::teams::DeveloperTeam;
|
||||
use crate::sideload::builder::ExtensionsBehavior;
|
||||
use crate::sideload::bundle::Bundle;
|
||||
use rootcause::option_ext::OptionExt;
|
||||
use rootcause::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
@@ -73,14 +78,140 @@ impl Application {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_sidestore(&self) -> bool {
|
||||
self.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"
|
||||
}
|
||||
pub fn get_special_app(&self) -> Option<SpecialApp> {
|
||||
let special_app = match self.bundle.bundle_identifier().unwrap_or("") {
|
||||
"com.rileytestut.AltStore" => Some(SpecialApp::AltStore),
|
||||
"com.SideStore.SideStore" => Some(SpecialApp::SideStore),
|
||||
_ => None,
|
||||
};
|
||||
if special_app.is_some() {
|
||||
return special_app;
|
||||
}
|
||||
|
||||
pub fn is_lc_and_sidestore(&self) -> bool {
|
||||
self.bundle
|
||||
if self
|
||||
.bundle
|
||||
.frameworks()
|
||||
.iter()
|
||||
.any(|f| f.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore")
|
||||
{
|
||||
return Some(SpecialApp::SideStoreLc);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn main_bundle_id(&self) -> Result<String, Report> {
|
||||
let str = self
|
||||
.bundle
|
||||
.bundle_identifier()
|
||||
.ok_or_report()
|
||||
.context("Failed to get main bundle identifier")?
|
||||
.to_string();
|
||||
|
||||
Ok(str)
|
||||
}
|
||||
|
||||
pub fn main_app_name(&self) -> Result<String, Report> {
|
||||
let str = self
|
||||
.bundle
|
||||
.bundle_name()
|
||||
.ok_or_report()
|
||||
.context("Failed to get main app name")?
|
||||
.to_string();
|
||||
|
||||
Ok(str)
|
||||
}
|
||||
|
||||
pub fn update_bundle_id(
|
||||
&mut self,
|
||||
main_app_bundle_id: &str,
|
||||
main_app_id_str: &str,
|
||||
) -> Result<(), Report> {
|
||||
let extensions = self.bundle.app_extensions_mut();
|
||||
for ext in extensions.iter_mut() {
|
||||
if let Some(id) = ext.bundle_identifier() {
|
||||
if !(id.starts_with(&main_app_bundle_id) && id.len() > main_app_bundle_id.len()) {
|
||||
bail!(SideloadError::InvalidBundle(format!(
|
||||
"Extension {} is not part of the main app bundle identifier: {}",
|
||||
ext.bundle_name().unwrap_or("Unknown"),
|
||||
id
|
||||
)));
|
||||
} else {
|
||||
ext.set_bundle_identifier(&format!(
|
||||
"{}{}",
|
||||
main_app_id_str,
|
||||
&id[main_app_bundle_id.len()..]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.bundle.set_bundle_identifier(&main_app_id_str);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn register_app_ids(
|
||||
&self,
|
||||
mode: &ExtensionsBehavior,
|
||||
dev_session: &mut DeveloperSession,
|
||||
team: &DeveloperTeam,
|
||||
) -> Result<Vec<AppId>, Report> {
|
||||
let extension_refs: Vec<_> = self.bundle.app_extensions().iter().collect();
|
||||
let mut bundles_with_app_id = vec![&self.bundle];
|
||||
bundles_with_app_id.extend(extension_refs);
|
||||
|
||||
let list_app_ids_response = dev_session
|
||||
.list_app_ids(&team, None)
|
||||
.await
|
||||
.context("Failed to list app IDs for the developer team")?;
|
||||
let app_ids_to_register = match mode {
|
||||
ExtensionsBehavior::RegisterAll => bundles_with_app_id
|
||||
.iter()
|
||||
.filter(|bundle| {
|
||||
let bundle_id = bundle.bundle_identifier().unwrap_or("");
|
||||
!list_app_ids_response
|
||||
.app_ids
|
||||
.iter()
|
||||
.any(|app_id| app_id.identifier == bundle_id)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
if let Some(available) = list_app_ids_response.available_quantity
|
||||
&& app_ids_to_register.len() > available.try_into().unwrap()
|
||||
{
|
||||
bail!(
|
||||
"Not enough available app IDs. {} are required, but only {} are available.",
|
||||
app_ids_to_register.len(),
|
||||
available
|
||||
);
|
||||
}
|
||||
|
||||
for bundle in app_ids_to_register {
|
||||
let id = bundle.bundle_identifier().unwrap_or("");
|
||||
let name = bundle.bundle_name().unwrap_or("");
|
||||
dev_session.add_app_id(&team, name, id, None).await?;
|
||||
}
|
||||
let list_app_id_response = dev_session.list_app_ids(&team, None).await?;
|
||||
let app_ids: Vec<_> = list_app_id_response
|
||||
.app_ids
|
||||
.into_iter()
|
||||
.filter(|app_id| {
|
||||
bundles_with_app_id
|
||||
.iter()
|
||||
.any(|bundle| app_id.identifier == bundle.bundle_identifier().unwrap_or(""))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(app_ids)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SpecialApp {
|
||||
SideStore,
|
||||
SideStoreLc,
|
||||
AltStore,
|
||||
StikStore,
|
||||
}
|
||||
|
||||
@@ -39,11 +39,44 @@ pub enum MaxCertsBehavior {
|
||||
Prompt(fn(&Vec<DevelopmentCertificate>) -> Option<Vec<DevelopmentCertificate>>),
|
||||
}
|
||||
|
||||
/// The actual behavior choices for extensions (non-prompt variants)
|
||||
pub enum ExtensionsBehaviorChoice {
|
||||
/// Use the main app id/profile for all sub-bundles
|
||||
ReuseMain,
|
||||
/// Create separate app ids/profiles for each sub-bundle
|
||||
RegisterAll,
|
||||
/// Remove all sub-bundles
|
||||
RemoveExtensions,
|
||||
}
|
||||
|
||||
/// Behavior used when an app contains sub bundles
|
||||
pub enum ExtensionsBehavior {
|
||||
/// Use the main app id/profile for all sub-bundles
|
||||
ReuseMain,
|
||||
/// Create separate app ids/profiles for each sub-bundle
|
||||
RegisterAll,
|
||||
/// Remove all sub-bundles
|
||||
RemoveExtensions,
|
||||
/// Prompt the user to choose one of the above behaviors
|
||||
Prompt(fn(&Vec<String>) -> ExtensionsBehaviorChoice),
|
||||
}
|
||||
|
||||
impl From<ExtensionsBehaviorChoice> for ExtensionsBehavior {
|
||||
fn from(choice: ExtensionsBehaviorChoice) -> Self {
|
||||
match choice {
|
||||
ExtensionsBehaviorChoice::ReuseMain => ExtensionsBehavior::ReuseMain,
|
||||
ExtensionsBehaviorChoice::RegisterAll => ExtensionsBehavior::RegisterAll,
|
||||
ExtensionsBehaviorChoice::RemoveExtensions => ExtensionsBehavior::RemoveExtensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SideloaderBuilder {
|
||||
developer_session: DeveloperSession,
|
||||
apple_email: String,
|
||||
team_selection: Option<TeamSelection>,
|
||||
max_certs_behavior: Option<MaxCertsBehavior>,
|
||||
extensions_behavior: Option<ExtensionsBehavior>,
|
||||
storage: Option<Box<dyn SideloadingStorage>>,
|
||||
machine_name: Option<String>,
|
||||
}
|
||||
@@ -57,6 +90,7 @@ impl SideloaderBuilder {
|
||||
machine_name: None,
|
||||
apple_email,
|
||||
max_certs_behavior: None,
|
||||
extensions_behavior: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +128,11 @@ impl SideloaderBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn extensions_behavior(mut self, behavior: ExtensionsBehavior) -> Self {
|
||||
self.extensions_behavior = Some(behavior);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the `Sideloader` instance with the provided configuration
|
||||
pub fn build(self) -> Sideloader {
|
||||
Sideloader::new(
|
||||
@@ -104,6 +143,8 @@ impl SideloaderBuilder {
|
||||
self.machine_name.unwrap_or_else(|| "isideload".to_string()),
|
||||
self.storage
|
||||
.unwrap_or_else(|| Box::new(crate::util::storage::new_storage())),
|
||||
self.extensions_behavior
|
||||
.unwrap_or(ExtensionsBehavior::RegisterAll),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::{
|
||||
teams::{DeveloperTeam, TeamsApi},
|
||||
},
|
||||
sideload::{
|
||||
TeamSelection, application::Application, builder::MaxCertsBehavior,
|
||||
TeamSelection,
|
||||
application::{Application, SpecialApp},
|
||||
builder::{ExtensionsBehavior, MaxCertsBehavior},
|
||||
cert_identity::CertificateIdentity,
|
||||
},
|
||||
util::{device::IdeviceInfo, storage::SideloadingStorage},
|
||||
@@ -24,6 +26,7 @@ pub struct Sideloader {
|
||||
machine_name: String,
|
||||
apple_email: String,
|
||||
max_certs_behavior: MaxCertsBehavior,
|
||||
extensions_behavior: ExtensionsBehavior,
|
||||
}
|
||||
|
||||
impl Sideloader {
|
||||
@@ -37,6 +40,7 @@ impl Sideloader {
|
||||
max_certs_behavior: MaxCertsBehavior,
|
||||
machine_name: String,
|
||||
storage: Box<dyn SideloadingStorage>,
|
||||
extensions_behavior: ExtensionsBehavior,
|
||||
) -> Self {
|
||||
Sideloader {
|
||||
team_selection,
|
||||
@@ -45,6 +49,7 @@ impl Sideloader {
|
||||
machine_name,
|
||||
apple_email,
|
||||
max_certs_behavior,
|
||||
extensions_behavior,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +78,44 @@ impl Sideloader {
|
||||
.await?;
|
||||
|
||||
let mut app = Application::new(app_path)?;
|
||||
let special = app.get_special_app();
|
||||
|
||||
let is_sidestore = app.is_sidestore();
|
||||
let is_lc_and_sidestore = app.is_lc_and_sidestore();
|
||||
let main_bundle_id = app.main_bundle_id()?;
|
||||
let main_app_name = app.main_app_name()?;
|
||||
let main_app_id_str = format!("{}.{}", main_bundle_id, team.team_id);
|
||||
app.update_bundle_id(&main_bundle_id, &main_app_id_str)?;
|
||||
let mut app_ids = app
|
||||
.register_app_ids(&self.extensions_behavior, &mut self.dev_session, &team)
|
||||
.await?;
|
||||
let main_app_id = match app_ids
|
||||
.iter()
|
||||
.find(|app_id| app_id.identifier == main_app_id_str)
|
||||
{
|
||||
Some(id) => id,
|
||||
None => {
|
||||
bail!(
|
||||
"Main app ID {} not found in registered app IDs",
|
||||
main_app_id_str
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
for app_id in app_ids.iter_mut() {
|
||||
app_id
|
||||
.ensure_group_feature(&mut self.dev_session, &team)
|
||||
.await?;
|
||||
|
||||
// TODO: Increased memory entitlement
|
||||
}
|
||||
|
||||
let group_identifier = format!(
|
||||
"group.{}",
|
||||
if Some(SpecialApp::SideStoreLc) == special {
|
||||
format!("com.SideStore.SideStore.{}", team.team_id)
|
||||
} else {
|
||||
main_app_id_str.clone()
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ pub trait PlistDataExtract {
|
||||
fn get_string(&self, key: &str) -> Result<String, Report>;
|
||||
fn get_signed_integer(&self, key: &str) -> Result<i64, Report>;
|
||||
fn get_dict(&self, key: &str) -> Result<&Dictionary, Report>;
|
||||
fn get_bool(&self, key: &str) -> Result<bool, Report>;
|
||||
fn get_struct<T: DeserializeOwned>(&self, key: &str) -> Result<T, Report>;
|
||||
}
|
||||
|
||||
@@ -120,4 +121,11 @@ impl PlistDataExtract for Dictionary {
|
||||
})?;
|
||||
Ok(struct_data)
|
||||
}
|
||||
|
||||
fn get_bool(&self, key: &str) -> Result<bool, Report> {
|
||||
self.get(key).and_then(|v| v.as_boolean()).ok_or_else(|| {
|
||||
report!("Plist missing boolean for key '{}'", key)
|
||||
.attach(SensitivePlistAttachment::new(self.clone()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user