RLMPermissionsAPITests.m 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2017 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import <XCTest/XCTest.h>
  19. #import "RLMSyncTestCase.h"
  20. #import "RLMTestUtils.h"
  21. @interface RLMSyncPermission ()
  22. - (RLMSyncPermission *)tildeExpandedSyncPermissionForUser:(RLMSyncUser *)user;
  23. @end
  24. @interface RLMSyncUser ()
  25. - (void)invalidate;
  26. @end
  27. #define APPLY_PERMISSION(ma_permission, ma_user) \
  28. APPLY_PERMISSION_WITH_MESSAGE(ma_permission, ma_user, ma_user, @"Setting a permission should work")
  29. #define APPLY_PERMISSION_WITH_MESSAGE(ma_permission, ma_user, ma_target_user, ma_message) do { \
  30. APPLY_PERMISSION_UNCHECKED(ma_permission, ma_user, ma_message); \
  31. CHECK_PERMISSION_PRESENT([self getPermissionResultsFor:ma_target_user], ma_permission, ma_user); \
  32. } while (0)
  33. #define APPLY_PERMISSION_UNCHECKED(ma_permission, ma_user, ma_message) do { \
  34. XCTestExpectation *ex = [self expectationWithDescription:ma_message]; \
  35. [ma_user applyPermission:ma_permission callback:^(NSError *err) { \
  36. XCTAssertNil(err, @"Received an error when applying permission: %@", err); \
  37. [ex fulfill]; \
  38. }]; \
  39. [self waitForExpectations:@[ex] timeout:2.0]; \
  40. } while (0)
  41. #define REVOKE_PERMISSION(ma_permission, ma_user) do { \
  42. XCTestExpectation *ex = [self expectationWithDescription:@"revoke permission"]; \
  43. [ma_user applyPermission:ma_permission callback:^(NSError *err) { \
  44. XCTAssertNil(err, @"Received an error when applying permission: %@", err); \
  45. [ex fulfill]; \
  46. }]; \
  47. [self waitForExpectations:@[ex] timeout:2.0]; \
  48. CHECK_PERMISSION_ABSENT([self getPermissionResultsFor:ma_user], ma_permission, ma_user); \
  49. } while (0)
  50. #define CHECK_COUNT_PENDING_DOWNLOAD(expected_count, m_type, m_realm) \
  51. CHECK_COUNT_PENDING_DOWNLOAD_CUSTOM_EXPECTATION(expected_count, m_type, m_realm, nil)
  52. /// This macro tries ten times to wait for downloads and then check for object count.
  53. /// If the object count does not match, it waits 0.1 second before trying again.
  54. /// It is most useful in cases where the test ROS might be expected to take some
  55. /// non-negligible amount of time performing an operation whose completion is required
  56. /// for the test on the client side to proceed.
  57. #define CHECK_COUNT_PENDING_DOWNLOAD_CUSTOM_EXPECTATION(expected_count, m_type, m_realm, m_exp) do { \
  58. RLMSyncConfiguration *m_config = m_realm.configuration.syncConfiguration; \
  59. XCTAssertNotNil(m_config, @"Realm passed to CHECK_COUNT_PENDING_DOWNLOAD() doesn't have a sync config!"); \
  60. RLMSyncUser *m_user = m_config.user; \
  61. NSURL *m_url = m_config.realmURL; \
  62. for (int i=0; i<10; i++) { \
  63. [self waitForDownloadsForUser:m_user url:m_url expectation:m_exp error:nil]; \
  64. if (expected_count == [m_type allObjectsInRealm:m_realm].count) { break; } \
  65. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
  66. } \
  67. CHECK_COUNT(expected_count, m_type, m_realm); \
  68. } while (0)
  69. static NSURL *makeTestURL(NSString *name, RLMSyncUser *owner) {
  70. NSString *userID = [owner identity] ?: @"~";
  71. return [[NSURL alloc] initWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9080/%@/%@", userID, name]];
  72. }
  73. static NSURL *makeTestGlobalURL(NSString *name) {
  74. return [[NSURL alloc] initWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9080/%@", name]];
  75. }
  76. static NSURL *makeTildeSubstitutedURL(NSURL *url, RLMSyncUser *user) {
  77. return [NSURL URLWithString:[[url absoluteString] stringByReplacingOccurrencesOfString:@"~" withString:user.identity]];
  78. }
  79. @interface RLMPermissionsAPITests : RLMSyncTestCase
  80. @property (nonatomic, strong) NSString *currentUsernameBase;
  81. @property (nonatomic, strong) RLMSyncUser *userA;
  82. @property (nonatomic, strong) RLMSyncUser *userB;
  83. @property (nonatomic, strong) RLMSyncUser *userC;
  84. @property (nonatomic, strong) NSString *userBUsername;
  85. @end
  86. @implementation RLMPermissionsAPITests
  87. - (void)setUp {
  88. [super setUp];
  89. NSString *accountNameBase = [[NSUUID UUID] UUIDString];
  90. self.currentUsernameBase = accountNameBase;
  91. NSString *userNameA = [accountNameBase stringByAppendingString:@"a"];
  92. self.userA = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameA register:YES]
  93. server:[RLMSyncTestCase authServerURL]];
  94. NSString *userNameB = [accountNameBase stringByAppendingString:@"b"];
  95. self.userBUsername = userNameB;
  96. self.userB = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameB register:YES]
  97. server:[RLMSyncTestCase authServerURL]];
  98. NSString *userNameC = [accountNameBase stringByAppendingString:@"c"];
  99. self.userC = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:userNameC register:YES]
  100. server:[RLMSyncTestCase authServerURL]];
  101. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, __unused RLMSyncSession *session) {
  102. XCTFail(@"Error handler should not be called unless explicitly expected. Error: %@", error);
  103. };
  104. }
  105. - (void)tearDown {
  106. self.currentUsernameBase = nil;
  107. [self.userA logOut];
  108. [self.userB logOut];
  109. [self.userC logOut];
  110. self.userBUsername = nil;
  111. [super tearDown];
  112. }
  113. #pragma mark - Permission validation methods
  114. #define CHECK_PERMISSION_PRESENT(ma_results, ma_permission, ma_user) \
  115. XCTAssertNotEqual([ma_results indexOfObject:[ma_permission tildeExpandedSyncPermissionForUser:ma_user]], NSNotFound)
  116. #define CHECK_PERMISSION_ABSENT(ma_results, ma_permission, ma_user) \
  117. XCTAssertEqual([ma_results indexOfObject:[ma_permission tildeExpandedSyncPermissionForUser:ma_user]], NSNotFound)
  118. #define CHECK_PERMISSION_COUNT_AT_LEAST(ma_results, ma_count) \
  119. XCTAssertGreaterThanOrEqual(ma_results.count, ma_count)
  120. #define CHECK_PERMISSION_COUNT(ma_results, ma_count) \
  121. XCTAssertEqual(ma_results.count, ma_count)
  122. #pragma mark - Helper methods
  123. - (NSArray<RLMSyncPermission *> *)getPermissionResultsFor:(RLMSyncUser *)user {
  124. return [self getPermissionResultsFor:user message:@"Get permission results"];
  125. }
  126. - (NSArray<RLMSyncPermission *> *)getPermissionResultsFor:(RLMSyncUser *)user message:(NSString *)message {
  127. // Get a reference to the permission results.
  128. XCTestExpectation *ex = [self expectationWithDescription:message];
  129. __block NSArray<RLMSyncPermission *> *results = nil;
  130. [user retrievePermissionsWithCallback:^(NSArray<RLMSyncPermission *> *r, NSError *error) {
  131. XCTAssertNil(error);
  132. XCTAssertNotNil(r);
  133. results = r;
  134. [ex fulfill];
  135. }];
  136. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  137. XCTAssertNotNil(results, @"getPermissionResultsFor: failed for user %@. No results.", user.identity);
  138. return results;
  139. }
  140. - (void)testErrorHandlingForInvalidUser {
  141. [self.userA invalidate];
  142. RLMSyncPermission *p;
  143. NSURL *url;
  144. __block bool called = false;
  145. void (^checkError)(NSError *) = ^(NSError *err) {
  146. XCTAssertNotNil(err);
  147. XCTAssertEqual(err.code, RLMSyncAuthErrorInvalidParameters);
  148. called = true;
  149. };
  150. [self.userA retrievePermissionsWithCallback:^(id permissions, NSError *err) {
  151. XCTAssertNil(permissions);
  152. checkError(err);
  153. }];
  154. XCTAssertTrue(called);
  155. called = false;
  156. [self.userA applyPermission:p callback:^(NSError *err) {
  157. checkError(err);
  158. }];
  159. XCTAssertTrue(called);
  160. called = false;
  161. [self.userA createOfferForRealmAtURL:url accessLevel:RLMSyncAccessLevelWrite expiration:nil callback:^(NSString *token, NSError *err) {
  162. XCTAssertNil(token);
  163. checkError(err);
  164. }];
  165. XCTAssertTrue(called);
  166. called = false;
  167. [self.userA acceptOfferForToken:@"" callback:^(NSURL *url, NSError *err) {
  168. XCTAssertNil(url);
  169. checkError(err);
  170. }];
  171. XCTAssertTrue(called);
  172. called = false;
  173. [self.userA invalidateOfferForToken:@"" callback:^(NSError *err) {
  174. checkError(err);
  175. }];
  176. XCTAssertTrue(called);
  177. }
  178. #pragma mark - Permissions
  179. /// If user A grants user B read access to a Realm, user B should be able to read from it.
  180. - (void)testReadAccess {
  181. NSString *testName = NSStringFromSelector(_cmd);
  182. // Open a Realm for user A.
  183. NSURL *userAURL = makeTestURL(testName, nil);
  184. RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
  185. // Have user A add some items to the Realm.
  186. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  187. [self waitForUploadsForRealm:userARealm];
  188. CHECK_COUNT(3, SyncObject, userARealm);
  189. // Give user B read permissions to that Realm.
  190. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
  191. identity:self.userB.identity
  192. accessLevel:RLMSyncAccessLevelRead];
  193. // Set the read permission.
  194. APPLY_PERMISSION(p, self.userA);
  195. // Open the same Realm for user B.
  196. NSURL *userBURL = makeTestURL(testName, self.userA);
  197. RLMRealmConfiguration *userBConfig = [self.userB configurationWithURL:userBURL fullSynchronization:YES];
  198. __block RLMRealm *userBRealm = nil;
  199. XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
  200. [RLMRealm asyncOpenWithConfiguration:userBConfig
  201. callbackQueue:dispatch_get_main_queue()
  202. callback:^(RLMRealm *realm, NSError *err){
  203. XCTAssertNil(err);
  204. XCTAssertNotNil(realm);
  205. userBRealm = realm;
  206. [asyncOpenEx fulfill];
  207. }];
  208. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  209. CHECK_COUNT(3, SyncObject, userBRealm);
  210. // Ensure user B can't actually write to the Realm.
  211. // Run this portion of the test on a background queue, since the error handler is dispatched onto the main queue.
  212. XCTestExpectation *deniedEx = [self expectationWithDescription:@"Expect a permission denied error."];
  213. RLMSyncManager.sharedManager.errorHandler = ^(NSError *err, __unused RLMSyncSession *session) {
  214. // Expect an error from the global error handler.
  215. XCTAssertNotNil(err);
  216. XCTAssertEqual(err.code, RLMSyncErrorPermissionDeniedError);
  217. [deniedEx fulfill];
  218. };
  219. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5", @"child-6"]];
  220. [self waitForExpectations:@[deniedEx] timeout:20.0];
  221. // TODO: if we can get the session itself we can check to see if it's been errored out (as expected).
  222. // Perhaps obviously, there should be no new objects.
  223. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userARealm);
  224. // Administering the Realm should fail.
  225. RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userBURL path]
  226. identity:self.userC.identity
  227. accessLevel:RLMSyncAccessLevelRead];
  228. XCTestExpectation *manageEx = [self expectationWithDescription:@"Managing a Realm you can't manage should fail."];
  229. [self.userB applyPermission:p2 callback:^(NSError *error) {
  230. XCTAssertNotNil(error);
  231. [manageEx fulfill];
  232. }];
  233. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  234. }
  235. /// If user A grants user B write access to a Realm, user B should be able to write to it.
  236. - (void)testWriteAccess {
  237. NSString *testName = NSStringFromSelector(_cmd);
  238. // Open a Realm for user A.
  239. NSURL *userAURL = makeTestURL(testName, nil);
  240. RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
  241. // Have user A add some items to the Realm.
  242. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  243. [self waitForUploadsForRealm:userARealm];
  244. CHECK_COUNT(3, SyncObject, userARealm);
  245. // Give user B write permissions to that Realm.
  246. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
  247. identity:self.userB.identity
  248. accessLevel:RLMSyncAccessLevelWrite];
  249. // Set the permission.
  250. APPLY_PERMISSION(p, self.userA);
  251. // Open the Realm for user B. Since user B has write privileges, they should be able to open it 'normally'.
  252. NSURL *userBURL = makeTestURL(testName, self.userA);
  253. RLMRealm *userBRealm = [self openRealmForURL:userBURL user:self.userB];
  254. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
  255. // Add some objects using user B.
  256. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
  257. [self waitForUploadsForRealm:userBRealm];
  258. CHECK_COUNT(5, SyncObject, userBRealm);
  259. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
  260. // Administering the Realm should fail.
  261. RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userBURL path]
  262. identity:self.userC.identity
  263. accessLevel:RLMSyncAccessLevelRead];
  264. XCTestExpectation *manageEx = [self expectationWithDescription:@"Managing a Realm you can't manage should fail."];
  265. [self.userB applyPermission:p2 callback:^(NSError *error) {
  266. XCTAssertNotNil(error);
  267. [manageEx fulfill];
  268. }];
  269. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  270. }
  271. /// If user A grants user B manage access to a Realm, user B should be able to set a permission for user C.
  272. - (void)testManageAccess {
  273. NSString *testName = NSStringFromSelector(_cmd);
  274. // Unresolved URL: ~/testManageAccess
  275. NSURL *userAURLUnresolved = makeTestURL(testName, nil);
  276. // Resolved URL: <User A ID>/testManageAccess
  277. NSURL *userAURLResolved = makeTestURL(testName, self.userA);
  278. // Open a Realm for user A.
  279. RLMRealm *userARealm = [self openRealmForURL:userAURLUnresolved user:self.userA];
  280. // Have user A add some items to the Realm.
  281. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  282. [self waitForUploadsForRealm:userARealm];
  283. CHECK_COUNT(3, SyncObject, userARealm);
  284. // Give user B admin permissions to that Realm.
  285. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURLUnresolved path]
  286. identity:self.userB.identity
  287. accessLevel:RLMSyncAccessLevelAdmin];
  288. // Set the permission.
  289. APPLY_PERMISSION(p, self.userA);
  290. // Open the Realm for user B. Since user B has admin privileges, they should be able to open it 'normally'.
  291. RLMRealm *userBRealm = [self openRealmForURL:userAURLResolved user:self.userB];
  292. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
  293. // Add some objects using user B.
  294. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
  295. [self waitForUploadsForRealm:userBRealm];
  296. CHECK_COUNT(5, SyncObject, userBRealm);
  297. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
  298. // User B should be able to give user C write permissions to user A's Realm.
  299. RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:[userAURLResolved path]
  300. identity:self.userC.identity
  301. accessLevel:RLMSyncAccessLevelWrite];
  302. APPLY_PERMISSION_WITH_MESSAGE(p2, self.userB, self.userC,
  303. @"User B should be able to give C write permissions to A's Realm.");
  304. // User C should be able to write to the Realm.
  305. RLMRealm *userCRealm = [self openRealmForURL:userAURLResolved user:self.userC];
  306. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
  307. [self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8"]];
  308. [self waitForUploadsForRealm:userCRealm];
  309. CHECK_COUNT(8, SyncObject, userCRealm);
  310. CHECK_COUNT_PENDING_DOWNLOAD(8, SyncObject, userARealm);
  311. CHECK_COUNT_PENDING_DOWNLOAD(8, SyncObject, userBRealm);
  312. }
  313. /// If user A grants user B write access to a Realm via username, user B should be able to write to it.
  314. - (void)testWriteAccessViaUsername {
  315. NSString *testName = NSStringFromSelector(_cmd);
  316. // Open a Realm for user A.
  317. NSURL *userAURL = makeTestURL(testName, nil);
  318. RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
  319. // Have user A add some items to the Realm.
  320. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  321. [self waitForUploadsForRealm:userARealm];
  322. CHECK_COUNT(3, SyncObject, userARealm);
  323. // Give user B write permissions to that Realm via user B's username.
  324. NSString *userAFullPath = [makeTildeSubstitutedURL(userAURL, self.userA) path];
  325. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:userAURL.path
  326. username:self.userBUsername
  327. accessLevel:RLMSyncAccessLevelWrite];
  328. // Set the permission.
  329. APPLY_PERMISSION_UNCHECKED(p, self.userA, @"Grant permission via email");
  330. RLMSyncPermission *expected = [[RLMSyncPermission alloc] initWithRealmPath:userAFullPath
  331. identity:self.userB.identity
  332. accessLevel:RLMSyncAccessLevelWrite];
  333. CHECK_PERMISSION_PRESENT([self getPermissionResultsFor:self.userB], expected, self.userB);
  334. // Open the Realm for user B. Since user B has write privileges, they should be able to open it 'normally'.
  335. NSURL *userBURL = makeTestURL(testName, self.userA);
  336. RLMRealm *userBRealm = [self openRealmForURL:userBURL user:self.userB];
  337. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
  338. // Add some objects using user B.
  339. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
  340. [self waitForUploadsForRealm:userBRealm];
  341. CHECK_COUNT(5, SyncObject, userBRealm);
  342. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userARealm);
  343. }
  344. /// Setting a permission for all users should work.
  345. - (void)testWildcardWriteAccess {
  346. // Open a Realm for user A.
  347. NSString *testName = NSStringFromSelector(_cmd);
  348. NSURL *ownerURL = makeTestURL(testName, nil);
  349. NSURL *guestURL = makeTestURL(testName, self.userA);
  350. RLMRealm *userARealm = [self openRealmForURL:ownerURL user:self.userA];
  351. // Give all users write permissions to that Realm.
  352. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[ownerURL path]
  353. identity:@"*"
  354. accessLevel:RLMSyncAccessLevelWrite];
  355. // Set the permission.
  356. APPLY_PERMISSION(p, self.userA);
  357. // Have user A write a few objects first.
  358. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  359. [self waitForUploadsForRealm:userARealm];
  360. CHECK_COUNT(3, SyncObject, userARealm);
  361. // User B should be able to write to the Realm.
  362. RLMRealm *userBRealm = [self openRealmForURL:guestURL user:self.userB];
  363. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
  364. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
  365. [self waitForUploadsForRealm:userBRealm];
  366. CHECK_COUNT(5, SyncObject, userBRealm);
  367. // User C should be able to write to the Realm.
  368. RLMRealm *userCRealm = [self openRealmForURL:guestURL user:self.userC];
  369. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
  370. [self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8", @"child-9"]];
  371. [self waitForUploadsForRealm:userCRealm];
  372. CHECK_COUNT(9, SyncObject, userCRealm);
  373. p = [[RLMSyncPermission alloc] initWithRealmPath:[ownerURL path]
  374. identity:@"*"
  375. accessLevel:RLMSyncAccessLevelNone];
  376. REVOKE_PERMISSION(p, self.userA);
  377. }
  378. /// It should be possible to grant read-only access to a global Realm.
  379. - (void)testWildcardGlobalRealmReadAccess {
  380. RLMSyncUser *admin = [self createAdminUserForURL:[RLMSyncTestCase authServerURL]
  381. username:[[NSUUID UUID] UUIDString]];
  382. // Open a Realm for the admin user.
  383. NSString *testName = NSStringFromSelector(_cmd);
  384. NSURL *globalRealmURL = makeTestGlobalURL(testName);
  385. RLMRealm *adminUserRealm = [self openRealmForURL:globalRealmURL user:admin];
  386. // Give all users read permissions to that Realm.
  387. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
  388. identity:@"*"
  389. accessLevel:RLMSyncAccessLevelRead];
  390. // Set the permission.
  391. APPLY_PERMISSION_WITH_MESSAGE(p, admin, self.userA, @"Setting wildcard permission should work.");
  392. // Have the admin user write a few objects first.
  393. [self addSyncObjectsToRealm:adminUserRealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  394. [self waitForUploadsForRealm:adminUserRealm];
  395. CHECK_COUNT(3, SyncObject, adminUserRealm);
  396. // User B should be able to read from the Realm.
  397. __block RLMRealm *userBRealm = nil;
  398. RLMRealmConfiguration *userBConfig = [self.userB configurationWithURL:globalRealmURL fullSynchronization:YES];
  399. XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
  400. [RLMRealm asyncOpenWithConfiguration:userBConfig
  401. callbackQueue:dispatch_get_main_queue()
  402. callback:^(RLMRealm *realm, NSError *err){
  403. XCTAssertNil(err);
  404. XCTAssertNotNil(realm);
  405. userBRealm = realm;
  406. [asyncOpenEx fulfill];
  407. }];
  408. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  409. CHECK_COUNT(3, SyncObject, userBRealm);
  410. // User C should be able to read from the Realm.
  411. __block RLMRealm *userCRealm = nil;
  412. RLMRealmConfiguration *userCConfig = [self.userC configurationWithURL:globalRealmURL fullSynchronization:YES];
  413. XCTestExpectation *asyncOpenEx2 = [self expectationWithDescription:@"Should asynchronously open a Realm"];
  414. [RLMRealm asyncOpenWithConfiguration:userCConfig
  415. callbackQueue:dispatch_get_main_queue()
  416. callback:^(RLMRealm *realm, NSError *err){
  417. XCTAssertNil(err);
  418. XCTAssertNotNil(realm);
  419. userCRealm = realm;
  420. [asyncOpenEx2 fulfill];
  421. }];
  422. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  423. CHECK_COUNT(3, SyncObject, userCRealm);
  424. p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
  425. identity:@"*"
  426. accessLevel:RLMSyncAccessLevelNone];
  427. REVOKE_PERMISSION(p, admin);
  428. }
  429. /// Setting a permission for all users on a global Realm (no `~`) should work.
  430. - (void)testWildcardGlobalRealmWriteAccess {
  431. RLMSyncUser *admin = [self createAdminUserForURL:[RLMSyncTestCase authServerURL]
  432. username:[[NSUUID UUID] UUIDString]];
  433. // Open a Realm for the admin user.
  434. NSString *testName = NSStringFromSelector(_cmd);
  435. NSURL *globalRealmURL = makeTestGlobalURL(testName);
  436. RLMRealm *adminUserRealm = [self openRealmForURL:globalRealmURL user:admin];
  437. // Give all users write permissions to that Realm.
  438. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
  439. identity:@"*"
  440. accessLevel:RLMSyncAccessLevelWrite];
  441. // Set the permission.
  442. APPLY_PERMISSION_WITH_MESSAGE(p, admin, self.userA, @"Should grant access to all users");
  443. // Have the admin user write a few objects first.
  444. [self addSyncObjectsToRealm:adminUserRealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  445. [self waitForUploadsForRealm:adminUserRealm];
  446. CHECK_COUNT(3, SyncObject, adminUserRealm);
  447. // User B should be able to write to the Realm.
  448. RLMRealm *userBRealm = [self openRealmForURL:globalRealmURL user:self.userB];
  449. CHECK_COUNT_PENDING_DOWNLOAD(3, SyncObject, userBRealm);
  450. [self addSyncObjectsToRealm:userBRealm descriptions:@[@"child-4", @"child-5"]];
  451. [self waitForUploadsForRealm:userBRealm];
  452. CHECK_COUNT(5, SyncObject, userBRealm);
  453. // User C should be able to write to the Realm.
  454. RLMRealm *userCRealm = [self openRealmForURL:globalRealmURL user:self.userC];
  455. CHECK_COUNT_PENDING_DOWNLOAD(5, SyncObject, userCRealm);
  456. [self addSyncObjectsToRealm:userCRealm descriptions:@[@"child-6", @"child-7", @"child-8", @"child-9"]];
  457. [self waitForUploadsForRealm:userCRealm];
  458. CHECK_COUNT(9, SyncObject, userCRealm);
  459. p = [[RLMSyncPermission alloc] initWithRealmPath:[globalRealmURL path]
  460. identity:@"*"
  461. accessLevel:RLMSyncAccessLevelNone];
  462. REVOKE_PERMISSION(p, admin);
  463. }
  464. - (void)testReadAccessWithClassSuperset {
  465. NSString *testName = NSStringFromSelector(_cmd);
  466. // Create a Realm with only a single object type
  467. NSURL *userAURL = makeTestURL(testName, nil);
  468. RLMRealmConfiguration *userAConfig = [self.userA configurationWithURL:userAURL fullSynchronization:YES];
  469. userAConfig.objectClasses = @[SyncObject.self];
  470. RLMRealm *userARealm = [self asyncOpenRealmWithConfiguration:userAConfig];
  471. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  472. [self waitForUploadsForRealm:userARealm];
  473. CHECK_COUNT(3, SyncObject, userARealm);
  474. // Give user B read-only permissions to that Realm so that it can't add new object types
  475. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
  476. identity:self.userB.identity
  477. accessLevel:RLMSyncAccessLevelRead];
  478. APPLY_PERMISSION(p, self.userA);
  479. // Open the same Realm s user B without limiting the set of object classes
  480. NSURL *userBURL = makeTestURL(testName, self.userA);
  481. RLMRealmConfiguration *userBConfig = [self.userB configurationWithURL:userBURL fullSynchronization:YES];
  482. userBConfig.readOnly = YES;
  483. RLMRealm *userBRealm = [self asyncOpenRealmWithConfiguration:userBConfig];
  484. CHECK_COUNT(3, SyncObject, userBRealm);
  485. // Verify that syncing is actually working and new objects written by A show up in B's Realm
  486. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-4"]];
  487. CHECK_COUNT_PENDING_DOWNLOAD(4, SyncObject, userBRealm);
  488. }
  489. #pragma mark - Permission change API
  490. /// Setting a permission should work, and then that permission should be able to be retrieved.
  491. - (void)testSettingPermission {
  492. // First, there should be no permissions.
  493. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userA];
  494. CHECK_PERMISSION_COUNT(results, 0);
  495. // Open a Realm for user A.
  496. NSURL *url = REALM_URL();
  497. [self openRealmForURL:url user:self.userA];
  498. // Give user B read permissions to that Realm.
  499. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
  500. identity:self.userB.identity
  501. accessLevel:RLMSyncAccessLevelRead];
  502. // Set the permission.
  503. APPLY_PERMISSION(p, self.userA);
  504. // Now retrieve the permissions again and make sure the new permission is properly set.
  505. results = [self getPermissionResultsFor:self.userB message:@"One permission after setting the permission."];
  506. // Expected permission: applies to user B, but for user A's Realm.
  507. CHECK_PERMISSION_PRESENT(results, p, self.userA);
  508. // Check getting permission by its index.
  509. NSUInteger index = [results indexOfObject:p];
  510. XCTAssertNotEqual(index, NSNotFound);
  511. XCTAssertEqualObjects(p, [results objectAtIndex:index]);
  512. }
  513. /// Deleting a permission should work.
  514. - (void)testDeletingPermission {
  515. // Open a Realm for user A.
  516. NSURL *url = REALM_URL();
  517. [self openRealmForURL:url user:self.userA];
  518. // Give user B read permissions to that Realm.
  519. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
  520. identity:self.userB.identity
  521. accessLevel:RLMSyncAccessLevelRead];
  522. // Set the permission.
  523. APPLY_PERMISSION(p, self.userA);
  524. // Now retrieve the permissions again and make sure the new permission is properly set.
  525. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB
  526. message:@"Setting new permission."];
  527. CHECK_PERMISSION_PRESENT(results, p, self.userA);
  528. // Delete the permission.
  529. RLMSyncPermission *p2 = [[RLMSyncPermission alloc] initWithRealmPath:url.path
  530. identity:self.userB.identity
  531. accessLevel:RLMSyncAccessLevelNone];
  532. REVOKE_PERMISSION(p2, self.userA);
  533. // Make sure the permission deletion is properly reflected.
  534. results = [self getPermissionResultsFor:self.userB message:@"Setting new permission."];
  535. CHECK_PERMISSION_COUNT(results, 0);
  536. }
  537. /// KVC getting and setting should work properly for `NSArray<RLMSyncPermission>`.
  538. - (void)testKVCWithPermissionsResults {
  539. NSURL *url1 = CUSTOM_REALM_URL(@"r1");
  540. NSURL *url2 = CUSTOM_REALM_URL(@"r2");
  541. __attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
  542. __attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
  543. NSString *uB = self.userB.identity;
  544. // Give user B read permissions to r1 and r2.
  545. NSString *path1 = [makeTildeSubstitutedURL(url1, self.userA) path];
  546. id p1 = [[RLMSyncPermission alloc] initWithRealmPath:path1
  547. identity:uB
  548. accessLevel:RLMSyncAccessLevelRead];
  549. APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, self.userA, @"Setting r1 permission for user B should work.");
  550. NSString *path2 = [makeTildeSubstitutedURL(url2, self.userA) path];
  551. id p2 = [[RLMSyncPermission alloc] initWithRealmPath:path2
  552. identity:uB
  553. accessLevel:RLMSyncAccessLevelRead];
  554. APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, self.userA, @"Setting r2 permission for user B should work.");
  555. // Wait for all the permissions to show up.
  556. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
  557. CHECK_PERMISSION_PRESENT(results, p1, self.userA);
  558. CHECK_PERMISSION_PRESENT(results, p2, self.userA);
  559. // Now use `valueForKey`
  560. NSArray *selfValues = [results valueForKey:@"self"];
  561. XCTAssert(selfValues.count == results.count);
  562. for (id object in selfValues) {
  563. XCTAssert([object isKindOfClass:[RLMSyncPermission class]]);
  564. }
  565. NSArray *identityValues = [results valueForKey:@"path"];
  566. XCTAssert(identityValues.count == results.count);
  567. XCTAssert([identityValues containsObject:path1]);
  568. XCTAssert([identityValues containsObject:path2]);
  569. // Since `RLMSyncPermission`s are read-only, KVC setting should fail.
  570. RLMAssertThrows([results setValue:@"foobar" forKey:@"path"]);
  571. }
  572. /// Filtering permissions results should work.
  573. - (void)testFilteringPermissions {
  574. // Open two Realms
  575. NSURL *url1 = CUSTOM_REALM_URL(@"r1");
  576. NSURL *url2 = CUSTOM_REALM_URL(@"r2");
  577. NSURL *url3 = CUSTOM_REALM_URL(@"r3");
  578. __attribute__((objc_precise_lifetime)) RLMRealm *r1 = [self openRealmForURL:url1 user:self.userA];
  579. __attribute__((objc_precise_lifetime)) RLMRealm *r2 = [self openRealmForURL:url2 user:self.userA];
  580. __attribute__((objc_precise_lifetime)) RLMRealm *r3 = [self openRealmForURL:url3 user:self.userA];
  581. NSString *uB = self.userB.identity;
  582. // Give user B permissions to realms r1, r2, and r3.
  583. id p1 = [[RLMSyncPermission alloc] initWithRealmPath:url1.path
  584. identity:uB
  585. accessLevel:RLMSyncAccessLevelRead];
  586. APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, self.userA, @"Setting r1 permission for user B should work.");
  587. NSString *finalPath = [makeTildeSubstitutedURL(url2, self.userA) path];
  588. id p2 = [[RLMSyncPermission alloc] initWithRealmPath:finalPath
  589. identity:uB
  590. accessLevel:RLMSyncAccessLevelRead];
  591. APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, self.userA, @"Setting r2 permission for user B should work.");
  592. id p3 = [[RLMSyncPermission alloc] initWithRealmPath:url3.path
  593. identity:uB
  594. accessLevel:RLMSyncAccessLevelRead];
  595. APPLY_PERMISSION_WITH_MESSAGE(p3, self.userA, self.userA, @"Setting r3 permission for user B should work.");
  596. // Wait for all the permissions to show up.
  597. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
  598. CHECK_PERMISSION_PRESENT(results, p1, self.userA);
  599. CHECK_PERMISSION_PRESENT(results, p2, self.userA);
  600. CHECK_PERMISSION_PRESENT(results, p3, self.userA);
  601. // Now make a filter.
  602. NSArray<RLMSyncPermission *> *filtered = [results filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path == %@", finalPath]];
  603. CHECK_PERMISSION_ABSENT(filtered, p1, self.userA);
  604. CHECK_PERMISSION_PRESENT(filtered, p2, self.userA);
  605. CHECK_PERMISSION_ABSENT(filtered, p3, self.userA);
  606. }
  607. - (void)testSortingPermissionsOnUserID {
  608. NSURL *url = REALM_URL();
  609. __attribute__((objc_precise_lifetime)) RLMRealm *r = [self openRealmForURL:url user:self.userA];
  610. // Give users B and C access to my Realm.
  611. id p1 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
  612. identity:self.userB.identity
  613. accessLevel:RLMSyncAccessLevelRead];
  614. APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, self.userA, @"Setting r permission for user B should work.");
  615. id p2 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
  616. identity:self.userC.identity
  617. accessLevel:RLMSyncAccessLevelRead];
  618. APPLY_PERMISSION_WITH_MESSAGE(p2, self.userA, self.userA, @"Setting r permission for user C should work.");
  619. // Now sort on user ID.
  620. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userA];
  621. NSArray<RLMSyncPermission *> *sorted = [results sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"identity" ascending:YES]]];
  622. CHECK_PERMISSION_COUNT(sorted, 3);
  623. NSArray *sortedIDs = [sorted valueForKey:@"identity"];
  624. // Make sure the IDs in sortedIDs are actually sorted.
  625. for (NSUInteger i = 0; i < sorted.count - 1; i++) {
  626. XCTAssertEqual([sortedIDs[i] compare:sortedIDs[i + 1]], NSOrderedAscending);
  627. }
  628. // Make sure the IDs in sortedIDs contain all 3 users' IDs.
  629. NSSet *sortedIDSet = [NSSet setWithArray:sortedIDs];
  630. XCTAssertTrue([sortedIDSet containsObject:self.userA.identity]);
  631. XCTAssertTrue([sortedIDSet containsObject:self.userB.identity]);
  632. XCTAssertTrue([sortedIDSet containsObject:self.userC.identity]);
  633. }
  634. - (void)testPermissionResultsIndexOfObject {
  635. NSString *uB = self.userB.identity;
  636. // Have A open a Realm and grant a permission to B.
  637. NSURL *url = REALM_URL();
  638. NSString *tildeSubstitutedPath = [makeTildeSubstitutedURL(url, self.userA) path];
  639. __attribute__((objc_precise_lifetime)) RLMRealm *r = [self openRealmForURL:url user:self.userA];
  640. id p1 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
  641. identity:uB
  642. accessLevel:RLMSyncAccessLevelRead];
  643. APPLY_PERMISSION_WITH_MESSAGE(p1, self.userA, self.userA,
  644. @"Setting read permission for user B should work.");
  645. // Wait for the permission to show up.
  646. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB];
  647. CHECK_PERMISSION_COUNT(results, 1);
  648. // Should be able to get the permission based on the actual permission.
  649. XCTAssertEqual(((NSInteger)[results indexOfObject:p1]), 0);
  650. // A permission with a differing access level should not match.
  651. id p2 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
  652. identity:uB
  653. accessLevel:RLMSyncAccessLevelAdmin];
  654. XCTAssertEqual([results indexOfObject:p2], NSNotFound);
  655. // A permission with a differing identity should not match.
  656. id p3 = [[RLMSyncPermission alloc] initWithRealmPath:tildeSubstitutedPath
  657. identity:self.userA.identity
  658. accessLevel:RLMSyncAccessLevelRead];
  659. XCTAssertEqual([results indexOfObject:p3], NSNotFound);
  660. // A permission with a differing path should not match.
  661. id p4 = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userB) path]
  662. identity:uB
  663. accessLevel:RLMSyncAccessLevelRead];
  664. XCTAssertEqual([results indexOfObject:p4], NSNotFound);
  665. }
  666. /// User should not be able to change a permission for a Realm they don't own.
  667. - (void)testSettingUnownedRealmPermission {
  668. // Open a Realm for user A.
  669. NSURL *url = REALM_URL();
  670. [self openRealmForURL:url user:self.userA];
  671. // Try to have user B give user C permissions to that Realm.
  672. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[makeTildeSubstitutedURL(url, self.userA) path]
  673. identity:self.userC.identity
  674. accessLevel:RLMSyncAccessLevelRead];
  675. // Set the permission.
  676. XCTestExpectation *ex2 = [self expectationWithDescription:@"Setting an invalid permission should fail."];
  677. [self.userB applyPermission:p callback:^(NSError *error) {
  678. XCTAssertNotNil(error);
  679. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  680. XCTAssertEqual(error.code, RLMSyncAuthErrorAccessDeniedOrInvalidPath);
  681. [ex2 fulfill];
  682. }];
  683. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  684. // Now retrieve the permissions again and make sure the new permission was not set.
  685. NSArray<RLMSyncPermission *> *results = [self getPermissionResultsFor:self.userB
  686. message:@"Retrieving the results should work."];
  687. CHECK_PERMISSION_ABSENT(results, p, self.userA);
  688. }
  689. #pragma mark - Permission offer/response
  690. - (NSString *)createOfferForRealmAtURL:(NSURL *)url
  691. user:(RLMSyncUser *)user
  692. accessLevel:(RLMSyncAccessLevel)level
  693. expiration:(NSDate *)expiration {
  694. __block NSString *token;
  695. XCTestExpectation *ex = [self expectationWithDescription:@"Should get a token when making an offer."];
  696. [user createOfferForRealmAtURL:url
  697. accessLevel:level
  698. expiration:expiration
  699. callback:^(NSString *t, NSError *error) {
  700. XCTAssertNil(error);
  701. token = t;
  702. XCTAssertNotNil(token);
  703. XCTAssertGreaterThan(token.length, 0);
  704. [ex fulfill];
  705. }];
  706. [self waitForExpectations:@[ex] timeout:10.0];
  707. return token;
  708. }
  709. /// Get a token which can be used to offer the permissions as defined
  710. - (void)testPermissionOffer {
  711. NSURL *url = REALM_URL();
  712. [self openRealmForURL:url user:self.userA];
  713. [self createOfferForRealmAtURL:url user:self.userA accessLevel:RLMSyncAccessLevelWrite expiration:nil];
  714. }
  715. /// Failed to process a permission offer object due to the offer expired
  716. - (void)testPermissionOfferIsExpired {
  717. NSURL *url = REALM_URL();
  718. // Create the Realm
  719. [self openRealmForURL:url user:self.userA];
  720. XCTestExpectation *ex = [self expectationWithDescription:@"Server should process the permission offer."];
  721. [self.userA createOfferForRealmAtURL:url
  722. accessLevel:RLMSyncAccessLevelWrite
  723. expiration:[NSDate dateWithTimeIntervalSinceNow:-30 * 24 * 60 * 60]
  724. callback:^(NSString *token, NSError *error) {
  725. XCTAssertNotNil(error);
  726. XCTAssertNil(token);
  727. XCTAssertEqual(error.code, RLMSyncAuthErrorExpiredPermissionOffer);
  728. XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"The permission offer is expired.");
  729. [ex fulfill];
  730. }];
  731. [self waitForExpectations:@[ex] timeout:10.0];
  732. }
  733. /// Get a permission offer token, then permission offer response will be processed, then open another user's Realm file
  734. - (void)testPermissionOfferResponse {
  735. NSURL *url = REALM_URL();
  736. // Create the Realm
  737. [self openRealmForURL:url user:self.userA];
  738. NSString *token = [self createOfferForRealmAtURL:url user:self.userA
  739. accessLevel:RLMSyncAccessLevelWrite expiration:nil];
  740. // Accept the offer.
  741. __block NSURL *realmURL = nil;
  742. XCTestExpectation *ex = [self expectationWithDescription:@"Server should process offer acceptance."];
  743. [self.userB acceptOfferForToken:token callback:^(NSURL *returnedURL, NSError *error) {
  744. XCTAssertNil(error);
  745. XCTAssertNotNil(returnedURL);
  746. realmURL = returnedURL;
  747. [ex fulfill];
  748. }];
  749. [self waitForExpectations:@[ex] timeout:20.0];
  750. XCTAssertEqualObjects([realmURL path], [makeTildeSubstitutedURL(url, self.userA) path]);
  751. // Open the Realm.
  752. XCTAssertNotNil([self openRealmForURL:realmURL user:self.userB]);
  753. }
  754. /// Failed to process a permission offer response object due to `token` is invalid
  755. - (void)testPermissionOfferResponseInvalidToken {
  756. NSString *badToken = @"invalid token";
  757. // Expect an error.
  758. __block NSError *error = nil;
  759. XCTestExpectation *ex = [self expectationWithDescription:@"Server should process offer acceptance."];
  760. [self.userA acceptOfferForToken:badToken callback:^(NSURL *returnedURL, NSError *err) {
  761. XCTAssertNil(returnedURL);
  762. XCTAssertNotNil(err);
  763. error = err;
  764. [ex fulfill];
  765. }];
  766. [self waitForExpectations:@[ex] timeout:20.0];
  767. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidParameters);
  768. XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey],
  769. @"Your request parameters did not validate. token: Invalid parameter 'token'!;");
  770. }
  771. /// Failed to process a permission offer response object due to `token` represents a Realm that does not exist
  772. - (void)testPermissionOfferResponseTokenNotExist {
  773. NSString *fakeToken = @"00000000000000000000000000000000:00000000-0000-0000-0000-000000000000";
  774. // Expect an error.
  775. __block NSError *error = nil;
  776. XCTestExpectation *ex = [self expectationWithDescription:@"Server should process offer acceptance."];
  777. [self.userA acceptOfferForToken:fakeToken callback:^(NSURL *returnedURL, NSError *err) {
  778. XCTAssertNil(returnedURL);
  779. XCTAssertNotNil(err);
  780. error = err;
  781. [ex fulfill];
  782. }];
  783. [self waitForExpectations:@[ex] timeout:20.0];
  784. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidParameters);
  785. XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey],
  786. @"Your request parameters did not validate. token: Invalid parameter 'token'!;");
  787. }
  788. - (void)testInvalidatePermissionOffer {
  789. NSURL *url = REALM_URL();
  790. // Create the Realm
  791. [self openRealmForURL:url user:self.userA];
  792. // Create an offer
  793. NSString *token = [self createOfferForRealmAtURL:url user:self.userA
  794. accessLevel:RLMSyncAccessLevelWrite expiration:nil];
  795. // Invalidate it
  796. XCTestExpectation *ex2 = [self expectationWithDescription:@"Should invalidate a offer token."];
  797. [self.userA invalidateOfferForToken:token callback:^(NSError *error) {
  798. XCTAssertNil(error);
  799. [ex2 fulfill];
  800. }];
  801. [self waitForExpectations:@[ex2] timeout:10.0];
  802. // Fail to accept the offer
  803. XCTestExpectation *ex3 = [self expectationWithDescription:@"Server should reject invalidated offer"];
  804. [self.userB acceptOfferForToken:token callback:^(NSURL *returnedURL, NSError *error) {
  805. XCTAssertNil(returnedURL);
  806. XCTAssertNotNil(error);
  807. XCTAssertEqual(error.code, RLMSyncAuthErrorExpiredPermissionOffer);
  808. XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], @"The permission offer is expired.");
  809. [ex3 fulfill];
  810. }];
  811. [self waitForExpectations:@[ex3] timeout:20.0];
  812. }
  813. - (void)testRetrievePermissionOffers {
  814. NSURL *url = REALM_URL();
  815. NSURL *expandedURL = makeTildeSubstitutedURL(url, self.userA);
  816. // Create the Realm
  817. [self openRealmForURL:url user:self.userA];
  818. NSDate *createdAt = [NSDate date];
  819. NSDate *expiresAt = [NSDate dateWithTimeIntervalSinceNow:100.0];
  820. // Create two offers
  821. NSString *token1 = [self createOfferForRealmAtURL:url user:self.userA
  822. accessLevel:RLMSyncAccessLevelRead expiration:expiresAt];
  823. NSString *token2 = [self createOfferForRealmAtURL:url user:self.userA
  824. accessLevel:RLMSyncAccessLevelWrite expiration:nil];
  825. id ex1 = [self expectationWithDescription:@"Retrieve offers"];
  826. [self.userA retrievePermissionOffersWithCallback:^(NSArray<RLMSyncPermissionOffer *> *offers, NSError *error) {
  827. XCTAssertNil(error);
  828. XCTAssertEqual(offers.count, 2U);
  829. for (RLMSyncPermissionOffer *offer in offers) {
  830. bool isFirst = [offer.token isEqualToString:token1];
  831. XCTAssertEqualObjects(offer.realmPath, expandedURL.path);
  832. XCTAssertGreaterThan(offer.createdAt.timeIntervalSince1970, createdAt.timeIntervalSince1970);
  833. if (isFirst) {
  834. XCTAssertEqualObjects(offer.token, token1);
  835. // May be up to a half ms off due to rounding
  836. XCTAssertLessThan(fabs([offer.expiresAt timeIntervalSinceDate:expiresAt]), 0.001);
  837. XCTAssertEqual(offer.accessLevel, RLMSyncAccessLevelRead);
  838. }
  839. else {
  840. XCTAssertEqualObjects(offer.token, token2);
  841. XCTAssertNil(offer.expiresAt);
  842. XCTAssertEqual(offer.accessLevel, RLMSyncAccessLevelWrite);
  843. }
  844. }
  845. [ex1 fulfill];
  846. }];
  847. [self waitForExpectations:@[ex1] timeout:10.0];
  848. // Invalidate one of the offers
  849. XCTestExpectation *ex2 = [self expectationWithDescription:@"Should invalidate a offer token."];
  850. [self.userA invalidateOfferForToken:token1 callback:^(NSError *error) {
  851. XCTAssertNil(error);
  852. [ex2 fulfill];
  853. }];
  854. [self waitForExpectations:@[ex2] timeout:10.0];
  855. // Verify that we only get non-invalidated offers
  856. id ex3 = [self expectationWithDescription:@"Retrieve offers"];
  857. [self.userA retrievePermissionOffersWithCallback:^(NSArray<RLMSyncPermissionOffer *> *offers, NSError *error) {
  858. XCTAssertNil(error);
  859. XCTAssertEqual(offers.count, 1U);
  860. RLMSyncPermissionOffer *offer = offers[0];
  861. XCTAssertEqualObjects(offer.realmPath, expandedURL.path);
  862. XCTAssertGreaterThan(offer.createdAt.timeIntervalSince1970, createdAt.timeIntervalSince1970);
  863. XCTAssertEqualObjects(offer.token, token2);
  864. XCTAssertNil(offer.expiresAt);
  865. XCTAssertEqual(offer.accessLevel, RLMSyncAccessLevelWrite);
  866. [ex3 fulfill];
  867. }];
  868. [self waitForExpectations:@[ex3] timeout:10.0];
  869. }
  870. #pragma mark - Delete Realm upon permission denied
  871. /// A Realm which is opened improperly should report an error allowing the app to recover.
  872. - (void)testDeleteRealmUponPermissionDenied {
  873. NSString *testName = NSStringFromSelector(_cmd);
  874. // Open a Realm for user A.
  875. NSURL *userAURL = makeTestURL(testName, nil);
  876. RLMRealm *userARealm = [self openRealmForURL:userAURL user:self.userA];
  877. // Have user A add some items to the Realm.
  878. [self addSyncObjectsToRealm:userARealm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  879. [self waitForUploadsForRealm:userARealm];
  880. CHECK_COUNT(3, SyncObject, userARealm);
  881. // Give user B read permissions to that Realm.
  882. RLMSyncPermission *p = [[RLMSyncPermission alloc] initWithRealmPath:[userAURL path]
  883. identity:self.userB.identity
  884. accessLevel:RLMSyncAccessLevelRead];
  885. // Set the read permission.
  886. APPLY_PERMISSION(p, self.userA);
  887. NSURL *userBURL = makeTestURL(testName, self.userA);
  888. RLMRealmConfiguration *userBConfig = [self.userB configurationWithURL:userBURL fullSynchronization:YES];
  889. __block NSError *theError = nil;
  890. // Incorrectly open the Realm for user B.
  891. NSURL *onDiskPath;
  892. @autoreleasepool {
  893. NSString *sessionName = NSStringFromSelector(_cmd);
  894. XCTestExpectation *ex2 = [self expectationWithDescription:@"We should get a permission denied error."];
  895. RLMSyncManager.sharedManager.errorHandler = ^(NSError *err, RLMSyncSession *session) {
  896. // Make sure we're actually looking at the right session.
  897. XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
  898. theError = err;
  899. [ex2 fulfill];
  900. };
  901. __attribute__((objc_precise_lifetime)) RLMRealm *bad = [RLMRealm realmWithConfiguration:userBConfig error:nil];
  902. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  903. onDiskPath = [RLMSyncTestCase onDiskPathForSyncedRealm:bad];
  904. }
  905. XCTAssertNotNil(onDiskPath);
  906. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:[onDiskPath path]]);
  907. // Check the error and perform the Realm deletion.
  908. XCTAssertNotNil(theError);
  909. RLMSyncErrorActionToken *errorToken = [theError rlmSync_errorActionToken];
  910. XCTAssertNotNil(errorToken);
  911. [RLMSyncSession immediatelyHandleError:errorToken];
  912. // Ensure the file is no longer on disk.
  913. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:[onDiskPath path]]);
  914. // Correctly open the same Realm for user B.
  915. __block RLMRealm *userBRealm = nil;
  916. XCTestExpectation *asyncOpenEx = [self expectationWithDescription:@"Should asynchronously open a Realm"];
  917. [RLMRealm asyncOpenWithConfiguration:userBConfig
  918. callbackQueue:dispatch_get_main_queue()
  919. callback:^(RLMRealm *realm, NSError *err){
  920. XCTAssertNil(err);
  921. XCTAssertNotNil(realm);
  922. userBRealm = realm;
  923. [asyncOpenEx fulfill];
  924. }];
  925. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  926. CHECK_COUNT(3, SyncObject, userBRealm);
  927. }
  928. @end