Commit 3589ef41 authored by Vincent Le gallic's avatar Vincent Le gallic

getfile*s*; putfile*s* et utilisation pour --recrypt-files

On peut récupérer/envoyer plusieurs fichiers à la fois.

A priori, le serveur n'est plus rétro-compatible avec les clients non à jour.

Conflicts:
	client.py
	server.py
parent 0f415a0d
......@@ -21,6 +21,7 @@ import re
import random
import string
import datetime
import gnupg
try:
import gnupg #disponible seulement sous wheezy
except ImportError:
......@@ -68,7 +69,7 @@ CLIPBOARD = bool(os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip')
#: Mode «ne pas demander confirmation»
FORCED = False
#: Droits à définir sur le fichier en édition
NROLES = None
NEWROLES = None
#: Serveur à interroger (peuplée à l'exécution)
SERVER = None
......@@ -128,10 +129,11 @@ def remote_command(command, arg = None, stdin_contents = None):
commande"""
sshin, sshout = ssh(command, arg)
if stdin_contents:
if not stdin_contents is None:
sshin.write(json.dumps(stdin_contents))
sshin.close()
return json.loads(sshout.read())
raw_out = sshout.read()
return json.loads(raw_out)
@simple_memoize
def all_keys():
......@@ -148,14 +150,13 @@ def all_files():
"""Récupère les fichiers du serveur distant"""
return remote_command("listfiles")
def get_file(filename):
"""Récupère le contenu du fichier distant"""
return remote_command("getfile", filename)
def get_files(filenames):
"""Récupère le contenu des fichiers distants"""
return remote_command("getfiles", stdin_contents=filenames)
def put_file(filename, roles, contents):
"""Dépose le fichier sur le serveur distant"""
return remote_command("putfile", filename, {'roles': roles,
'contents' : contents})
def put_files(files):
"""Dépose les fichiers sur le serveur distant"""
return remote_command("putfiles", stdin_contents=files)
def rm_file(filename):
"""Supprime le fichier sur le serveur distant"""
......@@ -238,8 +239,7 @@ def get_dest_of_roles(roles):
for rec in get_recipients_of_roles(roles) if allkeys[rec][1]]
def encrypt(roles, contents):
"""Chiffre le contenu pour les roles donnés"""
"""Chiffre ``contents`` pour les ``roles`` donnés"""
allkeys = all_keys()
recipients = get_recipients_of_roles(roles)
......@@ -270,20 +270,20 @@ def put_password(name, roles, contents):
"""Dépose le mot de passe après l'avoir chiffré pour les
destinataires donnés"""
success, enc_pwd_or_error = encrypt(roles, contents)
if NROLES != None:
roles = NROLES
if NEWROLES != None:
roles = NEWROLES
if VERB:
print(u"Pas de nouveaux rôles".encode("utf-8"))
if success:
enc_pwd = enc_pwd_or_error
return put_file(name, roles, enc_pwd)
return put_files([{'filename' : name, 'roles' : roles, 'contents' : enc_pwd}])[0]
else:
error = enc_pwd_or_error
return [False, error]
def get_password(name):
"""Récupère le mot de passe donné par name"""
gotit, remotefile = get_file(name)
gotit, remotefile = get_files([name])[0]
if gotit:
remotefile = decrypt(remotefile['contents'])
return [gotit, remotefile]
......@@ -367,7 +367,7 @@ def clipboard(texte):
def show_file(fname):
"""Affiche le contenu d'un fichier"""
gotit, value = get_file(fname)
gotit, value = get_files([fname])[0]
if not gotit:
print(value.encode("utf-8")) # value contient le message d'erreur
return
......@@ -385,7 +385,7 @@ def show_file(fname):
line = clipboard(catchPass.group(1))
ntexte += line + '\n'
showbin = "cat" if hidden else "less"
proc = subprocess.Popen(showbin, stdin=subprocess.PIPE, shell=True)
proc = subprocess.Popen([showbin], stdin=subprocess.PIPE)
out = proc.stdin
raw = u"Fichier %s:\n\n%s-----\nVisible par: %s\n" % (fname, ntexte, ','.join(value['roles']))
out.write(raw.encode("utf-8"))
......@@ -395,7 +395,7 @@ def show_file(fname):
def edit_file(fname):
"""Modifie/Crée un fichier"""
gotit, value = get_file(fname)
gotit, value = get_files([fname])[0]
nfile = False
annotations = u""
if not gotit and not "pas les droits" in value:
......@@ -423,7 +423,8 @@ Enregistrez le fichier vide pour annuler.\n"""
sin.write(value['contents'].encode("utf-8"))
sin.close()
texte = sout.read().decode("utf-8")
value['roles'] = NROLES or value['roles']
# On récupère les nouveaux roles si ils ont été précisés, sinon on garde les mêmes
value['roles'] = NEWROLES or value['roles']
annotations += u"""Ce fichier sera chiffré pour les rôles suivants :\n%s\n
C'est-à-dire pour les utilisateurs suivants :\n%s""" % (
......@@ -433,7 +434,7 @@ C'est-à-dire pour les utilisateurs suivants :\n%s""" % (
ntexte = editor(texte, annotations)
if ((not nfile and ntexte in [u'', texte] and NROLES == None) or # Fichier existant vidé ou inchangé
if ((not nfile and ntexte in [u'', texte] and NEWROLES == None) or # Fichier existant vidé ou inchangé
(nfile and ntexte == u'')): # Nouveau fichier créé vide
print(u"Pas de modification effectuée".encode("utf-8"))
else:
......@@ -470,26 +471,50 @@ def my_update_keys():
def recrypt_files():
"""Rechiffre les fichiers"""
roles = None
# Ici, la signification de NEWROLES est : on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles
rechiffre_roles = NEWROLES
my_roles = get_my_roles()
if roles == None:
# On ne conserve que les rôles qui finissent par -w
roles = [ r[:-2] for r in my_roles if r.endswith('-w')]
if type(roles) != list:
roles = [roles]
for (fname, froles) in all_files().iteritems():
if set(roles).intersection(froles) == set([]):
continue
print((u"Rechiffrement de %s" % fname).encode("utf-8"))
_, password = get_password(fname)
put_password(fname, froles, password)
my_roles_w = [r for r in my_roles if r.endswith("-w")]
if rechiffre_roles == None:
# Sans précisions, on prend tous les roles qu'on peut
rechiffre_roles = my_roles
# On ne conserve que les rôles en écriture
rechiffre_roles = [ r[:-2] for r in rechiffre_roles if r.endswith('-w')]
# La liste des fichiers
allfiles = all_files()
# On ne demande que les fichiers dans lesquels on peut écrire
# et qui ont au moins un role dans ``roles``
askfiles = [filename for (filename, fileroles) in allfiles.iteritems()
if set(fileroles).intersection(roles) != set()
and set(fileroles).intersection(my_roles_w) != set()]
files = get_files(askfiles)
# Au cas où on aurait échoué à récupérer ne serait-ce qu'un de ces fichiers,
# on affiche le message d'erreur correspondant et on abandonne.
for (success, message) in files:
if not success:
print(message.encode("utf-8"))
return
# On rechiffre
to_put = [{'filename' : f['filename'],
'roles' : f['roles'],
'contents' : encrypt(f['roles'], decrypt(f['contents']))}
for f in files]
if to_put:
print((u"Rechiffrement de %s" % (", ".join([f['filename'] for f in to_put]))).encode("utf-8"))
results = put_files(to_put)
# On affiche les messages de retour
for i in range(len(results)):
print (u"%s : %s" % (to_put[i]['filename'], results[i][1]))
else:
print(u"Aucun fichier n'a besoin d'être rechiffré".encode("utf-8"))
def parse_roles(strroles):
"""Interprête une liste de rôles fournie par l'utilisateur"""
"""Interprête une liste de rôles fournie par l'utilisateur.
Renvoie ``False`` si au moins un de ces rôles pose problème."""
if strroles == None: return None
roles = all_roles()
my_roles = filter(lambda r: SERVER['user'] in roles[r],roles.keys())
my_roles = filter(lambda r: SERVER['user'] in roles[r], roles.keys())
my_roles_w = [ r[:-2] for r in my_roles if r.endswith('-w') ]
ret = set()
writable = False
......@@ -555,10 +580,14 @@ if __name__ == "__main__":
help="Lister les serveurs")
action_grp.add_argument('--recrypt-files', action='store_const', dest='action',
default=show_file, const=recrypt_files,
help="Rechiffrer les mots de passe")
help="Rechiffrer les mots de passe. (Avec les mêmes rôles qu'avant, sert à rajouter un lecteur)")
parser.add_argument('--roles', nargs='?', default=None,
help="liste des roles à affecter au fichier")
help="""Liste de roles (séparés par des virgules).
Avec --edit, le fichier sera chiffré pour exactement ces roles
(par défaut, tous vos rôles en écriture seront utilisés).
Avec --recrypt-files, tous les fichiers ayant au moins un de ces roles (et pour lesquels vous avez le droit d'écriture) seront rechiffrés
(par défaut, tous les fichiers pour lesquels vous avez les droits en écriture sont rechiffrés).""")
parser.add_argument('fname', nargs='?', default=None,
help="Nom du fichier à afficher")
......@@ -569,9 +598,9 @@ if __name__ == "__main__":
if parsed.clipboard != None:
CLIPBOARD = parsed.clipboard
FORCED = parsed.force
NROLES = parse_roles(parsed.roles)
NEWROLES = parse_roles(parsed.roles)
if NROLES != False:
if NEWROLES != False:
if parsed.action.func_code.co_argcount == 0:
parsed.action()
elif parsed.fname == None:
......
......@@ -73,22 +73,20 @@ def getfile(filename):
obj = json.loads(open(filepath).read())
if not validate(obj['roles']):
return [False, u"Vous n'avez pas les droits de lecture sur le fichier %s." % filename]
obj["filename"] = filename
return [True, obj]
except IOError:
return [False, u"Le fichier %s n'existe pas." % filename]
def putfile(filename):
"""Écrit le fichier ``filename`` avec les données reçues sur stdin."""
filepath = getpath(filename)
def getfiles():
"""Récupère plusieurs fichiers, lit la liste des filenames demandés sur stdin"""
stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin)
try:
roles = parsed_stdin['roles']
contents = parsed_stdin['contents']
except KeyError:
return [False, u"Entrée invalide"]
filenames = json.loads(stdin)
return [getfile(f) for f in filenames]
def _putfile(filename, roles, contents):
"""Écrit ``contents`` avec les roles ``roles`` dans le fichier ``filename``"""
gotit, old = getfile(filename)
if not gotit:
old = u"[Création du fichier]"
......@@ -102,9 +100,38 @@ def putfile(filename):
backup(corps, filename, old)
notification(u"Modification de %s" % filename, corps, filename, old)
filepath = getpath(filename)
writefile(filepath, json.dumps({'roles': roles, 'contents': contents}))
return [True, u"Modification effectuée."]
def putfile(filename):
"""Écrit le fichier ``filename`` avec les données reçues sur stdin."""
stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin)
try:
roles = parsed_stdin['roles']
contents = parsed_stdin['contents']
except KeyError:
return [False, u"Entrée invalide"]
return _putfile(filename, roles, contents)
def putfiles():
"""Écrit plusieurs fichiers. Lit les filenames sur l'entrée standard avec le reste."""
stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin)
results = []
for fichier in parsed_stdin:
try:
filename = fichier['filename']
roles = fichier['roles']
contents = fichier['contents']
except KeyError:
results.append([False, u"Entrée invalide"])
else:
results.append(_putfile(filename, roles, contents))
return results
def rmfile(filename):
"""Supprime le fichier filename après avoir vérifié les droits sur le fichier"""
gotit, old = getfile(filename)
......@@ -120,6 +147,7 @@ def rmfile(filename):
return u"Vous n'avez pas les droits d'écriture sur le fichier %s." % filename
return u"Suppression effectuée"
def backup(corps, fname, old):
"""Backupe l'ancienne version du fichier"""
back = open(getpath(fname, backup=True), 'a')
......@@ -168,8 +196,13 @@ if __name__ == "__main__":
answer = listkeys()
elif command == "listfiles":
answer = listfiles()
elif command == "getfiles":
answer = getfiles()
elif command == "putfiles":
answer = putfiles()
else:
if not filename:
print("filename nécessaire pour cette opération", file=sys.stderr)
sys.exit(1)
if command == "getfile":
answer = getfile(filename)
......
Markdown is supported
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