Upgrade/downgrade shared folders access rights
This commit is contained in:
parent
8ca7e11150
commit
d443c93893
@ -158,11 +158,12 @@ define([
|
||||
});
|
||||
};
|
||||
common.addSharedFolder = function (teamId, secret, cb) {
|
||||
var href = secret.keys && secret.keys.editKeyStr ? '/drive/#' + Hash.getEditHashFromKeys(secret) : undefined;
|
||||
postMessage("ADD_SHARED_FOLDER", {
|
||||
teamId: teamId,
|
||||
path: ['root'],
|
||||
folderData: {
|
||||
href: '/drive/#' + Hash.getEditHashFromKeys(secret),
|
||||
href: href,
|
||||
roHref: '/drive/#' + Hash.getViewHashFromKeys(secret),
|
||||
channel: secret.channel,
|
||||
password: secret.password,
|
||||
|
||||
@ -544,7 +544,7 @@ define([
|
||||
Object.keys(folders).forEach(function (id) {
|
||||
var f = folders[id];
|
||||
var sfData = files.sharedFolders[id] || {};
|
||||
var parsed = Hash.parsePadUrl(sfData.href);
|
||||
var parsed = Hash.parsePadUrl(sfData.href || sfData.roHref);
|
||||
var secret = Hash.getSecrets('drive', parsed.hash, sfData.password);
|
||||
manager.addProxy(id, {proxy: f}, null, secret.keys.secondaryKey);
|
||||
});
|
||||
@ -2509,6 +2509,7 @@ define([
|
||||
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
|
||||
var data = manager.getSharedFolderData(id);
|
||||
var parsed = Hash.parsePadUrl(data.href);
|
||||
// XXX share modal shared folder read only
|
||||
if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); }
|
||||
var friends = common.getFriends();
|
||||
var teams = common.getMetadataMgr().getPrivateData().teams;
|
||||
|
||||
@ -27,6 +27,7 @@ define([
|
||||
if (parsed) {
|
||||
var proxy = proxyData.proxy;
|
||||
var oldFo = FO.init(parsed.drive, {
|
||||
readOnly: false,
|
||||
loggedIn: true,
|
||||
outer: true
|
||||
});
|
||||
|
||||
@ -43,7 +43,7 @@ define([
|
||||
MessengerUI.create = function ($container, common, toolbar) {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var origin = metadataMgr.getPrivateData().origin;
|
||||
var readOnly = metadataMgr.getPrivateData().readOnly;
|
||||
var readOnly = metadataMgr.getPrivateData().readOnly || toolbar.readOnly;
|
||||
|
||||
var isApp = typeof(toolbar) !== "undefined";
|
||||
|
||||
|
||||
@ -415,6 +415,41 @@ define([
|
||||
cb(false);
|
||||
};
|
||||
|
||||
handlers['TEAM_EDIT_RIGHTS'] = function (ctx, box, data, cb) {
|
||||
var msg = data.msg;
|
||||
var content = msg.content;
|
||||
|
||||
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
||||
if (!content.teamData) {
|
||||
console.log('Remove invalid notification');
|
||||
return void cb(true);
|
||||
}
|
||||
|
||||
// Make sure we are a member of this team
|
||||
var myTeams = Util.find(ctx, ['store', 'proxy', 'teams']) || {};
|
||||
var teamId;
|
||||
var team;
|
||||
Object.keys(myTeams).some(function (k) {
|
||||
var _team = myTeams[k];
|
||||
if (_team.channel === content.teamChannel) {
|
||||
teamId = k;
|
||||
team = _team;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!teamId) { return void cb(true); }
|
||||
|
||||
var dismiss = false;
|
||||
try {
|
||||
var module = ctx.store.modules['team'];
|
||||
// changeMyRights returns true if we can't change our rights
|
||||
dismiss = module.changeMyRights(teamId, content.state, content.teamData);
|
||||
} catch (e) { console.error(e); }
|
||||
|
||||
cb(dismiss);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return {
|
||||
add: function (ctx, box, data, cb) {
|
||||
|
||||
@ -814,6 +814,7 @@ define([
|
||||
var cb = Util.once(Util.mkAsync(function () {
|
||||
ctx.emit('TEAMCHAT_READY', chanId, [clientId]);
|
||||
_cb({
|
||||
readOnly: typeof(secret.keys) === "object" && !secret.keys.validateKey,
|
||||
channel: chanId
|
||||
});
|
||||
}));
|
||||
|
||||
@ -168,8 +168,8 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
if (members[curve]) { throw new Error("ALREADY_PRESENT"); }
|
||||
|
||||
var data = args[curve];
|
||||
// if no role was provided, assume VIEWER
|
||||
if (typeof(data.role) !== 'string') { data.role = 'VIEWER'; }
|
||||
// if no role was provided, assume MEMBER
|
||||
if (typeof(data.role) !== 'string') { data.role = 'MEMBER'; }
|
||||
|
||||
if (!canAddRole(author, data.role, members)) {
|
||||
throw new Error("INSUFFICIENT_PERMISSIONS");
|
||||
|
||||
@ -77,6 +77,10 @@ define([
|
||||
var secondaryKey = secret.keys.secondaryKey;
|
||||
|
||||
var sf = allSharedFolders[secret.channel];
|
||||
if (sf && sf.readOnly && secondaryKey) {
|
||||
// We were in readOnly mode and now we know the edit keys!
|
||||
SF.upgrade(secret.channel, secret);
|
||||
}
|
||||
if (sf && sf.ready && sf.rt) {
|
||||
// The shared folder is already loaded, return its data
|
||||
setTimeout(function () {
|
||||
@ -108,14 +112,15 @@ define([
|
||||
store: store,
|
||||
id: id
|
||||
}],
|
||||
team: [store.id || -1]
|
||||
team: [store.id || -1],
|
||||
readOnly: Boolean(secondaryKey)
|
||||
};
|
||||
|
||||
var owners = data.owners;
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
channel: secret.channel,
|
||||
readOnly: secret.keys && !secret.keys.editKeyStr,
|
||||
readOnly: Boolean(secondaryKey),
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
userName: 'sharedFolder',
|
||||
logLevel: 1,
|
||||
@ -148,6 +153,17 @@ define([
|
||||
return rt;
|
||||
};
|
||||
|
||||
SF.upgrade = function (channel, secret) {
|
||||
var sf = allSharedFolders[channel];
|
||||
if (!sf || !sf.readOnly) { return; }
|
||||
if (!sf.rt.setReadOnly) { return; }
|
||||
|
||||
if (!secret.keys || !secret.keys.editKeyStr) { return; }
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
sf.readOnly = false;
|
||||
sf.rt.setReadOnly(false, crypto);
|
||||
};
|
||||
|
||||
SF.leave = function (channel, teamId) {
|
||||
var sf = allSharedFolders[channel];
|
||||
if (!sf) { return; }
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -21,6 +21,8 @@ define([
|
||||
var sharedFolder = config.sharedFolder;
|
||||
var edPublic = config.edPublic;
|
||||
|
||||
var readOnly = config.readOnly;
|
||||
|
||||
var ROOT = exp.ROOT;
|
||||
var FILES_DATA = exp.FILES_DATA;
|
||||
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
|
||||
@ -31,8 +33,14 @@ define([
|
||||
|
||||
var debug = exp.debug;
|
||||
|
||||
exp._setReadOnly = function (state) {
|
||||
readOnly = state;
|
||||
if (!readOnly) { exp.fixFiles(); }
|
||||
};
|
||||
|
||||
exp.setHref = function (channel, id, href) {
|
||||
if (!id && !channel) { return; }
|
||||
if (readOnly) { return; }
|
||||
var ids = id ? [id] : exp.findChannels([channel]);
|
||||
ids.forEach(function (i) {
|
||||
var data = exp.getFileData(i, true);
|
||||
@ -42,6 +50,7 @@ define([
|
||||
|
||||
exp.setPadAttribute = function (href, attr, value, cb) {
|
||||
cb = cb || function () {};
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var id = exp.getIdFromHref(href);
|
||||
if (!id) { return void cb("E_INVAL_HREF"); }
|
||||
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
|
||||
@ -63,6 +72,7 @@ define([
|
||||
|
||||
exp.pushData = function (data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
var id = Util.createRandomInteger();
|
||||
// If we were given an edit link, encrypt its value if needed
|
||||
if (data.href) { data.href = exp.cryptor.encrypt(data.href); }
|
||||
@ -72,12 +82,21 @@ define([
|
||||
|
||||
exp.pushSharedFolder = function (data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
|
||||
// Check if we already have this shared folder in our drive
|
||||
var exists;
|
||||
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
|
||||
return files[SHARED_FOLDERS][k].channel === data.channel;
|
||||
if (files[SHARED_FOLDERS][k].channel === data.channel) {
|
||||
// We already know this shared folder. Check if we can get better access rights
|
||||
if (data.href && !files[SHARED_FOLDERS][k].href) {
|
||||
files[SHARED_FOLDERS][k].href = data.href;
|
||||
}
|
||||
exists = k;
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
return void cb ('EEXISTS');
|
||||
return void cb ('EEXISTS', exists);
|
||||
}
|
||||
|
||||
// Add the folder
|
||||
@ -92,6 +111,7 @@ define([
|
||||
|
||||
// FILES DATA
|
||||
var spliceFileData = function (id) {
|
||||
if (readOnly) { return; }
|
||||
delete files[FILES_DATA][id];
|
||||
};
|
||||
|
||||
@ -99,6 +119,7 @@ define([
|
||||
// FILES_DATA. If there are owned pads, remove them from server too.
|
||||
exp.checkDeletedFiles = function (cb) {
|
||||
if (!loggedIn && !config.testMode) { return void cb(); }
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
|
||||
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
var toClean = [];
|
||||
@ -144,21 +165,22 @@ define([
|
||||
cb(null, toClean, ownedRemoved);
|
||||
};
|
||||
var deleteHrefs = function (ids) {
|
||||
if (readOnly) { return; }
|
||||
ids.forEach(function (obj) {
|
||||
var idx = files[obj.root].indexOf(obj.id);
|
||||
files[obj.root].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultipleTrashRoot = function (roots) {
|
||||
if (readOnly) { return; }
|
||||
roots.forEach(function (obj) {
|
||||
var idx = files[TRASH][obj.name].indexOf(obj.el);
|
||||
files[TRASH][obj.name].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
|
||||
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
|
||||
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
|
||||
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
|
||||
if (readOnly) { return void cb('EFORBIDDEN'); }
|
||||
|
||||
var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
|
||||
|
||||
if (!loggedIn && !config.testMode) {
|
||||
@ -170,6 +192,10 @@ define([
|
||||
return void cb();
|
||||
}
|
||||
|
||||
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
|
||||
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
|
||||
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
|
||||
|
||||
var ids = [];
|
||||
hrefPaths.forEach(function (path) {
|
||||
var id = exp.find(path);
|
||||
@ -216,6 +242,7 @@ define([
|
||||
|
||||
// From another drive
|
||||
exp.copyFromOtherDrive = function (path, element, data, key) {
|
||||
if (readOnly) { return; }
|
||||
// Copy files data
|
||||
// We have to remove pads that are already in the current proxy to make sure
|
||||
// we won't create duplicates
|
||||
@ -275,6 +302,8 @@ define([
|
||||
|
||||
// From the same drive
|
||||
var pushToTrash = function (name, element, path) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var trash = files[TRASH];
|
||||
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
|
||||
var trashArray = trash[name];
|
||||
@ -285,6 +314,7 @@ define([
|
||||
trashArray.push(trashElement);
|
||||
};
|
||||
exp.copyElement = function (elementPath, newParentPath) {
|
||||
if (readOnly) { return; }
|
||||
if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
|
||||
var element = exp.find(elementPath);
|
||||
var newParent = exp.find(newParentPath);
|
||||
@ -332,6 +362,8 @@ define([
|
||||
|
||||
// FORGET (move with href not path)
|
||||
exp.forget = function (href) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var id = exp.getIdFromHref(href);
|
||||
if (!id) { return; }
|
||||
if (!loggedIn && !config.testMode) {
|
||||
@ -348,6 +380,8 @@ define([
|
||||
// If all the occurences of an href are in the trash, remove them and add the file in root.
|
||||
// This is use with setPadTitle when we open a stronger version of a deleted pad
|
||||
exp.restoreHref = function (href) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var idO = exp.getIdFromHref(href);
|
||||
|
||||
if (!idO || !exp.isFile(idO)) { return; }
|
||||
@ -370,6 +404,8 @@ define([
|
||||
};
|
||||
|
||||
exp.add = function (id, path) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
if (!loggedIn && !config.testMode) { return; }
|
||||
id = Number(id);
|
||||
var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
|
||||
@ -397,6 +433,8 @@ define([
|
||||
};
|
||||
|
||||
exp.setFolderData = function (path, key, value, cb) {
|
||||
if (readOnly) { return; }
|
||||
|
||||
var folder = exp.find(path);
|
||||
if (!exp.isFolder(folder) || exp.isSharedFolder(folder)) { return; }
|
||||
if (!exp.hasFolderData(folder)) {
|
||||
@ -423,7 +461,7 @@ define([
|
||||
};
|
||||
|
||||
exp.migrateReadOnly = function (cb) {
|
||||
if (!config.editKey) { return void cb({error: 'EFORBIDDEN'}); }
|
||||
if (readOnly || !config.editKey) { return void cb({error: 'EFORBIDDEN'}); }
|
||||
if (files.version >= 2) { return void cb(); } // Already migrated, nothing to do
|
||||
files.migrateRo = 1;
|
||||
var next = function () {
|
||||
@ -453,6 +491,7 @@ define([
|
||||
};
|
||||
|
||||
exp.migrate = function (cb) {
|
||||
if (readOnly) { return void cb(); }
|
||||
// Make sure unsorted doesn't exist anymore
|
||||
// Note: Unsorted only works with the old structure where pads are href
|
||||
// It should be called before the migration code
|
||||
@ -551,6 +590,9 @@ define([
|
||||
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
|
||||
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
|
||||
|
||||
// We can't fix anything in read-only mode: abort
|
||||
if (readOnly) { return; }
|
||||
|
||||
if (silent) { debug = function () {}; }
|
||||
|
||||
var t0 = +new Date();
|
||||
|
||||
@ -2,9 +2,10 @@ define([
|
||||
'/common/userObject.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/outer/sharedfolder.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function (UserObject, Util, Hash, Messages, nThen) {
|
||||
], function (UserObject, Util, Hash, SF, Messages, nThen) {
|
||||
|
||||
|
||||
var getConfig = function (Env) {
|
||||
@ -20,6 +21,7 @@ define([
|
||||
cfg.id = id;
|
||||
cfg.editKey = editKey;
|
||||
cfg.rt = lm.realtime;
|
||||
cfg.readOnly = Boolean(!editKey);
|
||||
var userObject = UserObject.init(lm.proxy, cfg);
|
||||
if (userObject.fixFiles) {
|
||||
// Only in outer
|
||||
@ -452,6 +454,12 @@ define([
|
||||
// 1. add the shared folder to our list of shared folders
|
||||
// NOTE: pushSharedFolder will encrypt the href directly in the object if needed
|
||||
Env.user.userObject.pushSharedFolder(folderData, waitFor(function (err, folderId) {
|
||||
if (err === "EEXISTS" && folderData.href && folderId) {
|
||||
var parsed = Hash.parsePadUrl(folderData.href);
|
||||
var secret = Hash.getSecrets('drive', parsed.hash, folderData.password);
|
||||
SF.upgrade(secret.channel, secret);
|
||||
Env.folders[folderId].userObject.setReadOnly(false, secret.keys.secondaryKey);
|
||||
}
|
||||
if (err) {
|
||||
waitFor.abort();
|
||||
return void cb(err);
|
||||
|
||||
@ -32,13 +32,15 @@ define([
|
||||
module.init = function (files, config) {
|
||||
var exp = {};
|
||||
|
||||
exp.cryptor = {
|
||||
encrypt : function (x) { return x; },
|
||||
decrypt : function (x) { return x; },
|
||||
};
|
||||
if (config.editKey) {
|
||||
exp.cryptor = {};
|
||||
var createCryptor = function (key) {
|
||||
if (!key) {
|
||||
exp.cryptor.encrypt = function (x) { return x; };
|
||||
exp.cryptor.decrypt = function (x) { return x; };
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var c = Crypto.createEncryptor(config.editKey);
|
||||
var c = Crypto.createEncryptor(key);
|
||||
exp.cryptor.encrypt = function (href) {
|
||||
// Never encrypt blob href, they are always read-only
|
||||
if (href.slice(0,7) === '/file/#') { return href; }
|
||||
@ -48,7 +50,17 @@ define([
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
createCryptor(config.editKey);
|
||||
|
||||
exp.setReadOnly = function (state, key) {
|
||||
config.editKey = key;
|
||||
createCryptor(key);
|
||||
if (exp._setReadOnly) {
|
||||
// Change outer
|
||||
exp._setReadOnly(state);
|
||||
}
|
||||
};
|
||||
|
||||
exp.getDefaultName = module.getDefaultName;
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ define([
|
||||
nThen(function (waitFor) {
|
||||
Object.keys(drive.sharedFolders).forEach(function (fId) {
|
||||
var sfData = drive.sharedFolders[fId] || {};
|
||||
var parsed = Hash.parsePadUrl(sfData.href);
|
||||
var parsed = Hash.parsePadUrl(sfData.href || sfData.roHref);
|
||||
var secret = Hash.getSecrets('drive', parsed.hash, sfData.password);
|
||||
sframeChan.query('Q_DRIVE_GETOBJECT', {
|
||||
sharedFolder: fId
|
||||
@ -54,6 +54,9 @@ define([
|
||||
if (manager && oldIds.indexOf(fId) === -1) {
|
||||
manager.addProxy(fId, { proxy: folders[fId] }, null, secret.keys.secondaryKey);
|
||||
}
|
||||
var readOnly = !secret.keys.editKeyStr;
|
||||
if (!manager || !manager.folders[fId]) { return; }
|
||||
manager.folders[fId].userObject.setReadOnly(readOnly, secret.keys.secondaryKey);
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
|
||||
@ -53,7 +53,7 @@ define([
|
||||
nThen(function (waitFor) {
|
||||
Object.keys(drive.sharedFolders).forEach(function (fId) {
|
||||
var sfData = drive.sharedFolders[fId] || {};
|
||||
var parsed = Hash.parsePadUrl(sfData.href);
|
||||
var parsed = Hash.parsePadUrl(sfData.href || sfData.roHref);
|
||||
var secret = Hash.getSecrets('drive', parsed.hash, sfData.password);
|
||||
sframeChan.query('Q_DRIVE_GETOBJECT', {
|
||||
sharedFolder: fId
|
||||
@ -467,7 +467,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var ROLES = ['MEMBER', 'ADMIN', 'OWNER'];
|
||||
var ROLES = ['VIEWER', 'MEMBER', 'ADMIN', 'OWNER'];
|
||||
var describeUser = function (common, curvePublic, data, icon) {
|
||||
APP.module.execCommand('DESCRIBE_USER', {
|
||||
teamId: APP.team,
|
||||
@ -505,10 +505,11 @@ define([
|
||||
var actions = h('span.cp-team-member-actions');
|
||||
var $actions = $(actions);
|
||||
var isMe = me && me.curvePublic === data.curvePublic;
|
||||
var myRole = me ? (ROLES.indexOf(me.role) || 0) : -1;
|
||||
var theirRole = ROLES.indexOf(data.role) || 0;
|
||||
var myRole = me ? (ROLES.indexOf(me.role) || 1) : -1;
|
||||
var theirRole = ROLES.indexOf(data.role) || 1;
|
||||
var ADMIN = ROLES.indexOf('ADMIN');
|
||||
// If they're an admin and I am an owner, I can promote them to owner
|
||||
if (!isMe && myRole > theirRole && theirRole === 1 && !data.pending) {
|
||||
if (!isMe && myRole > theirRole && theirRole === ADMIN && !data.pending) {
|
||||
var promoteOwner = h('span.fa.fa-angle-double-up', {
|
||||
title: Messages.team_rosterPromoteOwner
|
||||
});
|
||||
@ -530,28 +531,28 @@ define([
|
||||
});
|
||||
$actions.append(promoteOwner);
|
||||
}
|
||||
// If they're a member and I have a higher role than them, I can promote them to admin
|
||||
if (!isMe && myRole > theirRole && theirRole === 0 && !data.pending) {
|
||||
// If they're a viewer/member and I have a higher role than them, I can promote them to admin
|
||||
if (!isMe && myRole >= ADMIN && theirRole < ADMIN && !data.pending) {
|
||||
var promote = h('span.fa.fa-angle-double-up', {
|
||||
title: Messages.team_rosterPromote
|
||||
});
|
||||
$(promote).click(function () {
|
||||
$(promote).hide();
|
||||
describeUser(common, data.curvePublic, {
|
||||
role: 'ADMIN'
|
||||
role: ROLES[theirRole + 1]
|
||||
}, promote);
|
||||
});
|
||||
$actions.append(promote);
|
||||
}
|
||||
// If I'm not a member and I have an equal or higher role than them, I can demote them
|
||||
// (if they're not already a MEMBER)
|
||||
if (myRole >= theirRole && theirRole > 0 && !data.pending) {
|
||||
if (myRole >= theirRole && myRole >= ADMIN && theirRole > 0 && !data.pending) {
|
||||
var demote = h('span.fa.fa-angle-double-down', {
|
||||
title: Messages.team_rosterDemote
|
||||
});
|
||||
$(demote).click(function () {
|
||||
var todo = function () {
|
||||
var role = ROLES[theirRole - 1] || 'MEMBER';
|
||||
var role = ROLES[theirRole - 1] || 'VIEWER';
|
||||
$(demote).hide();
|
||||
describeUser(common, data.curvePublic, {
|
||||
role: role
|
||||
@ -569,9 +570,9 @@ define([
|
||||
$actions.append(demote);
|
||||
}
|
||||
}
|
||||
// If I'm not a member and I have an equal or higher role than them, I can remove them
|
||||
// If I'm at least an admin and I have an equal or higher role than them, I can remove them
|
||||
// Note: we can't remove owners, we have to demote them first
|
||||
if (!isMe && myRole > 0 && myRole >= theirRole && theirRole !== 2) {
|
||||
if (!isMe && myRole >= ADMIN && myRole >= theirRole && theirRole !== ROLES.indexOf('OWNER')) {
|
||||
var remove = h('span.fa.fa-times', {
|
||||
title: Messages.team_rosterKick
|
||||
});
|
||||
@ -637,6 +638,12 @@ define([
|
||||
}).map(function (k) {
|
||||
return makeMember(common, roster[k], me);
|
||||
});
|
||||
var viewers = Object.keys(roster).filter(function (k) {
|
||||
if (roster[k].pending) { return; }
|
||||
return roster[k].role === "VIEWER";
|
||||
}).map(function (k) {
|
||||
return makeMember(common, roster[k], me);
|
||||
});
|
||||
var pending = Object.keys(roster).filter(function (k) {
|
||||
if (!roster[k].pending) { return; }
|
||||
return roster[k].role === "MEMBER" || !roster[k].role;
|
||||
@ -671,7 +678,7 @@ define([
|
||||
$header.append(invite);
|
||||
}
|
||||
|
||||
if (me && (me.role === 'ADMIN' || me.role === 'MEMBER')) {
|
||||
if (me && (me.role !== 'OWNER')) {
|
||||
var leave = h('button.btn.btn-danger', Messages.team_leaveButton);
|
||||
$(leave).click(function () {
|
||||
UI.confirm(Messages.team_leaveConfirm, function (yes) {
|
||||
@ -698,6 +705,8 @@ define([
|
||||
h('div', admins),
|
||||
h('h3', Messages.team_members),
|
||||
h('div', members),
|
||||
h('h3', Messages.team_viewers || 'VIEWERS'), // XXX
|
||||
h('div', viewers),
|
||||
h('h3'+noPending, Messages.team_pending),
|
||||
h('div'+noPending, pending)
|
||||
];
|
||||
@ -721,7 +730,8 @@ define([
|
||||
common.setTeamChat(obj.channel);
|
||||
MessengerUI.create($(container), common, {
|
||||
chat: $('.cp-team-cat-chat'),
|
||||
team: true
|
||||
team: true,
|
||||
readOnly: obj.readOnly
|
||||
});
|
||||
cb(content);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user