UNPKG

realm-object-server

Version:

Realm Object Server

965 lines 44.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); var PermissionsService_1; const decorators_1 = require("../decorators"); const Constants_1 = require("../shared/Constants"); const shared_1 = require("../shared"); const realms_1 = require("../realms"); const errors = require("../errors"); const moment = require("moment"); const URI = require("urijs"); const _ = require("lodash"); const uuid = require("uuid"); const ConfigurableServiceBase_1 = require("./ConfigurableServiceBase"); function isPermissionConditionMetadata(object) { return "metadataKey" in object && "metadataValue" in object; } function isPermissionConditionUserId(object) { return "userId" in object; } function extractOffer(offer) { return { realmPath: offer.realmFile && offer.realmFile.path, expiresAt: offer.expiresAt, accessLevel: shared_1.getAccessLevel(offer), token: offer.id, createdAt: offer.createdAt, userId: offer.user && offer.user.userId }; } function extractPermission(p) { return { userId: p.user && p.user.userId, realmOwnerId: p.realmFile && p.realmFile.owner && p.realmFile.owner.userId, realmPath: p.realmFile && p.realmFile.path, mayRead: p.mayRead, mayWrite: p.mayWrite, mayManage: p.mayManage, updatedAt: p.updatedAt }; } let PermissionsService = PermissionsService_1 = class PermissionsService extends ConfigurableServiceBase_1.ConfigurableServiceBase { constructor(config = {}) { super(config); this.pendingHandlerCallbacks = 0; } static ensureInTransaction(mirror) { if (!mirror.isInTransaction) { throw new Error("user permission reflection attepmted outside of transaction"); } } setConfigCore(config = {}) { this.enablePermissionRealmReflection = shared_1.getValueOrDefault(config.enablePermissionRealmReflection, true); this.enableManagementRealmReflection = shared_1.getValueOrDefault(config.enableManagementRealmReflection, true); if (this.hasStarted) { this.setupHandlers().catch(err => { this.logger.error(`Error while trying to update config: ${err.stack}`, { error: err }); }); } } getManagementRealmForUserId(userId) { return __awaiter(this, void 0, void 0, function* () { const realmDefinition = realms_1.CreateUserManagementRealmDefinition(userId); return this.server.openRealm(realmDefinition); }); } getUserSpecificPermissionRealm(params) { return __awaiter(this, void 0, void 0, function* () { if (!params.adminRealm.objectForPrimaryKey("User", params.userId)) { return null; } const realmDefinition = realms_1.CreateUserPermissionRealmDefinition(params.userId); if (!params.forceOpenRealm && !params.adminRealm.objectForPrimaryKey("RealmFile", realmDefinition.remotePath)) { return null; } return this.server.openRealm(realmDefinition); }); } startCore(server) { return __awaiter(this, void 0, void 0, function* () { this.logger = server.logger; this.adminRealm = yield server.openRealm(realms_1.AdminRealm); this.wildcardPermissionRealm = yield server.openRealm(realms_1.WildcardPermissionRealm); yield this.setupHandlers(); server.discovery.waitForService("realms").then(() => __awaiter(this, void 0, void 0, function* () { this.grantReadWriteOnUserPermissionRealms(); yield this.grantReadOnlyAccessToWildcardPermissionRealm(); })).catch((err) => { this.logger.error(`Failed to grant wildcard permissions: ${err.stack}`, { error: err }); }); }); } setupHandlers() { return __awaiter(this, void 0, void 0, function* () { if (!this.server) { return; } const syncBaseUrl = `realm://${this.server.address}`; const authUrl = `http://${this.server.address}`; const adminCredentials = Realm.Sync.Credentials.adminToken(this.server.adminToken); const adminUser = Realm.Sync.User.login(authUrl, adminCredentials); const setupListeners = () => __awaiter(this, void 0, void 0, function* () { if (this.enablePermissionRealmReflection && !this.handleAdminChangeCallback) { const callback = this.handleAdminRealmChange.bind(this); yield Realm.Sync.addListener({ adminUser, filterRegex: PermissionsService_1.ADMIN_REALM_REGEX, serverUrl: syncBaseUrl, }, "change", callback); this.handleAdminChangeCallback = callback; } else if (!this.enablePermissionRealmReflection && this.handleAdminChangeCallback) { yield Realm.Sync.removeListener(PermissionsService_1.ADMIN_REALM_REGEX, "change", this.handleAdminChangeCallback); delete this.handleAdminChangeCallback; } if (this.enableManagementRealmReflection && !this.handleManagementChangeCallback) { const callback = this.handleManagementRealmChange.bind(this); yield Realm.Sync.addListener({ adminUser, filterRegex: PermissionsService_1.MANAGEMENT_REALM_REGEX, serverUrl: syncBaseUrl, }, "change", callback); this.handleManagementChangeCallback = callback; } else if (!this.enableManagementRealmReflection && this.handleManagementChangeCallback) { yield Realm.Sync.removeListener(PermissionsService_1.MANAGEMENT_REALM_REGEX, "change", this.handleManagementChangeCallback); delete this.handleManagementChangeCallback; } }); const maxRetries = 10; let currentRetry = 0; while (true) { try { yield setupListeners(); break; } catch (e) { if ((currentRetry++) < maxRetries) { this.logger.warn(`Failed to setup listeners. Retry #${currentRetry}.`, { error: e }); yield shared_1.delay(currentRetry * 1000); } else { throw e; } } } }); } stopping() { return __awaiter(this, void 0, void 0, function* () { this.enableManagementRealmReflection = false; this.enablePermissionRealmReflection = false; yield this.setupHandlers(); while (this.pendingHandlerCallbacks > 0) { yield shared_1.delay(100); } }); } stopCore() { return __awaiter(this, void 0, void 0, function* () { if (this.adminRealm) { this.adminRealm.close(); delete this.adminRealm; } if (this.wildcardPermissionRealm) { this.wildcardPermissionRealm.close(); delete this.wildcardPermissionRealm; } }); } onDemand(req) { return __awaiter(this, void 0, void 0, function* () { const userId = req.authToken.identity; if (!this.enablePermissionRealmReflection) { return { userId, affectedPermissionsCount: 0 }; } const permissionsInAdminRealm = this.adminRealm.objects("Permission") .filtered("user.userId = $0 || realmFile.owner.userId = $0", userId); const userSpecificRealm = yield this.getUserSpecificPermissionRealm({ userId, forceOpenRealm: true, adminRealm: this.adminRealm, }); try { let didUpdateAnyPermission = false; userSpecificRealm.beginTransaction(); for (const permissionFromAdminRealm of permissionsInAdminRealm.filtered("user != null").map(extractPermission)) { if (this.reflectPermissionIn(permissionFromAdminRealm, userSpecificRealm)) { didUpdateAnyPermission = true; } } if (didUpdateAnyPermission) { userSpecificRealm.commitTransaction(); } else { userSpecificRealm.cancelTransaction(); } } finally { shared_1.closeAfterUpload(userSpecificRealm); } return { userId, affectedPermissionsCount: permissionsInAdminRealm.length }; }); } applyPermissions(req, permissionRequest) { const user = this.adminRealm.objectForPrimaryKey("User", req.authToken.identity); if (!user) { throw new errors.realm.AccessDenied(); } const mayManage = permissionRequest.accessLevel === "admin"; const mayWrite = mayManage || permissionRequest.accessLevel === "write"; const mayRead = mayWrite || permissionRequest.accessLevel === "read"; const permissionChange = { realmPath: permissionRequest.realmPath, mayManage, mayWrite, mayRead, }; if (isPermissionConditionUserId(permissionRequest.condition)) { permissionChange.userId = permissionRequest.condition.userId; } else if (isPermissionConditionMetadata(permissionRequest.condition)) { permissionChange.metadataKey = permissionRequest.condition.metadataKey; permissionChange.metadataValue = permissionRequest.condition.metadataValue; } else { throw new errors.realm.InvalidParameters("condition"); } return this.handlePermissionChangeObject(permissionChange, user); } getOffers(req, includeOtherUsersString) { const user = this.adminRealm.objectForPrimaryKey("User", req.authToken.identity); if (!user) { throw new errors.realm.AccessDenied(); } const includeOtherUsers = user.isAdmin && includeOtherUsersString === "true"; let realmOffers = this.adminRealm.objects("PermissionOffer") .filtered("realmFile != null && (expiresAt == null || expiresAt > $0) && user != null", new Date()); if (!includeOtherUsers) { realmOffers = realmOffers.filtered("user = $0", user); } const offers = realmOffers.map(extractOffer); return { offers }; } offerPermissions(req, offerRequest) { const user = this.adminRealm.objectForPrimaryKey("User", req.authToken.identity); if (!user) { throw new errors.realm.AccessDenied(); } const realmPath = new URI(offerRequest.realmPath).path().replace("~", user.userId); const realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (!realmFile) { throw new errors.realm.InvalidParameters({ name: "realmFilePath", reason: `The provided realmFilePath (${realmPath}) does not exist.` }); } this.validateUserIdCanChangePermissionsForPath(user, realmPath); if (offerRequest.expiresAt && moment(offerRequest.expiresAt).isBefore(moment.now())) { throw new errors.realm.ExpiredPermissionOffer(); } const mayManage = offerRequest.accessLevel === "admin"; const mayWrite = mayManage || offerRequest.accessLevel === "write"; const mayRead = mayWrite || offerRequest.accessLevel === "read"; let permissionOffer; this.adminRealm.write(() => { permissionOffer = this.adminRealm.create("PermissionOffer", { createdAt: new Date(), expiresAt: offerRequest.expiresAt, id: uuid.v4(), isActive: true, user, realmFile, mayManage: mayManage, mayWrite: mayWrite, mayRead: mayRead, }); }); return extractOffer(permissionOffer); } invalidatePermissionOffer(req, token) { const user = this.adminRealm.objectForPrimaryKey("User", req.authToken.identity); if (!user) { throw new errors.realm.AccessDenied(); } const permissionOffer = this.adminRealm.objectForPrimaryKey("PermissionOffer", token); if (!permissionOffer) { throw new errors.realm.InvalidParameters("token"); } if (!user.isAdmin && !user._isSameObject(permissionOffer.user)) { throw new errors.realm.AccessDenied({ detail: "You don't have permissions to invalidate that offer." }); } this.adminRealm.write(() => { permissionOffer.expiresAt = new Date(); }); return {}; } acceptPermissionOffer(req, token) { const user = this.adminRealm.objectForPrimaryKey("User", req.authToken.identity); if (!user) { throw new errors.realm.AccessDenied(); } const permissionOffer = this.adminRealm.objectForPrimaryKey("PermissionOffer", token); if (!permissionOffer) { throw new errors.realm.InvalidParameters("token"); } if ((permissionOffer.expiresAt && moment(permissionOffer.expiresAt).isBefore(moment.now())) || !permissionOffer.realmFile) { throw new errors.realm.ExpiredPermissionOffer(); } const mayRead = permissionOffer.mayRead || null; const mayWrite = permissionOffer.mayWrite || null; const mayManage = permissionOffer.mayManage || null; const permission = this.applyPermissionChangeInAdminRealm({ realmFile: permissionOffer.realmFile, user, mayRead, mayWrite, mayManage, grantor: permissionOffer.user, }); return { path: permission.realmFile.path, accessLevel: shared_1.getAccessLevel(permission), }; } getGrantedPermissions(req, recipient) { const isAdmin = this.server.tokenValidator.isAdminToken(req.authToken); if (isAdmin) { const permissions = this.adminRealm.objects("RealmFile") .filtered('NOT path ENDSWITH "__permission" AND NOT path ENDSWITH "__management" AND NOT path ENDSWITH "__perm"') .map(f => { return { path: f.path, accessLevel: "admin", realmOwnerId: f.owner && f.owner.userId, updatedAt: undefined, updatedById: undefined, userId: req.authToken.identity, }; }); return { permissions }; } const realmPermissions = new Array(); recipient = (recipient || "any").toUpperCase(); if (recipient === "ANY" || recipient === "CURRENTUSER") { const currentUserPermissions = this.adminRealm.objects("Permission") .filtered("user = null || user.userId = $0 || realmFile.owner.userId = $0", req.authToken.identity) .filtered('NOT realmFile.path ENDSWITH "__permission" AND NOT realmFile.path ENDSWITH "__management" AND NOT realmFile.path ENDSWITH "__perm"'); realmPermissions.push(...currentUserPermissions); } if (recipient === "ANY" || recipient === "OTHERUSER") { const otherUserPermissions = this.adminRealm.objects("Permission") .filtered("realmFile.owner.userId = $0 && (user = null || user.userId != $0)", req.authToken.identity); realmPermissions.push(...otherUserPermissions); } const permissions = realmPermissions.map(p => { return { path: p.realmFile.path, accessLevel: shared_1.getAccessLevel(p), realmOwnerId: p.realmFile.owner && p.realmFile.owner.userId, updatedAt: p.updatedAt, updatedById: p.updatedBy && p.updatedBy.userId, userId: p.user && p.user.userId, }; }); return { permissions }; } unreflectPermissionIn(permission, mirror) { PermissionsService_1.ensureInTransaction(mirror); let reflection; if (permission.userId) { reflection = mirror.objects("Permission") .filtered("userId == $0 && path == $1", permission.userId, permission.realmPath)[0]; } else { reflection = mirror.objects("Permission") .filtered("path == $0", permission.realmPath)[0]; } if (!reflection) { return false; } mirror.delete(reflection); return true; } reflectPermissionIn(permission, mirror) { PermissionsService_1.ensureInTransaction(mirror); let reflection; const update = {}; if (permission.userId) { reflection = mirror.objects("Permission") .filtered("userId == $0 && path == $1", permission.userId, permission.realmPath)[0]; } else { reflection = mirror.objects("Permission") .filtered("path == $0", permission.realmPath)[0]; } const accessControlProperties = ["mayRead", "mayWrite", "mayManage"]; if (reflection) { for (const key of accessControlProperties) { if (reflection[key] !== permission[key]) { update[key] = permission[key]; } } } else { Object.assign(update, { userId: permission.userId || "*", path: permission.realmPath, mayRead: permission.mayRead, mayWrite: permission.mayWrite, mayManage: permission.mayManage, updatedAt: permission.updatedAt, }); } const updatedKeys = _.intersection(Object.keys(update), accessControlProperties); if (updatedKeys.length === 0) { return false; } if (reflection) { Object.assign(reflection, update); } else { mirror.create("Permission", update); } return true; } getUserIdsWithMetadata(key, value) { const userIds = new Set(); if (key === "email") { const account = this.adminRealm.objects("Account") .filtered("provider = $0 AND providerId = $1", "password", value)[0]; if (account && account.users.length > 0) { userIds.add(account.users[0].userId); } } if (key && value) { this.adminRealm.objects("UserMetadataRow") .filtered("key = $0 AND value = $1", key, value) .filter(m => m.users.length > 0) .forEach(m => userIds.add(m.users[0].userId)); } return Array.from(userIds); } grantReadOnlyAccessToWildcardPermissionRealm() { return __awaiter(this, void 0, void 0, function* () { const wildcardRealmFile = yield shared_1.waitAsync(() => this.adminRealm.objectForPrimaryKey("RealmFile", realms_1.WildcardPermissionRealm.remotePath), undefined, 15000, false); if (!wildcardRealmFile) { this.logger.error("Could not find the WildcardPermission RealmFile in Admin Realm!"); } else { const wildcardPerm = wildcardRealmFile.permissions.filtered("user == null")[0]; if (!wildcardPerm) { this.adminRealm.write(() => { this.adminRealm.create("Permission", { user: null, realmFile: wildcardRealmFile, updatedAt: new Date(), mayRead: true, mayWrite: false, mayManage: false, }); }); } } }); } grantReadWriteOnUserPermissionRealms() { this.adminRealm.write(() => { const wrongPerms = this.adminRealm.objects("Permission") .filtered('user != null AND mayWrite != true AND realmFile.path ENDSWITH "/__permission"'); for (const p of wrongPerms) { if (p.realmFile.path === `/${p.user.userId}/__permission`) { this.logger.debug(`granting read-write on ${p.realmFile.path} to ${p.user.userId}`); p.mayRead = true; p.mayWrite = true; } } }); } applyPermissionChangeInAdminRealm(input) { let realmFile; if (typeof input.realmFile === "string") { realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", input.realmFile); } else { realmFile = input.realmFile; } if (!realmFile) { throw new errors.realm.InvalidParameters({ name: "realmFilePath", reason: `The provided realmFilePath (${input.realmFile}) does not exist.` }); } let grantor; if (typeof input.grantor === "string") { grantor = this.adminRealm.objectForPrimaryKey("User", input.grantor); } else { grantor = input.grantor; } let permission; let user; if (typeof input.user === "string") { user = this.adminRealm.objectForPrimaryKey("User", input.user); } else { user = input.user; } const shouldDelete = (input.mayRead === false && input.mayWrite === false && input.mayManage === false); this.adminRealm.write(() => { if (user) { permission = realmFile.permissions.filtered("user = $0", user)[0]; } else { permission = realmFile.permissions.filtered("user = null")[0]; } if (permission) { if (shouldDelete) { this.adminRealm.delete(permission); } else { if (input.mayRead != null) { permission.mayRead = input.mayRead; } if (input.mayWrite != null) { permission.mayWrite = input.mayWrite; } if (input.mayManage != null) { permission.mayManage = input.mayManage; } permission.updatedAt = new Date(); permission.updatedBy = grantor; } } else if (!shouldDelete) { permission = this.adminRealm.create("Permission", { user: user, realmFile: realmFile, updatedAt: new Date(), mayRead: input.mayRead || false, mayWrite: input.mayWrite || false, mayManage: input.mayManage || false, updatedBy: grantor, }); } }); return permission; } handleAdminRealmChange(changeEvent) { if (!this.enablePermissionRealmReflection) { return; } return this.handleGNCallback(() => __awaiter(this, void 0, void 0, function* () { const permissionChanges = changeEvent.changes["Permission"]; if (permissionChanges) { const getRealmsForReflection = (permission, forceOpenRealm) => __awaiter(this, void 0, void 0, function* () { const mirrors = []; if (permission.userId) { const granteeId = permission.userId; const grantorId = permission.realmOwnerId; mirrors.push(yield this.getUserSpecificPermissionRealm({ userId: granteeId, forceOpenRealm, adminRealm: changeEvent.realm, })); if (grantorId && granteeId !== grantorId) { mirrors.push(yield this.getUserSpecificPermissionRealm({ userId: grantorId, forceOpenRealm, adminRealm: changeEvent.realm, })); } } else { this.wildcardPermissionRealm.open(); mirrors.push(this.wildcardPermissionRealm); } return mirrors.filter(m => m); }); const writeInAppropriateMirrors = (permission, shouldDelete) => __awaiter(this, void 0, void 0, function* () { if (!permission.realmPath) { this.logger.warn(`Attempted to write a permission with missing realmPath: ${JSON.stringify(permission)}.`); return; } const mirrors = yield getRealmsForReflection(permission, !shouldDelete); try { const operation = shouldDelete ? this.unreflectPermissionIn : this.reflectPermissionIn; for (const mirror of mirrors) { mirror.write(() => { operation(permission, mirror); }); } } finally { for (const mirror of mirrors) { shared_1.closeAfterUpload(mirror); } } }); const promises = new Array(); const newCollection = changeEvent.realm.objects("Permission"); const insertedPermissions = permissionChanges.insertions.map(i => extractPermission(newCollection[i])); const modifiedPermissions = permissionChanges.modifications.map(i => extractPermission(newCollection[i])); if (permissionChanges.deletions.length > 0) { const oldCollection = changeEvent.oldRealm.objects("Permission"); const deletedPermissions = permissionChanges.deletions.map(i => extractPermission(oldCollection[i])); if (insertedPermissions.length > 0) { let i = 0; while (i < deletedPermissions.length) { const deleted = deletedPermissions[i]; let matchingInsertion; for (let j = 0; j < insertedPermissions.length; j++) { if (_.isEqual(insertedPermissions[j], deleted)) { matchingInsertion = j; break; } } if (matchingInsertion !== undefined) { deletedPermissions.splice(i, 1); insertedPermissions.splice(matchingInsertion, 1); } else { i++; } } } promises.push(...deletedPermissions.map(p => writeInAppropriateMirrors(p, true))); } promises.push(...insertedPermissions.concat(modifiedPermissions).map(p => writeInAppropriateMirrors(p, false))); yield Promise.all(promises); } })); } handleManagementRealmChange(changeEvent) { if (!this.enableManagementRealmReflection) { return; } return this.handleGNCallback(() => __awaiter(this, void 0, void 0, function* () { const matches = changeEvent.path.match(PermissionsService_1.MANAGEMENT_REALM_REGEX); if (!matches) { return; } let userId = matches[1]; if (userId === "__auth") { userId = Constants_1.Constants.AdminUserId; } const owner = this.adminRealm.objectForPrimaryKey("User", userId); if (!owner) { this.logger.error(`Received change notification for management Realm without an owner: ${changeEvent.path}`); return; } const realm = changeEvent.realm; this.logger.debug(`A management realm with path ${changeEvent.path} has changes with userId: ${userId}`); if (realm.schema.findIndex(objSchema => objSchema.name === "PermissionOffer") !== -1) { const unprocessedPermissionOfferObjects = realm.objects("PermissionOffer") .filtered("statusCode == null") .snapshot(); for (const p of unprocessedPermissionOfferObjects) { this.handlePermissionOfferObject(p, owner, realm); } } if (realm.schema.findIndex(objSchema => objSchema.name === "PermissionOfferResponse") !== -1) { const unprocessedPermissionOfferResponseObjects = realm.objects("PermissionOfferResponse") .filtered("statusCode == null") .snapshot() .map(p => this.handlePermissionOfferResponseObject(p, owner, realm)); yield Promise.all(unprocessedPermissionOfferResponseObjects); } if (realm.schema.findIndex(objSchema => objSchema.name === "PermissionChange") !== -1) { const unprocessedPermissionChangeObjects = realm.objects("PermissionChange") .filtered("statusCode == null") .snapshot(); for (const p of unprocessedPermissionChangeObjects) { try { this.handlePermissionChangeObject({ realmPath: p.realmUrl, mayManage: p.mayManage, mayRead: p.mayRead, mayWrite: p.mayWrite, metadataKey: p.metadataKey, metadataValue: p.metadataValue, userId: p.userId, }, owner); realm.write(() => { p.statusCode = 0; p.statusMessage = "Successfully applied PermissionChange."; }); } catch (err) { const problem = err; this.logger.error(problem.toString()); realm.write(() => { p.statusMessage = problem.title; p.statusCode = problem.code; }); } } } })); } handleGNCallback(handler) { return __awaiter(this, void 0, void 0, function* () { this.pendingHandlerCallbacks++; try { yield handler(); } catch (err) { this.logger.error(`An error occurred while executing Global Notifier callback: ${err.stack}`); } finally { this.pendingHandlerCallbacks--; } }); } handlePermissionOfferObject(permissionOffer, owner, realm) { try { const realmPath = new URI(permissionOffer.realmUrl).path().replace("~", owner.userId); this.validateUserIdCanChangePermissionsForPath(owner, realmPath); if (permissionOffer.expiresAt && moment(permissionOffer.expiresAt).isBefore(moment.now())) { throw new errors.realm.ExpiredPermissionOffer(); } realm.write(() => { permissionOffer.realmUrl = realmPath; permissionOffer.token = `${owner.userId}:${permissionOffer.id}`; permissionOffer.statusCode = 0; }); } catch (err) { const problem = err; this.logger.error(problem.toString()); realm.write(() => { permissionOffer.statusMessage = problem.title; permissionOffer.statusCode = problem.code; }); } } handlePermissionOfferResponseObject(permissionOfferResponse, owner, realm) { return __awaiter(this, void 0, void 0, function* () { try { const tokenComponents = permissionOfferResponse.token.split(":"); if (tokenComponents.length < 2 || tokenComponents[0].length === 0) { throw new errors.realm.InvalidParameters("token"); } const offeringUserId = tokenComponents[0]; const offeringRealm = yield this.getManagementRealmForUserId(offeringUserId); try { const permissionOffers = offeringRealm.objects("PermissionOffer") .filtered("token = $0", permissionOfferResponse.token); if (permissionOffers.length === 0) { throw new errors.realm.InvalidParameters("token"); } if (permissionOffers.length > 1) { throw new errors.realm.AmbiguousPermissionOfferToken({ detail: `Unexpected count of permission offers (=${permissionOffers.length})` + ` matching the token '${permissionOfferResponse.token}' of the permission request!`, }); } const permissionOffer = permissionOffers[0]; if (permissionOffer.expiresAt && moment(permissionOffer.expiresAt).isBefore(moment.now())) { throw new errors.realm.ExpiredPermissionOffer(); } const realmPath = new URI(permissionOffer.realmUrl).path(); const mayRead = permissionOffer.mayRead || null; const mayWrite = permissionOffer.mayWrite || null; const mayManage = permissionOffer.mayManage || null; this.applyPermissionChangeInAdminRealm({ realmFile: realmPath, user: owner, mayRead, mayWrite, mayManage, grantor: offeringUserId, }); realm.write(() => { permissionOfferResponse.realmUrl = realmPath; permissionOfferResponse.statusCode = 0; }); } finally { shared_1.closeAfterUpload(offeringRealm); } } catch (err) { const problem = err; this.logger.error(problem.toString()); realm.write(() => { permissionOfferResponse.statusMessage = problem.title; permissionOfferResponse.statusCode = problem.code; }); } }); } handlePermissionChangeObject(permissionChange, owner) { const realmPath = new URI(permissionChange.realmPath).path().replace("~", owner.userId); if (permissionChange.realmPath === "*") { const userId = permissionChange.userId === "*" ? null : permissionChange.userId; this.adminRealm.objects(realms_1.RealmFile.schema.name) .filtered("owner = $0", owner) .forEach(realmFile => this.applyPermissionChangeInAdminRealm({ realmFile, user: userId, mayRead: permissionChange.mayRead, mayWrite: permissionChange.mayWrite, mayManage: permissionChange.mayManage, grantor: owner, })); return { affectedUsers: -1 }; } this.validateUserIdCanChangePermissionsForPath(owner, realmPath); if (permissionChange.userId === "*") { this.applyPermissionChangeInAdminRealm({ realmFile: realmPath, user: null, mayRead: permissionChange.mayRead, mayWrite: permissionChange.mayWrite, mayManage: permissionChange.mayManage, grantor: owner, }); return { affectedUsers: -1 }; } let userIds; if (permissionChange.metadataKey) { userIds = this.getUserIdsWithMetadata(permissionChange.metadataKey, permissionChange.metadataValue); if (userIds.length === 0) { throw new errors.realm.InvalidParameters({ name: "metadataValue", reason: `The provided metadata value (${permissionChange.metadataValue}) matched no users.` }); } this.logger.debug(`Apply permission change by metadata query (${permissionChange.metadataKey}=${permissionChange.metadataValue}) matching the userIds ${userIds}`); } else { this.logger.debug(`Apply permission change for user ${permissionChange.userId}`); userIds = [permissionChange.userId]; } const realmFiles = this.adminRealm.objects(realms_1.RealmFile.schema.name).filtered("path = $0", realmPath); for (const realmFile of realmFiles) { for (const userId of userIds) { this.applyPermissionChangeInAdminRealm({ realmFile, user: userId, mayRead: permissionChange.mayRead, mayWrite: permissionChange.mayWrite, mayManage: permissionChange.mayManage, grantor: owner, }); } } return { affectedUsers: userIds.length }; } validateUserIdCanChangePermissionsForPath(owner, path) { if (_.includes(path, "__management")) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: "The realmPath cannot be of a management realm" }); } if (_.includes(path, "__permission")) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: "The realmPath cannot be of a permission realm" }); } const realmFile = this.adminRealm.objectForPrimaryKey(realms_1.RealmFile.schema.name, path); if (!realmFile) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: `The realmPath '${path}' does not exist` }); } const permissions = realmFile.permissions.filtered("(user = $0 OR user == null) AND mayManage = true", owner); if (!owner.isAdmin && permissions.length === 0) { throw new errors.realm.AccessDenied(); } } }; PermissionsService.MANAGEMENT_REALM_REGEX = "^/([^/]+)/__management$"; PermissionsService.ADMIN_REALM_REGEX = "^/__admin$"; __decorate([ decorators_1.Stopping(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], PermissionsService.prototype, "stopping", null); __decorate([ decorators_1.Get("/ondemand"), __param(0, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], PermissionsService.prototype, "onDemand", null); __decorate([ decorators_1.Post("/apply"), __param(0, decorators_1.Request()), __param(1, decorators_1.Body()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "applyPermissions", null); __decorate([ decorators_1.Get("/offers"), __param(0, decorators_1.Request()), __param(1, decorators_1.Query("includeOtherUsers")), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "getOffers", null); __decorate([ decorators_1.Post("/offers"), __param(0, decorators_1.Request()), __param(1, decorators_1.Body()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "offerPermissions", null); __decorate([ decorators_1.Delete("/offers/:token"), __param(0, decorators_1.Request()), __param(1, decorators_1.Params("token")), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "invalidatePermissionOffer", null); __decorate([ decorators_1.Post("/offers/:token/accept"), __param(0, decorators_1.Request()), __param(1, decorators_1.Params("token")), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "acceptPermissionOffer", null); __decorate([ decorators_1.Get("/"), __param(0, decorators_1.Request()), __param(1, decorators_1.Query("recipient")), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Object) ], PermissionsService.prototype, "getGrantedPermissions", null); PermissionsService = PermissionsService_1 = __decorate([ decorators_1.BaseRoute("/permissions", { allowAnonymous: false }), decorators_1.ServiceName("permissions"), __metadata("design:paramtypes", [Object]) ], PermissionsService); exports.PermissionsService = PermissionsService; //# sourceMappingURL=PermissionsService.js.map