added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMSyncUser.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2016 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMSyncUser_Private.hpp"
20
21 #import "RLMJSONModels.h"
22 #import "RLMNetworkClient.h"
23 #import "RLMRealmConfiguration+Sync.h"
24 #import "RLMRealmConfiguration_Private.hpp"
25 #import "RLMRealmUtil.hpp"
26 #import "RLMResults_Private.hpp"
27 #import "RLMSyncManager_Private.h"
28 #import "RLMSyncPermissionResults.h"
29 #import "RLMSyncPermission_Private.hpp"
30 #import "RLMSyncSessionRefreshHandle.hpp"
31 #import "RLMSyncSession_Private.hpp"
32 #import "RLMSyncUtil_Private.hpp"
33 #import "RLMUtil.hpp"
34
35 #import "sync/sync_manager.hpp"
36 #import "sync/sync_session.hpp"
37 #import "sync/sync_user.hpp"
38
39 using namespace realm;
40 using ConfigMaker = std::function<Realm::Config(std::shared_ptr<SyncUser>, std::string)>;
41
42 namespace {
43
44 std::function<void(Results, std::exception_ptr)> RLMWrapPermissionResultsCallback(RLMPermissionResultsBlock callback) {
45     return [callback](Results results, std::exception_ptr ptr) {
46         if (ptr) {
47             NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeGet);
48             REALM_ASSERT(error);
49             callback(nil, error);
50         } else {
51             // Finished successfully
52             callback([[RLMSyncPermissionResults alloc] initWithResults:std::move(results)], nil);
53         }
54     };
55 }
56
57 NSString *tildeSubstitutedPathForRealmURL(NSURL *url, NSString *identity) {
58     return [[url path] stringByReplacingOccurrencesOfString:@"~" withString:identity];
59 }
60
61 }
62
63 void CocoaSyncUserContext::register_refresh_handle(const std::string& path, RLMSyncSessionRefreshHandle *handle)
64 {
65     REALM_ASSERT(handle);
66     std::lock_guard<std::mutex> lock(m_mutex);
67     auto it = m_refresh_handles.find(path);
68     if (it != m_refresh_handles.end()) {
69         [it->second invalidate];
70         m_refresh_handles.erase(it);
71     }
72     m_refresh_handles.insert({path, handle});
73 }
74
75 void CocoaSyncUserContext::unregister_refresh_handle(const std::string& path)
76 {
77     std::lock_guard<std::mutex> lock(m_mutex);
78     m_refresh_handles.erase(path);
79 }
80
81 void CocoaSyncUserContext::invalidate_all_handles()
82 {
83     std::lock_guard<std::mutex> lock(m_mutex);
84     for (auto& it : m_refresh_handles) {
85         [it.second invalidate];
86     }
87     m_refresh_handles.clear();
88 }
89
90 RLMUserErrorReportingBlock CocoaSyncUserContext::error_handler() const
91 {
92     std::lock_guard<std::mutex> lock(m_error_handler_mutex);
93     return m_error_handler;
94 }
95
96 void CocoaSyncUserContext::set_error_handler(RLMUserErrorReportingBlock block)
97 {
98     std::lock_guard<std::mutex> lock(m_error_handler_mutex);
99     m_error_handler = block;
100 }
101
102 PermissionChangeCallback RLMWrapPermissionStatusCallback(RLMPermissionStatusBlock callback) {
103     return [callback](std::exception_ptr ptr) {
104         if (ptr) {
105             NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeChange);
106             REALM_ASSERT(error);
107             callback(error);
108         } else {
109             // Finished successfully
110             callback(nil);
111         }
112     };
113 }
114
115 @interface RLMSyncUserInfo ()
116
117 @property (nonatomic, readwrite) NSArray *accounts;
118 @property (nonatomic, readwrite) NSDictionary *metadata;
119 @property (nonatomic, readwrite) NSString *identity;
120 @property (nonatomic, readwrite) BOOL isAdmin;
121
122 + (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model;
123
124 @end
125
126 @interface RLMSyncUser () {
127     std::shared_ptr<SyncUser> _user;
128     // FIXME: remove this when the object store ConfigMaker goes away
129     std::unique_ptr<ConfigMaker> _configMaker;
130 }
131
132 - (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
133
134 @end
135
136 @implementation RLMSyncUser
137
138 #pragma mark - static API
139
140 + (NSDictionary *)allUsers {
141     NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers];
142     return [NSDictionary dictionaryWithObjects:allUsers
143                                        forKeys:[allUsers valueForKey:@"identity"]];
144 }
145
146 + (RLMSyncUser *)currentUser {
147     NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers];
148     if (allUsers.count > 1) {
149         @throw RLMException(@"+currentUser cannot be called if more that one valid, logged-in user exists.");
150     }
151     return allUsers.firstObject;
152 }
153
154 #pragma mark - API
155
156 - (instancetype)initPrivate {
157     if (self = [super init]) {
158         _configMaker = std::make_unique<ConfigMaker>([](std::shared_ptr<SyncUser> user, std::string url) {
159             RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
160             NSURL *objCUrl = [NSURL URLWithString:@(url.c_str())];
161             RLMSyncUser *objCUser = [[RLMSyncUser alloc] initWithSyncUser:std::move(user)];
162             config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:objCUser realmURL:objCUrl];
163             return [config config];
164         });
165         return self;
166     }
167     return nil;
168 }
169
170 - (instancetype)initWithSyncUser:(std::shared_ptr<SyncUser>)user {
171     if (self = [self initPrivate]) {
172         _user = user;
173         return self;
174     }
175     return nil;
176 }
177
178 - (BOOL)isEqual:(id)object {
179     if (![object isKindOfClass:[RLMSyncUser class]]) {
180         return NO;
181     }
182     return _user == ((RLMSyncUser *)object)->_user;
183 }
184
185 + (void)logInWithCredentials:(RLMSyncCredentials *)credential
186                authServerURL:(NSURL *)authServerURL
187                 onCompletion:(RLMUserCompletionBlock)completion {
188     [self logInWithCredentials:credential
189                  authServerURL:authServerURL
190                        timeout:30
191                  callbackQueue:dispatch_get_main_queue()
192                   onCompletion:completion];
193 }
194
195 + (void)logInWithCredentials:(RLMSyncCredentials *)credential
196                authServerURL:(NSURL *)authServerURL
197                      timeout:(NSTimeInterval)timeout
198                callbackQueue:(dispatch_queue_t)callbackQueue
199                 onCompletion:(RLMUserCompletionBlock)completion {
200     RLMSyncUser *user = [[RLMSyncUser alloc] initPrivate];
201     [RLMSyncUser _performLogInForUser:user
202                           credentials:credential
203                         authServerURL:authServerURL
204                               timeout:timeout
205                         callbackQueue:callbackQueue
206                       completionBlock:completion];
207 }
208
209 - (void)logOut {
210     if (!_user) {
211         return;
212     }
213     _user->log_out();
214     context_for(_user).invalidate_all_handles();
215 }
216
217 - (RLMUserErrorReportingBlock)errorHandler {
218     if (!_user) {
219         return nil;
220     }
221     return context_for(_user).error_handler();
222 }
223
224 - (void)setErrorHandler:(RLMUserErrorReportingBlock)errorHandler {
225     if (!_user) {
226         return;
227     }
228     context_for(_user).set_error_handler([errorHandler copy]);
229 }
230
231 - (nullable RLMSyncSession *)sessionForURL:(NSURL *)url {
232     if (!_user) {
233         return nil;
234     }
235     auto path = SyncManager::shared().path_for_realm(*_user, [url.absoluteString UTF8String]);
236     if (auto session = _user->session_for_on_disk_path(path)) {
237         return [[RLMSyncSession alloc] initWithSyncSession:session];
238     }
239     return nil;
240 }
241
242 - (NSArray<RLMSyncSession *> *)allSessions {
243     if (!_user) {
244         return @[];
245     }
246     NSMutableArray<RLMSyncSession *> *buffer = [NSMutableArray array];
247     auto sessions = _user->all_sessions();
248     for (auto session : sessions) {
249         [buffer addObject:[[RLMSyncSession alloc] initWithSyncSession:std::move(session)]];
250     }
251     return [buffer copy];
252 }
253
254 - (NSString *)identity {
255     if (!_user) {
256         return nil;
257     }
258     return @(_user->identity().c_str());
259 }
260
261 - (RLMSyncUserState)state {
262     if (!_user) {
263         return RLMSyncUserStateError;
264     }
265     switch (_user->state()) {
266         case SyncUser::State::Active:
267             return RLMSyncUserStateActive;
268         case SyncUser::State::LoggedOut:
269             return RLMSyncUserStateLoggedOut;
270         case SyncUser::State::Error:
271             return RLMSyncUserStateError;
272     }
273 }
274
275 - (NSURL *)authenticationServer {
276     if (!_user || _user->token_type() == SyncUser::TokenType::Admin) {
277         return nil;
278     }
279     return [NSURL URLWithString:@(_user->server_url().c_str())];
280 }
281
282 - (BOOL)isAdmin {
283     if (!_user) {
284         return NO;
285     }
286     return _user->is_admin();
287 }
288
289 #pragma mark - Passwords
290
291 - (void)changePassword:(NSString *)newPassword completion:(RLMPasswordChangeStatusBlock)completion {
292     [self changePassword:newPassword forUserID:self.identity completion:completion];
293 }
294
295 - (void)changePassword:(NSString *)newPassword forUserID:(NSString *)userID completion:(RLMPasswordChangeStatusBlock)completion {
296     if (self.state != RLMSyncUserStateActive) {
297         completion([NSError errorWithDomain:RLMSyncErrorDomain
298                                        code:RLMSyncErrorClientSessionError
299                                    userInfo:nil]);
300         return;
301     }
302     [RLMNetworkClient sendRequestToEndpoint:[RLMSyncChangePasswordEndpoint endpoint]
303                                      server:self.authenticationServer
304                                        JSON:@{kRLMSyncTokenKey: self._refreshToken,
305                                               kRLMSyncUserIDKey: userID,
306                                               kRLMSyncDataKey: @{ kRLMSyncNewPasswordKey: newPassword }
307                                               }
308                                     timeout:60
309                                  completion:^(NSError *error, __unused NSDictionary *json) {
310         completion(error);
311     }];
312 }
313
314 #pragma mark - Administrator API
315
316 - (void)retrieveInfoForUser:(NSString *)providerUserIdentity
317            identityProvider:(RLMIdentityProvider)provider
318                  completion:(RLMRetrieveUserBlock)completion {
319     [RLMNetworkClient sendRequestToEndpoint:[RLMSyncGetUserInfoEndpoint endpoint]
320                                      server:self.authenticationServer
321                                        JSON:@{
322                                               kRLMSyncProviderKey: provider,
323                                               kRLMSyncProviderIDKey: providerUserIdentity,
324                                               kRLMSyncTokenKey: self._refreshToken
325                                               }
326                                  completion:^(NSError *error, NSDictionary *json) {
327                                      if (error) {
328                                          completion(nil, error);
329                                          return;
330                                      }
331                                      RLMUserResponseModel *model = [[RLMUserResponseModel alloc] initWithDictionary:json];
332                                      if (!model) {
333                                          completion(nil, make_auth_error_bad_response(json));
334                                          return;
335                                      }
336                                      completion([RLMSyncUserInfo syncUserInfoWithModel:model], nil);
337                                  }];
338 }
339
340 #pragma mark - Permissions API
341
342 static void verifyInRunLoop() {
343     if (!RLMIsInRunLoop()) {
344         @throw RLMException(@"Can only access or modify permissions from a thread which has a run loop (by default, only the main thread).");
345     }
346 }
347
348 - (void)retrievePermissionsWithCallback:(RLMPermissionResultsBlock)callback {
349     verifyInRunLoop();
350     if (!_user || _user->state() == SyncUser::State::Error) {
351         callback(nullptr, make_permission_error_get(@"Permissions cannot be retrieved using an invalid user."));
352         return;
353     }
354     Permissions::get_permissions(_user, RLMWrapPermissionResultsCallback(callback), *_configMaker);
355 }
356
357 - (void)applyPermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
358     verifyInRunLoop();
359     if (!_user || _user->state() == SyncUser::State::Error) {
360         callback(make_permission_error_change(@"Permissions cannot be applied using an invalid user."));
361         return;
362     }
363     Permissions::set_permission(_user,
364                                 [permission rawPermission],
365                                 RLMWrapPermissionStatusCallback(callback),
366                                 *_configMaker);
367 }
368
369 - (void)revokePermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
370     verifyInRunLoop();
371     if (!_user || _user->state() == SyncUser::State::Error) {
372         callback(make_permission_error_change(@"Permissions cannot be revoked using an invalid user."));
373         return;
374     }
375     Permissions::delete_permission(_user,
376                                    [permission rawPermission],
377                                    RLMWrapPermissionStatusCallback(callback),
378                                    *_configMaker);
379 }
380
381 - (void)createOfferForRealmAtURL:(NSURL *)url
382                      accessLevel:(RLMSyncAccessLevel)accessLevel
383                       expiration:(NSDate *)expirationDate
384                         callback:(RLMPermissionOfferStatusBlock)callback {
385     verifyInRunLoop();
386     if (!_user || _user->state() == SyncUser::State::Error) {
387         callback(nil, make_permission_error_change(@"A permission offer cannot be created using an invalid user."));
388         return;
389     }
390     auto cb = [callback](util::Optional<std::string> token, std::exception_ptr ptr) {
391         if (ptr) {
392             NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeOffer);
393             REALM_ASSERT_DEBUG(error);
394             callback(nil, error);
395         } else {
396             REALM_ASSERT_DEBUG(token);
397             callback(@(token->c_str()), nil);
398         }
399     };
400     auto offer = PermissionOffer{
401         [tildeSubstitutedPathForRealmURL(url, self.identity) UTF8String],
402         accessLevelForObjCAccessLevel(accessLevel),
403         RLMTimestampForNSDate(expirationDate),
404     };
405     Permissions::make_offer(_user, std::move(offer), std::move(cb), *_configMaker);
406 }
407
408 - (void)acceptOfferForToken:(NSString *)token
409                    callback:(RLMPermissionOfferResponseStatusBlock)callback {
410     verifyInRunLoop();
411     if (!_user || _user->state() == SyncUser::State::Error) {
412         callback(nil, make_permission_error_change(@"A permission offer cannot be accepted by an invalid user."));
413         return;
414     }
415     NSURLComponents *baseURL = [NSURLComponents componentsWithURL:self.authenticationServer
416                                           resolvingAgainstBaseURL:YES];
417     if ([baseURL.scheme isEqualToString:@"http"]) {
418         baseURL.scheme = @"realm";
419     } else if ([baseURL.scheme isEqualToString:@"https"]) {
420         baseURL.scheme = @"realms";
421     }
422     auto cb = [baseURL, callback](util::Optional<std::string> raw_path, std::exception_ptr ptr) {
423         if (ptr) {
424             NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeAcceptOffer);
425             REALM_ASSERT_DEBUG(error);
426             callback(nil, error);
427         } else {
428             // Note that ROS currently vends the path to the Realm, so we need to construct the full URL ourselves.
429             REALM_ASSERT_DEBUG(raw_path);
430             baseURL.path = @(raw_path->c_str());
431             callback([baseURL URL], nil);
432         }
433     };
434     Permissions::accept_offer(_user, [token UTF8String], std::move(cb), *_configMaker);
435 }
436
437 #pragma mark - Private API
438
439 + (void)_setUpBindingContextFactory {
440     SyncUser::set_binding_context_factory([] {
441         return std::make_shared<CocoaSyncUserContext>();
442     });
443 }
444
445 - (NSString *)_refreshToken {
446     if (!_user) {
447         return nil;
448     }
449     return @(_user->refresh_token().c_str());
450 }
451
452 - (std::shared_ptr<SyncUser>)_syncUser {
453     return _user;
454 }
455
456 + (void)_performLogInForUser:(RLMSyncUser *)user
457                  credentials:(RLMSyncCredentials *)credentials
458                authServerURL:(NSURL *)authServerURL
459                      timeout:(NSTimeInterval)timeout
460                callbackQueue:(dispatch_queue_t)callbackQueue
461              completionBlock:(RLMUserCompletionBlock)completion {
462     // Special credential login should be treated differently.
463     if (credentials.provider == RLMIdentityProviderAccessToken) {
464         [self _performLoginForDirectAccessTokenCredentials:credentials
465                                                       user:user
466                                              authServerURL:authServerURL
467                                            completionBlock:completion];
468         return;
469     }
470     if (!authServerURL) {
471         @throw RLMException(@"A user cannot be logged in without specifying an authentication server URL.");
472     }
473
474     // Prepare login network request
475     NSMutableDictionary *json = [@{
476                                    kRLMSyncProviderKey: credentials.provider,
477                                    kRLMSyncDataKey: credentials.token,
478                                    kRLMSyncAppIDKey: [RLMSyncManager sharedManager].appID,
479                                    } mutableCopy];
480     NSMutableDictionary *info = [(credentials.userInfo ?: @{}) mutableCopy];
481
482     if ([info count] > 0) {
483         // Munge user info into the JSON request.
484         json[@"user_info"] = info;
485     }
486
487     RLMSyncCompletionBlock handler = ^(NSError *error, NSDictionary *json) {
488         if (json && !error) {
489             RLMAuthResponseModel *model = [[RLMAuthResponseModel alloc] initWithDictionary:json
490                                                                         requireAccessToken:NO
491                                                                        requireRefreshToken:YES];
492             if (!model) {
493                 // Malformed JSON
494                 NSError *badResponseError = make_auth_error_bad_response(json);
495                 dispatch_async(callbackQueue, ^{
496                     completion(nil, badResponseError);
497                 });
498                 return;
499             } else {
500                 std::string server_url = authServerURL.absoluteString.UTF8String;
501                 SyncUserIdentifier identity{[model.refreshToken.tokenData.identity UTF8String], std::move(server_url)};
502                 auto sync_user = SyncManager::shared().get_user(identity , [model.refreshToken.token UTF8String]);
503                 if (!sync_user) {
504                     NSError *authError = make_auth_error_client_issue();
505                     dispatch_async(callbackQueue, ^{
506                         completion(nil, authError);
507                     });
508                     return;
509                 }
510                 sync_user->set_is_admin(model.refreshToken.tokenData.isAdmin);
511                 user->_user = sync_user;
512                 dispatch_async(callbackQueue, ^{
513                     completion(user, nil);
514                 });
515             }
516         } else {
517             // Something else went wrong
518             dispatch_async(callbackQueue, ^{
519                 completion(nil, error);
520             });
521         }
522     };
523     [RLMNetworkClient sendRequestToEndpoint:[RLMSyncAuthEndpoint endpoint]
524                                      server:authServerURL
525                                        JSON:json
526                                     timeout:timeout
527                                  completion:^(NSError *error, NSDictionary *dictionary) {
528                                      dispatch_async(callbackQueue, ^{
529                                          handler(error, dictionary);
530                                      });
531                                  }];
532 }
533
534 + (void)_performLoginForDirectAccessTokenCredentials:(RLMSyncCredentials *)credentials
535                                                 user:(RLMSyncUser *)user
536                                        authServerURL:(NSURL *)serverURL
537                                      completionBlock:(nonnull RLMUserCompletionBlock)completion {
538     NSString *identity = credentials.userInfo[kRLMSyncIdentityKey];
539     std::shared_ptr<SyncUser> sync_user;
540     if (serverURL) {
541         NSString *scheme = serverURL.scheme;
542         if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
543             @throw RLMException(@"The Realm Object Server authentication URL provided for this user, \"%@\", "
544                                 @" is invalid. It must begin with http:// or https://.", serverURL);
545         }
546         // Retrieve the user based on the auth server URL.
547         util::Optional<std::string> identity_string;
548         if (identity) {
549             identity_string = std::string(identity.UTF8String);
550         }
551         sync_user = SyncManager::shared().get_admin_token_user([serverURL absoluteString].UTF8String,
552                                                                credentials.token.UTF8String,
553                                                                std::move(identity_string));
554     } else {
555         // Retrieve the user based on the identity.
556         if (!identity) {
557             @throw RLMException(@"A direct access credential must specify either an identity, a server URL, or both.");
558         }
559         sync_user = SyncManager::shared().get_admin_token_user_from_identity(identity.UTF8String,
560                                                                              none,
561                                                                              credentials.token.UTF8String);
562     }
563     if (!sync_user) {
564         completion(nil, make_auth_error_client_issue());
565         return;
566     }
567     user->_user = sync_user;
568     completion(user, nil);
569 }
570
571 @end
572
573 #pragma mark - RLMSyncUserInfo
574
575 @implementation RLMSyncUserInfo
576
577 - (instancetype)initPrivate {
578     return [super init];
579 }
580
581 + (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model {
582     RLMSyncUserInfo *info = [[RLMSyncUserInfo alloc] initPrivate];
583     info.accounts = model.accounts;
584     info.metadata = model.metadata;
585     info.isAdmin = model.isAdmin;
586     info.identity = model.identity;
587     return info;
588 }
589
590 @end