RLMSyncPermission.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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 "RLMSyncPermission.h"
  19. #import "RLMArray.h"
  20. #import "RLMObjectSchema.h"
  21. #import "RLMRealm_Dynamic.h"
  22. #import "RLMResults.h"
  23. #import "RLMSyncUser.h"
  24. #import "RLMSyncUtil_Private.hpp"
  25. #import "RLMUtil.hpp"
  26. using namespace realm;
  27. static void verifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm, SEL sel) {
  28. if (!realm) {
  29. @throw RLMException(@"Cannot call %@ on an unmanaged object.", NSStringFromSelector(sel));
  30. }
  31. if (!realm.inWriteTransaction) {
  32. @throw RLMException(@"Cannot call %@ outside of a write transaction.", NSStringFromSelector(sel));
  33. }
  34. }
  35. id RLMPermissionForRole(RLMArray *array, id role) {
  36. RLMResults *filtered = [array objectsWhere:@"role.name = %@", [role name]];
  37. RLMPermission *permission;
  38. for (RLMPermission *p in filtered) {
  39. if (permission == nil) {
  40. permission = p;
  41. }
  42. // If there's more than one permission for the role, merge it into the first
  43. // one and then delete it as otherwise revoking permissions won't actually work
  44. else {
  45. if (p.canRead && !permission.canRead) {
  46. permission.canRead = true;
  47. }
  48. if (p.canUpdate && !permission.canUpdate) {
  49. permission.canUpdate = true;
  50. }
  51. if (p.canDelete && !permission.canDelete) {
  52. permission.canDelete = true;
  53. }
  54. if (p.canSetPermissions && !permission.canSetPermissions) {
  55. permission.canSetPermissions = true;
  56. }
  57. if (p.canQuery && !permission.canQuery) {
  58. permission.canQuery = true;
  59. }
  60. if (p.canCreate && !permission.canCreate) {
  61. permission.canCreate = true;
  62. }
  63. if (p.canModifySchema && !permission.canModifySchema) {
  64. permission.canModifySchema = true;
  65. }
  66. [array.realm deleteObject:p];
  67. }
  68. }
  69. if (!permission) {
  70. // Use the dynamic API to create the appropriate Permission class for the array
  71. permission = (id)[array.realm createObject:array.objectClassName withValue:@[role]];
  72. [array addObject:permission];
  73. }
  74. return permission;
  75. }
  76. @implementation RLMPermissionRole
  77. + (NSString *)_realmObjectName {
  78. return @"__Role";
  79. }
  80. + (NSString *)primaryKey {
  81. return @"name";
  82. }
  83. + (NSArray *)requiredProperties {
  84. return @[@"name"];
  85. }
  86. + (NSDictionary *)_realmColumnNames {
  87. return @{@"users": @"members"};
  88. }
  89. @end
  90. @implementation RLMPermissionUser
  91. + (NSString *)_realmObjectName {
  92. return @"__User";
  93. }
  94. + (NSString *)primaryKey {
  95. return @"identity";
  96. }
  97. + (NSArray *)requiredProperties {
  98. return @[@"identity"];
  99. }
  100. + (NSDictionary *)_realmColumnNames {
  101. return @{@"identity": @"id", @"role": @"role"};
  102. }
  103. + (NSDictionary *)linkingObjectsProperties {
  104. return @{@"roles": [RLMPropertyDescriptor descriptorWithClass:RLMPermissionRole.class propertyName:@"users"]};
  105. }
  106. + (RLMPermissionUser *)userInRealm:(RLMRealm *)realm withIdentity:(NSString *)identity {
  107. return [self createOrUpdateInRealm:realm withValue:@[identity]];
  108. }
  109. @end
  110. @implementation RLMPermission
  111. + (NSString *)_realmObjectName {
  112. return @"__Permission";
  113. }
  114. + (NSDictionary *)defaultPropertyValues {
  115. return @{@"canRead": @NO,
  116. @"canUpdate": @NO,
  117. @"canDelete": @NO,
  118. @"canSetPermissions": @NO,
  119. @"canQuery": @NO,
  120. @"canCreate": @NO,
  121. @"canModifySchema": @NO};
  122. }
  123. + (RLMPermission *)permissionForRole:(RLMPermissionRole *)role inArray:(RLMArray<RLMPermission *><RLMPermission> *)array {
  124. verifyInWriteTransaction(array.realm, _cmd);
  125. auto index = [array indexOfObjectWhere:@"role = %@", role];
  126. if (index != NSNotFound) {
  127. return array[index];
  128. }
  129. RLMPermission *permission = [RLMPermission createInRealm:role.realm withValue:@[role]];
  130. [array addObject:permission];
  131. return permission;
  132. }
  133. + (RLMPermission *)permissionForRoleNamed:(NSString *)roleName inArray:(RLMArray<RLMPermission *><RLMPermission> *)array {
  134. verifyInWriteTransaction(array.realm, _cmd);
  135. return RLMPermissionForRole(array, [RLMPermissionRole createOrUpdateInRealm:array.realm withValue:@{@"name": roleName}]);
  136. }
  137. + (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onRealm:(RLMRealm *)realm {
  138. verifyInWriteTransaction(realm, _cmd);
  139. return [self permissionForRoleNamed:roleName
  140. inArray:[RLMRealmPermission objectInRealm:realm].permissions];
  141. }
  142. + (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClass:(Class)cls realm:(RLMRealm *)realm {
  143. verifyInWriteTransaction(realm, _cmd);
  144. return [self permissionForRoleNamed:roleName
  145. inArray:[RLMClassPermission objectInRealm:realm forClass:cls].permissions];
  146. }
  147. + (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onClassNamed:(NSString *)className realm:(RLMRealm *)realm {
  148. verifyInWriteTransaction(realm, _cmd);
  149. return [self permissionForRoleNamed:roleName
  150. inArray:[RLMClassPermission objectInRealm:realm forClassNamed:className].permissions];
  151. }
  152. + (RLMPermission *)permissionForRoleNamed:(NSString *)roleName onObject:(RLMObject *)object {
  153. verifyInWriteTransaction(object.realm, _cmd);
  154. for (RLMProperty *prop in object.objectSchema.properties) {
  155. if (prop.array && [prop.objectClassName isEqualToString:@"RLMPermission"]) {
  156. return [self permissionForRoleNamed:roleName
  157. inArray:[object valueForKey:prop.name]];
  158. }
  159. }
  160. @throw RLMException(@"Object %@ does not have a RLMArray<RLMPermission *> property.", object);
  161. }
  162. @end
  163. @implementation RLMClassPermission
  164. + (NSString *)_realmObjectName {
  165. return @"__Class";
  166. }
  167. + (NSString *)primaryKey {
  168. return @"name";
  169. }
  170. + (NSArray *)requiredProperties {
  171. return @[@"name"];
  172. }
  173. + (instancetype)objectInRealm:(RLMRealm *)realm forClassNamed:(NSString *)name {
  174. return [RLMClassPermission objectInRealm:realm forPrimaryKey:name];
  175. }
  176. + (instancetype)objectInRealm:(RLMRealm *)realm forClass:(Class)cls {
  177. return [RLMClassPermission objectInRealm:realm forPrimaryKey:[cls _realmObjectName] ?: [cls className]];
  178. }
  179. @end
  180. @interface RLMRealmPermission ()
  181. @property (nonatomic) int pk;
  182. @end
  183. @implementation RLMRealmPermission
  184. + (NSString *)_realmObjectName {
  185. return @"__Realm";
  186. }
  187. + (NSDictionary *)_realmColumnNames {
  188. return @{@"pk": @"id"};
  189. }
  190. + (NSString *)primaryKey {
  191. return @"pk";
  192. }
  193. + (instancetype)objectInRealm:(RLMRealm *)realm {
  194. return [RLMRealmPermission objectInRealm:realm forPrimaryKey:@0];
  195. }
  196. @end
  197. #pragma mark - Permission
  198. @implementation RLMSyncPermission
  199. - (instancetype)initWithRealmPath:(NSString *)path
  200. identity:(NSString *)identity
  201. accessLevel:(RLMSyncAccessLevel)accessLevel {
  202. if (self = [super init]) {
  203. _accessLevel = accessLevel;
  204. _path = path;
  205. _identity = identity;
  206. if (!identity) {
  207. @throw RLMException(@"A permission value cannot be created without a valid user ID");
  208. }
  209. _updatedAt = [NSDate date];
  210. }
  211. return self;
  212. }
  213. - (instancetype)initWithRealmPath:(NSString *)path
  214. username:(NSString *)username
  215. accessLevel:(RLMSyncAccessLevel)accessLevel {
  216. if (self = [super init]) {
  217. _accessLevel = accessLevel;
  218. _path = path;
  219. _identity = nil;
  220. _key = @"email";
  221. _value = username;
  222. _updatedAt = [NSDate date];
  223. }
  224. return self;
  225. }
  226. + (BOOL)accessInstanceVariablesDirectly {
  227. return NO;
  228. }
  229. - (BOOL)mayRead {
  230. return self.accessLevel > RLMSyncAccessLevelNone;
  231. }
  232. - (BOOL)mayWrite {
  233. return self.accessLevel > RLMSyncAccessLevelRead;
  234. }
  235. - (BOOL)mayManage {
  236. return self.accessLevel == RLMSyncAccessLevelAdmin;
  237. }
  238. - (NSUInteger)hash {
  239. return self.identity.hash ^ self.accessLevel ^ self.path.hash ^ self.key.hash ^ self.value.hash;
  240. }
  241. - (BOOL)isEqual:(id)object {
  242. if (self == object) {
  243. return YES;
  244. }
  245. if (![object isKindOfClass:[RLMSyncPermission class]]) {
  246. return NO;
  247. }
  248. RLMSyncPermission *that = (RLMSyncPermission *)object;
  249. if (self.accessLevel != that.accessLevel) {
  250. return NO;
  251. }
  252. if (![self.path isEqualToString:that.path]) {
  253. return NO;
  254. }
  255. if (self.identity) {
  256. if (!that.identity || ![self.identity isEqualToString:that.identity]) {
  257. return NO;
  258. }
  259. }
  260. else {
  261. if (that.identity || ![self.key isEqualToString:that.key] || ![self.value isEqualToString:that.value]) {
  262. return NO;
  263. }
  264. }
  265. return YES;
  266. }
  267. - (RLMSyncPermission *)tildeExpandedSyncPermissionForUser:(RLMSyncUser *)user {
  268. if (![self.path hasPrefix:@"/~/"]) {
  269. return self;
  270. }
  271. NSString *path = [self.path stringByReplacingCharactersInRange:{1, 1} withString:user.identity];
  272. if (_identity) {
  273. return [[RLMSyncPermission alloc] initWithRealmPath:path identity:_identity accessLevel:_accessLevel];
  274. }
  275. return [[RLMSyncPermission alloc] initWithRealmPath:path username:_value accessLevel:_accessLevel];
  276. }
  277. - (NSString *)description {
  278. NSString *typeDescription = nil;
  279. if (self.identity) {
  280. typeDescription = [NSString stringWithFormat:@"identity: %@", self.identity];
  281. } else {
  282. typeDescription = [NSString stringWithFormat:@"key: %@, value: %@", self.key, self.value];
  283. }
  284. return [NSString stringWithFormat:@"<RLMSyncPermission> %@, path: %@, access level: %@",
  285. typeDescription, self.path, RLMSyncAccessLevelToString(self.accessLevel)];
  286. }
  287. NSString *RLMSyncAccessLevelToString(RLMSyncAccessLevel level) {
  288. switch (level) {
  289. case RLMSyncAccessLevelNone: return @"none";
  290. case RLMSyncAccessLevelRead: return @"read";
  291. case RLMSyncAccessLevelWrite: return @"write";
  292. case RLMSyncAccessLevelAdmin: return @"admin";
  293. }
  294. return @"unknown";
  295. }
  296. RLMSyncAccessLevel RLMSyncAccessLevelFromString(NSString *level) {
  297. if ([level isEqualToString:@"none"]) {
  298. return RLMSyncAccessLevelNone;
  299. }
  300. if ([level isEqualToString:@"read"]) {
  301. return RLMSyncAccessLevelRead;
  302. }
  303. if ([level isEqualToString:@"write"]) {
  304. return RLMSyncAccessLevelWrite;
  305. }
  306. if ([level isEqualToString:@"admin"]) {
  307. return RLMSyncAccessLevelAdmin;
  308. }
  309. @throw RLMException(@"Invalid access level %@", level);
  310. }
  311. @end
  312. @implementation RLMSyncPermissionOffer
  313. - (instancetype)initWithRealmPath:(NSString *)path
  314. token:(NSString *)token
  315. expiresAt:(NSDate *)expiresAt
  316. createdAt:(NSDate *)createdAt
  317. accessLevel:(RLMSyncAccessLevel)accessLevel {
  318. if (self = [super init]) {
  319. _realmPath = path;
  320. _token = token;
  321. _expiresAt = expiresAt;
  322. _createdAt = createdAt;
  323. _accessLevel = accessLevel;
  324. }
  325. return self;
  326. }
  327. + (BOOL)accessInstanceVariablesDirectly {
  328. return NO;
  329. }
  330. - (NSString *)description {
  331. return [NSString stringWithFormat:@"<RLMSyncPermissionOffer: %p> path: %@, access level: %@, token: %@, createdAt: %@, expiresAt: %@",
  332. self, _realmPath, RLMSyncAccessLevelToString(_accessLevel),
  333. _token, _createdAt, _expiresAt];
  334. }
  335. @end