realm-object-server
Version:
512 lines • 24 kB
JavaScript
"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