Upgrade/downgrade shared folders access rights

This commit is contained in:
yflory
2019-10-11 18:15:48 +02:00
parent 8ca7e11150
commit d443c93893
14 changed files with 315 additions and 55 deletions

View File

@@ -81,6 +81,7 @@ define([
try { team.listmap.stop(); } catch (e) {}
try { team.roster.stop(); } catch (e) {}
team.proxy = {};
team.stopped = true;
delete ctx.teams[teamId];
delete ctx.store.proxy.teams[teamId];
ctx.emit('LEAVE_TEAM', teamId, team.clients);
@@ -140,8 +141,10 @@ define([
roster: roster
};
// Subscribe to events
if (cId) { team.clients.push(cId); }
// Listen for roster changes
roster.on('change', function () {
var state = roster.getState();
var me = Util.find(ctx, ['store', 'proxy', 'curvePublic']);
@@ -158,16 +161,19 @@ define([
rosterData.lastKnownHash = hash;
});
// Update metadata
var state = roster.getState();
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', id]);
if (teamData) { teamData.metadata = state.metadata; }
// Broadcast an event to all the tabs displaying this team
team.sendEvent = function (q, data, sender) {
ctx.emit(q, data, team.clients.filter(function (cId) {
return cId !== sender;
}));
};
// Provide team chat keys to the messenger app
team.getChatData = function () {
var chatKeys = keys.chat || {};
var hash = chatKeys.edit || chatKeys.view;
@@ -177,7 +183,7 @@ define([
teamId: id,
channel: secret.channel,
secret: secret,
validateKey: secret.keys.validateKey
validateKey: chatKeys.validateKey
};
};
@@ -185,6 +191,7 @@ define([
team.pin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); };
team.unpin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); };
nThen(function (waitFor) {
// Init Team RPC
if (!keys.drive.edPrivate) { return; }
initRpc(ctx, team, keys.drive, waitFor(function (err) {
if (err) { return; }
@@ -208,6 +215,7 @@ define([
};
}));
}).nThen(function () {
// Create the proxy manager
var loadSharedFolder = function (id, data, cb) {
SF.load({
network: ctx.store.network,
@@ -217,9 +225,8 @@ define([
});
};
var teamData = ctx.store.proxy.teams[team.id];
if (teamData) {
secret = Hash.getSecrets('team', teamData.hash, teamData.password);
}
var hash = teamData.hash || teamData.roHash;
secret = Hash.getSecrets('team', hash, teamData.password);
var manager = team.manager = ProxyManager.create(proxy.drive, {
onSync: function (cb) { ctx.Store.onSync(id, cb); },
edPublic: keys.drive.edPublic,
@@ -251,12 +258,14 @@ define([
team.sendEvent("DRIVE_LOG", msg);
},
rt: team.realtime,
editKey: secret && secret.keys.secondaryKey
editKey: secret.keys.secondaryKey,
readOnly: Boolean(!secret.keys.secondaryKey)
});
team.secondaryKey = secret && secret.keys.secondaryKey;
team.userObject = manager.user.userObject;
team.userObject.fixFiles();
}).nThen(function (waitFor) {
// Load the shared folders
ctx.teams[id] = team;
registerChangeEvents(ctx, team, proxy);
SF.checkMigration(team.secondaryKey, proxy, team.userObject, waitFor());
@@ -296,10 +305,20 @@ define([
var openChannel = function (ctx, teamData, id, _cb) {
var cb = Util.once(_cb);
var secret = Hash.getSecrets('team', teamData.hash, teamData.password);
var hash = teamData.hash || teamData.roHash;
var secret = Hash.getSecrets('team', hash, teamData.password);
var crypto = Crypto.createEncryptor(secret.keys);
if (!teamData.roHash) {
teamData.roHash = Hash.getViewHashFromKeys(secret);
}
var keys = teamData.keys;
if (!keys.chat.validateKey && keys.chat.edit) {
var chatSecret = Hash.getSecrets('chat', keys.chat.edit);
keys.chat.validateKey = chatSecret.keys.validateKey;
}
var roster;
var lm;
@@ -373,6 +392,7 @@ define([
}, waitFor(function (err, _roster) {
if (err) {
waitFor.abort();
console.error(err);
return void cb({error: 'ROSTER_ERROR'});
}
roster = _roster;
@@ -421,6 +441,7 @@ define([
var password = Hash.createChannelId();
var hash = Hash.createRandomHash('team', password);
var secret = Hash.getSecrets('team', hash, password);
var roHash = Hash.getViewHashFromKeys(secret);
var keyPair = Nacl.sign.keyPair(); // keyPair.secretKey , keyPair.publicKey
var rosterSeed = Crypto.Team.createSeed();
@@ -520,6 +541,7 @@ define([
chat: {
edit: chatHashes.editHash,
view: chatHashes.viewHash,
validateKey: chatSecret.keys.validateKey,
channel: chatSecret.channel
},
roster: {
@@ -532,6 +554,7 @@ define([
owner: true,
channel: secret.channel,
hash: hash,
roHash: roHash,
password: password,
keys: keys,
//members: membersHashes.editHash,
@@ -665,7 +688,7 @@ define([
var joinTeam = function (ctx, data, cId, cb) {
var team = data.team;
if (!team.hash || !team.channel || !team.password
if (!(team.hash || team.roHash) || !team.channel || !team.password
|| !team.keys || !team.metadata) { return void cb({error: 'EINVAL'}); }
var id = Util.createRandomInteger();
ctx.store.proxy.teams[id] = team;
@@ -924,6 +947,92 @@ define([
});
};
var getInviteData = function (ctx, teamId, edit) {
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
if (!teamData) { return {}; }
var data = Util.clone(teamData);
if (!edit) {
// Delete edit keys
delete data.hash;
delete data.keys.drive.edPrivate;
delete data.keys.chat.edit;
}
// Delete owner key
delete data.owner;
return data;
};
// Update my edit rights in listmap (only upgrade) and userObject (upgrade and downgrade)
// We also need to propagate the changes to the shared folders
var updateMyRights = function (ctx, teamId, hash) {
if (!teamId) { return true; }
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
if (!teamData) { return true; }
var team = ctx.teams[teamId];
var secret = Hash.getSecrets('team', hash || teamData.roHash, teamData.password);
// Upgrade the listmap if we can
SF.upgrade(teamData.channel, secret);
// Set the new readOnly value in userObject
if (team.userObject) {
team.userObject.setReadOnly(!secret.keys.secondaryKey, secret.keys.secondaryKey);
}
// Upgrade the shared folders
var folders = Util.find(team, ['proxy', 'drive', 'sharedFolders']);
Object.keys(folders || {}).forEach(function (sfId) {
var data = team.manager.getSharedFolderData(sfId);
var parsed = Hash.parsePadUrl(data.href || data.roHref);
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
SF.upgrade(secret.channel, secret);
var uo = Util.find(team, ['manager', 'folders', sfId, 'userObject']);
if (uo) {
uo.setReadOnly(!secret.keys.secondaryKey, secret.keys.secondaryKey);
}
});
ctx.emit('ROSTER_CHANGE_RIGHTS', teamId, team.clients);
};
var changeMyRights = function (ctx, teamId, state, data) {
if (!teamId) { return true; }
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
if (!teamData) { return true; }
var team = ctx.teams[teamId];
if (!team) { return true; }
if (teamData.channel !== data.channel || teamData.password !== data.password) { return true; }
if (state) {
teamData.hash = data.hash;
teamData.keys.drive.edPrivate = data.keys.drive.edPrivate;
teamData.keys.chat.edit = data.keys.chat.edit;
} else {
delete teamData.hash;
delete teamData.keys.drive.edPrivate;
delete teamData.keys.chat.edit;
}
updateMyRights(ctx, teamId, data.hash);
};
var changeEditRights = function (ctx, teamId, user, state, cb) {
if (!teamId) { return void cb({error: 'EINVAL'}); }
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
if (!teamData) { return void cb ({error: 'ENOENT'}); }
var team = ctx.teams[teamId];
if (!team) { return void cb ({error: 'ENOENT'}); }
// Send mailbox to offer ownership
var myData = Messaging.createData(ctx.store.proxy, false);
ctx.store.mailbox.sendTo("TEAM_EDIT_RIGHTS", {
state: state,
teamData: getInviteData(ctx, teamId, state),
user: myData
}, {
channel: user.notifications,
curvePublic: user.curvePublic
}, cb);
};
var describeUser = function (ctx, data, cId, cb) {
var teamId = data.teamId;
if (!teamId) { return void cb({error: 'EINVAL'}); }
@@ -937,13 +1046,27 @@ define([
// It it is an ownership revocation, we have to set it in pad metadata first
if (user.role === "OWNER" && data.data.role !== "OWNER") {
revokeOwnership(ctx, teamId, user, function (err) {
if (!err) { return; }
if (!err) { return void cb(); }
console.error(err);
return void cb({error: err});
});
return;
}
// Viewer to editor
if (user.role === "VIEWER" && data.data.role !== "VIEWER") {
return void changeEditRights(ctx, teamId, user, true, function (err) {
return void cb({error: err});
});
}
// Editor to viewer
if (user.role !== "VIEWER" && data.data.role === "VIEWER") {
return void changeEditRights(ctx, teamId, user, false, function (err) {
return void cb({error: err});
});
}
var obj = {};
obj[data.curvePublic] = data.data;
team.roster.describe(obj, function (err) {
@@ -952,15 +1075,6 @@ define([
});
};
// TODO send guest keys only in the future
var getInviteData = function (ctx, teamId) {
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
if (!teamData) { return {}; }
var data = Util.clone(teamData);
delete data.owner;
return data;
};
var inviteToTeam = function (ctx, data, cId, cb) {
var teamId = data.teamId;
if (!teamId) { return void cb({error: 'EINVAL'}); }
@@ -975,6 +1089,7 @@ define([
var obj = {};
obj[user.curvePublic] = user;
obj[user.curvePublic].role = 'VIEWER';
team.roster.add(obj, function (err) {
if (err && err !== 'NO_CHANGE') { return void cb({error: err}); }
ctx.store.mailbox.sendTo('INVITE_TO_TEAM', {
@@ -1100,9 +1215,21 @@ define([
if (err) { return; }
}));
// Listen for changes in our access rights (if another worker receives edit access)
ctx.store.proxy.on('change', ['teams'], function (o, n, p) {
if (p[2] !== 'hash') { return; }
updateMyRights(ctx, p[1], n);
});
ctx.store.proxy.on('remove', ['teams'], function (o, p) {
if (p[2] !== 'hash') { return; }
updateMyRights(ctx, p[1]);
});
Object.keys(teams).forEach(function (id) {
ctx.onReadyHandlers[id] = [];
openChannel(ctx, teams[id], id, waitFor(function () {
openChannel(ctx, teams[id], id, waitFor(function (err) {
if (err) { return void console.error(err); }
console.debug('Team '+id+' ready');
}));
});
@@ -1121,7 +1248,7 @@ define([
edPublic: Util.find(teams[id], ['keys', 'drive', 'edPublic']),
avatar: Util.find(teams[id], ['metadata', 'avatar'])
};
if (safe) {
if (safe && ctx.teams[id]) {
t[id].secondaryKey = ctx.teams[id].secondaryKey;
}
});
@@ -1147,6 +1274,9 @@ define([
});
};
team.changeMyRights = function (id, edit, teamData) {
changeMyRights(ctx, id, edit, teamData);
};
team.updateMyData = function (data) {
Object.keys(ctx.teams).forEach(function (id) {
var team = ctx.teams[id];