UNPKG

realm-object-server

Version:

Realm Object Server

512 lines 24 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 RealmDirectoryService_1; const decorators_1 = require("../decorators"); const Logger_1 = require("../shared/Logger"); const Server_1 = require("../Server"); const errors = require("../errors"); const realms_1 = require("../realms"); const RealmType_1 = require("../realms/RealmType"); const DefaultRealm = require("../realms/DefaultRealm"); const util_1 = require("../shared/util"); const SseStream = require("ssestream"); const URI = require("urijs"); const SyncClient_1 = require("../service-clients/SyncClient"); const events_1 = require("events"); let RealmDirectoryService = RealmDirectoryService_1 = class RealmDirectoryService extends events_1.EventEmitter { constructor() { super(...arguments); this.logger = new Logger_1.MuteLogger(); this.protectedRealmPaths = [ "/__admin", "/__revocation", "/__wildcardpermissions", "/__password", "/__configuration" ]; } setLogger(l) { this.logger = l; } getRealms(req, res, watch) { return __awaiter(this, void 0, void 0, function* () { if (!this.server.tokenValidator.isAdminToken(req.authToken)) { throw new errors.realm.AccessDenied(); } const realmFiles = this.adminRealm.objects("RealmFile"); if (watch !== "true") { return realmFiles.map(RealmDirectoryService_1.getRealmFileDetails); } const sse = new SseStream(req); sse.pipe(res); let messageId = 0; const paths = new Set(); realmFiles.addListener((collection, change) => { if (messageId++ === 0) { const files = new Array(); for (const file of collection) { paths.add(file.path); files.push(RealmDirectoryService_1.getRealmFileDetails(file)); } sse.write({ id: messageId, event: "initial", data: { files } }); } else { const deletions = []; const insertions = []; const modifications = []; if (change.deletions.length) { const oldPaths = [...paths]; paths.clear(); for (const item of collection) { paths.add(item.path); } deletions.push(...oldPaths.filter(x => !paths.has(x))); } for (const insertion of change.insertions) { const realmDetails = RealmDirectoryService_1.getRealmFileDetails(collection[insertion]); insertions.push(realmDetails); paths.add(realmDetails.path); } for (const modification of change.newModifications) { modifications.push(RealmDirectoryService_1.getRealmFileDetails(collection[modification])); } sse.write({ id: messageId, event: "change", data: { deletions, insertions, modifications, }, }); } }); req.once("close", () => { realmFiles.removeAllListeners(); sse.unpipe(res); }); }); } findByPath(realmPath, req, shouldCreateString, realmTypeString, ownerId) { return __awaiter(this, void 0, void 0, function* () { realmPath = util_1.validateRealmPath(realmPath, ownerId || req.authToken.identity); let shouldCreate; switch (shouldCreateString) { case undefined: case "true": shouldCreate = true; break; case "false": shouldCreate = false; break; default: throw new errors.realm.InvalidParameters({ name: "shouldCreate", reason: "shouldCreate must be true or false" }); } let realmType = RealmType_1.RealmType.full; if (realmTypeString !== undefined) { realmType = RealmType_1.RealmType[realmTypeString]; if (realmType === undefined) { throw new errors.realm.InvalidParameters({ name: "realmType", reason: "realmType must be full, reference, or partial" }); } } if (!shouldCreate) { const realmFile = this.findRealmFile(realmPath); if (realmFile) { return { path: realmFile.path, exists: true, realmType: RealmType_1.RealmType[realmFile.realmType], syncLabel: realmFile.syncLabel }; } return { path: realmPath, exists: false, realmType: RealmType_1.RealmType.full, syncLabel: "" }; } let syncLabel; if (realmType === RealmType_1.RealmType.partial) { const partialInfo = util_1.extractPartialInfo(realmPath); if (!partialInfo.isPartial) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: "realmPath for a partial realm is invalid" }); } const referenceRealm = this.findRealmFile(partialInfo.canonicalPath); if (referenceRealm) { util_1.ensureRealmOfType(RealmType_1.RealmType[referenceRealm.realmType], RealmType_1.RealmType.reference); syncLabel = referenceRealm.syncLabel; } else { const response = yield this.findByPath(partialInfo.canonicalPath, req, shouldCreateString, RealmType_1.RealmType.reference.toString(), ownerId); util_1.ensureRealmOfType(response.realmType, RealmType_1.RealmType.reference); syncLabel = response.syncLabel; } } const realmFile = yield this.findOrCreateRealmFile(realmPath, () => { if (this.server.tokenValidator.isAdminToken(req.authToken)) { ownerId = ownerId || req.authToken.identity; } else { if (ownerId && ownerId !== req.authToken.identity) { throw new errors.realm.AccessDenied({ detail: `A non-admin user is not allowed to create realms for other users. UserId: '${req.authToken.identity}'. Attempted to impersonate user: '${ownerId}'` }); } ownerId = req.authToken.identity; const userIdFromRealmPath = util_1.getUserIdFromRealmPath(realmPath); if (userIdFromRealmPath !== ownerId) { throw new errors.realm.AccessDenied({ detail: `A non-admin user is not allowed to create realms outside their home folder. UserId: '${ownerId}'. RealmPath: '${realmPath}'` }); } } return this.adminRealm.objectForPrimaryKey("User", ownerId); }, realmType, syncLabel); return { path: realmFile.path, exists: true, realmType: RealmType_1.RealmType[realmFile.realmType], syncLabel: realmFile.syncLabel }; }); } remove(realmPath, req) { return __awaiter(this, void 0, void 0, function* () { realmPath = util_1.validateRealmPath(realmPath, req.authToken.identity); if (!this.server.tokenValidator.isAdminToken(req.authToken)) { throw new errors.realm.AccessDenied(); } const adminRealm = this.adminRealm; const realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (!realmFile) { throw new errors.realm.RealmNotFound(); } if (this.protectedRealmPaths.indexOf(realmFile.path) !== -1) { throw new errors.JSONError({ status: 403, title: "This Realm is protected from removal", }); } const syncServerTags = ["role=master", `label=${realmFile.syncLabel}`]; const client = new SyncClient_1.SyncClient(this.discovery, this.adminToken, this.authorizationHeaderName, this.serviceAgent, syncServerTags); try { yield client.deleteRealm(realmPath); } catch (err) { if (err.status === 503) { throw err; } if (err.status !== 404) { this.logger.error(`Could not delete realm from backend sync worker: ${err.stack}`); } } yield util_1.writeAsync(adminRealm, () => { const realmFile = adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (realmFile) { if (realmFile.realmType === RealmType_1.RealmType.reference) { const partialFiles = adminRealm.objects("RealmFile") .filtered("realmType = $0 AND path BEGINSWITH $1", RealmType_1.RealmType.partial, `${realmFile.path}/__partial/`); for (const partialFile of partialFiles) { adminRealm.delete(partialFile.permissions); adminRealm.delete(partialFile); } this.logger.detail(`Deleting reference Realm ${realmFile.path} as well as its associated partial views: ${partialFiles.join(", ")}`); } adminRealm.delete(realmFile.permissions); adminRealm.delete(realmFile); } }); return {}; }); } changeType(realmPath, realmTypeString, req) { return __awaiter(this, void 0, void 0, function* () { if (!this.server.tokenValidator.isAdminToken(req.authToken)) { throw new errors.realm.AccessDenied(); } realmPath = util_1.validateRealmPath(realmPath, req.authToken.identity); if (!realmTypeString) { throw new errors.realm.InvalidParameters("realmType"); } const realmType = RealmType_1.RealmType[realmTypeString]; if (realmType === undefined) { throw new errors.realm.InvalidParameters("realmType"); } const realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (!realmFile) { throw new errors.realm.RealmNotFound(); } if (realmFile.realmType !== realmType) { yield util_1.writeAsync(this.adminRealm, () => { realmFile.realmType = realmType; }); } return {}; }); } compact(req) { return __awaiter(this, void 0, void 0, function* () { if (!this.server.tokenValidator.isAdminToken(req.authToken)) { throw new errors.realm.AccessDenied(); } const syncServerMasterTags = ["role=master"]; const client = new SyncClient_1.SyncClient(this.discovery, this.adminToken, this.authorizationHeaderName, this.serviceAgent, syncServerMasterTags); yield client.compactRealms(); return {}; }); } calculateSize(realmPath, req) { return __awaiter(this, void 0, void 0, function* () { if (!this.server.tokenValidator.isAdminToken(req.authToken)) { throw new errors.realm.AccessDenied(); } const syncServerMasterTags = ["role=master"]; const client = new SyncClient_1.SyncClient(this.discovery, this.adminToken, this.authorizationHeaderName, this.serviceAgent, syncServerMasterTags); yield client.calculateSize(realmPath); return {}; }); } start(server) { return __awaiter(this, void 0, void 0, function* () { this.discovery = server.discovery; this.adminToken = server.adminToken; this.authorizationHeaderName = server["serverConfig"].authorizationHeaderName; this.serviceAgent = server["serverConfig"].httpsAgentForInternalComponents; this.server = server; this.adminRealm = yield server.openRealm(realms_1.AdminRealm); if (!this.findRealmFile(DefaultRealm.Definition.remotePath)) { this.logger.detail("Creating and initializing the default Realm"); yield this.findOrCreateRealmFile(DefaultRealm.Definition.remotePath, null, RealmType_1.RealmType.reference, DefaultRealm.Definition.syncLabel); const defaultRealm = yield server.openRealm(DefaultRealm.Definition); DefaultRealm.initialize(defaultRealm); defaultRealm.close(); } const realmsToCreate = [ { definition: realms_1.AdminRealm, realmType: RealmType_1.RealmType.full }, { definition: realms_1.WildcardPermissionRealm, realmType: RealmType_1.RealmType.full }, { definition: realms_1.ConfigurationRealm, realmType: RealmType_1.RealmType.full }, { definition: realms_1.MetricsRealm, realmType: RealmType_1.RealmType.full }, ]; for (const realm of realmsToCreate) { yield this.findOrCreateRealmFile(realm.definition.remotePath, null, realm.realmType, realm.definition.syncLabel); } }); } stop() { return __awaiter(this, void 0, void 0, function* () { if (this.adminRealm) { this.adminRealm.close(); delete this.adminRealm; } }); } findSyncLabel() { return __awaiter(this, void 0, void 0, function* () { const sync = yield this.discovery.find("sync", ["role=master"]); if (sync) { const labelTag = sync.tags.find((t) => { return t.startsWith("label="); }); if (labelTag) { return labelTag.slice("label=".length); } return "default"; } else { throw new errors.realm.ServiceUnavailable({ detail: "Could not discover sync service" }); } }); } findRealmFile(realmPath) { return this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); } findOrCreateRealmFile(realmPath, getOwner, realmType, syncLabel) { return __awaiter(this, void 0, void 0, function* () { let realmFile; if (!syncLabel) { syncLabel = yield this.findSyncLabel(); } const segments = new URI(realmPath).segment(); const userNamespace = segments[0]; realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (realmFile) { return realmFile; } this.adminRealm.beginTransaction(); realmFile = this.adminRealm.objectForPrimaryKey("RealmFile", realmPath); if (realmFile) { this.adminRealm.cancelTransaction(); return realmFile; } try { const duplicateFile = this.adminRealm.objects("RealmFile").filtered("path == [c]$0", realmPath)[0]; if (duplicateFile) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: `A Realm with that path but different casing already exists. Supplied path: '${realmPath}', existing Realm: '${duplicateFile.path}'` }); } const owner = getOwner && getOwner(); realmFile = this.adminRealm.create("RealmFile", { owner, path: realmPath, realmType, createdAt: new Date(), syncLabel, }); if (owner && !owner.isAdmin) { if (userNamespace === owner.userId) { this.adminRealm.create("Permission", { realmFile, mayManage: true, mayRead: true, mayWrite: true, updatedAt: new Date(), user: owner, }); } } else { const realmName = segments[segments.length - 1]; if (realmName === "__permission") { const newOwner = this.adminRealm.objectForPrimaryKey("User", userNamespace); if (newOwner) { realmFile.owner = newOwner; this.adminRealm.create("Permission", { realmFile, mayRead: true, mayWrite: true, mayManage: false, updatedAt: new Date(), user: newOwner }); } } } const overallStats = util_1.mapEnum(RealmType_1.RealmType, (type) => { return this.adminRealm.objects("RealmFile").filtered("realmType = $0 && !path beginswith '/__'", type).length; }); this.adminRealm.commitTransaction(); this.emit("realmCreated", { type: realmType, path: realmPath, syncLabel, overallStats }); } catch (e) { this.adminRealm.cancelTransaction(); throw e; } return realmFile; }); } static getRealmFileDetails(realmFile) { return { createdAt: realmFile.createdAt, ownerId: realmFile.owner ? realmFile.owner.userId : undefined, path: realmFile.path, realmType: realmFile.realmType, syncLabel: realmFile.syncLabel, }; } }; __decorate([ decorators_1.Unmute(), __metadata("design:type", Function), __metadata("design:paramtypes", [Logger_1.Logger]), __metadata("design:returntype", void 0) ], RealmDirectoryService.prototype, "setLogger", null); __decorate([ decorators_1.Get("/files"), __param(0, decorators_1.Request()), __param(1, decorators_1.Response()), __param(2, decorators_1.Query("watch")), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, String]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "getRealms", null); __decorate([ decorators_1.Get("/files/:realmPath"), __param(0, decorators_1.Params("realmPath")), __param(1, decorators_1.Request()), __param(2, decorators_1.Query("shouldCreate")), __param(3, decorators_1.Query("realmType")), __param(4, decorators_1.Query("ownerId")), __metadata("design:type", Function), __metadata("design:paramtypes", [String, Object, String, String, String]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "findByPath", null); __decorate([ decorators_1.Delete("/files/:realmPath"), __param(0, decorators_1.Params("realmPath")), __param(1, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [String, Object]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "remove", null); __decorate([ decorators_1.Patch("/files/:realmPath"), __param(0, decorators_1.Params("realmPath")), __param(1, decorators_1.Body("realmType")), __param(2, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [String, String, Object]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "changeType", null); __decorate([ decorators_1.Post("/compact"), __param(0, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "compact", null); __decorate([ decorators_1.Post("/calculate-size/:realmPath"), __param(0, decorators_1.Params("realmPath")), __param(1, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [String, Object]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "calculateSize", null); __decorate([ decorators_1.Start(), __metadata("design:type", Function), __metadata("design:paramtypes", [Server_1.Server]), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "start", null); __decorate([ decorators_1.Stop(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], RealmDirectoryService.prototype, "stop", null); RealmDirectoryService = RealmDirectoryService_1 = __decorate([ decorators_1.BaseRoute("/realms", { allowAnonymous: false }), decorators_1.ServiceName("realms") ], RealmDirectoryService); exports.RealmDirectoryService = RealmDirectoryService; //# sourceMappingURL=RealmDirectoryService.js.map