Commit 45db6c4f authored by Gilles Mouchard's avatar Gilles Mouchard
Browse files

Fixed issues with project management (update order of user roles).

Added CLI get_users for convenience.
parent 6342a4b4
Pipeline #18928 passed with stages
in 15 minutes and 59 seconds
......@@ -35,7 +35,8 @@
"items":
{
"type": "string",
"description": "role name"
"description": "role name",
"enum": [ "Owner", "Developer", "Reviewer", "Maintainer" ]
}
}
},
......
......@@ -52,7 +52,8 @@
"role":
{
"type": "string",
"description": "role name"
"description": "role name",
"enum": [ "Owner", "Developer", "Reviewer", "Maintainer" ]
}
},
"required":
......
#!/usr/bin/env bash
API=$(cd "$(dirname ${BASH_SOURCE})/.."; pwd)
node $API/cli/get_users.js "$@"
exit $?
#!/usr/bin/env bash
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"
/*
* This file is part of PKM (Persistent Knowledge Monitor).
* Copyright (c) 2020 Capgemini Group, Commissariat à l'énergie atomique et aux énergies alternatives,
* OW2, Sysgo AG, Technikon, Tree Technology, Universitat Politècnica de València.
*
* PKM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation.
*
* PKM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with PKM. If not, see <https://www.gnu.org/licenses/>.
*/
'use strict';
const PKM = require('../core/pkm');
const password_reader = require('../util/password_reader');
const parse_cmdline = require('../util/parse_cmdline.js').parse_cmdline;
function help()
{
console.log("Usage: get_users <options>");
console.log("");
console.log("Get all PKM users.");
console.log("");
console.log("See 'pkm_config.json' for default settings.");
console.log("");
console.log("Options:");
console.log("\t--help display this help and exit");
console.log("\t--version output version information and exit");
console.log("\t--user=<name> MongoDB user name");
console.log("\t--host=<host:port> MongoDB host (default: '" + PKM.global_config.db_host + "')");
console.log("\t--pkm-db=<database> MongoDB PKM management database (default: '" + PKM.global_config.pkm_db + "')");
console.log("\t--debug enable debugging messages (default: " + (PKM.global_config.debug ? "enabled" : "disabled") + ")");
console.log("");
console.log("Examples:");
console.log("");
console.log("get_users");
console.log("");
console.log("Report bugs to <" + PKM.info.contact.email + ">");
console.log("");
console.log(PKM.copyright_notice());
}
parse_cmdline(['help', 'version', 'user', 'host', 'pkm-db', 'debug!']).then((cmdline) =>
{
if(cmdline.options['help']) { help(); process.exit(0); }
if(cmdline.options['version']) { console.log(PKM.info.version); process.exit(0); }
const host = cmdline.options['host'];
const debug = cmdline.options['debug'];
const user_name = cmdline.options['user'];
const pkm_db = cmdline.options['pkm-db'];
if(!user_name) { console.log("No user specified"); help(); process.exit(1); }
if(cmdline.args.length) { console.log("No arguments after options are expected"); help(); process.exit(1); }
const config = {
db_host : host,
pkm_db : pkm_db,
debug : debug
};
const pkm_promise = password_reader.read_password(user_name + '@' + (pkm_db ? pkm_db : PKM.global_config.pkm_db) + '\'s password:').then((user_password) => PKM.login(user_name, user_password, config));
pkm_promise.then((pkm) =>
{
pkm.get_users().then((users) =>
{
console.log(JSON.stringify(users, null, 2));
pkm.close();
process.exit(0);
}).catch(function(err)
{
pkm.close();
console.error(err);
process.exit(1);
});
}).catch((err) =>
{
console.error(err);
process.exit(1);
});
}).catch((err) =>
{
// error on the command line
console.error(err);
help();
process.exit(1);
});
......@@ -63,11 +63,42 @@ function create_update_project(project, update)
// check that it is an array
if(Array.isArray(project.members))
{
// and then check that for each member, if roles exists it is an array
if(project.members.find(member => (member.roles !== undefined) && !Array.isArray(member.roles)) !== undefined)
const members = project.members;
// for each member
for(let member_idx = 0; member_idx < members.length; ++member_idx)
{
reject(this.BadRequest('project.members[].roles is not an array'));
return;
const member = members[member_idx];
// check that member has a name and it is a string
if((member.name === undefined) || (typeof member.name !== 'string'))
{
reject(this.BadRequest('some members have no name or it is not a string'));
return;
}
// check that member has at least one role or he has ownership flag set
if(Array.isArray(member.roles) && member.roles.length)
{
const roles = member.roles;
// for each role
for(let role_idx = 0; role_idx < roles.length; ++role_idx)
{
const role = roles[role_idx];
// check that role is valid
if(!this.get_role_names().includes(role))
{
reject(this.BadRequest('invalid member role'));
return;
}
}
}
else if(member.owner !== true)
{
reject(this.BadRequest('each member shall have at least one role or ownership flag'));
return;
}
}
}
else
......@@ -82,16 +113,6 @@ function create_update_project(project, update)
project.members = [];
}
if(project.members !== undefined)
{
// make sure that all members have a name
if(project.members.find(member => !member.name) !== undefined)
{
reject(this.BadRequest('some members have no name'));
return;
}
}
if(!update)
{
// if it's a project creation, make sure there's at least one member that is owner
......@@ -199,50 +220,19 @@ function create_update_project(project, update)
{
resolve(existing_project);
}
})).then((existing_project) =>
})).then((existing_project) => new Promise((resolve, reject) =>
{
if(project.members !== undefined)
{
let update_user_promises = [];
if((existing_project !== undefined) && Array.isArray(existing_project.members))
{
// revoke roles of members which are no longer in the project
existing_project.members.forEach((existing_member) =>
{
if(project.members.find((member) => member == existing_member) === undefined)
{
update_user_promises.push(new Promise((resolve, reject) =>
{
this.get_user(existing_member.name).then((user) =>
{
user.revoke(project.name);
this.update_user(user).then(() =>
{
resolve();
}).catch((err) =>
{
reject(this.Error(err));
});
}).catch((err) =>
{
reject(this.Error(err));
});
}));
}
});
}
let grant_user_promises = [];
// for each member gives corresponding role to the corresponding MongoDB user's
project.members.forEach((member) =>
{
update_user_promises.push(new Promise((resolve, reject) =>
grant_user_promises.push(new Promise((resolve, reject) =>
{
this.get_user(member.name).then((user) =>
{
user.revoke(project.name);
if(member.owner)
{
user.grant(project.name, 'Owner');
......@@ -269,20 +259,71 @@ function create_update_project(project, update)
});
}));
});
return Promise.all(update_user_promises);
Promise.all(grant_user_promises).then(() =>
{
if((existing_project !== undefined) && Array.isArray(existing_project.members))
{
let revoke_user_promises = [];
// revoke roles of members which are either no longer in the project or which role has disappeared
existing_project.members.forEach((existing_member) =>
{
this.get_user(existing_member.name).then((user) =>
{
const member = project.members.find((member) => member.name === existing_member.name);
if(member !== undefined)
{
existing_member.roles.forEach((existing_role) =>
{
if(!member.roles.includes(existing_role) || ((existing_role === 'Owner') && !member.owner))
{
user.revoke(project.name, existing_role);
}
});
}
else
{
user.revoke(project.name);
}
revoke_user_promises.push(this.update_user(user));
}).catch((err) =>
{
reject(this.Error(err));
});
});
Promise.all(revoke_user_promises).then(() =>
{
resolve();
}).catch((err) =>
{
reject(this.Error(err));
});
}
else
{
resolve();
}
}).catch((err) =>
{
reject(this.Error(err));
});
}
else
{
return Promise.resolve();
resolve();
}
}).then(() =>
})).then(() =>
{
// prepare project informations for insertion or updating in the database
if(Array.isArray(project.members))
{
for(let member_idx = 0; member_idx < project.members.length; ++member_idx)
{
const member = project.members[member_idx];
if(project.members[member_idx].roles !== undefined)
{
// roles are not stored but instead are built on demand when getting project informations
......
......@@ -31,107 +31,124 @@ function get_project(project_name)
{
const debug = this.debug;
// search for project name in project name list
this.get_documents(this.config.pkm_db, 'Projects', { name : project_name }).then(() =>
// retrieve the current user
this.get_user(this.user_name).then((user) =>
{
// retrieve project informations
this.get_documents(project_name, 'Project').then((documents) =>
// search for project name in project name list
this.get_documents(this.config.pkm_db, 'Projects', { name : project_name }).then(() =>
{
if(documents.length)
// retrieve project informations
this.get_documents(project_name, 'Project').then((documents) =>
{
let project = documents[0];
// make sure there's a member array in the project
if(project.members === undefined)
if(documents.length)
{
project.members = [];
}
new Promise((resolve, reject) =>
{
// get all users that have a role in the project
this.get_users(project_name).then((users) =>
// check if user has a role in the project or he is administrator
if(user.has_role(project_name) || user.is_admin())
{
// build member role and ownership from user's roles
users.forEach((user) =>
let project = documents[0];
// make sure there's a member array in the project
if(project.members === undefined)
{
let member_idx = project.members.findIndex(member => member.name == user.name);
let member = (member_idx == -1) ? { name : user.name } : project.members[member_idx];
member.owner = user.has_role(project_name, 'Owner');
member.roles = user.roles.filter(user_role => user_role.db == project_name).map(user_role => user_role.role);
if(member_idx == -1)
{
project.members.push(member);
}
else
{
project.members[member_idx] = member;
}
});
project.members = [];
}
resolve();
}).catch((err) =>
{
console.warn(err);
reject(this.Error(err));
});
}).then(() =>
{
let promises = [];
// get some project metadata from collections
let project_metadata = Object.keys(this.config.project);
project_metadata.forEach((metadata) =>
{
const get = this.config.project[metadata].get;
if((typeof get === 'string') && (typeof this[get] === 'function'))
new Promise((resolve, reject) =>
{
promises.push(new Promise((resolve, reject) =>
// get all users that have a role in the project
this.get_users(project_name).then((users) =>
{
this[get](project.name).then((metadata_value) =>
{
// add metadata to the project
project[metadata] = metadata_value;
resolve();
}).catch((err) =>
// build member role and ownership from user's roles
users.forEach((user) =>
{
const error = require('./error');
if(err instanceof error.PKM_NotFound)
let member_idx = project.members.findIndex(member => member.name == user.name);
let member = (member_idx == -1) ? { name : user.name } : project.members[member_idx];
member.owner = user.has_role(project_name, 'Owner');
member.roles = user.roles.filter(user_role => user_role.db == project_name).map(user_role => user_role.role);
if(member_idx == -1)
{
resolve();
project.members.push(member);
}
else
{
reject(this.Error(err));
project.members[member_idx] = member;
}
});
}));
}
});
return Promise.all(promises);
}).then(() =>
{
if(debug)
resolve();
}).catch((err) =>
{
console.warn(err);
reject(this.Error(err));
});
}).then(() =>
{
let promises = [];
// get some project metadata from collections
let project_metadata = Object.keys(this.config.project);
project_metadata.forEach((metadata) =>
{
const get = this.config.project[metadata].get;
if((typeof get === 'string') && (typeof this[get] === 'function'))
{
promises.push(new Promise((resolve, reject) =>
{
this[get](project.name).then((metadata_value) =>
{
// add metadata to the project
project[metadata] = metadata_value;
resolve();
}).catch((err) =>
{
const error = require('./error');
if(err instanceof error.PKM_NotFound)
{
resolve();
}
else
{
reject(this.Error(err));
}
});
}));
}
});
return Promise.all(promises);
}).then(() =>
{
if(debug)
{
const util = require('util');
console.log('get_project(\'' + project_name + '\') => ', util.inspect(project, { showHidden: false, depth: null }));
}
// serve the project informations
resolve(project);
}).catch((err) =>
{
reject(this.Error(err));
});
}
else
{
const util = require('util');
console.log('get_project(\'' + project_name + '\') => ', util.inspect(project, { showHidden: false, depth: null }));
// user has no role in the project and he is not an administrator: show only the project name
resolve({ name : project_name });
}
// serve the project informations
resolve(project);
}).catch((err) =>
}
else
{
reject(this.Error(err));
});
}
else
reject(this.NotFound());
}
}).catch((err) =>
{
reject(this.NotFound());
}
console.warn(err);
reject(this.Error(err));
});
}).catch((err) =>
{
console.warn(err);
reject(this.Error(err));
});
}).catch((err) =>
......
......@@ -43,10 +43,10 @@ function get_projects()
{
get_project_promises.push(new Promise((resolve, reject) =>
{
// check if user has a role in the project or is administrator
// check if user has a role in the project or he is administrator
if(user.has_role(project_name) || user.is_admin())
{
// user has a role in the project: retrieve the project information
// user has a role in the project or it is an administrator: retrieve the project information
this.get_project(project_name).then((project) =>
{
resolve(project);
......@@ -57,7 +57,7 @@ function get_projects()
}
else
{
// user has no role in the project: show only the project name
// user has no role in the project and he is not an administrator: show only the project name
resolve({ name : project_name });
}
}));
......
......@@ -22,17 +22,17 @@
* @memberof PKM
* @instance
* @param {PkmUser} user - a user
* @param {Array.<string>} [project_names] - project names
* @param {string} [project_name] - project name
*
* @return {Promise.<Array.<PkmProject>>} a promise
*/
function get_user_projects(user, project_names)
function get_user_projects(user, project_name)
{
return new Promise(function(resolve, reject)
{
const debug = this.debug;
if(project_names === undefined)
if(project_name === undefined)
{
// get all user's projects
......@@ -52,36 +52,21 @@ function get_user_projects(user, project_names)
}
else
{
// get user's projects by name
let get_project_promises = [];
project_names.forEach((project_name) =>
if(user.has_role(project_name))
{
get_project_promises.push(new Promise((resolve, reject) =>
// get project
this.get_project(project_name).then((project) =>
{
this.get_project(project_name).then((project) =>
{
resolve(project);
}).catch((err) =>
{
reject(this.Error(err));
});
}));
});
Promise.all(get_project_promises).then((user_projects) =>
{
if(user_projects.length)
{
resolve(user_projects);
}
else
resolve([ project ]);
}).catch((err) =>
{
reject(this.NotFound());
}
}).catch((err) =>
reject(this.Error(err));
});