web-dev-qa-db-ja.com

構成プロファイルをiPhoneにインストールする-プログラムで

IPhoneアプリケーションに構成プロファイルを同梱し、必要に応じてインストールしたいと思います。

念のため、プロビジョニングプロファイルではなく、構成プロファイルについて説明しています。

まず、このようなタスクが可能です。 Webページに構成プロファイルを配置し、Safariからそれをクリックすると、インストールされます。プロファイルを電子メールで送信して添付ファイルをクリックすると、それもインストールされます。この場合の「インストール済み」とは「インストールUIが呼び出される」ことを意味しますが、そこまで到達することさえできませんでした。

そのため、プロファイルのインストールを開始するには、URLとしてプロファイルに移動する必要があるという理論の下で作業していました。プロファイルをアプリバンドルに追加しました。

A)まず、バンドルにfile:// URLを使用して[sharedApp openURL]を試しました。そのような運はありません-何も起こりません。

B)次に、プロファイルへのリンクを持つバンドルにHTMLページを追加し、それをUIWebViewにロードしました。リンクをクリックしても何も起こりません。ただし、SafariのWebサーバーから同一のページを読み込むことは正常に機能します。リンクをクリックすると、プロファイルがインストールされます。 UIWebViewDelegateを提供し、すべてのナビゲーションリクエストにYESと答えました-違いはありません。

C)次に、Safariのバンドルから同じWebページをロードしようとしました([sharedApp openURL]を使用します-何も起こりません。アプリバンドル内のファイル。

D)Webサーバーにページとプロファイルをアップロードすることは可能ですが、障害の余分な原因は言うまでもなく、組織レベルでの痛み( 3Gカバレッジがない場合はどうなりますか?など)。

私の大きな質問は次のとおりです:**プログラムでプロファイルをインストールする方法は?

そして、小さな質問は次のとおりです。UIWebView内でリンクをクリックできないようにするものは何ですか? Safariのmyバンドルからfile:// URLをロードすることはできますか?そうでない場合、iPhoneにファイルを配置できる場所があり、Safariはそれらを見つけることができますか?

Bでの編集):問題は、プロファイルにリンクしているという事実にあります。名前を.mobileconfigから.xmlに変更し(実際はXMLであるため)、リンクを変更しました。そして、リンクは私のUIWebViewで機能しました。名前を変更しました-同じもの。 UIWebViewは、アプリケーション全体を処理することに消極的であるように見えます-プロファイルをインストールするとアプリが閉じられるためです。 UIWebViewDelegateを使用して問題ないことを伝えようとしましたが、納得できませんでした。 mailtoの場合と同じ動作:UIWebView内のURL。

mailto:URLの一般的な手法は、それらを[openURL]呼び出しに変換することですが、私の場合はまったく機能しません。シナリオAを参照してください。

Itmsの場合:URL、ただし、UIWebViewは期待どおりに機能します...

EDIT2:[openURL]を介してデータURLをSafariにフィードしようとしました-動作しません。こちらを参照してください: iPhone Open DATA:Url In Safari

EDIT3:Safariがfile:// URLをサポートしていない方法に関する多くの情報が見つかりました。ただし、UIWebViewは非常に多くのことを行います。また、シミュレーターのSafariで問題なく開きます。後者が最もイライラします。


EDIT4:解決策が見つかりませんでした。代わりに、ユーザーが電子メールで送信されたプロファイルを注文できる2ビットのWebインターフェイスを作成しました。

69
Seva Alekseyev

1) RoutingHTTPServer のようなローカルサーバーをインストールします

2)カスタムヘッダーを構成します。

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

3)mobileconfigファイル(ドキュメント)のローカルルートパスを構成します。

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

4)Webサーバーがファイルを送信する時間を確保するには、これを追加します。

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)コントローラーで、Documentsに保存されているmobileconfigの名前でsafariを呼び出します。

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

マリノアからの答えは私にとってはうまくいきましたが、ユーザーがmobileconfigをインストールした後に自動的にアプリに戻るソリューションが必要でした。

4時間かかりましたが、ローカルHTTPサーバーを持つというマリノアの考えに基づいたソリューションがあります。HTMLをサファリに返して、それ自体を更新します。サーバーが最初にmobileconfigを返し、2回目はアプリに戻るためのカスタムurl-schemeを返します。 UXは私が欲しかったものです。アプリがsafariを呼び出し、safariがmobileconfigを開き、ユーザーがmobileconfigで「完了」を押すと、safariがアプリを再度読み込みます(カスタムURLスキーム)。

- (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:)];

    NSMutableString* path = [NSMutableString stringWithString:[[NSBundle mainBundle] bundlePath]];
    [path appendString:@"/your.mobileconfig"];
    _mobileconfigData = [NSData dataWithContentsOfFile:path];

    [_httpServer start:NULL];

    return YES;
}

- (void)handleMobileconfigRootRequest:(RouteRequest *)request withResponse:(RouteResponse *)response {
    NSLog(@"handleMobileconfigRootRequest");
    [response respondWithString:@"<HTML><HEAD><title>Profile Install</title>\
     </HEAD><script> \
     function load() { window.location.href='http://localhost:8000/load/'; } \
     var int=self.setInterval(function(){load()},400); \
     </script><BODY></BODY></HTML>"];
}

- (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"];
    }
}

...そして、これはアプリ(viewcontroller)からこれを呼び出すコードです:

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

これが誰かを助けることを願っています。

27
xaphod

Safariを介してmobileconfigファイルをインストールし、アプリに戻るためのクラスを作成しました。これはhttpサーバーエンジンに依存しています Swifter これはうまく機能していることがわかりました。これを行うために、以下のコードを共有したいと思います。これは、wwwに浮かぶ複数のコードソースに触発されています。あなた自身のコードの断片を見つけたら、あなたに貢献してください。

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: String! = "Profile install"
    private var localServer: HttpServer!
    private var returnURL: String!
    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: String)
    {
        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(string: 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: String?) -> String
    {
        var page = "http://localhost:\(listeningPort)"
        if let component = pathComponent {
            page += "/\(component)"
        }
        return page
    }

    private func basePage(pathComponent: String?) -> String
    {
        var page = "<!doctype html><html>" + "<head><meta charset='utf-8'><title>\(self.configName)</title></head>"
        if let component = pathComponent {
            let script = "function load() { window.location.href='\(self.baseURL(component))'; }window.setInterval(load, 600);"
            page += "<script>\(script)</script>"
        }
        page += "<body></body></html>"
        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
        }
    }
}
6
freshking

探しているのは、Simple Certificate Enrollment Protocol(SCEP)を使用した「Over the Air Enrollment」です。 OTA登録ガイド および エンタープライズ展開ガイド のSCEPペイロードセクションをご覧ください。

Device Config Overview によると、4つのオプションしかありません。

  • USB経由のデスクトップインストール
  • 電子メールの添付ファイル)
  • Webサイト(Safari経由)
  • 無線での登録と配信
4
slf

拡張子が* .mobileconfigのWebサイトでファイルをホストし、MIMEタイプをapplication/x-Apple-aspen-configに設定するだけです。ユーザーにプロンプ​​トが表示されますが、プロファイルを受け入れる場合はプロファイルをインストールする必要があります。

これらのプロファイルをプログラムでインストールすることはできません。

0
Brandon

これはすばらしいスレッドで、特にブログ 上記 です。

Xamarinを使用している人のために、ここに2セントを追加しました。アプリにリーフ証明書をコンテンツとして埋め込み、次のコードを使用して確認しました:

        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

(男、私はそのコードがどれほどきれいか、対Appleの言語のどちらかが大好きです)

0
Eliot Gillum

構成プロファイルが必要な理由はわかりませんが、UIWebViewからこのデリゲートを使用してハッキングを試みることができます。

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

それ以外の場合は、安全なサーバーからのインストールを有効にすることを検討できます。

0
Hoang Pham

私はそれが機能するかもしれない別の方法を考えました(残念ながら、私はテストするための構成プロファイルを持っていません):

 // UIWebView 
-(void)viewDidLoadを含むUIViewControllerを作成{
 [super viewDidLoad]; 
 // webViewに設定プロファイルをロードするよう指示します
 [self.webView loadRequest:[NSURLRequest requestWithURL:self.cpUrl]]; 
} 
 
 //その後、プロファイルに'.installed:
 ConfigProfileViewController * cpVC = 
 [[ConfigProfileViewController alloc] initWithNibName:@ "MobileConfigView" 
 bundle:nil]; 
 NSString * cpPath = [ [NSBundle mainBundle] pathForResource:@ "configProfileName" 
 ofType:@ "。mobileconfig"]; 
 cpVC.cpURL = [NSURL URLWithString:cpPath]; 
 //アプリには、ビュー
 //をプッシュするだけのnavコントローラーがあり、モバイル構成(インストールする必要があります)をロードします。
 [self.navigationController pushViewController:control ler animated:YES]; 
 [cpVC release]; 
0
smountcastle

初めて起動したときにアプリにユーザーに設定プロファイルをメールで送信してみましたか?

-(IBAction)mailConfigProfile {
 MFMailComposeViewController * email = [[MFMailComposeViewController alloc] init]; 
 email.mailComposeDelegate = self; 
 
 [メールsetSubject:@ "My App's Configuration Profile"]; 
 
 NSString * filePath = [[NSBundle mainBundle] pathForResource:@ "MyAppConfig" ofType:@ "mobileconfig"]; 
 NSData * configData = [NSData dataWithContentsOfFile:filePath]; 
 [addAttachmentData:configData mimeType:@ "application/x-Apple-aspen-config" fileName:@ "MyAppConfig.mobileconfig"]; 
 
 NSString * emailBody = @ "添付ファイルをタップして、マイアプリの構成プロファイルをインストールしてください。 "; 
 [email setMessageBody:emailBody isHTML:YES]; 
 
 [self presentModalViewController:email animated:YES]; 
 [メールリリース]; 
} 

ユーザーがいつでも自分自身に再送信できるようにボタンに結び付けたい場合に備えて、IBActionにしました。上記の例では、正しいMIMEタイプがない可能性があることに注意してください。確認する必要があります。

0
smountcastle

このページ は、UIWebViewでバンドルの画像を使用する方法を説明しています。

おそらく、構成プロファイルでも同様に機能します。

0
Jon-Eric