added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMSyncUser.mm
diff --git a/iOS/Pods/Realm/Realm/RLMSyncUser.mm b/iOS/Pods/Realm/Realm/RLMSyncUser.mm
new file mode 100644 (file)
index 0000000..f843efb
--- /dev/null
@@ -0,0 +1,590 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2016 Realm Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////
+
+#import "RLMSyncUser_Private.hpp"
+
+#import "RLMJSONModels.h"
+#import "RLMNetworkClient.h"
+#import "RLMRealmConfiguration+Sync.h"
+#import "RLMRealmConfiguration_Private.hpp"
+#import "RLMRealmUtil.hpp"
+#import "RLMResults_Private.hpp"
+#import "RLMSyncManager_Private.h"
+#import "RLMSyncPermissionResults.h"
+#import "RLMSyncPermission_Private.hpp"
+#import "RLMSyncSessionRefreshHandle.hpp"
+#import "RLMSyncSession_Private.hpp"
+#import "RLMSyncUtil_Private.hpp"
+#import "RLMUtil.hpp"
+
+#import "sync/sync_manager.hpp"
+#import "sync/sync_session.hpp"
+#import "sync/sync_user.hpp"
+
+using namespace realm;
+using ConfigMaker = std::function<Realm::Config(std::shared_ptr<SyncUser>, std::string)>;
+
+namespace {
+
+std::function<void(Results, std::exception_ptr)> RLMWrapPermissionResultsCallback(RLMPermissionResultsBlock callback) {
+    return [callback](Results results, std::exception_ptr ptr) {
+        if (ptr) {
+            NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeGet);
+            REALM_ASSERT(error);
+            callback(nil, error);
+        } else {
+            // Finished successfully
+            callback([[RLMSyncPermissionResults alloc] initWithResults:std::move(results)], nil);
+        }
+    };
+}
+
+NSString *tildeSubstitutedPathForRealmURL(NSURL *url, NSString *identity) {
+    return [[url path] stringByReplacingOccurrencesOfString:@"~" withString:identity];
+}
+
+}
+
+void CocoaSyncUserContext::register_refresh_handle(const std::string& path, RLMSyncSessionRefreshHandle *handle)
+{
+    REALM_ASSERT(handle);
+    std::lock_guard<std::mutex> lock(m_mutex);
+    auto it = m_refresh_handles.find(path);
+    if (it != m_refresh_handles.end()) {
+        [it->second invalidate];
+        m_refresh_handles.erase(it);
+    }
+    m_refresh_handles.insert({path, handle});
+}
+
+void CocoaSyncUserContext::unregister_refresh_handle(const std::string& path)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    m_refresh_handles.erase(path);
+}
+
+void CocoaSyncUserContext::invalidate_all_handles()
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    for (auto& it : m_refresh_handles) {
+        [it.second invalidate];
+    }
+    m_refresh_handles.clear();
+}
+
+RLMUserErrorReportingBlock CocoaSyncUserContext::error_handler() const
+{
+    std::lock_guard<std::mutex> lock(m_error_handler_mutex);
+    return m_error_handler;
+}
+
+void CocoaSyncUserContext::set_error_handler(RLMUserErrorReportingBlock block)
+{
+    std::lock_guard<std::mutex> lock(m_error_handler_mutex);
+    m_error_handler = block;
+}
+
+PermissionChangeCallback RLMWrapPermissionStatusCallback(RLMPermissionStatusBlock callback) {
+    return [callback](std::exception_ptr ptr) {
+        if (ptr) {
+            NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeChange);
+            REALM_ASSERT(error);
+            callback(error);
+        } else {
+            // Finished successfully
+            callback(nil);
+        }
+    };
+}
+
+@interface RLMSyncUserInfo ()
+
+@property (nonatomic, readwrite) NSArray *accounts;
+@property (nonatomic, readwrite) NSDictionary *metadata;
+@property (nonatomic, readwrite) NSString *identity;
+@property (nonatomic, readwrite) BOOL isAdmin;
+
++ (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model;
+
+@end
+
+@interface RLMSyncUser () {
+    std::shared_ptr<SyncUser> _user;
+    // FIXME: remove this when the object store ConfigMaker goes away
+    std::unique_ptr<ConfigMaker> _configMaker;
+}
+
+- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation RLMSyncUser
+
+#pragma mark - static API
+
++ (NSDictionary *)allUsers {
+    NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers];
+    return [NSDictionary dictionaryWithObjects:allUsers
+                                       forKeys:[allUsers valueForKey:@"identity"]];
+}
+
++ (RLMSyncUser *)currentUser {
+    NSArray *allUsers = [[RLMSyncManager sharedManager] _allUsers];
+    if (allUsers.count > 1) {
+        @throw RLMException(@"+currentUser cannot be called if more that one valid, logged-in user exists.");
+    }
+    return allUsers.firstObject;
+}
+
+#pragma mark - API
+
+- (instancetype)initPrivate {
+    if (self = [super init]) {
+        _configMaker = std::make_unique<ConfigMaker>([](std::shared_ptr<SyncUser> user, std::string url) {
+            RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
+            NSURL *objCUrl = [NSURL URLWithString:@(url.c_str())];
+            RLMSyncUser *objCUser = [[RLMSyncUser alloc] initWithSyncUser:std::move(user)];
+            config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:objCUser realmURL:objCUrl];
+            return [config config];
+        });
+        return self;
+    }
+    return nil;
+}
+
+- (instancetype)initWithSyncUser:(std::shared_ptr<SyncUser>)user {
+    if (self = [self initPrivate]) {
+        _user = user;
+        return self;
+    }
+    return nil;
+}
+
+- (BOOL)isEqual:(id)object {
+    if (![object isKindOfClass:[RLMSyncUser class]]) {
+        return NO;
+    }
+    return _user == ((RLMSyncUser *)object)->_user;
+}
+
++ (void)logInWithCredentials:(RLMSyncCredentials *)credential
+               authServerURL:(NSURL *)authServerURL
+                onCompletion:(RLMUserCompletionBlock)completion {
+    [self logInWithCredentials:credential
+                 authServerURL:authServerURL
+                       timeout:30
+                 callbackQueue:dispatch_get_main_queue()
+                  onCompletion:completion];
+}
+
++ (void)logInWithCredentials:(RLMSyncCredentials *)credential
+               authServerURL:(NSURL *)authServerURL
+                     timeout:(NSTimeInterval)timeout
+               callbackQueue:(dispatch_queue_t)callbackQueue
+                onCompletion:(RLMUserCompletionBlock)completion {
+    RLMSyncUser *user = [[RLMSyncUser alloc] initPrivate];
+    [RLMSyncUser _performLogInForUser:user
+                          credentials:credential
+                        authServerURL:authServerURL
+                              timeout:timeout
+                        callbackQueue:callbackQueue
+                      completionBlock:completion];
+}
+
+- (void)logOut {
+    if (!_user) {
+        return;
+    }
+    _user->log_out();
+    context_for(_user).invalidate_all_handles();
+}
+
+- (RLMUserErrorReportingBlock)errorHandler {
+    if (!_user) {
+        return nil;
+    }
+    return context_for(_user).error_handler();
+}
+
+- (void)setErrorHandler:(RLMUserErrorReportingBlock)errorHandler {
+    if (!_user) {
+        return;
+    }
+    context_for(_user).set_error_handler([errorHandler copy]);
+}
+
+- (nullable RLMSyncSession *)sessionForURL:(NSURL *)url {
+    if (!_user) {
+        return nil;
+    }
+    auto path = SyncManager::shared().path_for_realm(*_user, [url.absoluteString UTF8String]);
+    if (auto session = _user->session_for_on_disk_path(path)) {
+        return [[RLMSyncSession alloc] initWithSyncSession:session];
+    }
+    return nil;
+}
+
+- (NSArray<RLMSyncSession *> *)allSessions {
+    if (!_user) {
+        return @[];
+    }
+    NSMutableArray<RLMSyncSession *> *buffer = [NSMutableArray array];
+    auto sessions = _user->all_sessions();
+    for (auto session : sessions) {
+        [buffer addObject:[[RLMSyncSession alloc] initWithSyncSession:std::move(session)]];
+    }
+    return [buffer copy];
+}
+
+- (NSString *)identity {
+    if (!_user) {
+        return nil;
+    }
+    return @(_user->identity().c_str());
+}
+
+- (RLMSyncUserState)state {
+    if (!_user) {
+        return RLMSyncUserStateError;
+    }
+    switch (_user->state()) {
+        case SyncUser::State::Active:
+            return RLMSyncUserStateActive;
+        case SyncUser::State::LoggedOut:
+            return RLMSyncUserStateLoggedOut;
+        case SyncUser::State::Error:
+            return RLMSyncUserStateError;
+    }
+}
+
+- (NSURL *)authenticationServer {
+    if (!_user || _user->token_type() == SyncUser::TokenType::Admin) {
+        return nil;
+    }
+    return [NSURL URLWithString:@(_user->server_url().c_str())];
+}
+
+- (BOOL)isAdmin {
+    if (!_user) {
+        return NO;
+    }
+    return _user->is_admin();
+}
+
+#pragma mark - Passwords
+
+- (void)changePassword:(NSString *)newPassword completion:(RLMPasswordChangeStatusBlock)completion {
+    [self changePassword:newPassword forUserID:self.identity completion:completion];
+}
+
+- (void)changePassword:(NSString *)newPassword forUserID:(NSString *)userID completion:(RLMPasswordChangeStatusBlock)completion {
+    if (self.state != RLMSyncUserStateActive) {
+        completion([NSError errorWithDomain:RLMSyncErrorDomain
+                                       code:RLMSyncErrorClientSessionError
+                                   userInfo:nil]);
+        return;
+    }
+    [RLMNetworkClient sendRequestToEndpoint:[RLMSyncChangePasswordEndpoint endpoint]
+                                     server:self.authenticationServer
+                                       JSON:@{kRLMSyncTokenKey: self._refreshToken,
+                                              kRLMSyncUserIDKey: userID,
+                                              kRLMSyncDataKey: @{ kRLMSyncNewPasswordKey: newPassword }
+                                              }
+                                    timeout:60
+                                 completion:^(NSError *error, __unused NSDictionary *json) {
+        completion(error);
+    }];
+}
+
+#pragma mark - Administrator API
+
+- (void)retrieveInfoForUser:(NSString *)providerUserIdentity
+           identityProvider:(RLMIdentityProvider)provider
+                 completion:(RLMRetrieveUserBlock)completion {
+    [RLMNetworkClient sendRequestToEndpoint:[RLMSyncGetUserInfoEndpoint endpoint]
+                                     server:self.authenticationServer
+                                       JSON:@{
+                                              kRLMSyncProviderKey: provider,
+                                              kRLMSyncProviderIDKey: providerUserIdentity,
+                                              kRLMSyncTokenKey: self._refreshToken
+                                              }
+                                 completion:^(NSError *error, NSDictionary *json) {
+                                     if (error) {
+                                         completion(nil, error);
+                                         return;
+                                     }
+                                     RLMUserResponseModel *model = [[RLMUserResponseModel alloc] initWithDictionary:json];
+                                     if (!model) {
+                                         completion(nil, make_auth_error_bad_response(json));
+                                         return;
+                                     }
+                                     completion([RLMSyncUserInfo syncUserInfoWithModel:model], nil);
+                                 }];
+}
+
+#pragma mark - Permissions API
+
+static void verifyInRunLoop() {
+    if (!RLMIsInRunLoop()) {
+        @throw RLMException(@"Can only access or modify permissions from a thread which has a run loop (by default, only the main thread).");
+    }
+}
+
+- (void)retrievePermissionsWithCallback:(RLMPermissionResultsBlock)callback {
+    verifyInRunLoop();
+    if (!_user || _user->state() == SyncUser::State::Error) {
+        callback(nullptr, make_permission_error_get(@"Permissions cannot be retrieved using an invalid user."));
+        return;
+    }
+    Permissions::get_permissions(_user, RLMWrapPermissionResultsCallback(callback), *_configMaker);
+}
+
+- (void)applyPermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
+    verifyInRunLoop();
+    if (!_user || _user->state() == SyncUser::State::Error) {
+        callback(make_permission_error_change(@"Permissions cannot be applied using an invalid user."));
+        return;
+    }
+    Permissions::set_permission(_user,
+                                [permission rawPermission],
+                                RLMWrapPermissionStatusCallback(callback),
+                                *_configMaker);
+}
+
+- (void)revokePermission:(RLMSyncPermission *)permission callback:(RLMPermissionStatusBlock)callback {
+    verifyInRunLoop();
+    if (!_user || _user->state() == SyncUser::State::Error) {
+        callback(make_permission_error_change(@"Permissions cannot be revoked using an invalid user."));
+        return;
+    }
+    Permissions::delete_permission(_user,
+                                   [permission rawPermission],
+                                   RLMWrapPermissionStatusCallback(callback),
+                                   *_configMaker);
+}
+
+- (void)createOfferForRealmAtURL:(NSURL *)url
+                     accessLevel:(RLMSyncAccessLevel)accessLevel
+                      expiration:(NSDate *)expirationDate
+                        callback:(RLMPermissionOfferStatusBlock)callback {
+    verifyInRunLoop();
+    if (!_user || _user->state() == SyncUser::State::Error) {
+        callback(nil, make_permission_error_change(@"A permission offer cannot be created using an invalid user."));
+        return;
+    }
+    auto cb = [callback](util::Optional<std::string> token, std::exception_ptr ptr) {
+        if (ptr) {
+            NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeOffer);
+            REALM_ASSERT_DEBUG(error);
+            callback(nil, error);
+        } else {
+            REALM_ASSERT_DEBUG(token);
+            callback(@(token->c_str()), nil);
+        }
+    };
+    auto offer = PermissionOffer{
+        [tildeSubstitutedPathForRealmURL(url, self.identity) UTF8String],
+        accessLevelForObjCAccessLevel(accessLevel),
+        RLMTimestampForNSDate(expirationDate),
+    };
+    Permissions::make_offer(_user, std::move(offer), std::move(cb), *_configMaker);
+}
+
+- (void)acceptOfferForToken:(NSString *)token
+                   callback:(RLMPermissionOfferResponseStatusBlock)callback {
+    verifyInRunLoop();
+    if (!_user || _user->state() == SyncUser::State::Error) {
+        callback(nil, make_permission_error_change(@"A permission offer cannot be accepted by an invalid user."));
+        return;
+    }
+    NSURLComponents *baseURL = [NSURLComponents componentsWithURL:self.authenticationServer
+                                          resolvingAgainstBaseURL:YES];
+    if ([baseURL.scheme isEqualToString:@"http"]) {
+        baseURL.scheme = @"realm";
+    } else if ([baseURL.scheme isEqualToString:@"https"]) {
+        baseURL.scheme = @"realms";
+    }
+    auto cb = [baseURL, callback](util::Optional<std::string> raw_path, std::exception_ptr ptr) {
+        if (ptr) {
+            NSError *error = translateSyncExceptionPtrToError(std::move(ptr), RLMPermissionActionTypeAcceptOffer);
+            REALM_ASSERT_DEBUG(error);
+            callback(nil, error);
+        } else {
+            // Note that ROS currently vends the path to the Realm, so we need to construct the full URL ourselves.
+            REALM_ASSERT_DEBUG(raw_path);
+            baseURL.path = @(raw_path->c_str());
+            callback([baseURL URL], nil);
+        }
+    };
+    Permissions::accept_offer(_user, [token UTF8String], std::move(cb), *_configMaker);
+}
+
+#pragma mark - Private API
+
++ (void)_setUpBindingContextFactory {
+    SyncUser::set_binding_context_factory([] {
+        return std::make_shared<CocoaSyncUserContext>();
+    });
+}
+
+- (NSString *)_refreshToken {
+    if (!_user) {
+        return nil;
+    }
+    return @(_user->refresh_token().c_str());
+}
+
+- (std::shared_ptr<SyncUser>)_syncUser {
+    return _user;
+}
+
++ (void)_performLogInForUser:(RLMSyncUser *)user
+                 credentials:(RLMSyncCredentials *)credentials
+               authServerURL:(NSURL *)authServerURL
+                     timeout:(NSTimeInterval)timeout
+               callbackQueue:(dispatch_queue_t)callbackQueue
+             completionBlock:(RLMUserCompletionBlock)completion {
+    // Special credential login should be treated differently.
+    if (credentials.provider == RLMIdentityProviderAccessToken) {
+        [self _performLoginForDirectAccessTokenCredentials:credentials
+                                                      user:user
+                                             authServerURL:authServerURL
+                                           completionBlock:completion];
+        return;
+    }
+    if (!authServerURL) {
+        @throw RLMException(@"A user cannot be logged in without specifying an authentication server URL.");
+    }
+
+    // Prepare login network request
+    NSMutableDictionary *json = [@{
+                                   kRLMSyncProviderKey: credentials.provider,
+                                   kRLMSyncDataKey: credentials.token,
+                                   kRLMSyncAppIDKey: [RLMSyncManager sharedManager].appID,
+                                   } mutableCopy];
+    NSMutableDictionary *info = [(credentials.userInfo ?: @{}) mutableCopy];
+
+    if ([info count] > 0) {
+        // Munge user info into the JSON request.
+        json[@"user_info"] = info;
+    }
+
+    RLMSyncCompletionBlock handler = ^(NSError *error, NSDictionary *json) {
+        if (json && !error) {
+            RLMAuthResponseModel *model = [[RLMAuthResponseModel alloc] initWithDictionary:json
+                                                                        requireAccessToken:NO
+                                                                       requireRefreshToken:YES];
+            if (!model) {
+                // Malformed JSON
+                NSError *badResponseError = make_auth_error_bad_response(json);
+                dispatch_async(callbackQueue, ^{
+                    completion(nil, badResponseError);
+                });
+                return;
+            } else {
+                std::string server_url = authServerURL.absoluteString.UTF8String;
+                SyncUserIdentifier identity{[model.refreshToken.tokenData.identity UTF8String], std::move(server_url)};
+                auto sync_user = SyncManager::shared().get_user(identity , [model.refreshToken.token UTF8String]);
+                if (!sync_user) {
+                    NSError *authError = make_auth_error_client_issue();
+                    dispatch_async(callbackQueue, ^{
+                        completion(nil, authError);
+                    });
+                    return;
+                }
+                sync_user->set_is_admin(model.refreshToken.tokenData.isAdmin);
+                user->_user = sync_user;
+                dispatch_async(callbackQueue, ^{
+                    completion(user, nil);
+                });
+            }
+        } else {
+            // Something else went wrong
+            dispatch_async(callbackQueue, ^{
+                completion(nil, error);
+            });
+        }
+    };
+    [RLMNetworkClient sendRequestToEndpoint:[RLMSyncAuthEndpoint endpoint]
+                                     server:authServerURL
+                                       JSON:json
+                                    timeout:timeout
+                                 completion:^(NSError *error, NSDictionary *dictionary) {
+                                     dispatch_async(callbackQueue, ^{
+                                         handler(error, dictionary);
+                                     });
+                                 }];
+}
+
++ (void)_performLoginForDirectAccessTokenCredentials:(RLMSyncCredentials *)credentials
+                                                user:(RLMSyncUser *)user
+                                       authServerURL:(NSURL *)serverURL
+                                     completionBlock:(nonnull RLMUserCompletionBlock)completion {
+    NSString *identity = credentials.userInfo[kRLMSyncIdentityKey];
+    std::shared_ptr<SyncUser> sync_user;
+    if (serverURL) {
+        NSString *scheme = serverURL.scheme;
+        if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
+            @throw RLMException(@"The Realm Object Server authentication URL provided for this user, \"%@\", "
+                                @" is invalid. It must begin with http:// or https://.", serverURL);
+        }
+        // Retrieve the user based on the auth server URL.
+        util::Optional<std::string> identity_string;
+        if (identity) {
+            identity_string = std::string(identity.UTF8String);
+        }
+        sync_user = SyncManager::shared().get_admin_token_user([serverURL absoluteString].UTF8String,
+                                                               credentials.token.UTF8String,
+                                                               std::move(identity_string));
+    } else {
+        // Retrieve the user based on the identity.
+        if (!identity) {
+            @throw RLMException(@"A direct access credential must specify either an identity, a server URL, or both.");
+        }
+        sync_user = SyncManager::shared().get_admin_token_user_from_identity(identity.UTF8String,
+                                                                             none,
+                                                                             credentials.token.UTF8String);
+    }
+    if (!sync_user) {
+        completion(nil, make_auth_error_client_issue());
+        return;
+    }
+    user->_user = sync_user;
+    completion(user, nil);
+}
+
+@end
+
+#pragma mark - RLMSyncUserInfo
+
+@implementation RLMSyncUserInfo
+
+- (instancetype)initPrivate {
+    return [super init];
+}
+
++ (instancetype)syncUserInfoWithModel:(RLMUserResponseModel *)model {
+    RLMSyncUserInfo *info = [[RLMSyncUserInfo alloc] initPrivate];
+    info.accounts = model.accounts;
+    info.metadata = model.metadata;
+    info.isAdmin = model.isAdmin;
+    info.identity = model.identity;
+    return info;
+}
+
+@end