Installation d’un profil de configuration sur iPhone – par programmation

Je souhaite envoyer un profil de configuration avec mon application iPhone et l’installer si nécessaire.

Attention, nous parlons d’un profil de configuration, pas d’un profil d’approvisionnement.

Tout d’abord, une telle tâche est possible. Si vous placez un profil de configuration sur une page Web et cliquez dessus depuis Safari, il sera installé. Si vous envoyez un profil par courrier électronique et cliquez sur la pièce jointe, celle-ci sera également installée. “Installé” dans ce cas signifie “L’installation de l’interface utilisateur est appelée” – mais je n’ai même pas pu aller aussi loin.

Je travaillais donc sur la théorie selon laquelle le lancement d’une installation de profil implique de naviguer vers celle-ci en tant qu’URL. J’ai ajouté le profil à mon lot d’applications.

A) D’abord, j’ai essayé [sharedApp openURL] avec le fichier: // URL dans mon bundle. Pas de chance – rien ne se passe.

B) J’ai ensuite ajouté une page HTML à mon bundle qui contient un lien vers le profil et l’a chargé dans une UIWebView. Cliquer sur le lien ne fait rien. Le chargement d’une page identique à partir d’un serveur Web dans Safari, cependant, fonctionne correctement – le lien est cliquable, le profil est installé. J’ai fourni un UIWebViewDelegate, répondant à OUI à chaque demande de navigation – aucune différence.

C) Ensuite, j’ai essayé de charger la même page Web depuis mon bundle dans Safari (en utilisant [sharedApp openURL] – rien ne se produit. Je suppose que Safari ne peut pas voir les fichiers dans mon bundle d’applications.

D) Le téléchargement de la page et du profil sur un serveur Web est faisable, mais une douleur au niveau de l’organisation, sans compter une source supplémentaire de pannes (et si aucune couverture 3G? Etc.).

Ma grande question est donc: ** comment installer un profil par programme?

Et les petites questions sont: qu’est-ce qui peut rendre un lien non cliquable dans un UIWebView? Est-il possible de charger un fichier: // URL depuis mon bundle dans Safari? Sinon, existe-t-il un emplacement local sur iPhone où je peux placer des fichiers et Safari peut les trouver?

EDIT on B): le problème réside dans le fait que nous sums liés à un profil. Je l’ai renommé de .mobileconfig en .xml (parce que c’est vraiment XML), j’ai modifié le lien. Et le lien a fonctionné dans mon UIWebView. Renommez-le – même chose. Il semble que UIWebView répugne à utiliser des applications – puisque l’installation du profil ferme l’application. J’ai essayé de lui dire que c’est OK – au moyen de UIWebViewDelegate – mais cela n’a pas convaincu. Même comportement pour mailto: URL dans UIWebView.

Pour mailto: URL, la technique courante consiste à les traduire en appels [openURL], mais cela ne fonctionne pas tout à fait dans mon cas, voir le scénario A.

Pour itms: URL, cependant, UIWebView fonctionne comme prévu …

EDIT2: essayé de fournir une URL de données à Safari via [openURL] – ne fonctionne pas, voir ici: iPhone Open DATA: URL dans Safari

EDIT3: trouvé beaucoup d’informations sur la façon dont Safari ne supporte pas les fichiers: // URL. UIWebView, cependant, fait beaucoup. De plus, Safari sur le simulateur les ouvre très bien. Ce dernier bit est le plus frustrant.


EDIT4: Je n’ai jamais trouvé de solution. Au lieu de cela, je mets en place une interface Web à deux bits où les utilisateurs peuvent commander le profil envoyé par courrier électronique.

1) Installer un serveur local comme RoutingHTTPServer

2) Configurez l’en-tête personnalisé:

[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"]; 

3) Configurez le chemin racine local pour le fichier mobileconfig (Documents):

 [httpServer setDocumentRoot:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]]; 

4) Pour permettre au serveur Web d’envoyer le fichier, ajoutez ceci:

 Appdelegate.h UIBackgroundTaskIdentifier bgTask; Appdelegate.m - (void)applicationDidEnterBackground:(UIApplication *)application { NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil); bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{ dispatch_async(dispatch_get_main_queue(), ^{ [application endBackgroundTask:self->bgTask]; self->bgTask = UIBackgroundTaskInvalid; }); }]; } 

5) Dans votre contrôleur, appelez safari avec le nom du mobileconfig stocké dans Documents:

 [[UIApplication sharedApplication] openURL:[NSURL URLWithSsortingng: @"http://localhost:12345/MyProfile.mobileconfig"]]; 

La réponse de malinois a fonctionné pour moi, MAIS, je voulais une solution qui reviendrait automatiquement à l’application après l’installation de mobileconfig.

Cela m’a pris 4 heures, mais voici la solution, basée sur l’idée de malinois d’avoir un serveur http local: vous renvoyez le HTML au safari qui se rafraîchit; la première fois que le serveur retourne le mobileconfig, et la deuxième fois, il retourne le schéma d’URL personnalisé pour revenir à votre application. L’UX est ce que je voulais: l’application appelle safari, safari ouvre mobileconfig, quand l’utilisateur frappe “done” sur mobileconfig, alors safari charge à nouveau votre application (schéma d’URL personnalisé).

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. _httpServer = [[RoutingHTTPServer alloc] init]; [_httpServer setPort:8000]; // TODO: make sure this port isn't already in use _firstTime = TRUE; [_httpServer handleMethod:@"GET" withPath:@"/start" target:self selector:@selector(handleMobileconfigRootRequest:withResponse:)]; [_httpServer handleMethod:@"GET" withPath:@"/load" target:self selector:@selector(handleMobileconfigLoadRequest:withResponse:)]; NSMutableSsortingng* path = [NSMutableSsortingng ssortingngWithSsortingng:[[NSBundle mainBundle] bundlePath]]; [path appendSsortingng:@"/your.mobileconfig"]; _mobileconfigData = [NSData dataWithContentsOfFile:path]; [_httpServer start:NULL]; return YES; } - (void)handleMobileconfigRootRequest:(RouteRequest *)request withResponse:(RouteResponse *)response { NSLog(@"handleMobileconfigRootRequest"); [response respondWithSsortingng:@"Profile Install\ "]; } - (void)handleMobileconfigLoadRequest:(RouteRequest *)request withResponse:(RouteResponse *)response { if( _firstTime ) { NSLog(@"handleMobileconfigLoadRequest, first time"); _firstTime = FALSE; [response setHeader:@"Content-Type" value:@"application/x-apple-aspen-config"]; [response respondWithData:_mobileconfigData]; } else { NSLog(@"handleMobileconfigLoadRequest, NOT first time"); [response setStatusCode:302]; // or 301 [response setHeader:@"Location" value:@"yourapp://custom/scheme"]; } } 

… et voici le code à appeler depuis l’application (ie viewcontroller):

 [[UIApplication sharedApplication] openURL:[NSURL URLWithSsortingng: @"http://localhost:8000/start/"]]; 

J’espère que cela aide quelqu’un.

J’ai écrit un cours pour installer un fichier mobileconfig via Safari, puis revenir à l’application. Il s’appuie sur le moteur de serveur http Swifter que j’ai trouvé fonctionner correctement. Je veux partager mon code ci-dessous pour ce faire. Il est inspiré par plusieurs sources de code que j’ai trouvées flottant sur le site www. Donc, si vous trouvez des morceaux de votre propre code, des consortingbutions à vous.

 class ConfigServer: NSObject { //TODO: Don't foget to add your custom app url scheme to info.plist if you have one! private enum ConfigState: Int { case Stopped, Ready, InstalledConfig, BackToApp } internal let listeningPort: in_port_t! = 8080 internal var configName: Ssortingng! = "Profile install" private var localServer: HttpServer! private var returnURL: Ssortingng! private var configData: NSData! private var serverState: ConfigState = .Stopped private var startTime: NSDate! private var registeredForNotifications = false private var backgroundTask = UIBackgroundTaskInvalid deinit { unregisterFromNotifications() } init(configData: NSData, returnURL: Ssortingng) { super.init() self.returnURL = returnURL self.configData = configData localServer = HttpServer() self.setupHandlers() } //MARK:- Control functions internal func start() -> Bool { let page = self.baseURL("start/") let url: NSURL = NSURL(ssortingng: page)! if UIApplication.sharedApplication().canOpenURL(url) { var error: NSError? localServer.start(listeningPort, error: &error) if error == nil { startTime = NSDate() serverState = .Ready registerForNotifications() UIApplication.sharedApplication().openURL(url) return true } else { self.stop() } } return false } internal func stop() { if serverState != .Stopped { serverState = .Stopped unregisterFromNotifications() } } //MARK:- Private functions private func setupHandlers() { localServer["/start"] = { request in if self.serverState == .Ready { let page = self.basePage("install/") return .OK(.HTML(page)) } else { return .NotFound } } localServer["/install"] = { request in switch self.serverState { case .Stopped: return .NotFound case .Ready: self.serverState = .InstalledConfig return HttpResponse.RAW(200, "OK", ["Content-Type": "application/x-apple-aspen-config"], self.configData!) case .InstalledConfig: return .MovedPermanently(self.returnURL) case .BackToApp: let page = self.basePage(nil) return .OK(.HTML(page)) } } } private func baseURL(pathComponent: Ssortingng?) -> Ssortingng { var page = "http://localhost:\(listeningPort)" if let component = pathComponent { page += "/\(component)" } return page } private func basePage(pathComponent: Ssortingng?) -> Ssortingng { var page = "" + "\(self.configName)" if let component = pathComponent { let script = "function load() { window.location.href='\(self.baseURL(component))'; }window.setInterval(load, 600);" page += "" } page += "" return page } private func returnedToApp() { if serverState != .Stopped { serverState = .BackToApp localServer.stop() } // Do whatever else you need to to } private func registerForNotifications() { if !registeredForNotifications { let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.addObserver(self, selector: "didEnterBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil) notificationCenter.addObserver(self, selector: "willEnterForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil) registeredForNotifications = true } } private func unregisterFromNotifications() { if registeredForNotifications { let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) notificationCenter.removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil) registeredForNotifications = false } } internal func didEnterBackground(notification: NSNotification) { if serverState != .Stopped { startBackgroundTask() } } internal func willEnterForeground(notification: NSNotification) { if backgroundTask != UIBackgroundTaskInvalid { stopBackgroundTask() returnedToApp() } } private func startBackgroundTask() { let application = UIApplication.sharedApplication() backgroundTask = application.beginBackgroundTaskWithExpirationHandler() { dispatch_async(dispatch_get_main_queue()) { self.stopBackgroundTask() } } } private func stopBackgroundTask() { if backgroundTask != UIBackgroundTaskInvalid { UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask) backgroundTask = UIBackgroundTaskInvalid } } } 

Je pense que ce que vous recherchez est “Inscription en direct” en utilisant le protocole SCEP (Simple Certificate Enrollment Protocol). Jetez un coup d’œil au Guide d’inscription OTA et à la section SCEP Payload du Guide de déploiement d’entreprise .

Selon la vue d’ ensemble de la configuration de l’ appareil, vous ne disposez que de quatre options:

  • Installation de bureau via USB
  • Pièce jointe)
  • Site Web (via Safari)
  • Inscription et dissortingbution en direct

Cette page explique comment utiliser les images de votre bundle dans une UIWebView.

La même chose fonctionnerait peut-être aussi pour un profil de configuration.

Avez-vous essayé de demander à l’application d’envoyer le profil de configuration à l’utilisateur la première fois qu’il démarre?

 - (IBAction) mailConfigProfile {
      MFMailComposeViewController * email = [[MFMailComposeViewController alloc] init];
      email.mailComposeDelegate = self;

      [email setSubject: @ "Profil de configuration de mon application"];

      NSSsortingng * filePath = [[NSBundle mainBundle] pathForResource: @ "MyAppConfig" de type: @ "mobileconfig"];  
      NSData * configData = [NSData dataWithContentsOfFile: filePath]; 
      [email addAttachmentData: configData mimeType: @ "application / x-apple-aspen-config" nomfichier: @ "MyAppConfig.mobileconfig"];

      NSSsortingng * emailBody = @ "S'il vous plaît appuyez sur la pièce jointe pour installer le profil de configuration pour mon application.";
      [email setMessageBody: emailBody isHTML: YES];

      [self presentModalViewController: email animé: OUI];
      [communiqué par courriel];
 }

J’en ai fait un IBAction au cas où vous souhaiteriez le relier à un bouton pour que l’utilisateur puisse le renvoyer à tout moment. Notez que je ne peux pas avoir le type MIME correct dans l’exemple ci-dessus, vous devriez le vérifier.

J’ai cependant une autre façon de fonctionner (malheureusement, je n’ai pas de profil de configuration à tester):

 // Crée un UIViewController contenant un UIWebView
 - (void) viewDidLoad {
     [super viewDidLoad];
     // Indique à webView de charger le profil de configuration
     [self.webView loadRequest: [NSURLRequest requestWithURL: self.cpUrl]];
 }

 // Ensuite dans votre code quand vous voyez que le profil n'a pas été installé:
 ConfigProfileViewController * cpVC = 
         [[ConfigProfileViewController alloc] initWithNibName: @ "MobileConfigView"
                                                       bundle: nil];
 NSSsortingng * cpPath = [[NSBundle mainBundle] pathForResource: @ "configProfileName"
                                                    ofType: @ ". mobileconfig"];
 cpVC.cpURL = [NSURL URLWithSsortingng: cpPath];
 // Ensuite, si votre application a un contrôleur de navigation, vous pouvez simplement pousser la vue 
 // on va charger votre configuration mobile (qui devrait l'installer).
 [self.navigationController pushViewController: contrôleur animé: OUI];
 [cpVC release];

Vous ne savez pas pourquoi vous avez besoin d’un profil de configuration, mais vous pouvez essayer de pirater avec ce délégué depuis UIWebView:

 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if (navigationType == UIWebViewNavigationTypeLinkClicked) { //do something with link clicked return NO; } return YES; } 

Sinon, vous pouvez envisager d’activer l’installation à partir d’un serveur sécurisé.

Hébergez simplement le fichier sur un site Web avec l’extension * .mobileconfig et définissez le type MIME sur application / x-apple-aspen-config. L’utilisateur sera invité, mais s’il accepte, le profil doit être installé.

Vous ne pouvez pas installer ces profils par programme.

C’est un excellent sujet, et en particulier le blog mentionné ci-dessus .

Pour ceux qui font Xamarin, voici mes 2 centimes supplémentaires. J’ai intégré le cert de feuille dans mon application en tant que contenu, puis j’ai utilisé le code suivant pour le vérifier:

  using Foundation; using Security; NSData data = NSData.FromFile("Leaf.cer"); SecCertificate cert = new SecCertificate(data); SecPolicy policy = SecPolicy.CreateBasicX509Policy(); SecTrust trust = new SecTrust(cert, policy); SecTrustResult result = trust.Evaluate(); return SecTrustResult.Unspecified == result; // true if installed 

(Man, j’adore la propreté de ce code par rapport à l’une des langues d’Apple)