programing

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

i4 2023. 8. 4. 22:39
반응형

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

iPhone 애플리케이션과 함께 구성 프로필을 발송하고 필요한 경우 설치하고 싶습니다.

프로비저닝 프로파일이 아니라 구성 프로파일을 말하는 것입니다.

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

그래서 저는 프로파일 설치를 시작하는 것은 URL로 탐색하는 것을 포함한다는 이론 아래 작업을 하고 있었습니다. 저는 프로파일을 앱 번들에 추가했습니다.

먼저 [공유 앱 열기]를 시도했습니다.URL] 파일:// URL을 내 번들에 포함합니다.그런 행운은 없습니다. 아무 일도 일어나지 않습니다.

그런 다음 프로필에 대한 링크가 있는 HTML 페이지를 번들에 추가하여 UI 웹 보기에 로드했습니다.링크를 클릭해도 아무런 효과가 없습니다.그러나 Safari의 웹 서버에서 동일한 페이지를 로드하면 문제가 없습니다. 링크를 클릭할 수 있고 프로필이 설치됩니다.저는 UI WebView 딜러를 제공하여 모든 내비게이션 요청에 대해 "예"라고 대답했습니다. 차이는 없습니다.

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

웹 서버에 페이지와 프로필을 업로드하는 것은 가능하지만, 추가적인 실패 원인은 말할 것도 없고, 조직 차원에서 어려움을 겪고 있습니다(3G 적용 범위가 없는 경우).등).

그래서 저의 중요한 질문은 **프로파일을 프로그래밍 방식으로 설치하는 방법입니다.

UI WebView에서 링크를 클릭할 수 없게 만드는 것은 무엇입니까?Safari에서 내 번들에서 파일:// URL을 로드할 수 있습니까?그렇지 않다면 iPhone에서 파일을 저장하고 Safari에서 찾을 수 있는 로컬 위치가 있습니까?

B에 편집): 문제는 우리가 프로필에 링크하고 있다는 사실에 있습니다..mobileconfig에서 .xml('진짜 XML이기 때문에')로 이름을 바꾸고 링크를 변경했습니다.링크는 UI WebView에서 작동했습니다.이름을 다시 바꿨습니다. 같은 것입니다.UI WebView는 프로필 설치로 앱이 닫히기 때문에 응용 프로그램 전반의 작업을 꺼리는 것처럼 보입니다.UI WebView 딜러를 통해 괜찮다고 말하려 했지만 설득력이 없었습니다.UI WebView 내의 mailto: URL에 대해서도 동일한 동작을 합니다.

mailto: URL의 경우 일반적인 기술은 [open]으로 변환하는 입니다.URL] 통화는 하지만 제 경우에는 잘 되지 않습니다. 시나리오 A를 참조하십시오.

항목: URL의 경우, 그러나 UI WebView는 예상대로 작동합니다...

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

EDIT3: Safari가 파일:// URL을 지원하지 않는 방법에 대한 많은 정보를 찾았습니다. 그러나 UIWebView는 매우 많이 지원합니다.또한, 시뮬레이터의 사파리는 그것들을 아주 잘 엽니다.후자가 가장 좌절감을 줍니다.


EDIT4: 해결책을 찾지 못했습니다.대신, 저는 사용자들이 이메일로 보낸 프로필을 주문할 수 있는 2비트 웹 인터페이스를 만들었습니다.

라우팅과 같은 로컬 서버 설치HTTPS 서버

사용자 헤더를 합니다.

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

mobileconfig 파일(문서)의 로컬 루트 경로를 구성합니다.

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

웹 서버가 파일을 보낼 시간을 허용하려면 다음을 추가합니다.

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;
        });
    }];
}

컨트롤러에서 Documents:에 저장된 모바일 구성의 이름으로 Safari를 호출합니다.

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

malinois의 답변은 저에게 효과가 있었지만, 저는 사용자가 모바일 구성을 설치한 후 자동으로 앱으로 돌아오는 솔루션을 원했습니다.

4시간이 걸렸지만, 여기에 해결책이 있습니다. 로컬 http 서버가 있다는 malinois의 아이디어를 기반으로 합니다. HTML을 자동으로 새로 고치는 사파리로 반환하고, 서버가 처음으로 모바일 구성을 반환하고, 두 번째로 사용자 지정 url-scheme을 반환하여 앱으로 돌아갑니다.UX는 제가 원했던 것입니다. 앱이 사파리를 호출하고, 사파리가 모바일 구성을 열고, 사용자가 모바일 구성에서 "완료"를 누르면 사파리가 앱을 다시 로드합니다(사용자 지정 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"];
    }
}

앱(즉, 뷰 컨트롤러)에서 이를 호출하는 코드는 다음과 같습니다.

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

이것이 누군가에게 도움이 되기를 바랍니다.

사파리를 통해 모바일 구성 파일을 설치한 후 앱으로 돌아가는 수업을 작성했습니다.그것은 제가 잘 작동하는 http 서버 엔진 스위퍼에 의존합니다.이를 위해 아래의 코드를 공유하고 싶습니다.그것은 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 페이로드 섹션을 살펴봅니다.

Device Config Overview(장치 구성 개요)에 따르면 다음 네 가지 옵션만 있습니다.

  • USB를 통한 데스크톱 설치
  • 전자 메일(첨부 파일)
  • 웹 사이트(Safari를 통해)
  • 무선 등록 및 배포

처음 시작할 때 앱이 사용자에게 구성 프로필을 메일로 보내도록 시도해 본 적이 있습니까?

-(IBAction)mailConfigProfile {MFMailComposeViewController *email = [MFMailComposeViewController alloc] in];email.mailComposeDelegate= self;
[이메일 세트 제목:@"내 앱의 구성 프로필"];
NSString *filePath = [NSBundle mainBundle] 경로Resource:@유형의 "MyAppConfig":@"mobileconfig"];NSData *configData = [ContentsOfFile:filePath를 포함하는 NSData 데이터];[email addAttachmentData:configData mime유형:@"application/x-apple-aspen-config" 파일 이름:@"MyAppConfig.mobileconfig"];
NSString *emailBody = @"첨부 파일을 눌러 내 앱에 대한 구성 프로필을 설치하십시오.";[email setMessageBody:emailBody isHTML:YES];
[자체 존재 ModalViewController: 이메일:예];[이메일 릴리스];
}

사용자가 언제든지 자신에게 다시 보낼 수 있도록 버튼에 묶고 싶을 때를 대비해 IBAaction으로 만들었습니다.위의 예에서 올바른 MIME 유형이 없을 수 있습니다. 확인해야 합니다.

*.mobileconfig 확장자를 가진 웹 사이트에서 파일을 호스팅하고 MIME 유형을 application/x-apple-aspen-config로 설정하기만 하면 됩니다.사용자에게 메시지가 표시되지만 사용자가 수락한 경우 프로필을 설치해야 합니다.

이러한 프로필은 프로그래밍 방식으로 설치할 수 없습니다.

이 페이지에서는 UI 웹 보기에서 번들의 이미지를 사용하는 방법을 설명합니다.

구성 프로필에도 동일한 내용이 적용될 수 있습니다.

작동할 수 있는 다른 방법을 생각해 보았습니다(불행히도 테스트할 구성 프로필이 없습니다).

UI WebView가 포함된 UIView 컨트롤러 생성(void)DidLoad { 보기[superviewDidLoad];webView에 구성 프로필을 로드하도록 지시합니다.[자신]webView 로드 요청:[NSURL 요청 URL:self.cpUrl];}
그러면 프로필이 설치되지 않은 것을 볼 때 코드에서 다음과 같이 표시됩니다.
ConfigProfileViewController *cpVC =[ConfigProfileViewController alloc] initNibName:@"MobileConfigView"번들:bundle];NSString *cpPath = [NSBundle mainBundle] 경로Resource:@"configProfileName"유형:@."mobileconfig"];cpVC.cpURL = [NSURLWithString:cpPath];그러면 앱에 내비게이션 컨트롤러가 있으면 보기를 누르면 됩니다.모바일 구성을 로드합니다(설치해야 함).
[self.navigationController pushViewController: 컨트롤러 애니메이션:예];[cpVC 릴리즈];

구성 프로필이 필요한 이유는 모르겠지만 UI 웹 보기에서 이 대리자를 사용하여 해킹을 시도할 수 있습니다.

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

그렇지 않으면 보안 서버에서 설치를 실행할 수 있습니다.

이것은 훌륭한 스레드이며, 특히 위에 언급된 블로그입니다.

사마린을 하는 사람들을 위해, 여기 제가 추가한 2센트가 있습니다.리프 인증서를 앱에 Content로 내장한 다음 다음 코드를 사용하여 확인했습니다.

        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

(저는 그 코드가 애플의 언어와 비교했을 때 얼마나 깨끗한지 좋아합니다.)

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

반응형