12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // 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
|