microsoftconnect / ms-intune-app-sdk-ios

Intune App SDK for iOS enables data protection and mobile app management features in iOS mobile apps with Microsoft Intune
92 stars 27 forks source link

First register timeout #122

Closed NicolasLourenco closed 3 years ago

NicolasLourenco commented 3 years ago

Describe the bug: Hello, Each time I connect my user for the first time, the enrollment fail. The status code is 216: IntuneMAMEnrollmentStatusTimeout = 216, // The operation has timed out

To Reproduce Steps to reproduce the behavior:

  1. Connect with MSAL
  2. registerAndEnrollAccount
  3. Failure
  4. Connect with MSAL
  5. registerAndEnrollAccount
  6. it succeed

Expected behavior: It should work at the first time.

Smartphone (please complete the following information):

Intune App SDK for iOS (please complete the following information):

Additional context: The aadAuthorityUriOverride is overrided with the result of MSALResult.

Kyle-Reis commented 3 years ago

Hi @NicolasLourenco, I'm not sure why the enrollment would timeout only on the first time. Does this happen consistently? Is there any difference in the two MSAL acquireToken calls before each enrollment attempt?

NicolasLourenco commented 3 years ago

Hello. There’s absolutely nothing different in aquireToken calls. It’s consistently despite the device or MS account.

I’ve tested using the webview and broker for login

NicolasLourenco commented 3 years ago

Also, in my case setting VerboseLoggingEnabled in MAMSettings do nothing 🤷

Kyle-Reis commented 3 years ago

@NicolasLourenco, were you doing any kind of debugging during the enrollment which might have paused execution (e.g. having some breakpoint set and stepping through some code)? Or does this reproduce without having any debugger attached?

NicolasLourenco commented 3 years ago

Hi @Kyle-Reis . No, There's no breakpoints or something else who can pause the execution. Moreover, this behavior also appear with an Adhoc/Appstore build.

Kyle-Reis commented 3 years ago

@NicolasLourenco, if you don't explicitly override the authority, does the issue reproduce? Note: values set on the dynamic override AAD properties are persisted across application launches, or until the app explicitly clears them or unenrolls.

Kyle-Reis commented 3 years ago

Intune should be able to get the correct authority from the MSAL cache in this scenario. The authority override property is really for apps that call loginAndEnrollAccount and don't want the generic auth experience of the common authority. Also, are you overriding the client ID and redirect URI to match the values used by your app?

NicolasLourenco commented 3 years ago

No, The authority ID is overridden since we have this information in the MSALResult. With the common authority, the behavior is the same. We've got a timeout, then it works.

We don't override the client ID and redirect ID. Our app is one app used by different companies. Like Word for iOS or something like that.

My latest tests are betters, but still unstable. (with and without common authority) I've less timeouts but the enrollment still take 35/40seconds.

Kyle-Reis commented 3 years ago

Hi @NicolasLourenco, your app must be specifying a client ID and redirect URI when performing its own MSAL auth prior to calling registerAndEnrollAccount. The Intune enrollment attempt should use the same values.

Kyle-Reis commented 3 years ago

Hey @NicolasLourenco, checking in to see if you're still having trouble here?

NicolasLourenco commented 3 years ago

Hello, yes. We're still facing the issue.

We've found a workaround. If we ignore the timeout, the enrollment finally succeeds, but it's very long!

NicolasLourenco commented 3 years ago

Hello, Here is how I enroll users:

enum IntuneError: LocalizedError {
  case notLicenced
  case unknown
  case wiped

  var errorDescription: String? {
    switch self {
    case .notLicenced:
      return NSLocalizedString("error.intune.notLicenced", comment: "")
    case .unknown:
      return NSLocalizedString("error.intune.generic", comment: "")
    case .wiped:
      return NSLocalizedString("error.intune.wiped", comment: "")
    }
  }
}

class IntuneManager: NSObject {
  static let shared = IntuneManager()

  private var enrollmentPromise: Promise<Void> = Promise<Void>()
  weak var delegate: IntuneManagerDelegate?

  private var enrollManager: IntuneMAMEnrollmentManager {
    return IntuneMAMEnrollmentManager.instance()
  }

  override init() {
    super.init()

    IntuneMAMPolicyManager.instance().delegate = self
    enrollManager.delegate = self
  }

  func generateEnrollmentPromise() {
    enrollmentPromise = Promise<Void>()
  }

  // MARK: - Intune
  func registerToIntuneIfNeeded(with result: MSALResult) -> Promise<Void> {
    generateEnrollmentPromise()

    guard let identity = result.account.username else {
      enrollmentPromise.reject(IntuneError.unknown)
      return enrollmentPromise
    }

    // In order to avoid multiple enrollment with same account
    if enrollManager.enrolledAccount() != nil, enrollManager.enrolledAccount() == result.account.username {
      enrollmentPromise.fulfill(())
    }

    enrollManager.registerAndEnrollAccount(identity)
    return enrollmentPromise
  }

  // Called when user is enrolled and has selected the environment (business case)
  func applyPolicies() -> Promise<Void> {
    let promise = Promise<Void>()

    if IntuneMAMPolicyManager.instance().isManagementEnabled() == false {
      restartApp()
    } else {
      promise.fulfill(())
    }

    return promise
  }

  private func restartApp() {
    let alert = UIAlertController(
      title: nil,
      message: NSLocalizedString("intune.need_restart.message", comment: ""),
      preferredStyle: .alert
    )

    alert.addAction(
      UIAlertAction(
        title: NSLocalizedString("generic.button.title.close", comment: ""),
        style: .default,
        handler: { _ in
          exit(0)
        }
      )
    )

    if var topController = UIApplication.shared.keyWindow?.rootViewController {
      while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
      }
      topController.present(alert, animated: true)
    }
  }
}

extension IntuneManager: IntuneMAMEnrollmentDelegate {
  func enrollmentRequest(with status: IntuneMAMEnrollmentStatus) {

    if status.statusCode == .wipeReceived {
      enrollManager.deRegisterAndUnenrollAccount(status.identity, withWipe: true)
      enrollmentPromise.reject(IntuneError.wiped)
      return
    } else if status.didSucceed {
      enrollmentPromise.fulfill(())
      return
    } else if status.statusCode == .accountNotLicensed {
      enrollmentPromise.reject(IntuneError.notLicenced)
      return
    } else if status.statusCode == .timeout {
      // In order to wait the right callback
      return
    }

    enrollmentPromise.reject(IntuneError.unknown)
  }
}

Here is the content of my plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>ADALClientId</key>
    <string>MY_CLIENT_ID</string>
    <key>ADALRedirectURI</key>
    <string>msauth.MY_BUNDLE_ID://auth</string>
    <key>ADALAuthority</key>
    <string>https://login.microsoftonline.com/common</string>
    <key>AppGroupIdentifiers</key>
    <array>
        <string>group.MY_APP_GROUP_IDENTIFIER</string>
    </array>
</dict>
</plist>