You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1196 lines
58 KiB
1196 lines
58 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2017 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 <XCTest/XCTest.h>
|
|
|
|
// FIXME: Many permission tests appears to fail with the ROS 3.0.0 alpha releases.
|
|
|
|
#import "RLMSyncTestCase.h"
|
|
|
|
#import "RLMTestUtils.h"
|
|
|
|
#define APPLY_PERMISSION(ma_permission, ma_user) \
|
|
APPLY_PERMISSION_WITH_MESSAGE(ma_permission, ma_user, @"Setting a permission should work")
|
|
|
|
#define APPLY_PERMISSION_WITH_MESSAGE(ma_permission, ma_user, ma_message) { \
|
|
XCTestExpectation *ex = [self expectationWithDescription:ma_message]; \
|
|
[ma_user applyPermission:ma_permission callback:^(NSError *err) { \
|
|
XCTAssertNil(err, @"Received an error when applying permission: %@", err); \
|
|
[ex fulfill]; \
|
|
}]; \
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil]; \
|
|
} \
|
|
|
|
static NSURL *makeTestURL(NSString *name, RLMSyncUser *owner) {
|
|
NSString *userID = [owner identity] ?: @"~";
|
|
return [[NSURL alloc] initWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9080/%@/%@", userID, name]];
|
|
}
|
|
|
|
static NSURL *makeTestGlobalURL(NSString *name) {
|
|
return [[NSURL alloc] initWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9080/%@", name]];
|
|
}
|
|
|
|
static NSURL *makeTildeSubstitutedURL(NSURL *url, RLMSyncUser *user) {
|
|
return [NSURL URLWithString:[[url absoluteString] stringByReplacingOccurrencesOfString:@"~" withString:user.identity]];
|
|
}
|
|
|
|
@interface RLMPermissionsAPITests : RLMSyncTestCase
|
|
|
|
@property (nonatomic, strong) NSString *currentUsernameBase;
|
|
|
|
@property (nonatomic, strong) RLMSyncUser *userA;
|
|
@property (nonatomic, strong) RLMSyncUser *userB;
|
|
@property (nonatomic, strong) RLMSyncUser *userC;
|
|
|
|
@property (nonatomic, strong) NSString *userBUsername;
|
|
|
|
@end
|
|
|
|
@implementation RLMPermissionsAPITests
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
NSString *accountNameBase = [[NSUUID UUID] UUIDString];
|
|
self.currentUsernameBase = accountNameBase;
|
|
NSString *userNameA = [accountNameBase stringByAppendingString:@"a"];
|
|
self.userA = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameA register:YES]
|
|
server:[RLMSyncTestCase authServerURL]];
|
|
|
|
NSString *userNameB = [accountNameBase stringByAppendingString:@"b"];
|
|
self.userBUsername = userNameB;
|
|
self.userB = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameB register:YES]
|
|
server:[RLMSyncTestCase authServerURL]];
|
|
|
|
NSString *userNameC = [accountNameBase stringByAppendingString:@"c"];
|
|
self.userC = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameC register:YES]
|
|
server:[RLMSyncTestCase authServerURL]];
|
|
}
|
|
|
|
- (void)tearDown {
|
|
self.currentUsernameBase = nil;
|
|
[self.userA logOut];
|
|
[self.userB logOut];
|
|
[self.userC logOut];
|
|
self.userBUsername = nil;
|
|
[super tearDown];
|
|
}
|
|
|
|
#pragma mark - Permission validation methods
|
|
|
|
// This macro is only used for the validation methods below.
|
|
#define RECORD_FAILURE(ma_msg) [self recordFailureWithDescription:ma_msg inFile:file atLine:line expected:YES]
|
|
|
|
#define CHECK_PERMISSION_PRESENT(ma_results, ma_permission) \
|
|
[self checkPresenceOfPermission:ma_permission inResults:ma_results line:__LINE__ file:@(__FILE__)]
|
|
|
|
/// Check that the targeted permission is present in, or eventually appears in the results.
|
|
- (void)checkPresenceOfPermission:(RLMSyncPermission *)permission
|
|
inResults:(RLMResults<RLMSyncPermission *> *)results
|
|
line:(NSUInteger)line
|
|
file:(NSString *)file {
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Checking presence of permission..."];
|
|
RLMNotificationToken *token = [results addNotificationBlock:^(RLMResults *r, __unused id c, NSError *err) {
|
|
if (err) {
|
|
RECORD_FAILURE(@"Failed to retrieve permissions.");
|
|
[ex fulfill];
|
|
return;
|
|
}
|
|
if ([r indexOfObject:permission] != NSNotFound) {
|
|
[ex fulfill];
|
|
}
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Timed out. The final state of the permissions is %@; the desired permission was %@",
|
|
results, permission);
|
|
}
|
|
}];
|
|
[token invalidate];
|
|
}
|
|
|
|
#define CHECK_PERMISSION_ABSENT(ma_results, ma_permission) \
|
|
[self checkAbsenceOfPermission:ma_permission inResults:ma_results line:__LINE__ file:@(__FILE__)]
|
|
|
|
/// Check that the targeted permission is absent from, or eventually disappears from the results.
|
|
- (void)checkAbsenceOfPermission:(RLMSyncPermission *)permission
|
|
inResults:(RLMResults<RLMSyncPermission *> *)results
|
|
line:(NSUInteger)line
|
|
file:(NSString *)file {
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Checking absence of permission..."];
|
|
RLMNotificationToken *token = [results addNotificationBlock:^(RLMResults *r, __unused id c, NSError *err) {
|
|
if (err) {
|
|
RECORD_FAILURE(@"Failed to retrieve permissions.");
|
|
[ex fulfill];
|
|
return;
|
|
}
|
|
if ([r indexOfObject:permission] == NSNotFound) {
|
|
[ex fulfill];
|
|
}
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Timed out. The final state of the permissions is %@; the permission to check for %@",
|
|
results, permission);
|
|
}
|
|
}];
|
|
[token invalidate];
|
|
}
|
|
|
|
#define CHECK_PERMISSION_COUNT_AT_LEAST(ma_results, ma_count) \
|
|
[self checkPermissionCountOfResults:ma_results atLeast:ma_count exact:NO line:__LINE__ file:@(__FILE__)];
|
|
|
|
#define CHECK_PERMISSION_COUNT(ma_results, ma_count) \
|
|
[self checkPermissionCountOfResults:ma_results atLeast:ma_count exact:YES line:__LINE__ file:@(__FILE__)];
|
|
|
|
- (void)checkPermissionCountOfResults:(RLMResults<RLMSyncPermission *> *)results
|
|
atLeast:(NSInteger)count
|
|
exact:(BOOL)exact
|
|
line:(NSUInteger)line
|
|
file:(NSString *)file {
|
|
// Check first.
|
|
if ((NSInteger)results.count == count || (!exact && (NSInteger)results.count > count)) {
|
|
return;
|
|
}
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Checking presence of permission..."];
|
|
RLMNotificationToken *token = [results addNotificationBlock:^(RLMResults *r, __unused id c, NSError *err) {
|
|
if (err) {
|
|
RECORD_FAILURE(@"Failed to retrieve permissions.");
|
|
[ex fulfill];
|
|
return;
|
|
}
|
|
NSInteger actualCount = (NSInteger)r.count;
|
|
if (actualCount == count || (!exact && actualCount > count)) {
|
|
[ex fulfill];
|
|
return;
|
|
}
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:20.0];
|
|
[token invalidate];
|
|
}
|
|
|
|
#undef RECORD_FAILURE
|
|
|
|
#pragma mark - Helper methods
|
|
|
|
- (RLMResults<RLMSyncPermission *> *)getPermissionResultsFor:(RLMSyncUser *)user {
|
|
return [self getPermissionResultsFor:user message:@"Get permission results"];
|
|
}
|
|
|
|
- (RLMResults<RLMSyncPermission *> *)getPermissionResultsFor:(RLMSyncUser *)user message:(NSString *)message {
|
|
// Get a reference to the permission results.
|
|
XCTestExpectation *ex = [self expectationWithDescription:message];
|
|
__block RLMResults<RLMSyncPermission *> *results = nil;
|
|
[user retrievePermissionsWithCallback:^(RLMResults<RLMSyncPermission *> *r, NSError *error) {
|
|
XCTAssertNil(error);
|
|
XCTAssertNotNil(r);
|
|
results = r;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
|
XCTAssertNotNil(results, @"getPermissionResultsFor: failed for user %@. No results.", user.identity);
|
|
return results;
|
|
}
|
|
|
|
#pragma mark - Permissions
|
|
|
|
// FIXME ROS 2.0: works when ROS is manually provided, not when ROS is run as part of tests
|
|
/// If user A grants user B read access to a Realm, user B should be able to read from it.
|
|
- (void)testReadAccess {
|
|
__block void(^errorBlock)(NSError *) = nil;
|
|
[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, __unused RLMSyncSession *session) {
|
|
if (errorBlock) {
|
|
errorBlock(error);
|
|
errorBlock = nil;
|
|
} else {
|
|
XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", error);
|
|
}
|
|
}];
|
|
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
// Open a Realm for user A.
|
|
NSURL *userAURL = makeTestURL(testName, nil);
|
|
RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
|
|
|
|
// Have user A add some items to the Realm.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:userAURL];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// Give user B read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
// Set the read permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Open the same Realm for user B.
|
|
NSURL *userBURL = makeTestURL(testName, self.userA);
|
|
RLMRealmConfiguration *userBConfig = [RLMRealmConfiguration defaultConfiguration];
|
|
userBConfig.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:self.userB realmURL:userBURL];
|
|
__block RLMRealm *userBRealm = nil;
|
|
XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
|
|
[RLMRealm asyncOpenWithConfiguration:userBConfig
|
|
callbackQueue:dispatch_get_main_queue()
|
|
callback:^(RLMRealm *realm, NSError *err){
|
|
XCTAssertNil(err);
|
|
XCTAssertNotNil(realm);
|
|
userBRealm = realm;
|
|
[asyncOpenEx fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
CHECK_COUNT(3, SyncObject, userBRealm);
|
|
|
|
// Ensure user B can't actually write to the Realm.
|
|
// Run this portion of the test on a background queue, since the error handler is dispatched onto the main queue.
|
|
XCTestExpectation *deniedEx = [self expectationWithDescription:@"Expect a permission denied error."];
|
|
errorBlock = ^(NSError *err) {
|
|
// Expect an error from the global error handler.
|
|
XCTAssertNotNil(err);
|
|
XCTAssertEqual(err.code, RLMSyncErrorPermissionDeniedError);
|
|
[deniedEx fulfill];
|
|
};
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5", @"child-6"]];
|
|
[self waitForExpectations:@[deniedEx] timeout:20.0];
|
|
|
|
// TODO: if we can get the session itself we can check to see if it's been errored out (as expected).
|
|
|
|
// Perhaps obviously, there should be no new objects.
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userARealm);
|
|
|
|
// Administering the Realm should fail.
|
|
RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userBURL path]
|
|
identity:self.userC.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
XCTestExpectation *manageEx = [self expectationWithDescription:@"Managing a Realm you can't manage should fail."];
|
|
[self.userB applyPermission:p2 callback:^(NSError *error) {
|
|
XCTAssertNotNil(error);
|
|
[manageEx fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
|
}
|
|
|
|
/// If user A grants user B write access to a Realm, user B should be able to write to it.
|
|
- (void)testWriteAccess {
|
|
__block void(^errorBlock)(NSError *) = nil;
|
|
[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, __unused RLMSyncSession *session) {
|
|
if (errorBlock) {
|
|
errorBlock(error);
|
|
errorBlock = nil;
|
|
} else {
|
|
XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", error);
|
|
}
|
|
}];
|
|
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
// Open a Realm for user A.
|
|
NSURL *userAURL = makeTestURL(testName, nil);
|
|
RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
|
|
|
|
// Have user A add some items to the Realm.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:userAURL];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// Give user B write permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelWrite];
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Open the Realm for user B. Since user B has write privileges, they should be able to open it 'normally'.
|
|
NSURL *userBURL = makeTestURL(testName, self.userA);
|
|
RLMRealm *userBRealm = [self openRealmForURL:userBURL user:self.userB];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
|
|
|
|
// Add some objects using user B.
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
|
|
[self waitForUploadsForUser:self.userB url:userBURL];
|
|
CHECK_COUNT(5, SyncObject, userBRealm);
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
|
|
|
|
// Administering the Realm should fail.
|
|
RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userBURL path]
|
|
identity:self.userC.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
XCTestExpectation *manageEx = [self expectationWithDescription:@"Managing a Realm you can't manage should fail."];
|
|
[self.userB applyPermission:p2 callback:^(NSError *error) {
|
|
XCTAssertNotNil(error);
|
|
[manageEx fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
|
}
|
|
|
|
/// If user A grants user B manage access to a Realm, user B should be able to set a permission for user C.
|
|
- (void)testManageAccess {
|
|
__block void(^errorBlock)(NSError *) = nil;
|
|
[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, __unused RLMSyncSession *session) {
|
|
if (errorBlock) {
|
|
errorBlock(error);
|
|
errorBlock = nil;
|
|
} else {
|
|
XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", error);
|
|
}
|
|
}];
|
|
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
// Unresolved URL: ~/testManageAccess
|
|
NSURL *userAURLUnresolved = makeTestURL(testName, nil);
|
|
// Resolved URL: <User A ID>/testManageAccess
|
|
NSURL *userAURLResolved = makeTestURL(testName, self.userA);
|
|
|
|
// Open a Realm for user A.
|
|
RLMRealm *userARealm = [self openRealmForURL:userAURLUnresolved user:self.userA];
|
|
|
|
// Have user A add some items to the Realm.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:userAURLUnresolved];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// Give user B admin permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURLUnresolved path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelAdmin];
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Open the Realm for user B. Since user B has admin privileges, they should be able to open it 'normally'.
|
|
RLMRealm *userBRealm = [self openRealmForURL:userAURLResolved user:self.userB];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
|
|
|
|
// Add some objects using user B.
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
|
|
[self waitForUploadsForUser:self.userB url:userAURLResolved];
|
|
CHECK_COUNT(5, SyncObject, userBRealm);
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
|
|
|
|
// User B should be able to give user C write permissions to user A's Realm.
|
|
RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userAURLResolved path]
|
|
identity:self.userC.identity
|
|
accessLevel:RLMSyncAccessLevelWrite];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userB, @"User B should be able to give C write permissions to A's Realm.");
|
|
|
|
// User C should be able to write to the Realm.
|
|
RLMRealm *userCRealm = [self openRealmForURL:userAURLResolved user:self.userC];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
|
|
[self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8"]];
|
|
[self waitForUploadsForUser:self.userC url:userAURLResolved];
|
|
CHECK_COUNT(8, SyncObject, userCRealm);
|
|
CHECK_COUNT_PENDING_DOWNLOAD(8, SyncObject, userARealm);
|
|
CHECK_COUNT_PENDING_DOWNLOAD(8, SyncObject, userBRealm);
|
|
}
|
|
|
|
/// If user A grants user B write access to a Realm via username, user B should be able to write to it.
|
|
- (void)testWriteAccessViaUsername {
|
|
__block void(^workBlock)(NSError *) = ^(NSError *err) {
|
|
XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", err);
|
|
};
|
|
[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, __unused RLMSyncSession *session) {
|
|
if (workBlock) {
|
|
workBlock(error);
|
|
}
|
|
}];
|
|
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
// Open a Realm for user A.
|
|
NSURL *userAURL = makeTestURL(testName, nil);
|
|
RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
|
|
|
|
// Have user A add some items to the Realm.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:userAURL];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// Give user B write permissions to that Realm via user B's username.
|
|
NSString *userAFullPath = [makeTildeSubstitutedURL(userAURL, self.userA) path];
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:userAFullPath
|
|
username:self.userBUsername
|
|
accessLevel:RLMSyncAccessLevelWrite];
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Open the Realm for user B. Since user B has write privileges, they should be able to open it 'normally'.
|
|
NSURL *userBURL = makeTestURL(testName, self.userA);
|
|
RLMRealm *userBRealm = [self openRealmForURL:userBURL user:self.userB];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
|
|
|
|
// Add some objects using user B.
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
|
|
[self waitForUploadsForUser:self.userB url:userBURL];
|
|
CHECK_COUNT(5, SyncObject, userBRealm);
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
|
|
}
|
|
|
|
/// Setting a permission for all users should work.
|
|
- (void)testWildcardWriteAccess {
|
|
// Open a Realm for user A.
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
NSURL *ownerURL = makeTestURL(testName, nil);
|
|
NSURL *guestURL = makeTestURL(testName, self.userA);
|
|
RLMRealm *userARealm = [self openRealmForURL:ownerURL user:self.userA];
|
|
|
|
// Give all users write permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[ownerURL path]
|
|
identity:@"*"
|
|
accessLevel:RLMSyncAccessLevelWrite];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Have user A write a few objects first.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:ownerURL];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// User B should be able to write to the Realm.
|
|
RLMRealm *userBRealm = [self openRealmForURL:guestURL user:self.userB];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
|
|
[self waitForUploadsForUser:self.userB url:guestURL];
|
|
CHECK_COUNT(5, SyncObject, userBRealm);
|
|
|
|
// User C should be able to write to the Realm.
|
|
RLMRealm *userCRealm = [self openRealmForURL:guestURL user:self.userC];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
|
|
[self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8", @"child-9"]];
|
|
[self waitForUploadsForUser:self.userC url:guestURL];
|
|
CHECK_COUNT(9, SyncObject, userCRealm);
|
|
}
|
|
|
|
/// It should be possible to grant read-only access to a global Realm.
|
|
- (void)testWildcardGlobalRealmReadAccess {
|
|
RLMSyncUser *admin = [self createAdminUserForURL:[RLMSyncTestCase authServerURL]
|
|
username:[[NSUUID UUID] UUIDString]];
|
|
|
|
// Open a Realm for the admin user.
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
NSURL *globalRealmURL = makeTestGlobalURL(testName);
|
|
RLMRealm *adminUserRealm = [self openRealmForURL:globalRealmURL user:admin];
|
|
|
|
// Give all users read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
|
|
identity:@"*"
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION_WITH_MESSAGE(p, admin, @"Setting wildcard permission should work.");
|
|
|
|
// Have the admin user write a few objects first.
|
|
[self addSyncObjectsToRealm:adminUserRealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:admin url:globalRealmURL];
|
|
CHECK_COUNT(3, SyncObject, adminUserRealm);
|
|
|
|
// User B should be able to read from the Realm.
|
|
__block RLMRealm *userBRealm = nil;
|
|
RLMRealmConfiguration *userBConfig = [RLMRealmConfiguration defaultConfiguration];
|
|
userBConfig.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:self.userB realmURL:globalRealmURL];
|
|
XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
|
|
[RLMRealm asyncOpenWithConfiguration:userBConfig
|
|
callbackQueue:dispatch_get_main_queue()
|
|
callback:^(RLMRealm *realm, NSError *err){
|
|
XCTAssertNil(err);
|
|
XCTAssertNotNil(realm);
|
|
userBRealm = realm;
|
|
[asyncOpenEx fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
CHECK_COUNT(3, SyncObject, userBRealm);
|
|
|
|
// User C should be able to read from the Realm.
|
|
__block RLMRealm *userCRealm = nil;
|
|
RLMRealmConfiguration *userCConfig = [RLMRealmConfiguration defaultConfiguration];
|
|
userCConfig.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:self.userC realmURL:globalRealmURL];
|
|
XCTestExpectation *asyncOpenEx2 = [self expectationWithDescription:@"Should asynchronously open a Realm"];
|
|
[RLMRealm asyncOpenWithConfiguration:userCConfig
|
|
callbackQueue:dispatch_get_main_queue()
|
|
callback:^(RLMRealm *realm, NSError *err){
|
|
XCTAssertNil(err);
|
|
XCTAssertNotNil(realm);
|
|
userCRealm = realm;
|
|
[asyncOpenEx2 fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
CHECK_COUNT(3, SyncObject, userCRealm);
|
|
}
|
|
|
|
/// Setting a permission for all users on a global Realm (no `~`) should work.
|
|
- (void)testWildcardGlobalRealmWriteAccess {
|
|
RLMSyncUser *admin = [self createAdminUserForURL:[RLMSyncTestCase authServerURL]
|
|
username:[[NSUUID UUID] UUIDString]];
|
|
|
|
// Open a Realm for the admin user.
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
NSURL *globalRealmURL = makeTestGlobalURL(testName);
|
|
RLMRealm *adminUserRealm = [self openRealmForURL:globalRealmURL user:admin];
|
|
|
|
// Give all users write permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
|
|
identity:@"*"
|
|
accessLevel:RLMSyncAccessLevelWrite];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, admin);
|
|
|
|
// Have the admin user write a few objects first.
|
|
[self addSyncObjectsToRealm:adminUserRealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:admin url:globalRealmURL];
|
|
CHECK_COUNT(3, SyncObject, adminUserRealm);
|
|
|
|
// User B should be able to write to the Realm.
|
|
RLMRealm *userBRealm = [self openRealmForURL:globalRealmURL user:self.userB];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
|
|
[self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
|
|
[self waitForUploadsForUser:self.userB url:globalRealmURL];
|
|
CHECK_COUNT(5, SyncObject, userBRealm);
|
|
|
|
// User C should be able to write to the Realm.
|
|
RLMRealm *userCRealm = [self openRealmForURL:globalRealmURL user:self.userC];
|
|
CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
|
|
[self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8", @"child-9"]];
|
|
[self waitForUploadsForUser:self.userC url:globalRealmURL];
|
|
CHECK_COUNT(9, SyncObject, userCRealm);
|
|
}
|
|
|
|
#pragma mark - Permission change API
|
|
|
|
/// Setting a permission should work, and then that permission should be able to be retrieved.
|
|
- (void)testSettingPermission {
|
|
// First, there should be no permissions.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userA];
|
|
CHECK_PERMISSION_COUNT(results, 0);
|
|
|
|
// Open a Realm for user A.
|
|
NSURL *url = REALM_URL();
|
|
[self openRealmForURL:url user:self.userA];
|
|
|
|
// Give user B read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Now retrieve the permissions again and make sure the new permission is properly set.
|
|
results = [self getPermissionResultsFor:self.userB message:@"One permission after setting the permission."];
|
|
|
|
// Expected permission: applies to user B, but for user A's Realm.
|
|
CHECK_PERMISSION_PRESENT(results, p);
|
|
|
|
// Check getting permission by its index.
|
|
NSUInteger index = [results indexOfObject:p];
|
|
XCTAssertNotEqual(index, NSNotFound);
|
|
XCTAssertEqualObjects(p, [results objectAtIndex:index]);
|
|
}
|
|
|
|
/// Deleting a permission should work.
|
|
- (void)testDeletingPermission {
|
|
// Open a Realm for user A.
|
|
NSURL *url = REALM_URL();
|
|
[self openRealmForURL:url user:self.userA];
|
|
|
|
// Give user B read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Now retrieve the permissions again and make sure the new permission is properly set.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB
|
|
message:@"Setting new permission."];
|
|
CHECK_PERMISSION_PRESENT(results, p);
|
|
|
|
// Delete the permission.
|
|
XCTestExpectation *ex3 = [self expectationWithDescription:@"Deleting a permission should work."];
|
|
[self.userA revokePermission:p callback:^(NSError *error) {
|
|
XCTAssertNil(error);
|
|
[ex3 fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
|
|
|
// Make sure the permission deletion is properly reflected.
|
|
CHECK_PERMISSION_COUNT(results, 0);
|
|
}
|
|
|
|
/// Observing permission changes should work.
|
|
- (void)testObservingPermission {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
// Open a Realm for user A.
|
|
NSURL *url = REALM_URL();
|
|
[self openRealmForURL:url user:self.userA];
|
|
|
|
// Register notifications.
|
|
XCTestExpectation *noteEx = [self expectationWithDescription:@"Notification should fire."];
|
|
RLMNotificationToken *token = [results addNotificationBlock:^(__unused id r, __unused id c, NSError *error) {
|
|
XCTAssertNil(error);
|
|
if (results.count > 0) {
|
|
[noteEx fulfill];
|
|
}
|
|
}];
|
|
|
|
// Give user B read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
|
|
// Set the permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
// Wait for the notification to be fired.
|
|
[self waitForExpectations:@[noteEx] timeout:2.0];
|
|
[token invalidate];
|
|
CHECK_PERMISSION_PRESENT(results, p);
|
|
}
|
|
|
|
/// KVC getting and setting should work properly for `RLMResults<RLMSyncPermission>`.
|
|
- (void)testKVCWithPermissionsResults {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
NSURL *url1 = CUSTOM_REALM_URL(@"r1");
|
|
NSURL *url2 = CUSTOM_REALM_URL(@"r2");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
|
|
NSString *uB = self.userB.identity;
|
|
|
|
// Give user B read permissions to r1 and r2.
|
|
NSString *path1 = [makeTildeSubstitutedURL(url1, self.userA) path];
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:path1
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r1 permission for user B should work.");
|
|
NSString *path2 = [makeTildeSubstitutedURL(url2, self.userA) path];
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:path2
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, @"Setting r2 permission for user B should work.");
|
|
|
|
// Wait for all the permissions to show up.
|
|
CHECK_PERMISSION_PRESENT(results, p1);
|
|
CHECK_PERMISSION_PRESENT(results, p2);
|
|
|
|
// Now use `valueForKey`
|
|
NSArray *selfValues = [results valueForKey:@"self"];
|
|
XCTAssert(selfValues.count == results.count);
|
|
for (id object in selfValues) {
|
|
XCTAssert([object isKindOfClass:[RLMSyncPermission class]]);
|
|
}
|
|
|
|
NSArray *identityValues = [results valueForKey:@"path"];
|
|
XCTAssert(identityValues.count == results.count);
|
|
XCTAssert([identityValues containsObject:path1]);
|
|
XCTAssert([identityValues containsObject:path2]);
|
|
|
|
// Since `RLMSyncPermission`s are read-only, KVC setting should fail.
|
|
RLMAssertThrows([results setValue:@"foobar" forKey:@"path"]);
|
|
}
|
|
|
|
/// Filtering permissions results should work.
|
|
- (void)testFilteringPermissions {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
// Open two Realms
|
|
NSURL *url1 = CUSTOM_REALM_URL(@"r1");
|
|
NSURL *url2 = CUSTOM_REALM_URL(@"r2");
|
|
NSURL *url3 = CUSTOM_REALM_URL(@"r3");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r3 = [self openRealmForURL:url3 user:self.userA];
|
|
NSString *uB = self.userB.identity;
|
|
|
|
// Give user B permissions to realms r1, r2, and r3.
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url1, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r1 permission for user B should work.");
|
|
NSString *finalPath = [makeTildeSubstitutedURL(url2, self.userA) path];
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:finalPath
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, @"Setting r2 permission for user B should work.");
|
|
id p3 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url3, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p3, self.userA, @"Setting r3 permission for user B should work.");
|
|
|
|
// Wait for all the permissions to show up.
|
|
CHECK_PERMISSION_PRESENT(results, p1);
|
|
CHECK_PERMISSION_PRESENT(results, p2);
|
|
CHECK_PERMISSION_PRESENT(results, p3);
|
|
|
|
// Now make a filter.
|
|
RLMResults<RLMSyncPermission *> *filtered = [results objectsWithPredicate:[NSPredicate predicateWithFormat:@"%K == %@",
|
|
RLMSyncPermissionSortPropertyPath,
|
|
finalPath]];
|
|
CHECK_PERMISSION_ABSENT(filtered, p1);
|
|
CHECK_PERMISSION_PRESENT(filtered, p2);
|
|
CHECK_PERMISSION_ABSENT(filtered, p3);
|
|
}
|
|
|
|
- (void)testSortingPermissionsOnUserID {
|
|
// Get a reference to my own permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userA];
|
|
|
|
// Open my Realm.
|
|
NSURL *url = REALM_URL();
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Give users B and C access to my Realm.
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r permission for user B should work.");
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userC.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, @"Setting r permission for user C should work.");
|
|
|
|
// Now sort on user ID.
|
|
RLMResults<RLMSyncPermission *> *sorted = [results sortedResultsUsingKeyPath:RLMSyncPermissionSortPropertyUserID
|
|
ascending:YES];
|
|
// Wait for changes to propagate
|
|
CHECK_PERMISSION_COUNT(sorted, 3);
|
|
NSMutableArray *sortedIDs = [NSMutableArray array];
|
|
for (NSUInteger i = 0; i < sorted.count; i++) {
|
|
[sortedIDs addObject:[sorted objectAtIndex:i].identity];
|
|
}
|
|
// Make sure the IDs in sortedIDs are actually sorted.
|
|
for (NSUInteger i = 0; i < sorted.count - 1; i++) {
|
|
XCTAssertEqual([sortedIDs[i] compare:sortedIDs[i + 1]], NSOrderedAscending);
|
|
}
|
|
// Make sure the IDs in sortedIDs contain all 3 users' IDs.
|
|
NSSet *sortedIDSet = [NSSet setWithArray:sortedIDs];
|
|
XCTAssertTrue([sortedIDSet containsObject:self.userA.identity]);
|
|
XCTAssertTrue([sortedIDSet containsObject:self.userB.identity]);
|
|
XCTAssertTrue([sortedIDSet containsObject:self.userC.identity]);
|
|
}
|
|
|
|
- (void)testSortingPermissionsOnPath {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
// Open three Realms
|
|
NSURL *url1 = CUSTOM_REALM_URL(@"r1");
|
|
NSURL *url2 = CUSTOM_REALM_URL(@"r2");
|
|
NSURL *url3 = CUSTOM_REALM_URL(@"r3");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r3 = [self openRealmForURL:url3 user:self.userA];
|
|
NSString *uB = self.userB.identity;
|
|
|
|
// Give user B read permissions for all three Realms.
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url1, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r1 permission for user B should work.");
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url2, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, @"Setting r2 permission for user B should work.");
|
|
id p3 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url3, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p3, self.userA, @"Setting r3 permission for user B should work.");
|
|
|
|
// Now sort on Realm URL.
|
|
RLMResults<RLMSyncPermission *> *sorted = [results sortedResultsUsingKeyPath:RLMSyncPermissionSortPropertyPath
|
|
ascending:YES];
|
|
// Wait for changes to propagate
|
|
CHECK_PERMISSION_COUNT(sorted, 3);
|
|
|
|
CHECK_PERMISSION_PRESENT(sorted, p1);
|
|
CHECK_PERMISSION_PRESENT(sorted, p2);
|
|
CHECK_PERMISSION_PRESENT(sorted, p3);
|
|
NSUInteger idx1 = [sorted indexOfObject:p1];
|
|
NSUInteger idx2 = [sorted indexOfObject:p2];
|
|
NSUInteger idx3 = [sorted indexOfObject:p3];
|
|
// Make sure they are actually in ascending order.
|
|
XCTAssertNotEqual(idx1, NSNotFound);
|
|
XCTAssertNotEqual(idx2, NSNotFound);
|
|
XCTAssertNotEqual(idx3, NSNotFound);
|
|
XCTAssertLessThan(idx1, idx2);
|
|
XCTAssertLessThan(idx2, idx3);
|
|
}
|
|
|
|
- (void)testSortingPermissionsOnDate {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
// Open three Realms
|
|
NSURL *url1 = CUSTOM_REALM_URL(@"-r1");
|
|
NSURL *url2 = CUSTOM_REALM_URL(@"-r2");
|
|
NSURL *url3 = CUSTOM_REALM_URL(@"-r3");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r3 = [self openRealmForURL:url3 user:self.userA];
|
|
NSString *uB = self.userB.identity;
|
|
|
|
// Give user B read permissions for all three Realms.
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url3, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r3 permission for user B should work.");
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url1, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, @"Setting r1 permission for user B should work.");
|
|
id p3 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url2, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p3, self.userA, @"Setting r2 permission for user B should work.");
|
|
|
|
// Now sort on date.
|
|
RLMResults<RLMSyncPermission *> *sorted = [results sortedResultsUsingKeyPath:RLMSyncPermissionSortPropertyUpdated
|
|
ascending:YES];
|
|
|
|
// Wait for changes to propagate
|
|
CHECK_PERMISSION_COUNT(sorted, 3);
|
|
RLMSyncPermission *n1 = [sorted objectAtIndex:0];
|
|
RLMSyncPermission *n2 = [sorted objectAtIndex:1];
|
|
RLMSyncPermission *n3 = [sorted objectAtIndex:2];
|
|
|
|
XCTAssertTrue([n1.path rangeOfString:@"r3"].location != NSNotFound);
|
|
XCTAssertTrue([n2.path rangeOfString:@"r1"].location != NSNotFound);
|
|
XCTAssertTrue([n3.path rangeOfString:@"r2"].location != NSNotFound);
|
|
|
|
// Make sure they are actually in ascending order.
|
|
XCTAssertLessThan([n1.updatedAt timeIntervalSinceReferenceDate], [n2.updatedAt timeIntervalSinceReferenceDate]);
|
|
XCTAssertLessThan([n2.updatedAt timeIntervalSinceReferenceDate], [n3.updatedAt timeIntervalSinceReferenceDate]);
|
|
}
|
|
|
|
- (void)testPermissionResultsIndexOfObject {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
NSString *uB = self.userB.identity;
|
|
|
|
// Have A open a Realm and grant a permission to B.
|
|
NSURL *url = REALM_URL();
|
|
NSString *tildeSubstitutedPath = [makeTildeSubstitutedURL(url, self.userA) path];
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *r = [self openRealmForURL:url user:self.userA];
|
|
id p1 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, @"Setting r permission for user B should work.");
|
|
|
|
// Wait for the permission to show up.
|
|
CHECK_PERMISSION_COUNT(results, 1);
|
|
// Should be able to get the permission based on the actual permission.
|
|
XCTAssertEqual(((NSInteger)[results indexOfObject:p1]), 0);
|
|
// A permission with a differing access level should not match.
|
|
id p2 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelAdmin];
|
|
XCTAssertEqual([results indexOfObject:p2], NSNotFound);
|
|
// A permission with a differing identity should not match.
|
|
id p3 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
|
|
identity:self.userA.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
XCTAssertEqual([results indexOfObject:p3], NSNotFound);
|
|
// A permission with a differing path should not match.
|
|
id p4 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userB) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
XCTAssertEqual([results indexOfObject:p4], NSNotFound);
|
|
}
|
|
|
|
- (void)testPermissionResultsIndexOfObjectWithPredicate {
|
|
// Get a reference to the permission results.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
|
|
|
|
NSString *uB = self.userB.identity;
|
|
// Open a Realm
|
|
{
|
|
NSURL *url = CUSTOM_REALM_URL(@"r1");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Give user B read permission for the Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p, self.userA, @"Setting r1 permission for user B should work.");
|
|
}
|
|
|
|
NSString *finalPath;
|
|
{
|
|
// Do this again so there's more than one permission in the permission Realm.
|
|
NSURL *url = CUSTOM_REALM_URL(@"r2");
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Give user B read permission for the Realm.
|
|
finalPath = [makeTildeSubstitutedURL(url, self.userA) path];
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:finalPath
|
|
identity:uB
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
APPLY_PERMISSION_WITH_MESSAGE(p, self.userA, @"Setting r2 permission for user B should work.");
|
|
}
|
|
|
|
// Wait for changes to propagate
|
|
CHECK_PERMISSION_COUNT_AT_LEAST(results, 2);
|
|
|
|
// Create the predicate and retrieve the index of the object.
|
|
NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K == %@", RLMSyncPermissionSortPropertyPath, finalPath];
|
|
NSUInteger index = [results indexOfObjectWithPredicate:pred];
|
|
XCTAssertNotEqual(index, NSNotFound);
|
|
if (index == NSNotFound) {
|
|
return;
|
|
}
|
|
RLMSyncPermission *target = [results objectAtIndex:index];
|
|
XCTAssertEqualObjects(target.path, finalPath);
|
|
}
|
|
|
|
/// User should not be able to change a permission for a Realm they don't own.
|
|
- (void)testSettingUnownedRealmPermission {
|
|
// Open a Realm for user A.
|
|
NSURL *url = REALM_URL();
|
|
[self openRealmForURL:url user:self.userA];
|
|
|
|
// Try to have user B give user C permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
|
|
identity:self.userC.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
|
|
// Set the permission.
|
|
XCTestExpectation *ex2 = [self expectationWithDescription:@"Setting an invalid permission should fail."];
|
|
[self.userB applyPermission:p callback:^(NSError *error) {
|
|
XCTAssertNotNil(error);
|
|
XCTAssertEqual(error.domain, RLMSyncPermissionErrorDomain);
|
|
XCTAssertEqual(error.code, RLMSyncPermissionErrorChangeFailed);
|
|
[ex2 fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
|
|
|
// Now retrieve the permissions again and make sure the new permission was not set.
|
|
RLMResults<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB
|
|
message:@"Retrieving the results should work."];
|
|
CHECK_PERMISSION_ABSENT(results, p);
|
|
}
|
|
|
|
- (void)testRetrievingPermissionsChecksThreadHasRunLoop {
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMAssertThrowsWithReason([self.userA retrievePermissionsWithCallback:^(__unused RLMResults *r, __unused NSError *e) {
|
|
XCTFail(@"callback should not have been invoked");
|
|
}], @"Can only access or modify permissions from a thread which has a run loop");
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Permission offer/response
|
|
|
|
/// Get a token which can be used to offer the permissions as defined
|
|
- (void)testPermissionOffer {
|
|
NSURL *url = REALM_URL();
|
|
// Open the Realm.
|
|
__unused RLMRealm *realm = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Create the offer.
|
|
__block NSString *token = nil;
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Should get a token when making an offer."];
|
|
[self.userA createOfferForRealmAtURL:url
|
|
accessLevel:RLMSyncAccessLevelWrite
|
|
expiration:[NSDate dateWithTimeIntervalSinceNow:30 * 24 * 60 * 60]
|
|
callback:^(NSString *t, NSError *error) {
|
|
XCTAssertNil(error);
|
|
XCTAssertNotNil(t);
|
|
token = t;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:10.0];
|
|
XCTAssertTrue([token length] > 0);
|
|
}
|
|
|
|
/// Failed to process a permission offer object due to the offer expired
|
|
- (void)testPermissionOfferIsExpired {
|
|
NSURL *url = REALM_URL();
|
|
// Open the Realm.
|
|
__unused RLMRealm *realm = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Create the offer.
|
|
__block NSError *error = nil;
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Server should process the permission offer."];
|
|
[self.userA createOfferForRealmAtURL:url
|
|
accessLevel:RLMSyncAccessLevelWrite
|
|
expiration:[NSDate dateWithTimeIntervalSinceNow:-30 * 24 * 60 * 60]
|
|
callback:^(NSString *token, NSError *err) {
|
|
XCTAssertNotNil(err);
|
|
XCTAssertNil(token);
|
|
error = err;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:10.0];
|
|
XCTAssertEqual(error.code, RLMSyncPermissionErrorOfferFailed);
|
|
XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"The permission offer is expired.");
|
|
}
|
|
|
|
/// Get a permission offer token, then permission offer response will be processed, then open another user's Realm file
|
|
- (void)testPermissionOfferResponse {
|
|
NSURL *url = REALM_URL();
|
|
// Open the Realm.
|
|
__unused RLMRealm *realm = [self openRealmForURL:url user:self.userA];
|
|
|
|
// Create the offer.
|
|
__block NSString *token = nil;
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Should get a token when making an offer."];
|
|
[self.userA createOfferForRealmAtURL:url
|
|
accessLevel:RLMSyncAccessLevelWrite
|
|
expiration:[NSDate dateWithTimeIntervalSinceNow:30 * 24 * 60 * 60]
|
|
callback:^(NSString *t, NSError *error) {
|
|
XCTAssertNil(error);
|
|
XCTAssertNotNil(t);
|
|
token = t;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:10.0];
|
|
XCTAssertTrue([token length] > 0);
|
|
|
|
// Accept the offer.
|
|
__block NSURL *realmURL = nil;
|
|
XCTestExpectation *ex2 = [self expectationWithDescription:@"Server should process offer acceptance."];
|
|
[self.userB acceptOfferForToken:token callback:^(NSURL *returnedURL, NSError *error) {
|
|
XCTAssertNil(error);
|
|
XCTAssertNotNil(returnedURL);
|
|
realmURL = returnedURL;
|
|
[ex2 fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex2] timeout:20.0];
|
|
XCTAssertEqualObjects([realmURL path], [makeTildeSubstitutedURL(url, self.userA) path]);
|
|
|
|
// Open the Realm.
|
|
XCTAssertNotNil([self openRealmForURL:realmURL user:self.userB]);
|
|
}
|
|
|
|
/// Failed to process a permission offer response object due to `token` is invalid
|
|
- (void)testPermissionOfferResponseInvalidToken {
|
|
NSString *badToken = @"invalid token";
|
|
|
|
// Expect an error.
|
|
__block NSError *error = nil;
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Server should process offer acceptance."];
|
|
[self.userA acceptOfferForToken:badToken callback:^(NSURL *returnedURL, NSError *err) {
|
|
XCTAssertNil(returnedURL);
|
|
XCTAssertNotNil(err);
|
|
error = err;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:20.0];
|
|
XCTAssertEqual(error.code, RLMSyncPermissionErrorAcceptOfferFailed);
|
|
XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"Your request parameters did not validate.");
|
|
}
|
|
|
|
/// Failed to process a permission offer response object due to `token` represents a Realm that does not exist
|
|
- (void)testPermissionOfferResponseTokenNotExist {
|
|
NSString *fakeToken = @"00000000000000000000000000000000:00000000-0000-0000-0000-000000000000";
|
|
|
|
// Expect an error.
|
|
__block NSError *error = nil;
|
|
XCTestExpectation *ex = [self expectationWithDescription:@"Server should process offer acceptance."];
|
|
[self.userA acceptOfferForToken:fakeToken callback:^(NSURL *returnedURL, NSError *err) {
|
|
XCTAssertNil(returnedURL);
|
|
XCTAssertNotNil(err);
|
|
error = err;
|
|
[ex fulfill];
|
|
}];
|
|
[self waitForExpectations:@[ex] timeout:20.0];
|
|
XCTAssertEqual(error.code, RLMSyncPermissionErrorAcceptOfferFailed);
|
|
XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"Your request parameters did not validate.");
|
|
}
|
|
|
|
#pragma mark - Delete Realm upon permission denied
|
|
|
|
// FIXME ROS 2.0: works when ROS is manually provided, not when ROS is run as part of tests
|
|
/// A Realm which is opened improperly should report an error allowing the app to recover.
|
|
- (void)testDeleteRealmUponPermissionDenied {
|
|
__block void(^errorBlock)(NSError *, RLMSyncSession *session) = nil;
|
|
[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) {
|
|
if (errorBlock) {
|
|
errorBlock(error, session);
|
|
errorBlock = nil;
|
|
} else {
|
|
XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", error);
|
|
}
|
|
}];
|
|
|
|
NSString *testName = NSStringFromSelector(_cmd);
|
|
// Open a Realm for user A.
|
|
NSURL *userAURL = makeTestURL(testName, nil);
|
|
RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
|
|
|
|
// Have user A add some items to the Realm.
|
|
[self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
|
|
[self waitForUploadsForUser:self.userA url:userAURL];
|
|
CHECK_COUNT(3, SyncObject, userARealm);
|
|
|
|
// Give user B read permissions to that Realm.
|
|
RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
|
|
identity:self.userB.identity
|
|
accessLevel:RLMSyncAccessLevelRead];
|
|
// Set the read permission.
|
|
APPLY_PERMISSION(p, self.userA);
|
|
|
|
NSURL *userBURL = makeTestURL(testName, self.userA);
|
|
RLMRealmConfiguration *userBConfig = [RLMRealmConfiguration defaultConfiguration];
|
|
userBConfig.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:self.userB realmURL:userBURL];
|
|
__block NSError *theError = nil;
|
|
|
|
// Incorrectly open the Realm for user B.
|
|
NSURL *onDiskPath;
|
|
@autoreleasepool {
|
|
NSString *sessionName = NSStringFromSelector(_cmd);
|
|
XCTestExpectation *ex2 = [self expectationWithDescription:@"We should get a permission denied error."];
|
|
errorBlock = ^(NSError *err, RLMSyncSession *session) {
|
|
// Make sure we're actually looking at the right session.
|
|
XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
|
|
theError = err;
|
|
[ex2 fulfill];
|
|
};
|
|
__attribute__((objc_precise_lifetime)) RLMRealm *bad = [RLMRealm realmWithConfiguration:userBConfig error:nil];
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
onDiskPath = [RLMSyncTestCase onDiskPathForSyncedRealm:bad];
|
|
}
|
|
XCTAssertNotNil(onDiskPath);
|
|
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:[onDiskPath path]]);
|
|
|
|
// Check the error and perform the Realm deletion.
|
|
XCTAssertNotNil(theError);
|
|
RLMSyncErrorActionToken *errorToken = [theError rlmSync_errorActionToken];
|
|
XCTAssertNotNil(errorToken);
|
|
[RLMSyncSession immediatelyHandleError:errorToken];
|
|
|
|
// Ensure the file is no longer on disk.
|
|
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:[onDiskPath path]]);
|
|
|
|
// Correctly open the same Realm for user B.
|
|
__block RLMRealm *userBRealm = nil;
|
|
XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
|
|
[RLMRealm asyncOpenWithConfiguration:userBConfig
|
|
callbackQueue:dispatch_get_main_queue()
|
|
callback:^(RLMRealm *realm, NSError *err){
|
|
XCTAssertNil(err);
|
|
XCTAssertNotNil(realm);
|
|
userBRealm = realm;
|
|
[asyncOpenEx fulfill];
|
|
}];
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
CHECK_COUNT(3, SyncObject, userBRealm);
|
|
}
|
|
|
|
@end
|
|
|