realm-object-server
Version:
965 lines • 44.3 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 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