"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 });
const shared_1 = require("../shared");
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 AdminRealm_1 = require("../realms/AdminRealm");
const DefaultRealm = require("../realms/DefaultRealm");
const util_1 = require("../shared/util");
const URI = require("urijs");
const SyncClient_1 = require("../service-clients/SyncClient");
let RealmDirectoryService = class RealmDirectoryService {
    constructor() {
        this.logger = new Logger_1.MuteLogger();
        this.protectedRealmPaths = [
            "/__admin",
            "/__revocation",
            "/__wildcardpermissions",
            "/__password",
            "/__perm",
            "/__configuration"
        ];
    }
    setLogger(l) {
        this.logger = l;
    }
    findByPath(realmPath, req, shouldCreateString, realmTypeString, ownerId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!req.authToken) {
                throw new errors.realm.AccessDenied();
            }
            const token = req.authToken;
            realmPath = util_1.validateRealmPath(realmPath, ownerId || token.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 = AdminRealm_1.RealmType.full;
            if (realmTypeString !== undefined) {
                realmType = AdminRealm_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: AdminRealm_1.RealmType[realmFile.realmType],
                        syncLabel: realmFile.syncLabel
                    };
                }
                return {
                    path: realmPath,
                    exists: false,
                    realmType: AdminRealm_1.RealmType.full,
                    syncLabel: ''
                };
            }
            const realmFile = yield this.findOrCreateRealmFile(realmPath, () => {
                if (shared_1.isAdminToken(token)) {
                    ownerId = ownerId || token.identity;
                }
                else {
                    if (ownerId && ownerId !== token.identity) {
                        throw new errors.realm.AccessDenied({
                            detail: 'A non-admin user is not allowed to create realms for other users.'
                        });
                    }
                    ownerId = token.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.'
                        });
                    }
                }
                return this.adminRealm.objectForPrimaryKey('User', ownerId);
            }, realmType);
            return {
                path: realmFile.path,
                exists: true,
                realmType: AdminRealm_1.RealmType[realmFile.realmType],
                syncLabel: realmFile.syncLabel
            };
        });
    }
    remove(realmPath, req) {
        return __awaiter(this, void 0, void 0, function* () {
            const token = req.authToken;
            if (!token) {
                throw new errors.realm.AccessDenied();
            }
            realmPath = util_1.validateRealmPath(realmPath, token.identity);
            if (!shared_1.isAdminToken(token)) {
                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, 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}`);
                }
            }
            adminRealm.write(() => {
                const realmFile = adminRealm.objectForPrimaryKey("RealmFile", realmPath);
                if (realmFile) {
                    adminRealm.delete(realmFile.permissions);
                    adminRealm.delete(realmFile);
                }
            });
            return {};
        });
    }
    changeType(realmPath, realmTypeString, req) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!shared_1.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 = AdminRealm_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) {
                this.adminRealm.write(() => {
                    realmFile.realmType = realmType;
                });
            }
            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.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, AdminRealm_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: AdminRealm_1.RealmType.full },
                { definition: realms_1.WildcardPermissionRealm, realmType: AdminRealm_1.RealmType.full },
                { definition: realms_1.LegacyWildcardPermissionRealm, realmType: AdminRealm_1.RealmType.full },
                { definition: realms_1.ConfigurationRealm, realmType: AdminRealm_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();
            }
        });
    }
    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 partialIndex = realmPath.indexOf('/__partial/');
                let owner;
                const shouldGetOwner = true;
                if (shouldGetOwner) {
                    owner = getOwner && getOwner();
                }
                realmFile = this.adminRealm.create('RealmFile', {
                    owner,
                    path: realmPath,
                    realmType: 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 (['__permission', '__perm'].indexOf(realmName) !== -1) {
                        const owner = this.adminRealm.objectForPrimaryKey('User', userNamespace);
                        if (owner) {
                            realmFile.owner = owner;
                            const mayWrite = realmName === '__permission';
                            this.adminRealm.create('Permission', {
                                realmFile,
                                mayRead: true,
                                mayWrite,
                                mayManage: false,
                                updatedAt: new Date(),
                                user: owner
                            });
                        }
                    }
                }
                this.adminRealm.commitTransaction();
            }
            catch (e) {
                this.adminRealm.cancelTransaction();
                throw e;
            }
            return realmFile;
        });
    }
};
__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/: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.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 = __decorate([
    decorators_1.BaseRoute('/realms'),
    decorators_1.ServiceName('realms')
], RealmDirectoryService);
exports.RealmDirectoryService = RealmDirectoryService;
//# sourceMappingURL=RealmDirectoryService.js.map