
    Jj3                        d Z ddlZddlZddlZddlZddlZddlZej                            ej        	                    e
                    Zej                            de           dZdZdZdZdZd Zd2d	Zd
 Zd Zd Zd3dZd Zd4dZd Zd Zd Zd ZdZd Zd Z d Z!d Z"d Z#d Z$d Z%d Z&d Z'e(d k    rddl)Z* e*j+                    Z, ee,            ee,            e-d! ee,          rd"nd#z              e-d$d%z              ee,          D ]EZ. e-e.d&         d'd(e.d)         d*d(e.d+         d,d(e.d-         rd"nd#d.d(e.d/         rd0nd1	           Fe,/                                 dS dS )5u  
PLANNING TEAM - comptes, mots de passe et sessions.

Prepare la mise en ligne (lot 4). Tant que l'outil tourne en local, la connexion peut
rester desactivee ; elle devra imperativement etre activee AVANT de publier quoi que ce
soit, puisque la page portera des donnees RH nominatives.

Deux roles :
  * gestion — Jerome et Aurore : tout voir, tout modifier, valider les demandes ;
  * equipe  — les salaries : leur propre planning, la presence des collegues sans leurs
              heures ni leurs conges, et leurs propres demandes.

Les mots de passe ne sont jamais stockes : on garde une empreinte PBKDF2-HMAC-SHA256
avec un sel tire au hasard pour chacun, 240 000 iterations. Tout est dans la
bibliotheque standard, aucune dependance a installer.
    Ni    )gestionequipea  
CREATE TABLE IF NOT EXISTS compte (
    id                INTEGER PRIMARY KEY,
    salarie_id        INTEGER NOT NULL UNIQUE REFERENCES salarie(id) ON DELETE CASCADE,
    identifiant       TEXT NOT NULL UNIQUE,
    empreinte         TEXT,           -- NULL tant qu'aucun mot de passe n'est defini
    sel               TEXT,
    role              TEXT NOT NULL DEFAULT 'equipe',
    actif             INTEGER NOT NULL DEFAULT 1,
    doit_changer      INTEGER NOT NULL DEFAULT 1,
    cree_le           TEXT NOT NULL,
    derniere_connexion TEXT
);

-- Invitation de premiere connexion : un lien a usage unique, valable 14 jours, par
-- lequel chacun choisit lui-meme son mot de passe. Jerome ne connait donc jamais les
-- mots de passe de l'equipe.
CREATE TABLE IF NOT EXISTS invitation (
    jeton     TEXT PRIMARY KEY,
    compte_id INTEGER NOT NULL REFERENCES compte(id) ON DELETE CASCADE,
    creee_le  TEXT NOT NULL,
    expire_le TEXT NOT NULL,
    utilisee_le TEXT
);

CREATE TABLE IF NOT EXISTS session (
    jeton      TEXT PRIMARY KEY,
    compte_id  INTEGER NOT NULL REFERENCES compte(id) ON DELETE CASCADE,
    ouverte_le TEXT NOT NULL,
    expire_le  TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS reglage (
    cle    TEXT PRIMARY KEY,
    valeur TEXT NOT NULL
);
)compte
invitationsessionreglagec                     d |                      d          D             t          fdt          D                       rd S |                     t                     |                                  d S )Nc                     h | ]
}|d          S )r    .0ls     %/opt/planning-team/travail/comptes.py	<setcomp>zcreer_schema.<locals>.<setcomp>M   s.     @ @ @!1 @ @ @    z3SELECT name FROM sqlite_master WHERE type = 'table'c              3       K   | ]}|v V  	d S Nr   )r   t	presentess     r   	<genexpr>zcreer_schema.<locals>.<genexpr>O   s'      
*
*a1	>
*
*
*
*
*
*r   )executeallTABLESexecutescriptSCHEMAcommit)cxr   s    @r   creer_schemar   L   s    @ @rzz= ?  ? @ @ @I

*
*
*
*6
*
*
*** VIIKKKKKr   c                     t          |            |                     d|f                                          }|r|d         n|S )Nz(SELECT valeur FROM reglage WHERE cle = ?valeur)r   r   fetchone)r   cledefautr   s       r   r	   r	   W   sE    


=vFFOOQQA'1X;;'r   c                     t          |            |                     d|t          |          f           |                                  d S )NzyINSERT INTO reglage (cle, valeur) VALUES (?, ?)
                  ON CONFLICT(cle) DO UPDATE SET valeur = excluded.valeur)r   r   strr   )r   r#   r!   s      r   definir_reglager'   ]   sM    JJ MS[[!# # # IIKKKKKr   c                 ,    t          | dd          dk    S )u  
    Faut-il se connecter pour utiliser l'outil ?

    Reste a « non » tant qu'on travaille en local. **A passer a « oui » avant toute
    mise en ligne** : sans cela, n'importe qui atteignant l'adresse verrait les heures,
    les conges et les absences de toute l'equipe.
    connexion_exigeenonoui)r	   r   s    r   r)   r)   e   s     2)511U::r   c                     t          j        d|                     d          t                              |          t
                                                    S )Nsha256zutf-8)hashlibpbkdf2_hmacencodebytesfromhex
ITERATIONShex)mot_de_passesels     r   
_empreinter8   r   sD    x)<)<W)E)E$}}S11:? ??BsuuEr   Fc                 $   t          |pd          dk     rt          d          t          j        d          }|                     dt          ||          ||rdnd|f           |                     d|f           |                                  d S )	N    u1   le mot de passe doit faire au moins 8 caractères   zGUPDATE compte SET empreinte = ?, sel = ?, doit_changer = ? WHERE id = ?   r   'DELETE FROM session WHERE compte_id = ?)len
ValueErrorsecrets	token_hexr   r8   r   )r   	compte_idr6   
provisoirer7   s        r   definir_mot_de_passerE   w   s    
<2""OPPP

B

CJJQ	L#	&	&*-CQQ!YOQ Q Q JJ89,GGGIIKKKKKr   c                    t          |            |                     d|pd                                                                f                                          }||d         s&t          |pdt          j        d                     dS t          j	        t          |pd|d                   |d                   sdS t          |          S )z9Renvoie le compte si le mot de passe est bon, None sinon.zSELECT c.*, s.prenom, s.initiale FROM compte c
             JOIN salarie s ON s.id = c.salarie_id
            WHERE c.identifiant = ? AND c.actif = 1r:   N	empreinter<   r7   )r   r   striplowerr"   r8   rA   rB   hmaccompare_digestdict)r   identifiantr6   r   s       r   verifierrN      s    


	7 
	"	"	$	$	*	*	,	,.		0 	0 19

	 
 	y+y 	<%2w'8'<'<===tz,*<"ahGG;XX t77Nr   Tc                 r   t          |            t          j                                                            d          }d |                     d          D             }d |                     d          D             }g }|                     d          D ]}|d         |v rt          |d	                                                                       d
d                              dd          }|d}	}||v rd||	fz  |	dz   }	}||v |                    |           |d         s|d	         dk    rdnd}
|                     d|d         ||
|f           |	                    ||
f           | 
                                 |r1|r/t          dd                    d |D                       z             |S )u   
    Un compte par salarie actif. L'identifiant est le prenom en minuscules, sans
    accent ; le role « gestion » va a Jerome et Aurore, « equipe » aux autres.
    Aucun mot de passe n'est pose : chacun sera defini depuis l'onglet Acces.
    secondstimespecc                     h | ]
}|d          S )
salarie_idr   r   s     r   r   z*creer_comptes_manquants.<locals>.<setcomp>   s    VVVQ<VVVr   zSELECT salarie_id FROM comptec                     h | ]
}|d          S )rM   r   r   s     r   r   z*creer_comptes_manquants.<locals>.<setcomp>   s    SSSAmSSSr   zSELECT identifiant FROM comptezzSELECT id, prenom, initiale, hors_planning FROM salarie
                WHERE actif = 1 ORDER BY hors_planning DESC, ordreidprenom r:   -   z%s%dr=   hors_planningAurorer   r   z^INSERT INTO compte (salarie_id, identifiant, role, cree_le)
               VALUES (?, ?, ?, ?)u   Comptes créés : %sz, c              3       K   | ]	}d |z  V  
dS )z%s (%s)Nr   )r   cs     r   r   z*creer_comptes_manquants.<locals>.<genexpr>   s&      77A)a-777777r   )r   datetimenow	isoformatr   _sans_accentrI   replaceaddappendr   printjoin)r   bavard
maintenant	existantspriscreessbase_idrM   nroles              r   creer_comptes_manquantsrq      s    "&&((22I2FFJVV"**5T*U*UVVVISSbjj1Q&R&RSSSDEZZFG G * * T7iq{++1133;;CDDLLSRTUU !QT!!#wl2AEK T!!/Y1X;(3J3JyyQY


&()$dJ'O	Q 	Q 	Q 	k4())))IIKKK 9% 9)		77777778 	9 	9 	9Lr   c                 h    dddddddddddddddd	                     fd
| D                       S )Neaiour^   E)   é   è   ê   ë   à   â   î   ï   ô   ù   û   ç   É   Èr:   c              3   D   K   | ]}                     ||          V  d S r   )get)r   r^   tables     r   r   z_sans_accent.<locals>.<genexpr>   s/      22q599Q??222222r   )rg   )texter   s    @r   rb   rb      sY    C#cC#cC#E EE 772222E222222r   c                     t          |            |                     d                                          }d |D             S )Na>  SELECT c.id, c.identifiant, c.role, c.actif, c.doit_changer,
                  c.derniere_connexion, c.empreinte IS NOT NULL AS a_mot_de_passe,
                  s.id AS salarie_id, s.prenom, s.groupe, s.couleur
             FROM compte c JOIN salarie s ON s.id = c.salarie_id
            ORDER BY c.role DESC, s.ordrec                 P    g | ]"fd                                  D             #S )c                 "    i | ]}||         S r   r   )r   kr   s     r   
<dictcomp>z$liste.<locals>.<listcomp>.<dictcomp>   s    '''Q!'''r   )keysr   s    @r   
<listcomp>zliste.<locals>.<listcomp>   s6    888A''''affhh'''888r   )r   r   fetchall)r   ligness     r   lister      sL    ZZ	-. .
 /7hjj  988888r   c                     |t           vrt          d|z            |                     d||f           |                                  d S )Nu   rôle inconnu : %sz'UPDATE compte SET role = ? WHERE id = ?)ROLESr@   r   r   )r   rC   rp   s      r   definir_roler      sL    5047888JJ84:KLLLIIKKKKKr   c                     |                      d|rdnd|f           |s|                      d|f           |                                  d S )Nz(UPDATE compte SET actif = ? WHERE id = ?r=   r   r>   r   r   )r   rC   actifs      r   activerr      s]    JJ9!9-/ / / L


<ylKKKIIKKKKKr      c           
         t          |            |                     d|f           t          j        d          }t          j                                        }|                     d|||                    d          |t	          j        t                    z                       d          f           | 	                                 |S )z
    Un lien de premiere connexion, a usage unique. Toute invitation precedente encore
    valable est annulee : un seul lien vivant a la fois.
    zBDELETE FROM invitation WHERE compte_id = ? AND utilisee_le IS NULL   z]INSERT INTO invitation (jeton, compte_id, creee_le, expire_le)
           VALUES (?, ?, ?, ?)rP   rQ   days)
r   r   rA   token_urlsafer_   r`   ra   	timedeltaDUREE_INVITATION_JOURSr   r   rC   jetonri   s       r   creer_invitationr      s    
 JJS|  !"%%E"&&((JJJ	"		:///CC
x)/EFFF
F
)Y)
'
'	)* * * IIKKKLr   c                 ,   |sdS t          |            |                     d|f                                          }||d         rdS |d         t          j                                                            d          k     rdS t          |          S )zALe compte derriere un lien d'invitation, s'il est encore valable.Na!  SELECT i.jeton, i.expire_le, i.utilisee_le, c.id AS compte_id,
                  c.identifiant, c.role, s.prenom
             FROM invitation i
             JOIN compte c ON c.id = i.compte_id
             JOIN salarie s ON s.id = c.salarie_id
            WHERE i.jeton = ? AND c.actif = 1utilisee_le	expire_lerP   rQ   )r   r   r"   r_   r`   ra   rL   r   r   r   s      r   compte_par_invitationr      s     t


	1
 49(	< 	< =EHJJ  	yAm$yt~)--//9999MMMMt77Nr   c                 Z   t          | |          }|t          d          t          | |d         |d           |                     dt          j                                                            d          |f           |                                  t          | |d                   S )	z
    Le salarie choisit son mot de passe et se retrouve connecte. C'est le seul chemin
    qui pose un mot de passe : personne d'autre ne le connait, jamais.
    Nu1   ce lien n’est plus valable — en redemander unrC   F)rD   z5UPDATE invitation SET utilisee_le = ? WHERE jeton = ?rP   rQ   )	r   r@   rE   r   r_   r`   ra   r   ouvrir_session)r   r   r6   blocs       r   activer_par_invitationr     s    
 !U++D|MNNNT+.OOOOJJF!%%''1191EEuMO O OIIKKK"d;/000r   c                     t          |            t          j                                                            d          }d |                     d|f          D             S )zE{ compte_id : jeton } des liens encore valables, pour les reafficher.rP   rQ   c                 ,    i | ]}|d          |d         S )rC   r   r   r   s     r   r   z(invitations_en_cours.<locals>.<dictcomp>  s4     L L L1AkNAgJ L L Lr   z_SELECT compte_id, jeton FROM invitation
            WHERE utilisee_le IS NULL AND expire_le > ?)r   r_   r`   ra   r   )r   ri   s     r   invitations_en_coursr     su    "&&((22I2FFJL L

	;=GM1K 1K L L L Lr   c                      g d} dt          j        |           t          j        |           t          j        d          dz   fz  S )zNUn mot de passe lisible, a communiquer de vive voix, a changer a la connexion.)tilleulmenthelavanderomarinsureauaubepine	valerianemelisse	camomillegentianearnicabardanez%s-%s-%dZ   
   )rA   choice	randbelow)motss    r   mot_de_passe_provisoirer     sS    R R RD--w~d/C/C *2..35 5 5r   c           
         t          j        d          }t          j                                        }|                     d|||                    d          |t          j        t                    z                       d          f           |                     d|                    d          |f           |                                  |S )N    z\INSERT INTO session (jeton, compte_id, ouverte_le, expire_le)
           VALUES (?, ?, ?, ?)rP   rQ   r   z5UPDATE compte SET derniere_connexion = ? WHERE id = ?)	rA   r   r_   r`   r   ra   r   DUREE_SESSION_JOURSr   r   s       r   r   r   '  s    !"%%E"&&((JJJ	"		:///CC
x)/BCCC
C
)Y)
'
'	)* * * JJF$$i$88)DF F FIIKKKLr   c                 r   |sdS t          |            |                     d|f                                          }|dS |d         t          j                                                            d          k     r-|                     d|f           |                                  dS t          |          S )zGLe compte derriere un jeton, ou None si le jeton est inconnu ou perime.Na0  SELECT c.id AS compte_id, c.identifiant, c.role, c.doit_changer,
                  c.salarie_id, s.prenom, s.initiale, e.expire_le
             FROM session e
             JOIN compte c ON c.id = e.compte_id
             JOIN salarie s ON s.id = c.salarie_id
            WHERE e.jeton = ? AND c.actif = 1r   rP   rQ   #DELETE FROM session WHERE jeton = ?)r   r   r"   r_   r`   ra   r   rL   r   s      r   r   r   6  s     t


	1
 49(	< 	< =EHJJ  	yt~)--//9999MMMM


85(CCC
		t77Nr   c                 \    |                      d|f           |                                  d S )Nr   r   )r   r   s     r   fermer_sessionr   K  s*    JJ4uh???IIKKKKKr   c                     |                      dt          j                                                            d          f           |                                  d S )Nz'DELETE FROM session WHERE expire_le < ?rP   rQ   )r   r_   r`   ra   r   r,   s    r   purger_sessionsr   P  sU    JJ8!%%''1191EEGI I IIIKKKKKr   __main__u   
Connexion exigée : %sr+   r*   z
%-14s %-12s %-9s %-8s %s)Identifiantu   Prénomu   RôleActifzMot de passerM   14rX   rW   12rp   9r   8a_mot_de_passeu   définiu   —r   )F)T)0__doc__r_   r/   rJ   osrA   syspathdirnameabspath__file__BASEinsertr4   r   r   r   r   r   r	   r'   r)   r8   rE   rN   rq   rb   r   r   r   r   r   r   r   r   r   r   r   r   r   __name__basebd	connexionr   rf   r^   closer   r   r   <module>r      s7   "    				  



	wrwx0011 4   
 $
N 
8  ( ( ( (  ; ; ;E E E
     (! ! ! !H3 3 39 9 9        (  &1 1 1L L L5 5 5    *  
   z	BLB	E
'5E5Eb5I5I+U66v
VWWW	E
'QR S S SU2YY C C=!!!!1X;;;;&				W:1611!"#3!4@&@B 	C 	C 	C 	C HHJJJJJ r   