PageRenderTime 54ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Pods/NXOAuth2Client/Sources/OAuth2Client/NXOAuth2Client.m

https://gitlab.com/duongbadu/Instagram-app-iOS-Coursera
Objective C | 573 lines | 446 code | 109 blank | 18 comment | 98 complexity | a819b23535e4f3170a231306af75dfe9 MD5 | raw file
  1. //
  2. // NXOAuth2Client.m
  3. // OAuth2Client
  4. //
  5. // Created by Ullrich Schäfer on 27.08.10.
  6. //
  7. // Copyright 2010 nxtbgthng. All rights reserved.
  8. //
  9. // Licenced under the new BSD-licence.
  10. // See README.md in this repository for
  11. // the full licence.
  12. //
  13. #import "NXOAuth2Connection.h"
  14. #import "NXOAuth2ConnectionDelegate.h"
  15. #import "NXOAuth2AccessToken.h"
  16. #import "NSURL+NXOAuth2.h"
  17. #import "NXOAuth2Client.h"
  18. NSString * const NXOAuth2ClientConnectionContextTokenRequest = @"tokenRequest";
  19. NSString * const NXOAuth2ClientConnectionContextTokenRefresh = @"tokenRefresh";
  20. @interface NXOAuth2Client ()
  21. @property (nonatomic, readwrite, getter = isAuthenticating) BOOL authenticating;
  22. - (void)requestTokenWithAuthGrant:(NSString *)authGrant redirectURL:(NSURL *)redirectURL;
  23. - (void)removeConnectionFromWaitingQueue:(NXOAuth2Connection *)aConnection;
  24. @end
  25. @implementation NXOAuth2Client
  26. #pragma mark Lifecycle
  27. - (id)initWithClientID:(NSString *)aClientId
  28. clientSecret:(NSString *)aClientSecret
  29. authorizeURL:(NSURL *)anAuthorizeURL
  30. tokenURL:(NSURL *)aTokenURL
  31. delegate:(NSObject<NXOAuth2ClientDelegate> *)aDelegate;
  32. {
  33. return [self initWithClientID:aClientId
  34. clientSecret:aClientSecret
  35. authorizeURL:anAuthorizeURL
  36. tokenURL:aTokenURL
  37. accessToken:nil
  38. keyChainGroup:nil
  39. persistent:YES
  40. delegate:aDelegate];
  41. }
  42. - (id)initWithClientID:(NSString *)aClientId
  43. clientSecret:(NSString *)aClientSecret
  44. authorizeURL:(NSURL *)anAuthorizeURL
  45. tokenURL:(NSURL *)aTokenURL
  46. accessToken:(NXOAuth2AccessToken *)anAccessToken
  47. keyChainGroup:(NSString *)aKeyChainGroup
  48. persistent:(BOOL)shouldPersist
  49. delegate:(NSObject<NXOAuth2ClientDelegate> *)aDelegate;
  50. {
  51. return [self initWithClientID:aClientId
  52. clientSecret:aClientSecret
  53. authorizeURL:anAuthorizeURL
  54. tokenURL:aTokenURL
  55. accessToken:anAccessToken
  56. tokenType:nil
  57. keyChainGroup:aKeyChainGroup
  58. persistent:shouldPersist
  59. delegate:aDelegate];
  60. }
  61. - (id)initWithClientID:(NSString *)aClientId
  62. clientSecret:(NSString *)aClientSecret
  63. authorizeURL:(NSURL *)anAuthorizeURL
  64. tokenURL:(NSURL *)aTokenURL
  65. accessToken:(NXOAuth2AccessToken *)anAccessToken
  66. tokenType:(NSString *)aTokenType
  67. keyChainGroup:(NSString *)aKeyChainGroup
  68. persistent:(BOOL)shouldPersist
  69. delegate:(NSObject<NXOAuth2ClientDelegate> *)aDelegate;
  70. {
  71. NSAssert(aTokenURL != nil && anAuthorizeURL != nil, @"No token or no authorize URL");
  72. self = [super init];
  73. if (self) {
  74. refreshConnectionDidRetryCount = 0;
  75. clientId = [aClientId copy];
  76. clientSecret = [aClientSecret copy];
  77. authorizeURL = [anAuthorizeURL copy];
  78. tokenURL = [aTokenURL copy];
  79. tokenType = [aTokenType copy];
  80. accessToken = anAccessToken;
  81. self.tokenRequestHTTPMethod = @"POST";
  82. self.acceptType = @"application/json";
  83. keyChainGroup = aKeyChainGroup;
  84. self.persistent = shouldPersist;
  85. self.delegate = aDelegate;
  86. }
  87. return self;
  88. }
  89. - (void)dealloc;
  90. {
  91. [authConnection cancel];
  92. }
  93. #pragma mark Accessors
  94. @synthesize clientId, clientSecret, tokenType;
  95. @synthesize desiredScope, userAgent;
  96. @synthesize delegate, persistent, accessToken, authenticating;
  97. @synthesize additionalAuthenticationParameters;
  98. - (void)setAdditionalAuthenticationParameters:(NSDictionary *)value;
  99. {
  100. if (value == additionalAuthenticationParameters) return;
  101. NSArray *forbiddenKeys = @[ @"grant_type", @"client_id",
  102. @"client_secret",
  103. @"username", @"password",
  104. @"redirect_uri", @"code",
  105. @"assertion_type", @"assertion" ];
  106. for (id key in value) {
  107. if ([forbiddenKeys containsObject:key]) {
  108. [[NSException exceptionWithName:NSInvalidArgumentException
  109. reason:[NSString stringWithFormat:@"'%@' is not allowed as a key for additionalAuthenticationParameters", key]
  110. userInfo:nil] raise];
  111. }
  112. }
  113. additionalAuthenticationParameters = value;
  114. }
  115. - (void)setPersistent:(BOOL)shouldPersist;
  116. {
  117. if (persistent == shouldPersist) return;
  118. if (shouldPersist && accessToken) {
  119. [self.accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]];
  120. }
  121. if (persistent && !shouldPersist) {
  122. [accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]];
  123. }
  124. [self willChangeValueForKey:@"persistent"];
  125. persistent = shouldPersist;
  126. [self didChangeValueForKey:@"persistent"];
  127. }
  128. - (NXOAuth2AccessToken *)accessToken;
  129. {
  130. if (accessToken) return accessToken;
  131. if (persistent) {
  132. accessToken = [NXOAuth2AccessToken tokenFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]];
  133. if (accessToken) {
  134. if ([delegate respondsToSelector:@selector(oauthClientDidGetAccessToken:)]) {
  135. [delegate oauthClientDidGetAccessToken:self];
  136. }
  137. }
  138. return accessToken;
  139. } else {
  140. return nil;
  141. }
  142. }
  143. - (void)setAccessToken:(NXOAuth2AccessToken *)value;
  144. {
  145. if (self.accessToken == value) return;
  146. BOOL authorisationStatusChanged = ((accessToken == nil) || (value == nil)); //They can't both be nil, see one line above. So they have to have changed from or to nil.
  147. if (!value) {
  148. [self.accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]];
  149. }
  150. [self willChangeValueForKey:@"accessToken"];
  151. accessToken = value;
  152. [self didChangeValueForKey:@"accessToken"];
  153. if (persistent) {
  154. [accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]];
  155. }
  156. if (authorisationStatusChanged) {
  157. if (accessToken) {
  158. if ([delegate respondsToSelector:@selector(oauthClientDidGetAccessToken:)]) {
  159. [delegate oauthClientDidGetAccessToken:self];
  160. }
  161. } else {
  162. if ([delegate respondsToSelector:@selector(oauthClientDidLoseAccessToken:)]) {
  163. [delegate oauthClientDidLoseAccessToken:self];
  164. }
  165. }
  166. } else {
  167. if ([delegate respondsToSelector:@selector(oauthClientDidRefreshAccessToken:)]) {
  168. [delegate oauthClientDidRefreshAccessToken:self];
  169. }
  170. }
  171. }
  172. - (void)setDesiredScope:(NSSet *)aDesiredScope;
  173. {
  174. if (desiredScope == aDesiredScope) {
  175. return;
  176. }
  177. desiredScope = [aDesiredScope copy];
  178. }
  179. #pragma mark Flow
  180. - (void)requestAccess;
  181. {
  182. if (!self.accessToken) {
  183. [delegate oauthClientNeedsAuthentication:self];
  184. }
  185. }
  186. - (NSURL *)authorizationURLWithRedirectURL:(NSURL *)redirectURL;
  187. {
  188. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  189. @"code", @"response_type",
  190. clientId, @"client_id",
  191. [redirectURL absoluteString], @"redirect_uri",
  192. nil];
  193. if (self.additionalAuthenticationParameters) {
  194. [parameters addEntriesFromDictionary:self.additionalAuthenticationParameters];
  195. }
  196. if (self.desiredScope.count > 0) {
  197. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  198. }
  199. return [authorizeURL nxoauth2_URLByAddingParameters:parameters];
  200. }
  201. // Web Server Flow only
  202. - (BOOL)openRedirectURL:(NSURL *)URL;
  203. {
  204. NSString *accessGrant = [URL nxoauth2_valueForQueryParameterKey:@"code"];
  205. if (accessGrant) {
  206. [self requestTokenWithAuthGrant:accessGrant redirectURL:[URL nxoauth2_URLWithoutQueryString]];
  207. return YES;
  208. }
  209. NSString *errorString = [URL nxoauth2_valueForQueryParameterKey:@"error"];
  210. if (errorString) {
  211. NSInteger errorCode = 0;
  212. NSString *localizedError = nil;
  213. if ([errorString caseInsensitiveCompare:@"invalid_request"] == NSOrderedSame) {
  214. errorCode = NXOAuth2InvalidRequestErrorCode;
  215. localizedError = NSLocalizedString(@"Invalid request to OAuth2 Server", @"NXOAuth2InvalidRequestErrorCode description");
  216. } else if ([errorString caseInsensitiveCompare:@"invalid_client"] == NSOrderedSame) {
  217. errorCode = NXOAuth2InvalidClientErrorCode;
  218. localizedError = NSLocalizedString(@"Invalid OAuth2 Client", @"NXOAuth2InvalidClientErrorCode description");
  219. } else if ([errorString caseInsensitiveCompare:@"unauthorized_client"] == NSOrderedSame) {
  220. errorCode = NXOAuth2UnauthorizedClientErrorCode;
  221. localizedError = NSLocalizedString(@"Unauthorized Client", @"NXOAuth2UnauthorizedClientErrorCode description");
  222. } else if ([errorString caseInsensitiveCompare:@"redirect_uri_mismatch"] == NSOrderedSame) {
  223. errorCode = NXOAuth2RedirectURIMismatchErrorCode;
  224. localizedError = NSLocalizedString(@"Redirect URI mismatch", @"NXOAuth2RedirectURIMismatchErrorCode description");
  225. } else if ([errorString caseInsensitiveCompare:@"access_denied"] == NSOrderedSame) {
  226. errorCode = NXOAuth2AccessDeniedErrorCode;
  227. localizedError = NSLocalizedString(@"Access denied", @"NXOAuth2AccessDeniedErrorCode description");
  228. } else if ([errorString caseInsensitiveCompare:@"unsupported_response_type"] == NSOrderedSame) {
  229. errorCode = NXOAuth2UnsupportedResponseTypeErrorCode;
  230. localizedError = NSLocalizedString(@"Unsupported response type", @"NXOAuth2UnsupportedResponseTypeErrorCode description");
  231. } else if ([errorString caseInsensitiveCompare:@"invalid_scope"] == NSOrderedSame) {
  232. errorCode = NXOAuth2InvalidScopeErrorCode;
  233. localizedError = NSLocalizedString(@"Invalid scope", @"NXOAuth2InvalidScopeErrorCode description");
  234. }
  235. if (errorCode != 0) {
  236. NSDictionary *userInfo = nil;
  237. if (localizedError) {
  238. userInfo = [NSDictionary dictionaryWithObject:localizedError forKey:NSLocalizedDescriptionKey];
  239. }
  240. if ([delegate respondsToSelector:@selector(oauthClient:didFailToGetAccessTokenWithError:)]) {
  241. [delegate oauthClient:self didFailToGetAccessTokenWithError:[NSError errorWithDomain:NXOAuth2ErrorDomain
  242. code:errorCode
  243. userInfo:userInfo]];
  244. }
  245. }
  246. }
  247. return NO;
  248. }
  249. #pragma mark Request Token
  250. // Web Server Flow only
  251. - (void)requestTokenWithAuthGrant:(NSString *)authGrant redirectURL:(NSURL *)redirectURL;
  252. {
  253. NSAssert1(!authConnection, @"authConnection already running with: %@", authConnection);
  254. NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
  255. [tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
  256. [authConnection cancel]; // just to be sure
  257. self.authenticating = YES;
  258. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  259. @"authorization_code", @"grant_type",
  260. clientId, @"client_id",
  261. clientSecret, @"client_secret",
  262. [redirectURL absoluteString], @"redirect_uri",
  263. authGrant, @"code",
  264. nil];
  265. if (self.desiredScope) {
  266. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  267. }
  268. if (self.customHeaderFields) {
  269. [self.customHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  270. [tokenRequest addValue:obj forHTTPHeaderField:key];
  271. }];
  272. }
  273. if (self.additionalAuthenticationParameters) {
  274. [parameters addEntriesFromDictionary:self.additionalAuthenticationParameters];
  275. }
  276. authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
  277. requestParameters:parameters
  278. oauthClient:self
  279. delegate:self];
  280. authConnection.context = NXOAuth2ClientConnectionContextTokenRequest;
  281. }
  282. // Client Credential Flow
  283. - (void)authenticateWithClientCredentials;
  284. {
  285. NSAssert1(!authConnection, @"authConnection already running with: %@", authConnection);
  286. NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
  287. [tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
  288. [authConnection cancel]; // just to be sure
  289. self.authenticating = YES;
  290. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  291. @"client_credentials", @"grant_type",
  292. clientId, @"client_id",
  293. clientSecret, @"client_secret",
  294. nil];
  295. if (self.desiredScope) {
  296. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  297. }
  298. if (self.customHeaderFields) {
  299. [self.customHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  300. [tokenRequest addValue:obj forHTTPHeaderField:key];
  301. }];
  302. }
  303. authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
  304. requestParameters:parameters
  305. oauthClient:self
  306. delegate:self];
  307. authConnection.context = NXOAuth2ClientConnectionContextTokenRequest;
  308. }
  309. // User Password Flow Only
  310. - (void)authenticateWithUsername:(NSString *)username password:(NSString *)password;
  311. {
  312. NSAssert1(!authConnection, @"authConnection already running with: %@", authConnection);
  313. NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
  314. [tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
  315. [authConnection cancel]; // just to be sure
  316. self.authenticating = YES;
  317. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  318. @"password", @"grant_type",
  319. clientId, @"client_id",
  320. clientSecret, @"client_secret",
  321. username, @"username",
  322. password, @"password",
  323. nil];
  324. if (self.desiredScope) {
  325. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  326. }
  327. if (self.additionalAuthenticationParameters) {
  328. [parameters addEntriesFromDictionary:self.additionalAuthenticationParameters];
  329. }
  330. if (self.customHeaderFields) {
  331. [self.customHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  332. [tokenRequest addValue:obj forHTTPHeaderField:key];
  333. }];
  334. }
  335. authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
  336. requestParameters:parameters
  337. oauthClient:self
  338. delegate:self];
  339. authConnection.context = NXOAuth2ClientConnectionContextTokenRequest;
  340. }
  341. // Assertion
  342. - (void)authenticateWithAssertionType:(NSURL *)anAssertionType assertion:(NSString *)anAssertion;
  343. {
  344. NSAssert1(!authConnection, @"authConnection already running with: %@", authConnection);
  345. NSParameterAssert(anAssertionType);
  346. NSParameterAssert(anAssertion);
  347. NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
  348. [tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
  349. [authConnection cancel]; // just to be sure
  350. self.authenticating = YES;
  351. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  352. @"assertion", @"grant_type",
  353. clientId, @"client_id",
  354. clientSecret, @"client_secret",
  355. anAssertionType.absoluteString, @"assertion_type",
  356. anAssertion, @"assertion",
  357. nil];
  358. if (self.desiredScope) {
  359. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  360. }
  361. authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
  362. requestParameters:parameters
  363. oauthClient:self
  364. delegate:self];
  365. authConnection.context = NXOAuth2ClientConnectionContextTokenRequest;
  366. }
  367. #pragma mark Public
  368. - (void)refreshAccessToken
  369. {
  370. [self refreshAccessTokenAndRetryConnection:nil];
  371. }
  372. - (void)refreshAccessTokenAndRetryConnection:(NXOAuth2Connection *)retryConnection;
  373. {
  374. if (retryConnection) {
  375. if (!waitingConnections) waitingConnections = [[NSMutableArray alloc] init];
  376. [waitingConnections addObject:retryConnection];
  377. }
  378. if (!authConnection) {
  379. NSAssert((accessToken.refreshToken != nil), @"invalid state");
  380. NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
  381. [tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
  382. [authConnection cancel]; // not needed, but looks more clean to me :)
  383. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  384. @"refresh_token", @"grant_type",
  385. clientId, @"client_id",
  386. clientSecret, @"client_secret",
  387. accessToken.refreshToken, @"refresh_token",
  388. nil];
  389. if (self.desiredScope) {
  390. [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"];
  391. }
  392. authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
  393. requestParameters:parameters
  394. oauthClient:self
  395. delegate:self];
  396. authConnection.context = NXOAuth2ClientConnectionContextTokenRefresh;
  397. }
  398. }
  399. - (void)removeConnectionFromWaitingQueue:(NXOAuth2Connection *)aConnection;
  400. {
  401. if (!aConnection) return;
  402. [waitingConnections removeObject:aConnection];
  403. }
  404. #pragma mark NXOAuth2ConnectionDelegate
  405. - (void)oauthConnection:(NXOAuth2Connection *)connection didFinishWithData:(NSData *)data;
  406. {
  407. if (connection == authConnection) {
  408. self.authenticating = NO;
  409. NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  410. NXOAuth2AccessToken *newToken = [NXOAuth2AccessToken tokenWithResponseBody:result tokenType:self.tokenType
  411. ];
  412. NSAssert(newToken != nil, @"invalid response?");
  413. [newToken restoreWithOldToken:self.accessToken];
  414. self.accessToken = newToken;
  415. for (NXOAuth2Connection *retryConnection in waitingConnections) {
  416. [retryConnection retry];
  417. }
  418. [waitingConnections removeAllObjects];
  419. authConnection = nil;
  420. refreshConnectionDidRetryCount = 0; // reset
  421. }
  422. }
  423. - (void)oauthConnection:(NXOAuth2Connection *)connection didFailWithError:(NSError *)error;
  424. {
  425. NSString *body = [[NSString alloc] initWithData:connection.data encoding:NSUTF8StringEncoding];
  426. NSLog(@"oauthConnection Error: %@", body);
  427. if (connection == authConnection) {
  428. self.authenticating = NO;
  429. id context = connection.context;
  430. authConnection = nil;
  431. if ([context isEqualToString:NXOAuth2ClientConnectionContextTokenRefresh]
  432. && [[error domain] isEqualToString:NXOAuth2HTTPErrorDomain]
  433. && error.code >= 500 && error.code < 600
  434. && refreshConnectionDidRetryCount < 4) {
  435. // no token refresh because of a server issue. don't give up just yet.
  436. [self performSelector:@selector(refreshAccessToken) withObject:nil afterDelay:1];
  437. refreshConnectionDidRetryCount++;
  438. } else {
  439. if ([context isEqualToString:NXOAuth2ClientConnectionContextTokenRefresh]) {
  440. NSError *retryFailedError = [NSError errorWithDomain:NXOAuth2ErrorDomain
  441. code:NXOAuth2CouldNotRefreshTokenErrorCode
  442. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  443. NSLocalizedString(@"Access token could not be refreshed", @"NXOAuth2CouldNotRefreshTokenErrorCode description"), NSLocalizedDescriptionKey,
  444. nil]];
  445. NSArray *failedConnections = [waitingConnections copy];
  446. [waitingConnections removeAllObjects];
  447. for (NXOAuth2Connection *connection in failedConnections) {
  448. id<NXOAuth2ConnectionDelegate> connectionDelegate = connection.delegate;
  449. if ([connectionDelegate respondsToSelector:@selector(oauthConnection:didFailWithError:)]) {
  450. [connectionDelegate oauthConnection:connection didFailWithError:retryFailedError];
  451. }
  452. }
  453. }
  454. if ([[error domain] isEqualToString:NXOAuth2HTTPErrorDomain]
  455. && error.code == 401) {
  456. self.accessToken = nil; // reset the token since it got invalid
  457. }
  458. if ([delegate respondsToSelector:@selector(oauthClient:didFailToGetAccessTokenWithError:)]) {
  459. [delegate oauthClient:self didFailToGetAccessTokenWithError:error];
  460. }
  461. }
  462. }
  463. }
  464. @end