1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2016 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
19 #import "RLMSyncUser_Private.hpp"
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"
35 #import "sync/sync_manager.hpp"
36 #import "sync/sync_session.hpp"
37 #import "sync/sync_user.hpp"
39 using namespace realm;
40 using ConfigMaker = std::function<Realm::Config(std::shared_ptr<SyncUser>, std::string)>;
44 std::function<void(Results, std::exception_ptr)> RLMWrapPermissionResultsCallback(RLMPermissionResultsBlock callback) {
45 return [callback](Results results, std::exception_ptr ptr) {
47 NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeGet);
51 // Finished successfully
52 callback([[RLMSyncPermissionResults alloc] initWithResults:std::move(results)], nil);
57 NSString *tildeSubstitutedPathForRealmURL(NSURL *url, NSString *identity) {
58 return [[url path] stringByReplacingOccurrencesOfString:@"~" withString:identity];
63 void CocoaSyncUserContext::register_refresh_handle(const std::string& path, RLMSyncSessionRefreshHandle *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);
72 m_refresh_handles.insert({path, handle});
75 void CocoaSyncUserContext::unregister_refresh_handle(const std::string& path)
77 std::lock_guard<std::mutex> lock(m_mutex);
78 m_refresh_handles.erase(path);
81 void CocoaSyncUserContext::invalidate_all_handles()
83 std::lock_guard<std::mutex> lock(m_mutex);
84 for (auto& it : m_refresh_handles) {
85 [it.second invalidate];
87 m_refresh_handles.clear();
90 RLMUserErrorReportingBlock CocoaSyncUserContext::error_handler() const
92 std::lock_guard<std::mutex> lock(m_error_handler_mutex);
93 return m_error_handler;
96 void CocoaSyncUserContext::set_error_handler(RLMUserErrorReportingBlock block)
98 std::lock_guard<std::mutex> lock(m_error_handler_mutex);
99 m_error_handler = block;
102 PermissionChangeCallback RLMWrapPermissionStatusCallback(RLMPermissionStatusBlock callback) {
103 return [callback](std::exception_ptr ptr) {
105 NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeChange);
109 // Finished successfully
115 @interface RLMSyncUserInfo ()
117 @property (nonatomic, readwrite) NSArray *accounts;
118 @property (nonatomic, readwrite) NSDictionary *metadata;
119 @property (nonatomic, readwrite) NSString *identity;
120 @property (nonatomic, readwrite) BOOL isAdmin;
122 + (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model;
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;
132 - (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
136 @implementation RLMSyncUser
138 #pragma mark - static API
140 + (NSDictionary *)allUsers {
141 NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers];
142 return [NSDictionary dictionaryWithObjects:allUsers
143 forKeys:[allUsers valueForKey:@"identity"]];
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.");
151 return allUsers.firstObject;
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];
170 - (instancetype)initWithSyncUser:(std::shared_ptr<SyncUser>)user {
171 if (self = [self initPrivate]) {
178 - (BOOL)isEqual:(id)object {
179 if (![object isKindOfClass:[RLMSyncUser class]]) {
182 return _user == ((RLMSyncUser *)object)->_user;
185 + (void)logInWithCredentials:(RLMSyncCredentials *)credential
186 authServerURL:(NSURL *)authServerURL
187 onCompletion:(RLMUserCompletionBlock)completion {
188 [self logInWithCredentials:credential
189 authServerURL:authServerURL
191 callbackQueue:dispatch_get_main_queue()
192 onCompletion:completion];
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
205 callbackQueue:callbackQueue
206 completionBlock:completion];
214 context_for(_user).invalidate_all_handles();
217 - (RLMUserErrorReportingBlock)errorHandler {
221 return context_for(_user).error_handler();
224 - (void)setErrorHandler:(RLMUserErrorReportingBlock)errorHandler {
228 context_for(_user).set_error_handler([errorHandler copy]);
231 - (nullable RLMSyncSession *)sessionForURL:(NSURL *)url {
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];
242 - (NSArray<RLMSyncSession *> *)allSessions {
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)]];
251 return [buffer copy];
254 - (NSString *)identity {
258 return @(_user->identity().c_str());
261 - (RLMSyncUserState)state {
263 return RLMSyncUserStateError;
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;
275 - (NSURL *)authenticationServer {
276 if (!_user || _user->token_type() == SyncUser::TokenType::Admin) {
279 return [NSURL URLWithString:@(_user->server_url().c_str())];
286 return _user->is_admin();
289 #pragma mark - Passwords
291 - (void)changePassword:(NSString *)newPassword completion:(RLMPasswordChangeStatusBlock)completion {
292 [self changePassword:newPassword forUserID:self.identity completion:completion];
295 - (void)changePassword:(NSString *)newPassword forUserID:(NSString *)userID completion:(RLMPasswordChangeStatusBlock)completion {
296 if (self.state != RLMSyncUserStateActive) {
297 completion([NSError errorWithDomain:RLMSyncErrorDomain
298 code:RLMSyncErrorClientSessionError
302 [RLMNetworkClient sendRequestToEndpoint:[RLMSyncChangePasswordEndpoint endpoint]
303 server:self.authenticationServer
304 JSON:@{kRLMSyncTokenKey: self._refreshToken,
305 kRLMSyncUserIDKey: userID,
306 kRLMSyncDataKey: @{ kRLMSyncNewPasswordKey: newPassword }
309 completion:^(NSError *error, __unused NSDictionary *json) {
314 #pragma mark - Administrator API
316 - (void)retrieveInfoForUser:(NSString *)providerUserIdentity
317 identityProvider:(RLMIdentityProvider)provider
318 completion:(RLMRetrieveUserBlock)completion {
319 [RLMNetworkClient sendRequestToEndpoint:[RLMSyncGetUserInfoEndpoint endpoint]
320 server:self.authenticationServer
322 kRLMSyncProviderKey: provider,
323 kRLMSyncProviderIDKey: providerUserIdentity,
324 kRLMSyncTokenKey: self._refreshToken
326 completion:^(NSError *error, NSDictionary *json) {
328 completion(nil, error);
331 RLMUserResponseModel *model = [[RLMUserResponseModel alloc] initWithDictionary:json];
333 completion(nil, make_auth_error_bad_response(json));
336 completion([RLMSyncUserInfo syncUserInfoWithModel:model], nil);
340 #pragma mark - Permissions API
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).");
348 - (void)retrievePermissionsWithCallback:(RLMPermissionResultsBlock)callback {
350 if (!_user || _user->state() == SyncUser::State::Error) {
351 callback(nullptr, make_permission_error_get(@"Permissions cannot be retrieved using an invalid user."));
354 Permissions::get_permissions(_user, RLMWrapPermissionResultsCallback(callback), *_configMaker);
357 - (void)applyPermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
359 if (!_user || _user->state() == SyncUser::State::Error) {
360 callback(make_permission_error_change(@"Permissions cannot be applied using an invalid user."));
363 Permissions::set_permission(_user,
364 [permission rawPermission],
365 RLMWrapPermissionStatusCallback(callback),
369 - (void)revokePermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
371 if (!_user || _user->state() == SyncUser::State::Error) {
372 callback(make_permission_error_change(@"Permissions cannot be revoked using an invalid user."));
375 Permissions::delete_permission(_user,
376 [permission rawPermission],
377 RLMWrapPermissionStatusCallback(callback),
381 - (void)createOfferForRealmAtURL:(NSURL *)url
382 accessLevel:(RLMSyncAccessLevel)accessLevel
383 expiration:(NSDate *)expirationDate
384 callback:(RLMPermissionOfferStatusBlock)callback {
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."));
390 auto cb = [callback](util::Optional<std::string> token, std::exception_ptr ptr) {
392 NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeOffer);
393 REALM_ASSERT_DEBUG(error);
394 callback(nil, error);
396 REALM_ASSERT_DEBUG(token);
397 callback(@(token->c_str()), nil);
400 auto offer = PermissionOffer{
401 [tildeSubstitutedPathForRealmURL(url, self.identity) UTF8String],
402 accessLevelForObjCAccessLevel(accessLevel),
403 RLMTimestampForNSDate(expirationDate),
405 Permissions::make_offer(_user, std::move(offer), std::move(cb), *_configMaker);
408 - (void)acceptOfferForToken:(NSString *)token
409 callback:(RLMPermissionOfferResponseStatusBlock)callback {
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."));
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";
422 auto cb = [baseURL, callback](util::Optional<std::string> raw_path, std::exception_ptr ptr) {
424 NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeAcceptOffer);
425 REALM_ASSERT_DEBUG(error);
426 callback(nil, error);
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);
434 Permissions::accept_offer(_user, [token UTF8String], std::move(cb), *_configMaker);
437 #pragma mark - Private API
439 + (void)_setUpBindingContextFactory {
440 SyncUser::set_binding_context_factory([] {
441 return std::make_shared<CocoaSyncUserContext>();
445 - (NSString *)_refreshToken {
449 return @(_user->refresh_token().c_str());
452 - (std::shared_ptr<SyncUser>)_syncUser {
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
466 authServerURL:authServerURL
467 completionBlock:completion];
470 if (!authServerURL) {
471 @throw RLMException(@"A user cannot be logged in without specifying an authentication server URL.");
474 // Prepare login network request
475 NSMutableDictionary *json = [@{
476 kRLMSyncProviderKey: credentials.provider,
477 kRLMSyncDataKey: credentials.token,
478 kRLMSyncAppIDKey: [RLMSyncManager sharedManager].appID,
480 NSMutableDictionary *info = [(credentials.userInfo ?: @{}) mutableCopy];
482 if ([info count] > 0) {
483 // Munge user info into the JSON request.
484 json[@"user_info"] = info;
487 RLMSyncCompletionBlock handler = ^(NSError *error, NSDictionary *json) {
488 if (json && !error) {
489 RLMAuthResponseModel *model = [[RLMAuthResponseModel alloc] initWithDictionary:json
490 requireAccessToken:NO
491 requireRefreshToken:YES];
494 NSError *badResponseError = make_auth_error_bad_response(json);
495 dispatch_async(callbackQueue, ^{
496 completion(nil, badResponseError);
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]);
504 NSError *authError = make_auth_error_client_issue();
505 dispatch_async(callbackQueue, ^{
506 completion(nil, authError);
510 sync_user->set_is_admin(model.refreshToken.tokenData.isAdmin);
511 user->_user = sync_user;
512 dispatch_async(callbackQueue, ^{
513 completion(user, nil);
517 // Something else went wrong
518 dispatch_async(callbackQueue, ^{
519 completion(nil, error);
523 [RLMNetworkClient sendRequestToEndpoint:[RLMSyncAuthEndpoint endpoint]
527 completion:^(NSError *error, NSDictionary *dictionary) {
528 dispatch_async(callbackQueue, ^{
529 handler(error, dictionary);
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;
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);
546 // Retrieve the user based on the auth server URL.
547 util::Optional<std::string> identity_string;
549 identity_string = std::string(identity.UTF8String);
551 sync_user = SyncManager::shared().get_admin_token_user([serverURL absoluteString].UTF8String,
552 credentials.token.UTF8String,
553 std::move(identity_string));
555 // Retrieve the user based on the identity.
557 @throw RLMException(@"A direct access credential must specify either an identity, a server URL, or both.");
559 sync_user = SyncManager::shared().get_admin_token_user_from_identity(identity.UTF8String,
561 credentials.token.UTF8String);
564 completion(nil, make_auth_error_client_issue());
567 user->_user = sync_user;
568 completion(user, nil);
573 #pragma mark - RLMSyncUserInfo
575 @implementation RLMSyncUserInfo
577 - (instancetype)initPrivate {
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;