From b1a42465613ccc74d5b5c5a96412388fe9fb88b4 Mon Sep 17 00:00:00 2001
From: Alexandre Iooss <erdnaxe@crans.org>
Date: Tue, 14 Apr 2020 12:52:11 +0200
Subject: [PATCH] Update server config

---
 cpasswords/server.py         | 101 ++++++++++++++---------------
 docs/serverconfig.example.py | 120 +++++------------------------------
 2 files changed, 64 insertions(+), 157 deletions(-)

diff --git a/cpasswords/server.py b/cpasswords/server.py
index 2c61ea6..4b64fc2 100755
--- a/cpasswords/server.py
+++ b/cpasswords/server.py
@@ -11,9 +11,6 @@ Authors : Daniel Stan <daniel.stan@crans.org>
 SPDX-License-Identifier: GPL-3.0-or-later
 """
 
-from __future__ import print_function
-import serverconfig
-
 import glob
 import os
 import pwd
@@ -22,30 +19,35 @@ import json
 import datetime
 import socket
 import subprocess
-import itertools
+import logging
 from email.mime.text import MIMEText
 from email.mime.multipart import MIMEMultipart
 
+# Configuration loading
+# Guess name as we do not have config
+bootstrap_cmd_name = os.path.split(sys.argv[0])[1].replace("-server", "")
+default_config_path = "/etc/" + bootstrap_cmd_name
+config_path = os.getenv(
+    "CRANSPASSWORDS_SERVER_CONFIG_DIR", default_config_path)
 try:
-    from cpasswords import clientlib
-except ImportError:
-    print("Couldn't import clientlib. Remote sync may not work", file=sys.stderr)
-
-# Même problème que pour le client, il faut bootstraper le nom de la commande
-# Pour accéder à la config
-conf_path = os.getenv('CRANSPASSWORDS_SERVER_CONFIG_DIR', None)
-if not conf_path:
-    cmd_name = os.path.split(sys.argv[0])[1].replace("-server", "")
-    conf_path = "/etc/%s/" % (cmd_name,)
-
-sys.path.append(conf_path)
-
+    sys.path.append(config_path)
+    import serverconfig
+except ModuleNotFoundError:
+    # If config could not be imported, display an error if required
+    # Do not use logger as it has not been initialized yet
+    print("%s/serverconfig.py could not be found or read.\n"
+          "Please copy `docs/serverconfig.example.py` from the source "
+          "repository and customize." % config_path)
+    exit(1)
+
+# Local logger
+log = logging.getLogger(__name__)
+
+# Get user name that launch the server
 MYUID = pwd.getpwuid(os.getuid())[0]
 if MYUID == 'root':
     MYUID = os.environ['SUDO_USER']
 
-# Fonctions internes au serveur
-
 
 def validate(roles, mode='r'):
     """Vérifie que l'appelant appartient bien aux roles précisés
@@ -54,27 +56,26 @@ def validate(roles, mode='r'):
     for role in roles:
         if mode == 'w':
             role += '-w'
-        if serverconfig.ROLES.has_key(role) and MYUID in serverconfig.ROLES[role]:
+        if role in serverconfig.ROLES.keys() and MYUID in serverconfig.ROLES[role]:
             return True
     return False
 
 
 def getpath(filename, backup=False):
     """Récupère le chemin du fichier ``filename``"""
-    assert(isinstance(filename, unicode))
     filename = filename.encode('utf-8')
     return os.path.join(serverconfig.STORE, '%s.%s' % (filename, 'bak' if backup else 'json'))
 
 
 def writefile(filename, contents):
     """Écrit le fichier avec les bons droits UNIX"""
-    os.umask(0077)
+    os.umask(0o077)
     f = open(filename, 'w')
     f.write(contents.encode("utf-8"))
     f.close()
 
 
-class server_command(object):
+class ServerCommand(object):
     """
     Une instance est un décorateur pour la fonction servant de commande
     externe du même nom"""
@@ -104,14 +105,14 @@ class server_command(object):
         self.name = name
         self.stdin_input = stdin_input
         self.write = write
-        server_command.by_name[name] = self
+        ServerCommand.by_name[name] = self
 
     def __call__(self, fun):
         self.decorated = fun
         return fun
 
 # Fonction exposées par le serveur
-@server_command('keep-alive')
+@ServerCommand('keep-alive')
 def keepalive():
     """ Commande permettant de réaliser un tunnel json (un datagramme par ligne)
     Un message entre le client et le serveur consiste en l'échange de dico
@@ -128,7 +129,7 @@ def keepalive():
         try:
             # Une action du protocole = de l'ascii
             action = data['action'].encode('ascii')
-            content = server_command.by_name[action].decorated(*data['args'])
+            content = ServerCommand.by_name[action].decorated(*data['args'])
             status = u'ok'
         except Exception as e:
             status = u'error'
@@ -141,25 +142,25 @@ def keepalive():
         sys.stdout.flush()
 
 
-@server_command('listroles')
+@ServerCommand('listroles')
 def listroles():
     """Liste des roles existant et de leurs membres.
        Renvoie également un rôle particulier ``"whoami"``, contenant l'username
        de l'utilisateur qui s'est connecté."""
     d = serverconfig.ROLES
-    if d.has_key("whoami"):
+    if "whoami" in d.keys():
         raise ValueError('La rôle "whoami" ne devrait pas exister')
     d["whoami"] = MYUID
     return d
 
 
-@server_command('listkeys')
+@ServerCommand('listkeys')
 def listkeys():
     """Liste les usernames et les (mail, fingerprint) correspondants"""
     return serverconfig.KEYS
 
 
-@server_command('listfiles')
+@ServerCommand('listfiles')
 def listfiles():
     """Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder"""
     os.chdir(serverconfig.STORE)
@@ -173,7 +174,7 @@ def listfiles():
     return files
 
 
-@server_command('restorefiles')
+@ServerCommand('restorefiles')
 def restorefiles():
     """Si un fichier a été corrompu, on restore son dernier backup valide"""
     os.chdir(serverconfig.STORE)
@@ -204,7 +205,7 @@ def restorefiles():
     return files
 
 
-@server_command('getfile')
+@ServerCommand('getfile')
 def getfile(filename):
     """Récupère le fichier ``filename``"""
     filepath = getpath(filename)
@@ -218,7 +219,7 @@ def getfile(filename):
         return [False, u"Le fichier %s n'existe pas." % filename]
 
 
-@server_command('getfiles', stdin_input=True)
+@ServerCommand('getfiles', stdin_input=True)
 def getfiles(filenames):
     """Récupère plusieurs fichiers, lit la liste des filenames demandés sur stdin"""
     return [getfile(f) for f in filenames]
@@ -243,10 +244,6 @@ def _putfile(filename, roles, contents):
     notification(u"Modification", filename, MYUID)
 
     filepath = getpath(filename)
-    if type(contents) != unicode:
-        return [False, u"Erreur: merci de patcher votre cpasswords !"
-                + "(contents should be encrypted str)"]
-        # Or fuck yourself
 
     writefile(filepath, json.dumps({'roles': roles, 'contents': contents}))
 
@@ -257,7 +254,7 @@ def _putfile(filename, roles, contents):
     return [True, u"Modification effectuée."]
 
 
-@server_command('putfile', stdin_input=True, write=True)
+@ServerCommand('putfile', stdin_input=True, write=True)
 def putfile(filename, parsed_stdin):
     """Écrit le fichier ``filename`` avec les données reçues sur stdin."""
     try:
@@ -268,7 +265,7 @@ def putfile(filename, parsed_stdin):
     return _putfile(filename, roles, contents)
 
 
-@server_command('putfiles', stdin_input=True, write=True)
+@ServerCommand('putfiles', stdin_input=True, write=True)
 def putfiles(parsed_stdin):
     """Écrit plusieurs fichiers. Lit les filenames sur l'entrée standard avec le
     reste."""
@@ -285,7 +282,7 @@ def putfiles(parsed_stdin):
     return results
 
 
-@server_command('rmfile', write=True)
+@ServerCommand('rmfile', write=True)
 def rmfile(filename):
     """Supprime le fichier filename après avoir vérifié les droits sur le fichier"""
     gotit, old = getfile(filename)
@@ -305,7 +302,7 @@ def rmfile(filename):
 # TODO monter plus haut
 def backup(corps, fname, old):
     """Backupe l'ancienne version du fichier"""
-    os.umask(0077)
+    os.umask(0o077)
     back = open(getpath(fname, backup=True), 'a')
     back.write(json.dumps(old))
     back.write('\n')
@@ -344,30 +341,30 @@ def notification_mail():
     actions = set(task[1] for task in _notif_todo)
 
     msg = MIMEMultipart(_charset="utf-8")
-    msg['Subject'] = u"Modification de la base (%s)" % (', '.join(actions))
+    msg['Subject'] = "Modification de la base (%s)" % (', '.join(actions))
     msg['X-Mailer'] = serverconfig.cmd_name.decode()
     msg['From'] = frommail
     msg['To'] = tomail
-    msg.preamble = u"%s report" % (serverconfig.cmd_name.decode(),)
+    msg.preamble = "%s report" % (serverconfig.cmd_name.decode(),)
 
-    liste = (u" * %s de %s par %s" % task for task in _notif_todo)
+    liste = (" * %s de %s par %s" % task for task in _notif_todo)
 
-    info = MIMEText(u"Des modifications ont été faites:\n" +
-                    u"\n".join(liste) +
-                    u"\n\nDes sauvegardes ont été réalisées." +
-                    u"\n\nModification effectuée sur %s." % socket.gethostname() +
-                    u"\n\n-- \nCranspasswords.py", _charset="utf-8")
+    info = MIMEText("Des modifications ont été faites:\n" +
+                    "\n".join(liste) +
+                    "\n\nDes sauvegardes ont été réalisées." +
+                    "\n\nModification effectuée sur %s." % socket.gethostname() +
+                    "\n\n-- \nCranspasswords.py", _charset="utf-8")
     msg.attach(info)
-    mailProcess = subprocess.Popen(
+    mail_process = subprocess.Popen(
         [serverconfig.sendmail_cmd, "-t"], stdin=subprocess.PIPE)
-    mailProcess.communicate(msg.as_string())
+    mail_process.communicate(msg.as_string())
 
 
 def main():
     argv = sys.argv[0:]
     command_name = argv[1]
 
-    command = server_command.by_name[command_name]
+    command = ServerCommand.by_name[command_name]
     if serverconfig.READONLY and command.write:
         raise IOError("Ce serveur est read-only.")
 
diff --git a/docs/serverconfig.example.py b/docs/serverconfig.example.py
index b49cbbd..3982252 100755
--- a/docs/serverconfig.example.py
+++ b/docs/serverconfig.example.py
@@ -1,13 +1,8 @@
-#!/usr/bin/env python2
-# -*- encoding: utf-8 -*-
-
-""" Configuration Serveur de cranspasswords.
+"""
+Configuration Serveur de cranspasswords.
 
 Sont définis ici les utilisateurs et les rôles associés.
-Ce fichier est donné à titre d'exemple, mais n'est PAS
-utilisé lors du fonctionnement en mode client.
-
-Dans le futur, pourra être remplacé par une connexion ldap.
+Ce fichier est donné à titre d'exemple.
 """
 
 #: Pour override le nom si vous voulez renommer la commande
@@ -23,125 +18,40 @@ STORE = '/var/lib/%s/db/' % (cmd_name,)
 READONLY = False
 
 #: Expéditeur du mail de notification
-CRANSP_MAIL = u"%s <root@crans.org>" % (cmd_name,)
+CRANSP_MAIL = "%s <root@crans.org>" % (cmd_name,)
 
 #: Destinataire du mail de notification
-DEST_MAIL = u"root@crans.org"
+DEST_MAIL = "root@crans.org"
 
 #: Mapping des utilisateurs et de leurs (mail, fingerprint GPG)
 KEYS = {
-    u'aza-vallina': (u'Damien.Aza-Vallina@crans.org', None),
-    u'becue': (u'becue@crans.org', u'9AE04D986400E3B67528F4930D442664194974E2'),
-    u'blockelet': (u'blockelet@crans.org', u'550A057BC913EA4637D250495314C173AF087A52'),
-    u'boilard': (u'boilard@crans.org', u'E73A648AAB5E81BE38038350C1690AB9C39EB6F4'),
-    u'cauderlier': (u'cauderlier@crans.org', u'4D302A566C2956334DF6AD8E1B199227E5ABC502'),
-    u'chambart': (u'pierre.chambart@crans.org', u'085D0DFB66EAF9448C42979C43680A46F2530FCE'),
-    u'dandrimont': (u'nicolas.dandrimont@crans.org', u'791F12396630DD71FD364375B8E5087766475AAF'),
-    u'dimino': (u'jdimino@dptinfo.ens-cachan.fr', u'2C938EAC93A16F8129F807C81E8A30532127F85A'),
-    u'dstan': (u'daniel.stan@crans.org', u'90520CFDE846E7651A1B751FBC9BF8456E1C820B'),
-    u'durand-gasselin': (u'adg@crans.org', u'B3EA34ED8A4EA3B5C3E6C04D30F01C448E96ACDA'),
-    u'glondu': (u'Stephane.Glondu@crans.org', u'58EB0999C64E897EE894B8037853DA4D49881AD3'),
-    u'huber': (u'olivier.huber@crans.org', u'3E9473AF796C530F9C4DE7DB1EF81A95E0DCF376'),
-    u'iffrig': (u'iffrig@crans.org', u'26A210E2584208FEF6BE8F3718068DEA354B0045'),
-    u'lagorce': (u'xavier.lagorce@crans.org', u'08C26F5AABC5570E5E2F52B39D9D7CE70BF3708E'),
-    u'lajus': (u'lajus@crans.org', None),
-    u'legallic': (u'legallic@crans.org', u'4BDD2DC3F10C26B9BC3B0BD93602E1C9A94025B0'),
-    u'lerisson': (u'lerisson@crans.org', None),
-    u'maioli': (u'maioli@crans.org', None),
-    u'parret-freaud': (u'apf@crans.org', u'41049BA24909F12F958386A336381C4C2CBB08AA'),
-    u'samir': (u'samir@crans.org', u'C7B8823E96E8DC2798970340C86AD2AA41C2B76B'),
-    u'tvincent': (u'vincent.thomas@crans.org', u'DFB04CE4394B1115C587AE101C6BE33AC5C4ACC0'),
-#Autogen
-    u'besson': (u'lbesson@ens-cachan.fr', None),#u'BF105A8DC75491B9D6EDAC5D01AACDB9C108F8A0',
-    u'tilquin': (u'tilquin@crans.org', None),
-    u'pvincent': (u'pvincent@crans.org', None),
-    u'pommeret': (u'pommeret@crans.org', u'8D9C890BD2B783A052DBE71405504FF0CF875FE1'),
-    u'lasseri': (u'lasseri@crans.org', u'31EF775095485A1CA4CC7CAAA2A902AE80403321'),
-    u'moisy-mabille': (u'moisy-mabille@crans.org', None),
-    u'guiraud': (u'guiraud@crans.org', u'8C8F34952DCBA75CD2963A4C33ECE62B57DA1CD4'),
-    u'soret': (u'soret@crans.org', u'C244290074A0A4A8C05FCA1ACF25D25F17DA8589'),
-    u'serrano': (u'serrano@crans.org', u'64ABC0C087EDAA14B79F5F7DEDE22762F030FDC5'),
-    u'kherouf': (u'kherouf@crans.org', None),
-    u'baste': (u'baste@crans.org', None),
-    u'quelennec': (u'quelennec@crans.org', None),
-    u'grande': (u'grande@crans.org', None),
-    u'gstalter': (u'gstalter@crans.org', None),
-    u'duplouy': (u'duplouy@crans.org', None),
-    u'randazzo': (u'randazzo@crans.org', None),
-    u'epalle': (u'epalle@crans.org', None),
-    u'bonaque': (u'bonaque@crans.org', None),
-    u'kviard': (u'kviard@crans.org', None),
-    u'delorme': (u'jordy@crans.org', u'E2250729CF7D2C801748D33F8219ECBA6B1DD5EF'),
+    'dstan': (u'daniel.stan@crans.org', u'90520CFDE846E7651A1B751FBC9BF8456E1C820B'),
+    'legallic': (u'legallic@crans.org', u'4BDD2DC3F10C26B9BC3B0BD93602E1C9A94025B0'),
 }
 
 #: Les variables suivantes sont utilisées pour définir le dictionnaire des
 #: rôles.
 RTC = [
-    u'samir'
-    ]
+    'dstan'
+]
 
 #: Liste des usernames des nounous
 NOUNOUS = RTC + [
-    u'blockelet',
-    u'becue',
-    u'dstan',
-    u'chambart',
-    u'dimino',
-    u'durand-gasselin',
-    u'glondu',
-    u'huber',
-    u'lagorce',
-    u'parret-freaud',
-    u'cauderlier',
-    u'maioli',
-    u'iffrig',
-    u'boilard',
-    u'legallic',
-    u'pommeret',
-    u'serrano',
-    u'lasseri',
-    u'delorme',
-    ]
+    'legallic',
+]
 
 # Autogen:
 #: Liste des usernames des apprentis
 APPRENTIS = [
-    u'grande',
-    u'bonaque',
-    u'moisy-mabille',
-    u'baste',
-    u'duplouy',
-    u'besson',
-    u'pvincent',
-    u'quelennec',
-    u'guiraud',
-    u'kherouf',
-    u'randazzo',
-    u'tilquin',
-    u'epalle',
-    u'soret',
-    u'gstalter',
-    u'kviard']
+]
 
 #: Liste des usernames des membres du CA
 CA = [
-    u'becue',
-    u'duplouy',
-    u'epalle',
-    u'guiraud',
-    u'lajus',
-    u'lasseri',
-    u'lerisson',
-    u'randazzo',
-    u'soret',
-    ]
+]
 
 #: Liste des trésoriers
 TRESORERIE = RTC + [
-    u'soret',
-    u'guiraud',
-    u'randazzo',
-    ]
+]
 
 #: Les roles utilisés pour savoir qui a le droit le lire/écrire quoi
 ROLES = {
@@ -153,4 +63,4 @@ ROLES = {
     "apprentis-w": NOUNOUS,
     "tresorerie": TRESORERIE,
     "tresorerie-w": TRESORERIE,
-    }
+}
-- 
GitLab