Nice programing

iPhone에 구성 프로파일 설치-프로그래밍 방식

nicepro 2020. 10. 27. 23:27
반응형

iPhone에 구성 프로파일 설치-프로그래밍 방식


내 iPhone 응용 프로그램과 함께 구성 프로파일을 제공하고 필요한 경우 설치하고 싶습니다.

우리는 프로비저닝 프로파일이 아니라 구성 프로파일에 대해 이야기하고 있습니다.

우선 이러한 작업이 가능합니다. 웹 페이지에 구성 프로파일을 배치하고 Safari에서 클릭하면 설치됩니다. 프로필을 이메일로 보내고 첨부 파일을 클릭하면 설치됩니다. 이 경우 "설치됨"은 "설치 UI가 호출 됨"을 의미합니다.하지만 그 정도까지는 갈 수 없었습니다.

그래서 저는 프로필 설치를 시작하는 데 URL로 이동하는 것이 포함된다는 이론에 따라 작업했습니다. 내 앱 번들에 프로필을 추가했습니다.

A) 먼저 내 번들에 file : // URL로 [sharedApp openURL]을 시도했습니다. 그런 행운이 없습니다-아무 일도 일어나지 않습니다.

B) 그런 다음 프로필에 대한 링크가있는 내 번들에 HTML 페이지를 추가하고 UIWebView에로드했습니다. 링크를 클릭해도 아무 효과가 없습니다. 그러나 Safari의 웹 서버에서 동일한 페이지를로드하는 것은 정상적으로 작동합니다. 링크를 클릭 할 수 있고 프로필이 설치됩니다. UIWebViewDelegate를 제공하여 모든 탐색 요청에 대해 YES라고 대답했습니다. 차이는 없습니다.

C) 그런 다음 Safari의 번들에서 동일한 웹 페이지를로드하려고했습니다 ([sharedApp openURL] 사용-아무 일도 일어나지 않습니다. Safari가 내 앱 번들 내의 파일을 볼 수없는 것 같습니다.

D) 웹 서버에 페이지와 프로필을 업로드하는 것은 가능하지만 조직 수준에서 고통을 겪습니다. 추가 실패 원인은 말할 것도 없습니다 (3G 서비스 범위가 없다면 어떨까요? 등).

그래서 내 큰 질문은 ** 프로그래밍 방식으로 프로필을 어떻게 설치합니까?

그리고 작은 질문은 다음과 같습니다. UIWebView 내에서 링크를 클릭 할 수 없게 만드는 것은 무엇입니까? // URL을에서 : 그것은 파일을로드 할 수 있습니다 Safari에서 번들? 그렇지 않은 경우 iPhone에 파일을 저장할 수 있고 Safari에서 찾을 수있는 로컬 위치가 있습니까?

편집 B) : 문제는 우리가 프로필에 연결되어 있다는 사실에 있습니다. .mobileconfig에서 .xml로 이름을 바꾸고 (실제로 XML이기 때문에) 링크를 변경했습니다. 그리고 링크는 내 UIWebView에서 작동했습니다. 다시 이름을 변경했습니다. 프로필을 설치하면 앱이 닫히기 때문에 UIWebView가 응용 프로그램 전체의 작업을 꺼리는 것처럼 보입니다. UIWebViewDelegate를 통해 괜찮다고 말했지만 확신하지 못했습니다. mailto : UIWebView 내의 URL에 대해 동일한 동작.

mailto : URL의 경우 일반적인 기술은 [openURL] 호출로 변환하는 것이지만 제 경우에는 작동하지 않습니다. 시나리오 A를 참조하십시오.

itms : URL, 그러나 UIWebView는 예상대로 작동합니다.

EDIT2 : [openURL]을 통해 Safari에 데이터 URL을 공급하려고했습니다 . 작동하지 않습니다. 여기를 참조하십시오. iPhone Open DATA : Safari의 Url

EDIT3 : Safari가 file : // URL을 지원하지 않는 방법에 대한 많은 정보를 찾았습니다. 그러나 UIWebView는 매우 많습니다. 또한 시뮬레이터의 Safari는 정상적으로 열립니다. 후자의 비트가 가장 실망 스럽습니다.


EDIT4 : 해결책을 찾지 못했습니다. 대신 사용자가 이메일로받은 프로필을 주문할 수있는 2 비트 웹 인터페이스를 구성했습니다.


1) RoutingHTTPServer 와 같은 로컬 서버를 설치합니다.

2) 사용자 지정 헤더를 구성합니다.

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

3) mobileconfig 파일 (문서)의 로컬 루트 경로를 구성하십시오.

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

4) 웹 서버가 파일을 보낼 시간을 허용하려면 다음을 추가하십시오.

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

malinois의 대답은 저에게 효과적이지만 사용자가 mobileconfig를 설치 한 후 자동으로 앱으로 돌아 오는 솔루션을 원했습니다.

4 시간이 걸렸지 만 여기에 로컬 http 서버가 있다는 malinois의 아이디어를 기반으로 구축 된 솔루션이 있습니다. HTML을 사파리로 반환하여 자체적으로 새로 고쳐집니다. 서버가 처음으로 mobileconfig를 반환하고 두 번째로 사용자 지정 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/"]];

이것이 누군가를 돕기를 바랍니다.


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
        }
    }
}

귀하가 찾고있는 것은 SCEP (Simple Certificate Enrollment Protocol)를 사용하는 "Over the Air Enrollment"라고 생각합니다. OTA 등록 가이드엔터프라이즈 배포 가이드 의 SCEP 페이로드 섹션을 살펴보십시오 .

According to the Device Config Overview you only have four options:

  • Desktop installation via USB
  • Email (attachment)
  • Website (via Safari)
  • Over-the-Air Enrollment and Distribution

This page explains how to use images from your bundle in a UIWebView.

Perhaps the same would work for a configuration profile as well.


Have you tried just having the app mail the user the config profile the first time it starts up?

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

     [email setSubject:@"My App's Configuration Profile"];

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

     NSString *emailBody = @"Please tap the attachment to install the configuration profile for My App.";
     [email setMessageBody:emailBody isHTML:YES];

     [self presentModalViewController:email animated:YES];
     [email release];
}

I made it an IBAction in case you want to tie it to a button so the user can re-send it to themselves at any time. Note that I may not have the correct MIME type in the example above, you should verify that.


I've though of another way in which it might work (unfortunately I don't have a configuration profile to test out with):

// Create a UIViewController which contains a UIWebView
- (void)viewDidLoad {
    [super viewDidLoad];
    // Tells the webView to load the config profile
    [self.webView loadRequest:[NSURLRequest requestWithURL:self.cpUrl]];
}

// Then in your code when you see that the profile hasn't been installed:
ConfigProfileViewController *cpVC = 
        [[ConfigProfileViewController alloc] initWithNibName:@"MobileConfigView"
                                                      bundle:nil];
NSString *cpPath = [[NSBundle mainBundle] pathForResource:@"configProfileName"
                                                   ofType:@".mobileconfig"];
cpVC.cpURL = [NSURL URLWithString:cpPath];
// Then if your app has a nav controller you can just push the view 
// on and it will load your mobile config (which should install it).
[self.navigationController pushViewController:controller animated:YES];
[cpVC release];

Not sure why you need a configuration profile, but you can try to hack with this delegate from the UIWebView:

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

Otherwise, you may consider enable the installation from a secure server.


Just host the file on a website with the extension *.mobileconfig and set the MIME type to application/x-apple-aspen-config. The user will be prompted, but if they accept the profile should be installed.

You cannot install these profiles programmatically.


This is a great thread, and especially the blog mentioned above.

For those doing Xamarin, here's my added 2 cents. I embedded the leaf cert in my app as Content, then used the following code to check it:

        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, I love how clean that code is, vs. either of Apple's languages)

참고URL : https://stackoverflow.com/questions/2338035/installing-a-configuration-profile-on-iphone-programmatically

반응형