GIDEMMErrorHandler.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  15. #import <UIKit/UIKit.h>
  16. #import "GoogleSignIn/Sources/GIDSignInStrings.h"
  17. NS_ASSUME_NONNULL_BEGIN
  18. // The error key in the server response.
  19. static NSString *const kErrorKey = @"error";
  20. // Error strings in the server response.
  21. static NSString *const kGeneralErrorPrefix = @"emm_";
  22. static NSString *const kScreenlockRequiredError = @"emm_passcode_required";
  23. static NSString *const kAppVerificationRequiredErrorPrefix = @"emm_app_verification_required";
  24. // Optional separator between error prefix and the payload.
  25. static NSString *const kErrorPayloadSeparator = @":";
  26. // A list for recognized error codes.
  27. typedef enum {
  28. ErrorCodeNone = 0,
  29. ErrorCodeDeviceNotCompliant,
  30. ErrorCodeScreenlockRequired,
  31. ErrorCodeAppVerificationRequired,
  32. } ErrorCode;
  33. @implementation GIDEMMErrorHandler {
  34. // Whether or not a dialog is pending user interaction.
  35. BOOL _pendingDialog;
  36. }
  37. + (instancetype)sharedInstance {
  38. static dispatch_once_t once;
  39. static GIDEMMErrorHandler *sharedInstance;
  40. dispatch_once(&once, ^{
  41. sharedInstance = [[self alloc] init];
  42. });
  43. return sharedInstance;
  44. }
  45. - (BOOL)handleErrorFromResponse:(NSDictionary<NSString *, id> *)response
  46. completion:(void (^)())completion {
  47. ErrorCode errorCode = ErrorCodeNone;
  48. NSURL *appVerificationURL;
  49. @synchronized(self) { // for accessing _pendingDialog
  50. if (!_pendingDialog && [UIAlertController class] &&
  51. [response isKindOfClass:[NSDictionary class]]) {
  52. id errorValue = response[kErrorKey];
  53. if ([errorValue isEqual:kScreenlockRequiredError]) {
  54. errorCode = ErrorCodeScreenlockRequired;
  55. } else if ([errorValue hasPrefix:kAppVerificationRequiredErrorPrefix]) {
  56. errorCode = ErrorCodeAppVerificationRequired;
  57. NSString *appVerificationString =
  58. [errorValue substringFromIndex:kAppVerificationRequiredErrorPrefix.length];
  59. if ([appVerificationString hasPrefix:kErrorPayloadSeparator]) {
  60. appVerificationString =
  61. [appVerificationString substringFromIndex:kErrorPayloadSeparator.length];
  62. }
  63. appVerificationString = [appVerificationString
  64. stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
  65. if (appVerificationString.length) {
  66. appVerificationURL = [NSURL URLWithString:appVerificationString];
  67. }
  68. } else if ([errorValue hasPrefix:kGeneralErrorPrefix]) {
  69. errorCode = ErrorCodeDeviceNotCompliant;
  70. }
  71. if (errorCode) {
  72. _pendingDialog = YES;
  73. }
  74. }
  75. }
  76. if (!errorCode) {
  77. completion();
  78. return NO;
  79. }
  80. // All UI must happen in the main thread.
  81. dispatch_async(dispatch_get_main_queue(), ^() {
  82. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
  83. CGRect keyWindowBounds = CGRectIsEmpty(keyWindow.bounds) ?
  84. keyWindow.bounds : [UIScreen mainScreen].bounds;
  85. UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:keyWindowBounds];
  86. alertWindow.backgroundColor = [UIColor clearColor];
  87. alertWindow.rootViewController = [[UIViewController alloc] init];
  88. alertWindow.rootViewController.view.backgroundColor = [UIColor clearColor];
  89. alertWindow.windowLevel = UIWindowLevelAlert;
  90. [alertWindow makeKeyAndVisible];
  91. void (^finish)() = ^{
  92. alertWindow.hidden = YES;
  93. alertWindow.rootViewController = nil;
  94. [keyWindow makeKeyAndVisible];
  95. _pendingDialog = NO;
  96. completion();
  97. };
  98. UIAlertController *alert;
  99. switch (errorCode) {
  100. case ErrorCodeNone:
  101. break;
  102. case ErrorCodeScreenlockRequired:
  103. alert = [self passcodeRequiredAlertWithCompletion:finish];
  104. break;
  105. case ErrorCodeAppVerificationRequired:
  106. alert = [self appVerificationRequiredAlertWithURL:appVerificationURL completion:finish];
  107. break;
  108. case ErrorCodeDeviceNotCompliant:
  109. alert = [self deviceNotCompliantAlertWithCompletion:finish];
  110. break;
  111. }
  112. if (alert) {
  113. [alertWindow.rootViewController presentViewController:alert animated:YES completion:nil];
  114. } else {
  115. // Should not happen but just in case.
  116. finish();
  117. }
  118. });
  119. return YES;
  120. }
  121. #pragma mark - Alerts
  122. // Returns an alert controller for device not compliant error.
  123. - (UIAlertController *)deviceNotCompliantAlertWithCompletion:(void (^)())completion {
  124. UIAlertController *alert =
  125. [UIAlertController alertControllerWithTitle:[self unableToAccessString]
  126. message:[self deviceNotCompliantString]
  127. preferredStyle:UIAlertControllerStyleAlert];
  128. [alert addAction:[UIAlertAction actionWithTitle:[self okayString]
  129. style:UIAlertActionStyleDefault
  130. handler:^(UIAlertAction *action) {
  131. completion();
  132. }]];
  133. return alert;
  134. };
  135. // Returns an alert controller for passcode required error.
  136. - (UIAlertController *)passcodeRequiredAlertWithCompletion:(void (^)())completion {
  137. UIAlertController *alert =
  138. [UIAlertController alertControllerWithTitle:[self unableToAccessString]
  139. message:[self passcodeRequiredString]
  140. preferredStyle:UIAlertControllerStyleAlert];
  141. BOOL canOpenSettings = YES;
  142. if ([[UIDevice currentDevice].systemVersion hasPrefix:@"10."]) {
  143. // In iOS 10, `UIApplicationOpenSettingsURLString` fails to open the Settings app if the
  144. // opening app does not have Setting bundle.
  145. NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
  146. NSString* settingsBundlePath = [mainBundlePath
  147. stringByAppendingPathComponent:@"Settings.bundle"];
  148. if (![NSBundle bundleWithPath:settingsBundlePath]) {
  149. canOpenSettings = NO;
  150. }
  151. }
  152. if (canOpenSettings) {
  153. [alert addAction:[UIAlertAction actionWithTitle:[self cancelString]
  154. style:UIAlertActionStyleCancel
  155. handler:^(UIAlertAction *action) {
  156. completion();
  157. }]];
  158. [alert addAction:[UIAlertAction actionWithTitle:[self settingsString]
  159. style:UIAlertActionStyleDefault
  160. handler:^(UIAlertAction *action) {
  161. completion();
  162. [[UIApplication sharedApplication]
  163. openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
  164. }]];
  165. } else {
  166. [alert addAction:[UIAlertAction actionWithTitle:[self okayString]
  167. style:UIAlertActionStyleCancel
  168. handler:^(UIAlertAction *action) {
  169. completion();
  170. }]];
  171. }
  172. return alert;
  173. };
  174. // Returns an alert controller for app verification required error.
  175. - (UIAlertController *)appVerificationRequiredAlertWithURL:(nullable NSURL *)url
  176. completion:(void (^)())completion {
  177. UIAlertController *alert;
  178. if (url) {
  179. // If the URL is provided, prompt user to open this URL or cancel.
  180. alert = [UIAlertController alertControllerWithTitle:[self appVerificationTitleString]
  181. message:[self appVerificationTextString]
  182. preferredStyle:UIAlertControllerStyleAlert];
  183. [alert addAction:[UIAlertAction actionWithTitle:[self cancelString]
  184. style:UIAlertActionStyleCancel
  185. handler:^(UIAlertAction *action) {
  186. completion();
  187. }]];
  188. [alert addAction:[UIAlertAction actionWithTitle:[self appVerificationActionString]
  189. style:UIAlertActionStyleDefault
  190. handler:^(UIAlertAction *action) {
  191. completion();
  192. [[UIApplication sharedApplication] openURL:url];
  193. }]];
  194. } else {
  195. // If the URL is not provided, simple let user acknowledge the issue. This is not supposed to
  196. // happen but just to fail gracefully.
  197. alert = [UIAlertController alertControllerWithTitle:[self unableToAccessString]
  198. message:[self appVerificationTextString]
  199. preferredStyle:UIAlertControllerStyleAlert];
  200. [alert addAction:[UIAlertAction actionWithTitle:[self okayString]
  201. style:UIAlertActionStyleDefault
  202. handler:^(UIAlertAction *action) {
  203. completion();
  204. }]];
  205. }
  206. return alert;
  207. }
  208. #pragma mark - Localization
  209. // The English version of the strings are used as back-up in case the bundle resource is missing
  210. // from the third-party app. Please keep them in sync with the strings in the bundle.
  211. // Returns a localized string for unable to access the account.
  212. - (NSString *)unableToAccessString {
  213. return [GIDSignInStrings localizedStringForKey:@"EmmErrorTitle"
  214. text:@"Unable to sign in to account"];
  215. }
  216. // Returns a localized string for device passcode required error.
  217. - (NSString *)passcodeRequiredString {
  218. NSString *defaultText =
  219. @"Your administrator requires you to set a passcode on this device to access this account. "
  220. "Please set a passcode and try again.";
  221. return [GIDSignInStrings localizedStringForKey:@"EmmPasscodeRequired" text:defaultText];
  222. }
  223. // Returns a localized string for app verification error dialog title.
  224. - (NSString *)appVerificationTitleString {
  225. return [GIDSignInStrings localizedStringForKey:@"EmmConnectTitle"
  226. text:@"Connect with Device Policy App?"];
  227. }
  228. // Returns a localized string for app verification error dialog message.
  229. - (NSString *)appVerificationTextString {
  230. NSString *defaultText = @"In order to protect your organization's data, "
  231. "you must connect with the Device Policy app before logging in.";
  232. return [GIDSignInStrings localizedStringForKey:@"EmmConnectText" text:defaultText];
  233. }
  234. // Returns a localized string for app verification error dialog action button label.
  235. - (NSString *)appVerificationActionString {
  236. return [GIDSignInStrings localizedStringForKey:@"EmmConnectLabel" text:@"Connect"];
  237. }
  238. // Returns a localized string for general device non-compliance error.
  239. - (NSString *)deviceNotCompliantString {
  240. NSString *defaultText =
  241. @"The device is not compliant with the security policy set by your administrator.";
  242. return [GIDSignInStrings localizedStringForKey:@"EmmGeneralError" text:defaultText];
  243. }
  244. // Returns a localized string for "Settings".
  245. - (NSString *)settingsString {
  246. return [GIDSignInStrings localizedStringForKey:@"SettingsAppName" text:@"Settings"];
  247. }
  248. // Returns a localized string for "OK".
  249. - (NSString *)okayString {
  250. return [GIDSignInStrings localizedStringForKey:@"OK" text:@"OK"];
  251. }
  252. // Returns a localized string for "Cancel".
  253. - (NSString *)cancelString {
  254. return [GIDSignInStrings localizedStringForKey:@"Cancel" text:@"Cancel"];
  255. }
  256. @end
  257. NS_ASSUME_NONNULL_END