Skip to content
Snippets Groups Projects
Commit 82b20064 authored by Quentin Bramas's avatar Quentin Bramas
Browse files

add delete users command line and verbose mode

parent 79c4e0d5
Branches
Tags
No related merge requests found
"""
full_help = """
admin.py
This script is used to manage and interact with the server using socket.io for real-time communication.
......@@ -15,12 +15,20 @@ Dependencies:
pip install "python-socketio[client]"
Usage:
- To create users: python admin.py create-users USER1,USER2,USER3
- To create users:
python admin.py create-users USER1,USER2,USER3
or
python admin.py create-users -f <file>
- To delete users:
python admin.py delete-users USER1,USER2,USER3
or
python admin.py delete-users -f <file>
- To list users: python admin.py list-users
- To list classes: python admin.py list-classes
- To export results: python admin.py export-results
Options:
- -v : print more information
- --url-to <KEY>: Print the connection URL to a particular exam when listing users.
- --front-end-url <URL>: Set the front end URL when listing users.
......@@ -56,10 +64,12 @@ usage = '''Usage: python3 admin.py <action>
action can be
- list-users
- create-users USER1,USER2,USER3
- create-users [USER1,USER2,USER3] [-f <file>]
- delete-users [USER1,USER2,USER3] [-f <file>]
- create-super-user USERNAME
- list-classes
- export-results CLASS_KEY
- help <------------ to display the full help
'''
if len(sys.argv) < 2:
......@@ -67,44 +77,58 @@ if len(sys.argv) < 2:
exit(1)
action = sys.argv[1]
if action == 'help':
print(full_help)
exit(0)
if action not in ['list-users', 'create-users', 'create-super-user', 'list-classes', 'export-results']:
if action not in ['list-users', 'create-users', 'create-super-user', 'list-classes', 'export-results', 'delete-users', 'help']:
print('Invalid action.\n'+usage, file=sys.stderr)
exit(1)
urlTo = None
frontEndUrl = None
verbose = False
# retrieve the optional arguments (--url-to, --front-end-url)
for i in range(2, len(sys.argv)-1):
if sys.argv[i] == '--url-to':
urlTo = sys.argv[i+1]
elif sys.argv[i] == '--front-end-url':
frontEndUrl = sys.argv[i+1]
for i in range(2, len(sys.argv)):
if sys.argv[i] == '-v':
verbose = True
if sys.argv[i-1] == '--url-to':
urlTo = sys.argv[i]
elif sys.argv[i-1] == '--front-end-url':
frontEndUrl = sys.argv[i]
elif sys.argv[i] == '--help':
print(usage)
print(full_help)
exit(0)
elif sys.argv[i].startswith('--'):
print('Invalid option: '+sys.argv[i], file=sys.stderr)
exit(1)
def printv(*args):
global verbose
if verbose:
print(*args, file=sys.stderr)
sio = socketio.Client()
@sio.event
def connect():
print('connection established', file=sys.stderr)
printv('connection established')
@sio.on('sudo-result')
def sudo_result(data):
global urlTo, frontEndUrl
print('OK', file=sys.stderr)
printv('OK')
if action == 'list-users':
if urlTo is None:
print('You can print the connection url to a particular exam with --url-to <KEY>', file=sys.stderr)
printv('You can print the connection url to a particular exam with --url-to <KEY>')
if frontEndUrl is None:
print('You can customize the front end url with --front-end-url <URL>', file=sys.stderr)
printv('You can customize the front end url with --front-end-url <URL>')
frontEndUrl = API_URL
# TODO customize front end url
......@@ -117,6 +141,9 @@ def sudo_result(data):
elif action == 'list-classes':
for c in data:
print(c['info']['classKey'], c['created'], str(len(c['info']['answers'].keys()))+' answers')
elif action == 'delete-users':
print(data)
elif action == 'export-results':
# print csv
print('username,started,answers')
......@@ -143,22 +170,43 @@ def sudo_error():
sio.disconnect()
sys.exit(1)
users_to_delete = []
@sio.on('sudo-ask-confirm')
def sudo_ask_confirm(data):
print(data)
answer = ''
while answer not in ['yes', 'no']:
answer = input('confirm deletion? (yes/no): ')
if answer == 'yes':
sio.emit('sudo-confirm-delete', users_to_delete)
else:
sio.emit('sudo-confirm-delete', False)
@sio.event
def disconnect():
print('disconnected from server', file=sys.stderr)
printv('disconnected from server')
@sio.on('connectedAs')
def connected_as(data):
print('connected as', data['user']['username'], data['user']['role'], file=sys.stderr)
printv('connected as '+data['user']['username']+' with role:'+data['user']['role'])
if action == 'list-users':
sio.emit('sudo', 'list-users')
elif action == 'create-users':
if len(sys.argv) < 3:
print('Usage: python3 admin.py create-users <users>', file=sys.stderr)
print('Usage: python3 admin.py create-users <users> or -f <file>', file=sys.stderr)
sio.disconnect()
exit(1)
users = sys.argv[2].split(',')
sio.emit('sudo', ('create-users', users))
elif action == 'delete-users':
if len(sys.argv) < 3:
print('Usage: python3 admin.py delete-users <users> or -f <file>', file=sys.stderr)
sio.disconnect()
exit(1)
global users_to_delete
users_to_delete = sys.argv[2].split(',')
sio.emit('sudo', ('delete-users', users_to_delete))
elif action == 'create-super-user':
if len(sys.argv) < 3:
print('Usage: python3 admin.py create-super-user <username>', file=sys.stderr)
......@@ -174,7 +222,7 @@ def connected_as(data):
sio.connect(API_URL, transports=['websocket'], namespaces=['/'], wait_timeout = 4)
print('login user '+os.environ['QCM_USER_NAME']+'...', file=sys.stderr)
printv('login user '+os.environ['QCM_USER_NAME']+'...')
sio.emit('verify', os.environ['QCM_USER_NAME']+':'+os.environ['QCM_USER_TOKEN'])
sio.wait()
......@@ -259,6 +259,7 @@ io.on('connection', function (socket) {
socket = {
disconnect: socket.disconnect.bind(socket),
on: socket.on.bind(socket),
once: socket.once.bind(socket),
emit: socket.emit.bind(socket),
removeListener: socket.removeListener.bind(socket),
__socket: socket,
......@@ -464,6 +465,50 @@ io.on('connection', function (socket) {
}
socket.emit('sudo-result', classe[0].info);
}
if (cmd === 'delete-users') {
if (!options || options.length === 0) {
socket.emit('sudo-error', 'no names given');
return;
}
const toBeDeleted = [];
let nb_delete = 0;
for (let name of options) {
if (!name.startsWith(socket.user.name + '_')) {
socket.emit('sudo-error', 'you are not allowed to delete user ' + name);
return;
}
toBeDeleted.push(name);
let user = await db.Users.get({ username: name });
if (user) {
nb_delete++;
}
}
socket.emit('sudo-ask-confirm', 'This will delete ' + nb_delete + ' users. Are you sure?');
socket.once('sudo-confirm-delete', async (names_check) => {
if (names_check === false) {
socket.emit('sudo-result', 'deletion cancelled');
return;
}
// check if the names are the same
for (let name of names_check) {
if (!toBeDeleted.includes(name)) {
socket.emit('sudo-error', 'names do not match ' + name + ' not in previous list');
return;
}
}
for (let name of toBeDeleted) {
if (!names_check.includes(name)) {
socket.emit('sudo-error', 'names do not match ' + name + ' not in new list');
return;
}
}
for (let name of toBeDeleted) {
await db.Users.delete({ username: name });
}
socket.emit('sudo-result', 'deleted');
})
return;
}
});
......
......@@ -5,9 +5,9 @@ import { makeid, makeSecureId } from './util.js';
import Class from './class.js';
const dbPromise = open({
filename: './storage/db.sqlite3',
driver: sqlite3.Database
});
filename: './storage/db.sqlite3',
driver: sqlite3.Database
});
let db = null;
......@@ -16,142 +16,132 @@ const classesCache = {};
const self = {
init: async () => {
db = await dbPromise;
await db.migrate({migrationsPath:'./server/migrations'});
init: async () => {
db = await dbPromise;
await db.migrate({ migrationsPath: './server/migrations' });
},
Classes: {
getAll: async ({ owner_id }) => {
let l = await db.all('SELECT * FROM classes WHERE owner_id = ?', [owner_id]);
return l.map(r => new Class(r)).map(c => ({
created: c.created,
info: c.info,
}));
},
Classes: {
getAll: async ({owner_id}) => {
let l = await db.all('SELECT * FROM classes WHERE owner_id = ?', [owner_id]);
return l.map(r => new Class(r)).map(c => ({
created: c.created,
info: c.info,
}));
},
get: async (class_key, owner_id) => {
if(owner_id)
{
console.log('DEPRECEATED do not use owner_id argument in Classes.get');
}
if(!classesCache[class_key])
{
const row = await db.get(
'SELECT * FROM classes WHERE class_key = ?',
[class_key]);
if(!row)
{
return null;
}
/*if(owner_id && !row.owner_id !== owner_id)
{
return null;
}*/
classesCache[class_key] = new Class(row);
}
return classesCache[class_key];
},
createClassKey: async (baseKey) =>
get: async (class_key, owner_id) => {
if (owner_id) {
console.log('DEPRECEATED do not use owner_id argument in Classes.get');
}
if (!classesCache[class_key]) {
const row = await db.get(
'SELECT * FROM classes WHERE class_key = ?',
[class_key]);
if (!row) {
return null;
}
/*if(owner_id && !row.owner_id !== owner_id)
{
let k = baseKey || makeid(5);
while(true)
{
let r = await db.all('SELECT * FROM classes WHERE class_key = ? AND expire_date < datetime(\'now\')', [k]);
if(r.length == 0) return k;
k = makeid(5);
}
},
save: async (classObject) => {
const classInfo = classObject.info;
if( !classInfo.classKey
|| classInfo.expireDate < Date.now())
{
classInfo.classKey = await self.Classes.createClassKey(classInfo.classKey)
}
classesCache[classInfo.classKey] = classObject;
let info = null
try{
info = JSON.stringify(classInfo);
}
catch(e)
{
console.log('db.js:save(): error during stringify');
console.log(e);
console.log(classInfo);
throw 'db.js:save(): error during stringify';
}
if(classInfo.id)
{
const ex = await db.run('UPDATE classes \
return null;
}*/
classesCache[class_key] = new Class(row);
}
return classesCache[class_key];
},
createClassKey: async (baseKey) => {
let k = baseKey || makeid(5);
while (true) {
let r = await db.all('SELECT * FROM classes WHERE class_key = ? AND expire_date < datetime(\'now\')', [k]);
if (r.length == 0) return k;
k = makeid(5);
}
},
save: async (classObject) => {
const classInfo = classObject.info;
if (!classInfo.classKey
|| classInfo.expireDate < Date.now()) {
classInfo.classKey = await self.Classes.createClassKey(classInfo.classKey)
}
classesCache[classInfo.classKey] = classObject;
let info = null
try {
info = JSON.stringify(classInfo);
}
catch (e) {
console.log('db.js:save(): error during stringify');
console.log(e);
console.log(classInfo);
throw 'db.js:save(): error during stringify';
}
if (classInfo.id) {
const ex = await db.run('UPDATE classes \
SET info = ?, \
last_save = datetime(\'now\'), \
expire_date = datetime(\'now\', \'+1 month\'), \
owner_id = ?, \
class_key = ? \
WHERE id = ?',
[
info,
classInfo.owner && classInfo.owner.id,
classInfo.classKey,
classInfo.id
]
);
return;
}
const owner_id = classInfo.owner && classInfo.owner.id;
const ex = await db.run(
'INSERT INTO classes (owner_id, info, created, last_save, class_key) \
WHERE id = ?',
[
info,
classInfo.owner && classInfo.owner.id,
classInfo.classKey,
classInfo.id
]
);
return;
}
const owner_id = classInfo.owner && classInfo.owner.id;
const ex = await db.run(
'INSERT INTO classes (owner_id, info, created, last_save, class_key) \
VALUES (?, ?, datetime(\'now\'), datetime(\'now\'), ?)', [owner_id, info, classInfo.classKey]);
classInfo.id = ex.lastID;
}
},
Users: {
getOrCreate: async ({id, username, role, email, token}) =>
{
let user = await self.Users.get({id, username, email});
if(!user){
const ex = await db.run('INSERT INTO users (username, email, role, token) \
classInfo.id = ex.lastID;
}
},
Users: {
getOrCreate: async ({ id, username, role, email, token }) => {
let user = await self.Users.get({ id, username, email });
if (!user) {
const ex = await db.run('INSERT INTO users (username, email, role, token) \
VALUES (?, ?, ?, ?)', [username || null, email || null, role || 'user', token]);
user = await db.get('SELECT * FROM users WHERE id = ?', [ex.lastID]);
}
return user;
},
getByToken: async ({username, token}) =>
{
if(!username || !token)
{
return null;
}
let users = await db.all('SELECT * FROM users WHERE username = ? AND token = ?', [username, token]);
if(users.length !== 1)
{
return null
}
return users[0];
},
get: async ({id, username, email}) =>
{
let users = await db.all('SELECT * FROM users WHERE id = ? OR username = ? OR email = ?', [id, username, email]);
if(users.length !== 1)
{
return null
}
return users[0];
},
update: async ({id, role, token}) => {
let ex = await db.run('UPDATE users SET role = ?, token = ? WHERE id = ?', [role, token, id]);
return ex.changes;
},
getSubUsers: async (prefix) => {
let users = await db.all('SELECT * FROM users WHERE username LIKE ?', [prefix+'_%']);
return users;
}
user = await db.get('SELECT * FROM users WHERE id = ?', [ex.lastID]);
}
return user;
},
getByToken: async ({ username, token }) => {
if (!username || !token) {
return null;
}
let users = await db.all('SELECT * FROM users WHERE username = ? AND token = ?', [username, token]);
if (users.length !== 1) {
return null
}
return users[0];
},
get: async ({ id, username, email }) => {
let users = await db.all('SELECT * FROM users WHERE id = ? OR username = ? OR email = ?', [id, username, email]);
if (users.length !== 1) {
return null
}
return users[0];
},
update: async ({ id, role, token }) => {
let ex = await db.run('UPDATE users SET role = ?, token = ? WHERE id = ?', [role, token, id]);
return ex.changes;
},
getSubUsers: async (prefix) => {
let users = await db.all('SELECT * FROM users WHERE username LIKE ?', [prefix + '_%']);
return users;
},
delete: async ({ username }) => {
let ex = await db.run('DELETE FROM users WHERE username = ?', [username]);
return ex.changes;
}
}
}
export default self;
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment