1323 lines
51 KiB
Python
1323 lines
51 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
""" Gestion des utilisateurs (table SQL pour Zope User Folder)
|
|
"""
|
|
import string
|
|
import re
|
|
import time
|
|
import md5
|
|
import base64
|
|
import jaxml
|
|
|
|
from sco_zope import * # pylint: disable=unused-wildcard-import
|
|
|
|
# ---------------
|
|
import sco_utils as scu
|
|
import notesdb as ndb
|
|
from notes_log import log
|
|
from scolog import logdb
|
|
from scolars import format_prenom, format_nom
|
|
import sco_import_users
|
|
import sco_excel
|
|
from TrivialFormulator import TrivialFormulator, TF, tf_error_message
|
|
from gen_tables import GenTable
|
|
import scolars
|
|
import sco_cache
|
|
import sco_users
|
|
from sco_permissions import (
|
|
ScoEditAllEvals,
|
|
ScoEditAllNotes,
|
|
ScoImplement,
|
|
ScoSuperAdmin,
|
|
ScoUsersAdmin,
|
|
ScoUsersView,
|
|
ScoView,
|
|
)
|
|
from sco_exceptions import (
|
|
AccessDenied,
|
|
ScoException,
|
|
ScoValueError,
|
|
ScoInvalidDateError,
|
|
ScoLockedFormError,
|
|
ScoGenError,
|
|
)
|
|
|
|
# ---------------
|
|
# cache global: chaque instance, repérée par son URL, a un cache
|
|
# qui est recréé à la demande
|
|
# On cache ici la liste des utilisateurs, pour une duree limitee
|
|
# (une minute).
|
|
|
|
CACHE_userlist = {}
|
|
|
|
# ---------------
|
|
|
|
|
|
class ZScoUsers(
|
|
ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit
|
|
):
|
|
|
|
"ZScousers object"
|
|
|
|
meta_type = "ZScoUsers"
|
|
security = ClassSecurityInfo()
|
|
|
|
# This is the list of the methods associated to 'tabs' in the ZMI
|
|
# Be aware that The first in the list is the one shown by default, so if
|
|
# the 'View' tab is the first, you will never see your tabs by cliquing
|
|
# on the object.
|
|
manage_options = (
|
|
({"label": "Contents", "action": "manage_main"},)
|
|
+ PropertyManager.manage_options # add the 'Properties' tab
|
|
+ ({"label": "View", "action": "index_html"},)
|
|
+ Item.manage_options # add the 'Undo' & 'Owner' tab
|
|
+ RoleManager.manage_options # add the 'Security' tab
|
|
)
|
|
|
|
# no permissions, only called from python
|
|
def __init__(self, id, title):
|
|
"initialise a new instance"
|
|
self.id = id
|
|
self.title = title
|
|
|
|
# Connexion to SQL database of users:
|
|
|
|
# Ugly but necessary during transition out of Zope:
|
|
_db_cnx_string = "dbname=SCOUSERS port=5432"
|
|
security.declareProtected("Change DTML Documents", "GetUsersDBConnexion")
|
|
GetUsersDBConnexion = ndb.GetUsersDBConnexion
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# Users (top level)
|
|
#
|
|
# --------------------------------------------------------------------
|
|
# used to view content of the object
|
|
security.declareProtected(ScoUsersView, "index_html")
|
|
|
|
def index_html(self, REQUEST, all=0, with_olds=0, format="html"):
|
|
"gestion utilisateurs..."
|
|
all = int(all)
|
|
with_olds = int(with_olds)
|
|
# Controle d'acces
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
user_name = str(authuser)
|
|
# log('user: %s roles: %s'%(user_name,authuser.getRolesInContext(self)))
|
|
user = self._user_list(args={"user_name": user_name})
|
|
if not user:
|
|
zope_roles = authuser.getRolesInContext(self)
|
|
if ("Manager" in zope_roles) or ("manage" in zope_roles):
|
|
dept = "" # special case for zope admin
|
|
else:
|
|
raise AccessDenied("Vous n'avez pas la permission de voir cette page")
|
|
else:
|
|
dept = user[0]["dept"]
|
|
|
|
H = [self.sco_header(REQUEST, page_title="Gestion des utilisateurs")]
|
|
H.append("<h2>Gestion des utilisateurs</h2>")
|
|
|
|
if authuser.has_permission(ScoUsersAdmin, self):
|
|
H.append(
|
|
'<p><a href="create_user_form" class="stdlink">Ajouter un utilisateur</a>'
|
|
)
|
|
H.append(
|
|
' <a href="import_users_form" class="stdlink">Importer des utilisateurs</a></p>'
|
|
)
|
|
if all:
|
|
checked = "checked"
|
|
else:
|
|
checked = ""
|
|
if with_olds:
|
|
olds_checked = "checked"
|
|
else:
|
|
olds_checked = ""
|
|
H.append(
|
|
"""<p><form name="f" action="%s">
|
|
<input type="checkbox" name="all" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
|
|
<input type="checkbox" name="with_olds" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
|
|
</form></p>"""
|
|
% (REQUEST.URL0, checked, olds_checked)
|
|
)
|
|
|
|
L = self.list_users(
|
|
dept,
|
|
all=all,
|
|
with_olds=with_olds,
|
|
format=format,
|
|
REQUEST=REQUEST,
|
|
with_links=authuser.has_permission(ScoUsersAdmin, self),
|
|
)
|
|
if format != "html":
|
|
return L
|
|
H.append(L)
|
|
|
|
F = self.sco_footer(REQUEST)
|
|
return "\n".join(H) + F
|
|
|
|
_userEditor = ndb.EditableTable(
|
|
"sco_users",
|
|
"user_id",
|
|
(
|
|
"user_id",
|
|
"user_name",
|
|
"passwd",
|
|
"roles",
|
|
"date_modif_passwd",
|
|
"nom",
|
|
"prenom",
|
|
"email",
|
|
"dept",
|
|
"passwd_temp",
|
|
"status",
|
|
"date_expiration",
|
|
),
|
|
output_formators={
|
|
"date_modif_passwd": ndb.DateISOtoDMY,
|
|
"date_expiration": ndb.DateISOtoDMY,
|
|
},
|
|
input_formators={
|
|
"date_modif_passwd": ndb.DateDMYtoISO,
|
|
"date_expiration": ndb.DateDMYtoISO,
|
|
},
|
|
sortkey="nom",
|
|
filter_nulls=False,
|
|
)
|
|
|
|
def _user_list(self, **kw):
|
|
# list info sur utilisateur(s)
|
|
cnx = self.GetUsersDBConnexion()
|
|
users = self._userEditor.list(cnx, **kw)
|
|
for u in users:
|
|
if u["status"] == "old":
|
|
u["status_txt"] = "(ancien)"
|
|
else:
|
|
u["status_txt"] = ""
|
|
|
|
return users
|
|
|
|
def _user_edit(self, user_name, vals):
|
|
# edit user
|
|
cnx = self.GetUsersDBConnexion()
|
|
vals["user_name"] = user_name
|
|
self._userEditor.edit(cnx, vals)
|
|
self.get_userlist_cache().inval_cache() # >
|
|
self.acl_users.cache_removeUser(user_name) # exUserFolder's caches
|
|
self.acl_users.xcache_removeUser(user_name)
|
|
# Ensure that if status is "old", login is disabled
|
|
# note that operation is reversible without having to re-enter a password
|
|
# We change the roles (to avoid dealing with passwd hash, controled by exUserFolder)
|
|
u = self._user_list(args={"user_name": user_name})[0]
|
|
if u["status"] == "old" and u["roles"] and u["roles"][0] != "-":
|
|
roles = ["-" + r for r in u["roles"].split(",")]
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
|
|
self.get_userlist_cache().inval_cache()
|
|
elif not u["status"] and u["roles"] and u["roles"][0] == "-":
|
|
roles = [r[1:] for r in u["roles"].split(",") if (r and r[0] == "-")]
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
self.acl_users.scodoc_editUser(cursor, user_name, roles=roles)
|
|
self.get_userlist_cache().inval_cache()
|
|
|
|
def _user_delete(self, user_name):
|
|
# delete user
|
|
cnx = self.GetUsersDBConnexion()
|
|
user_id = self._user_list(args={"user_name": user_name})[0]["user_id"]
|
|
self._userEditor.delete(cnx, user_id)
|
|
self.get_userlist_cache().inval_cache() # >
|
|
|
|
def _all_roles(self):
|
|
"ensemble de tous les roles attribués ou attribuables"
|
|
roles = set(self.DeptUsersRoles())
|
|
cnx = self.GetUsersDBConnexion()
|
|
L = self._userEditor.list(cnx, {})
|
|
for l in L:
|
|
roles.update([x.strip() for x in l["roles"].split(",")])
|
|
return [r for r in roles if r and r[0] != "-"]
|
|
|
|
security.declareProtected(ScoUsersAdmin, "user_info")
|
|
|
|
def user_info(self, user_name=None, user=None):
|
|
"""Donne infos sur l'utilisateur (qui peut ne pas etre dans notre base).
|
|
Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict.
|
|
"""
|
|
if user_name:
|
|
infos = self._user_list(args={"user_name": user_name})
|
|
else:
|
|
infos = [user.copy()]
|
|
user_name = user["user_name"]
|
|
|
|
if not infos:
|
|
# special case: user is not in our database
|
|
return {
|
|
"user_name": user_name,
|
|
"nom": user_name,
|
|
"prenom": "",
|
|
"email": "",
|
|
"dept": "",
|
|
"nomprenom": user_name,
|
|
"prenomnom": user_name,
|
|
"prenom_fmt": "",
|
|
"nom_fmt": user_name,
|
|
"nomcomplet": user_name,
|
|
"nomplogin": user_name,
|
|
"nomnoacc": scu.suppress_accents(user_name),
|
|
"passwd_temp": 0,
|
|
"status": "",
|
|
"date_expiration": None,
|
|
}
|
|
else:
|
|
info = infos[0]
|
|
# always conceal password !
|
|
del info["passwd"] # always conceal password !
|
|
#
|
|
if info["prenom"]:
|
|
p = format_prenom(info["prenom"])
|
|
else:
|
|
p = ""
|
|
if info["nom"]:
|
|
n = format_nom(
|
|
info["nom"], uppercase=False
|
|
) # strcapitalize(strlower(info['nom']))
|
|
else:
|
|
n = user_name
|
|
|
|
prenom_abbrv = scu.abbrev_prenom(p)
|
|
# nomprenom est le nom capitalisé suivi de l'initiale du prénom
|
|
info["nomprenom"] = (n + " " + prenom_abbrv).strip()
|
|
# prenomnom est l'initiale du prénom suivie du nom
|
|
info["prenomnom"] = (prenom_abbrv + " " + n).strip()
|
|
# nom_fmt et prenom_fmt: minuscule capitalisé
|
|
info["nom_fmt"] = n
|
|
info["prenom_fmt"] = scolars.format_prenom(p)
|
|
# nomcomplet est le prenom et le nom complets
|
|
info["nomcomplet"] = info["prenom_fmt"] + " " + info["nom_fmt"]
|
|
# nomplogin est le nom en majuscules suivi du prénom et du login
|
|
# e.g. Dupont Pierre (dupont)
|
|
info["nomplogin"] = "%s %s (%s)" % (scu.strupper(n), p, info["user_name"])
|
|
# nomnoacc est le nom en minuscules sans accents
|
|
info["nomnoacc"] = scu.suppress_accents(scu.strlower(info["nom"]))
|
|
|
|
return info
|
|
|
|
def _can_handle_passwd(self, authuser, user_name, allow_admindepts=False):
|
|
"""true if authuser can see or change passwd of user_name.
|
|
If allow_admindepts, allow Admin from all depts (so they can view users from other depts
|
|
and add roles to them).
|
|
authuser is a Zope user object. user_name is a string.
|
|
"""
|
|
# Is authuser a zope admin ?
|
|
zope_roles = authuser.getRolesInContext(self)
|
|
if ("Manager" in zope_roles) or ("manage" in zope_roles):
|
|
return True
|
|
# Anyone can change its own passwd (or see its informations)
|
|
if str(authuser) == user_name:
|
|
return True
|
|
# has permission ?
|
|
if not authuser.has_permission(ScoUsersAdmin, self):
|
|
return False
|
|
# Ok, now check that authuser can manage users from this departement
|
|
# Get user info
|
|
user = self._user_list(args={"user_name": user_name})
|
|
if not user:
|
|
return False # we don't have infos on this user !
|
|
# Get authuser info
|
|
auth_name = str(authuser)
|
|
authuser_info = self._user_list(args={"user_name": auth_name})
|
|
if not authuser_info:
|
|
return False # not admin, and not in out database
|
|
auth_dept = authuser_info[0]["dept"]
|
|
if not auth_dept:
|
|
return True # if no dept, can access users from all depts !
|
|
if auth_dept == user[0]["dept"] or allow_admindepts:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def do_change_password(self, user_name, password):
|
|
user = self._user_list(args={"user_name": user_name})
|
|
assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user)
|
|
# should not occur, already tested in _can_handle_passwd
|
|
cnx = self.GetUsersDBConnexion() # en mode autocommit
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
cursor.execute(
|
|
"update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s",
|
|
{"user_name": user_name},
|
|
)
|
|
|
|
# Laisse le exUserFolder modifier les donnees:
|
|
self.acl_users.scodoc_editUser(
|
|
cursor, user_name, password=password, roles=[user[0]["roles"]]
|
|
)
|
|
|
|
log("change_password: change ok for %s" % user_name)
|
|
self.get_userlist_cache().inval_cache() # >
|
|
|
|
security.declareProtected(ScoView, "change_password")
|
|
|
|
def change_password(self, user_name, password, password2, REQUEST):
|
|
"change a password"
|
|
# ScoUsersAdmin: modif tous les passwd de SON DEPARTEMENT
|
|
# sauf si pas de dept (admin global)
|
|
H = []
|
|
F = self.sco_footer(REQUEST)
|
|
# Check access permission
|
|
if not self._can_handle_passwd(REQUEST.AUTHENTICATED_USER, user_name):
|
|
# access denied
|
|
log(
|
|
"change_password: access denied (authuser=%s, user_name=%s, ip=%s)"
|
|
% (REQUEST.AUTHENTICATED_USER, user_name, REQUEST.REMOTE_ADDR)
|
|
)
|
|
raise AccessDenied(
|
|
"vous n'avez pas la permission de changer ce mot de passe"
|
|
)
|
|
# check password
|
|
if password != password2:
|
|
H.append(
|
|
"""<p>Les deux mots de passes saisis sont différents !</p>
|
|
<p><a href="form_change_password?user_name=%s" class="stdlink">Recommencer</a></p>"""
|
|
% user_name
|
|
)
|
|
else:
|
|
if not sco_users.is_valid_password(password):
|
|
H.append(
|
|
"""<p><b>ce mot de passe n\'est pas assez compliqué !</b><br/>(oui, il faut un mot de passe vraiment compliqué !)</p>
|
|
<p><a href="form_change_password?user_name=%s" class="stdlink">Recommencer</a></p>
|
|
"""
|
|
% user_name
|
|
)
|
|
else:
|
|
# ok, strong password
|
|
# MD5 hash (now computed by exUserFolder)
|
|
# digest = md5.new()
|
|
# digest.update(password)
|
|
# digest = digest.digest()
|
|
# md5pwd = string.strip(base64.encodestring(digest))
|
|
#
|
|
self.do_change_password(user_name, password)
|
|
#
|
|
# ici page simplifiee car on peut ne plus avoir
|
|
# le droit d'acceder aux feuilles de style
|
|
H.append(
|
|
"<h2>Changement effectué !</h2><p>Ne notez pas ce mot de passe, mais mémorisez le !</p><p>Rappel: il est <b>interdit</b> de communiquer son mot de passe à un tiers, même si c'est un collègue de confiance !</p><p><b>Si vous n'êtes pas administrateur, le système va vous redemander votre login et nouveau mot de passe au prochain accès.</b></p>"
|
|
)
|
|
return (
|
|
"""<?xml version="1.0" encoding="%s"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html>
|
|
<head>
|
|
<title>Mot de passe changé</title>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=%s" />
|
|
<body><h1>Mot de passe changé !</h1>
|
|
"""
|
|
% (scu.SCO_ENCODING, scu.SCO_ENCODING)
|
|
+ "\n".join(H)
|
|
+ '<a href="%s" class="stdlink">Continuer</a></body></html>'
|
|
% self.ScoURL()
|
|
)
|
|
return self.sco_header(REQUEST) + "\n".join(H) + F
|
|
|
|
security.declareProtected(ScoView, "form_change_password")
|
|
|
|
def form_change_password(self, REQUEST, user_name=None):
|
|
"""Formulaire changement mot de passe
|
|
Un utilisateur peut toujours changer son mot de passe"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
if not user_name:
|
|
user_name = str(authuser)
|
|
H = [self.sco_header(REQUEST, user_check=False)]
|
|
F = self.sco_footer(REQUEST)
|
|
# check access
|
|
if not self._can_handle_passwd(authuser, user_name):
|
|
return (
|
|
"\n".join(H)
|
|
+ "<p>Vous n'avez pas la permission de changer ce mot de passe</p>"
|
|
+ F
|
|
)
|
|
#
|
|
H.append(
|
|
"""<h2>Changement du mot de passe de <font color="red">%(user_name)s</font></h2>
|
|
<p>
|
|
<form action="change_password" method="post" action="%(url)s"><table>
|
|
<tr><td>Nouveau mot de passe:</td><td><input type="password" size="14" name="password"/></td></tr>
|
|
<tr><td>Confirmation: </td><td><input type="password" size="14" name="password2" /></td></tr>
|
|
</table>
|
|
<input type="hidden" value="%(user_name)s" name="user_name">
|
|
<input type="submit" value="Changer">
|
|
</p>
|
|
<p>Vous pouvez aussi: <a class="stdlink" href="reset_password_form?user_name=%(user_name)s">renvoyer un mot de passe aléatoire temporaire par mail à l'utilisateur</a>
|
|
"""
|
|
% {"user_name": user_name, "url": REQUEST.URL0}
|
|
)
|
|
return "\n".join(H) + F
|
|
|
|
security.declareProtected(ScoView, "userinfo")
|
|
|
|
def userinfo(self, user_name=None, REQUEST=None):
|
|
"display page of info about connected user"
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
if not user_name:
|
|
user_name = str(authuser)
|
|
# peut on divulguer ces infos ?
|
|
if not self._can_handle_passwd(
|
|
REQUEST.AUTHENTICATED_USER, user_name, allow_admindepts=True
|
|
):
|
|
raise AccessDenied("Vous n'avez pas la permission de voir cette page")
|
|
H = [self.sco_header(REQUEST, page_title="Utilisateur %s" % user_name)]
|
|
F = self.sco_footer(REQUEST)
|
|
H.append("<h2>Utilisateur: %s" % user_name)
|
|
info = self._user_list(args={"user_name": user_name})
|
|
if info:
|
|
H.append("%(status_txt)s" % info[0])
|
|
H.append("</h2>")
|
|
if not info:
|
|
H.append(
|
|
"<p>L' utilisateur '%s' n'est pas défini dans ce module.</p>"
|
|
% user_name
|
|
)
|
|
if authuser.has_permission(ScoEditAllNotes, self):
|
|
H.append("<p>(il peut modifier toutes les notes)</p>")
|
|
if authuser.has_permission(ScoEditAllEvals, self):
|
|
H.append("<p>(il peut modifier toutes les évaluations)</p>")
|
|
if authuser.has_permission(ScoImplement, self):
|
|
H.append("<p>(il peut creer des formations)</p>")
|
|
else:
|
|
H.append(
|
|
"""<p>
|
|
<b>Login :</b> %(user_name)s<br/>
|
|
<b>Nom :</b> %(nom)s<br/>
|
|
<b>Prénom :</b> %(prenom)s<br/>
|
|
<b>Mail :</b> %(email)s<br/>
|
|
<b>Roles :</b> %(roles)s<br/>
|
|
<b>Dept :</b> %(dept)s<br/>
|
|
<b>Dernière modif mot de passe:</b> %(date_modif_passwd)s<br/>
|
|
<b>Date d'expiration:</b> %(date_expiration)s
|
|
<p><ul>
|
|
<li><a class="stdlink" href="form_change_password?user_name=%(user_name)s">changer le mot de passe</a></li>"""
|
|
% info[0]
|
|
)
|
|
if authuser.has_permission(ScoUsersAdmin, self):
|
|
H.append(
|
|
"""
|
|
<li><a class="stdlink" href="create_user_form?user_name=%(user_name)s&edit=1">modifier/déactiver ce compte</a></li>
|
|
<li><a class="stdlink" href="delete_user_form?user_name=%(user_name)s">supprimer cet utilisateur</a> <em>(à n'utiliser qu'en cas d'erreur !)</em></li>
|
|
"""
|
|
% info[0]
|
|
)
|
|
|
|
H.append("</ul>")
|
|
|
|
if str(authuser) == user_name:
|
|
H.append(
|
|
'<p><b>Se déconnecter: <a class="stdlink" href="acl_users/logout">logout</a></b></p>'
|
|
)
|
|
# Liste des permissions
|
|
H.append(
|
|
'<div class="permissions"><p>Permission de cet utilisateur:</p><ul>'
|
|
)
|
|
permissions = self.ac_inherited_permissions(1)
|
|
scoperms = [p for p in permissions if p[0][:3] == "Sco"]
|
|
try:
|
|
thisuser = self.acl_users.getUser(user_name)
|
|
except:
|
|
# expired from cache ? retry...
|
|
thisuser = self.acl_users.getUser(user_name)
|
|
if not thisuser:
|
|
# Cas de figure incompris ? (bug IUT Amiens janvier 2014: login avec accent ?)
|
|
H.append(
|
|
"<li><em>impossible de retrouver les permissions de l'utilisateur (contacter l'administrateur)</em></li>"
|
|
)
|
|
else:
|
|
for p in scoperms:
|
|
permname, _ = p[:2]
|
|
if thisuser.has_permission(permname, self):
|
|
b = "oui"
|
|
else:
|
|
b = "non"
|
|
H.append("<li>%s : %s</li>" % (permname, b))
|
|
H.append("</ul></div>")
|
|
if authuser.has_permission(ScoUsersAdmin, self):
|
|
H.append(
|
|
'<p><a class="stdlink" href="%s/Users">Liste de tous les utilisateurs</a></p>'
|
|
% self.ScoURL()
|
|
)
|
|
return "\n".join(H) + F
|
|
|
|
security.declareProtected(ScoUsersAdmin, "create_user_form")
|
|
|
|
def create_user_form(self, REQUEST, user_name=None, edit=0):
|
|
"form. creation ou edit utilisateur"
|
|
# Get authuser info
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
auth_name = str(authuser)
|
|
authuser_info = self._user_list(args={"user_name": auth_name})
|
|
initvalues = {}
|
|
|
|
# Access control
|
|
zope_roles = authuser.getRolesInContext(self)
|
|
if (
|
|
not authuser_info
|
|
and not ("Manager" in zope_roles)
|
|
and not ("manage" in zope_roles)
|
|
):
|
|
# not admin, and not in database
|
|
raise AccessDenied("invalid user (%s)" % auth_name)
|
|
if authuser_info:
|
|
auth_dept = authuser_info[0]["dept"]
|
|
else:
|
|
auth_dept = ""
|
|
#
|
|
edit = int(edit)
|
|
H = [self.sco_header(REQUEST, bodyOnLoad="init_tf_form('')")]
|
|
F = self.sco_footer(REQUEST)
|
|
if edit:
|
|
if not user_name:
|
|
raise ValueError("missing argument: user_name")
|
|
initvalues = self._user_list(args={"user_name": user_name})[0]
|
|
H.append("<h2>Modification de l'utilisateur %s</h2>" % user_name)
|
|
else:
|
|
H.append("<h2>Création d'un utilisateur</h2>")
|
|
|
|
if authuser.has_permission(ScoSuperAdmin, self):
|
|
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
|
|
|
|
# Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue
|
|
# si pas SuperAdmin, restreint aux rôles EnsX, SecrX, DeptX
|
|
#
|
|
if authuser.has_permission(ScoSuperAdmin, self):
|
|
log("create_user_form called by %s (super admin)" % (auth_name,))
|
|
editable_roles = set(self._all_roles())
|
|
else:
|
|
editable_roles = set(self.DeptUsersRoles())
|
|
# log('create_user_form: editable_roles=%s' % editable_roles)
|
|
#
|
|
if not edit:
|
|
submitlabel = "Créer utilisateur"
|
|
orig_roles = set()
|
|
else:
|
|
submitlabel = "Modifier utilisateur"
|
|
initvalues["roles"] = initvalues["roles"].split(",") or []
|
|
orig_roles = set(initvalues["roles"])
|
|
if initvalues["status"] == "old":
|
|
editable_roles = set() # can't change roles of a disabled user
|
|
# add existing user roles
|
|
displayed_roles = list(editable_roles.union(orig_roles))
|
|
displayed_roles.sort()
|
|
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
|
|
for i in range(len(displayed_roles)):
|
|
if displayed_roles[i] not in editable_roles:
|
|
disabled_roles[i] = True
|
|
|
|
# log('create_user_form: displayed_roles=%s' % displayed_roles)
|
|
|
|
descr = [
|
|
("edit", {"input_type": "hidden", "default": edit}),
|
|
("nom", {"title": "Nom", "size": 20, "allow_null": False}),
|
|
("prenom", {"title": "Prénom", "size": 20, "allow_null": False}),
|
|
]
|
|
if auth_name != user_name: # no one can't change its own status
|
|
descr.append(
|
|
(
|
|
"status",
|
|
{
|
|
"title": "Statut",
|
|
"input_type": "radio",
|
|
"labels": ("actif", "ancien"),
|
|
"allowed_values": ("", "old"),
|
|
},
|
|
)
|
|
)
|
|
if not edit:
|
|
descr += [
|
|
(
|
|
"user_name",
|
|
{
|
|
"title": "Pseudo (login)",
|
|
"size": 20,
|
|
"allow_null": False,
|
|
"explanation": "nom utilisé pour la connexion. Doit être unique parmi tous les utilisateurs.",
|
|
},
|
|
),
|
|
(
|
|
"passwd",
|
|
{
|
|
"title": "Mot de passe",
|
|
"input_type": "password",
|
|
"size": 14,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
(
|
|
"passwd2",
|
|
{
|
|
"title": "Confirmer mot de passe",
|
|
"input_type": "password",
|
|
"size": 14,
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
]
|
|
else:
|
|
descr += [
|
|
(
|
|
"user_name",
|
|
{"input_type": "hidden", "default": initvalues["user_name"]},
|
|
),
|
|
("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}),
|
|
]
|
|
descr += [
|
|
(
|
|
"email",
|
|
{
|
|
"title": "e-mail",
|
|
"input_type": "text",
|
|
"explanation": "vivement recommandé: utilisé pour contacter l'utilisateur",
|
|
"size": 20,
|
|
"allow_null": True,
|
|
},
|
|
)
|
|
]
|
|
|
|
if not auth_dept:
|
|
# si auth n'a pas de departement (admin global)
|
|
# propose de choisir le dept du nouvel utilisateur
|
|
# sinon, il sera créé dans le même département que auth
|
|
descr.append(
|
|
(
|
|
"dept",
|
|
{
|
|
"title": "Département",
|
|
"input_type": "text",
|
|
"size": 12,
|
|
"allow_null": True,
|
|
"explanation": """département d\'appartenance de l\'utilisateur (s'il s'agit d'un administrateur, laisser vide si vous voulez qu'il puisse créer des utilisateurs dans d'autres départements)""",
|
|
},
|
|
)
|
|
)
|
|
can_choose_dept = True
|
|
else:
|
|
can_choose_dept = False
|
|
if edit:
|
|
descr.append(
|
|
(
|
|
"d",
|
|
{
|
|
"input_type": "separator",
|
|
"title": "L'utilisateur appartient au département %s"
|
|
% auth_dept,
|
|
},
|
|
)
|
|
)
|
|
else:
|
|
descr.append(
|
|
(
|
|
"d",
|
|
{
|
|
"input_type": "separator",
|
|
"title": "L'utilisateur sera crée dans le département %s"
|
|
% auth_dept,
|
|
},
|
|
)
|
|
)
|
|
|
|
descr += [
|
|
(
|
|
"date_expiration",
|
|
{
|
|
"title": "Date d'expiration", # j/m/a
|
|
"input_type": "date",
|
|
"explanation": "j/m/a, laisser vide si pas de limite",
|
|
"size": 9,
|
|
"allow_null": True,
|
|
},
|
|
),
|
|
(
|
|
"roles",
|
|
{
|
|
"title": "Rôles",
|
|
"input_type": "checkbox",
|
|
"vertical": True,
|
|
"allowed_values": displayed_roles,
|
|
"disabled_items": disabled_roles,
|
|
},
|
|
),
|
|
(
|
|
"force",
|
|
{
|
|
"title": "Ignorer les avertissements",
|
|
"input_type": "checkbox",
|
|
"explanation": "passer outre les avertissements (homonymes, etc)",
|
|
"labels": ("",),
|
|
"allowed_values": ("1",),
|
|
},
|
|
),
|
|
]
|
|
|
|
if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form:
|
|
REQUEST.form["roles"] = []
|
|
if "tf-submitted" in REQUEST.form:
|
|
# Ajoute roles existants mais non modifiables (disabled dans le form)
|
|
# orig_roles - editable_roles
|
|
REQUEST.form["roles"] = list(
|
|
set(REQUEST.form["roles"]).union(orig_roles - editable_roles)
|
|
)
|
|
|
|
tf = TrivialFormulator(
|
|
REQUEST.URL0,
|
|
REQUEST.form,
|
|
descr,
|
|
initvalues=initvalues,
|
|
submitlabel=submitlabel,
|
|
cancelbutton="Annuler",
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + "\n" + tf[1] + F
|
|
elif tf[0] == -1:
|
|
return REQUEST.RESPONSE.redirect(self.UsersURL())
|
|
else:
|
|
vals = tf[2]
|
|
roles = set(vals["roles"]).intersection(editable_roles)
|
|
if REQUEST.form.has_key("edit"):
|
|
edit = int(REQUEST.form["edit"])
|
|
else:
|
|
edit = 0
|
|
try:
|
|
force = int(vals["force"][0])
|
|
except:
|
|
force = 0
|
|
|
|
if edit:
|
|
user_name = initvalues["user_name"]
|
|
else:
|
|
user_name = vals["user_name"]
|
|
# ce login existe ?
|
|
err = None
|
|
users = self._user_list(args={"user_name": user_name})
|
|
if edit and not users: # safety net, le user_name ne devrait pas changer
|
|
err = "identifiant %s inexistant" % user_name
|
|
if not edit and users:
|
|
err = "identifiant %s déjà utilisé" % user_name
|
|
if err:
|
|
H.append(tf_error_message("""Erreur: %s""" % err))
|
|
return "\n".join(H) + "\n" + tf[1] + F
|
|
|
|
if not force:
|
|
ok, msg = self._check_modif_user(
|
|
edit,
|
|
user_name=user_name,
|
|
nom=vals["nom"],
|
|
prenom=vals["prenom"],
|
|
email=vals["email"],
|
|
roles=vals["roles"],
|
|
)
|
|
if not ok:
|
|
H.append(
|
|
tf_error_message(
|
|
"""Attention: %s (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
|
|
% msg
|
|
)
|
|
)
|
|
|
|
return "\n".join(H) + "\n" + tf[1] + F
|
|
|
|
if edit: # modif utilisateur (mais pas passwd)
|
|
if (not can_choose_dept) and vals.has_key("dept"):
|
|
del vals["dept"]
|
|
if vals.has_key("passwd"):
|
|
del vals["passwd"]
|
|
if vals.has_key("date_modif_passwd"):
|
|
del vals["date_modif_passwd"]
|
|
if vals.has_key("user_name"):
|
|
del vals["user_name"]
|
|
if (auth_name == user_name) and vals.has_key("status"):
|
|
del vals["status"] # no one can't change its own status
|
|
|
|
# traitement des roles: ne doit pas affecter les roles
|
|
# que l'on en controle pas:
|
|
for role in orig_roles:
|
|
if role and not role in editable_roles:
|
|
roles.add(role)
|
|
|
|
vals["roles"] = ",".join(roles)
|
|
|
|
# ok, edit
|
|
log("sco_users: editing %s by %s" % (user_name, auth_name))
|
|
# log('sco_users: previous_values=%s' % initvalues)
|
|
# log('sco_users: new_values=%s' % vals)
|
|
self._user_edit(user_name, vals)
|
|
return REQUEST.RESPONSE.redirect(
|
|
"userinfo?user_name=%s&head_message=Utilisateur %s modifié"
|
|
% (user_name, user_name)
|
|
)
|
|
else: # creation utilisateur
|
|
vals["roles"] = ",".join(vals["roles"])
|
|
# check identifiant
|
|
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]):
|
|
msg = tf_error_message(
|
|
"identifiant invalide (pas d'accents ni de caractères spéciaux)"
|
|
)
|
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
|
# check passwords
|
|
if vals["passwd"] != vals["passwd2"]:
|
|
msg = tf_error_message(
|
|
"""Les deux mots de passes ne correspondent pas !"""
|
|
)
|
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
|
if not sco_users.is_valid_password(vals["passwd"]):
|
|
msg = tf_error_message(
|
|
"""Mot de passe trop simple, recommencez !"""
|
|
)
|
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
|
if not can_choose_dept:
|
|
vals["dept"] = auth_dept
|
|
# ok, go
|
|
log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name))
|
|
self.create_user(vals, REQUEST=REQUEST)
|
|
|
|
def _check_modif_user(
|
|
self, edit, user_name="", nom="", prenom="", email="", roles=[]
|
|
):
|
|
"""Vérifie que et utilisateur peut etre crée (edit=0) ou modifié (edit=1)
|
|
Cherche homonymes.
|
|
returns (ok, msg)
|
|
- ok : si vrai, peut continuer avec ces parametres
|
|
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
|
- msg: message warning a presenter l'utilisateur
|
|
"""
|
|
if not user_name or not nom or not prenom:
|
|
return False, "champ requis vide"
|
|
if not email:
|
|
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
|
# ce login existe ?
|
|
users = self._user_list(args={"user_name": user_name})
|
|
if edit and not users: # safety net, le user_name ne devrait pas changer
|
|
return False, "identifiant %s inexistant" % user_name
|
|
if not edit and users:
|
|
return False, "identifiant %s déjà utilisé" % user_name
|
|
|
|
# Des noms/prénoms semblables existent ?
|
|
cnx = self.GetUsersDBConnexion()
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
cursor.execute(
|
|
"select * from sco_users where lower(nom) ~ %(nom)s and lower(prenom) ~ %(prenom)s;",
|
|
{"nom": nom.lower().strip(), "prenom": prenom.lower().strip()},
|
|
)
|
|
res = cursor.dictfetchall()
|
|
if edit:
|
|
minmatch = 1
|
|
else:
|
|
minmatch = 0
|
|
if len(res) > minmatch:
|
|
return (
|
|
False,
|
|
"des utilisateurs proches existent: "
|
|
+ ", ".join(
|
|
[
|
|
"%s %s (pseudo=%s)" % (x["prenom"], x["nom"], x["user_name"])
|
|
for x in res
|
|
]
|
|
),
|
|
)
|
|
# Roles ?
|
|
if not roles and not (edit and users[0]["status"] == "old"):
|
|
# nb: si utilisateur desactivé (old), pas de role attribué
|
|
return False, "aucun rôle sélectionné, êtes vous sûr ?"
|
|
# ok
|
|
return True, ""
|
|
|
|
security.declareProtected(ScoUsersAdmin, "import_users_form")
|
|
|
|
def import_users_form(self, REQUEST, user_name=None, edit=0):
|
|
"""Import utilisateurs depuis feuille Excel"""
|
|
head = self.sco_header(REQUEST, page_title="Import utilisateurs")
|
|
H = [
|
|
head,
|
|
"""<h2>Téléchargement d'une nouvelle liste d'utilisateurs</h2>
|
|
<p style="color: red">A utiliser pour importer de <b>nouveaux</b> utilisateurs (enseignants ou secrétaires)
|
|
</p>
|
|
<p>
|
|
L'opération se déroule en deux étapes. Dans un premier temps,
|
|
vous téléchargez une feuille Excel type. Vous devez remplir
|
|
cette feuille, une ligne décrivant chaque utilisateur. Ensuite,
|
|
vous indiquez le nom de votre fichier dans la case "Fichier Excel"
|
|
ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur
|
|
votre liste.
|
|
</p>
|
|
""",
|
|
]
|
|
help = """<p class="help">
|
|
Lors de la creation des utilisateurs, les opérations suivantes sont effectuées:
|
|
</p>
|
|
<ol class="help">
|
|
<li>vérification des données;</li>
|
|
<li>génération d'un mot de passe alétoire pour chaque utilisateur;</li>
|
|
<li>création de chaque utilisateur;</li>
|
|
<li>envoi à chaque utilisateur de son <b>mot de passe initial par mail</b>.</li>
|
|
</ol>"""
|
|
H.append(
|
|
"""<ol><li><a class="stdlink" href="import_users_generate_excel_sample">
|
|
Obtenir la feuille excel à remplir</a></li><li>"""
|
|
)
|
|
F = self.sco_footer(REQUEST)
|
|
tf = TrivialFormulator(
|
|
REQUEST.URL0,
|
|
REQUEST.form,
|
|
(
|
|
(
|
|
"xlsfile",
|
|
{"title": "Fichier Excel:", "input_type": "file", "size": 40},
|
|
),
|
|
("formsemestre_id", {"input_type": "hidden"}),
|
|
),
|
|
submitlabel="Télécharger",
|
|
)
|
|
if tf[0] == 0:
|
|
return "\n".join(H) + tf[1] + "</li></ol>" + help + F
|
|
elif tf[0] == -1:
|
|
return REQUEST.RESPONSE.redirect(self.UsersURL())
|
|
else:
|
|
# IMPORT
|
|
diag = sco_import_users.import_excel_file(
|
|
tf[2]["xlsfile"], REQUEST=REQUEST, context=self
|
|
)
|
|
H = [head]
|
|
H.append("<p>Import excel: %s</p>" % diag)
|
|
H.append("<p>OK, import terminé !</p>")
|
|
H.append(
|
|
'<p><a class="stdlink" href="%s">Continuer</a></p>' % self.UsersURL()
|
|
)
|
|
return "\n".join(H) + help + F
|
|
|
|
security.declareProtected(ScoUsersAdmin, "import_users_generate_excel_sample")
|
|
|
|
def import_users_generate_excel_sample(self, REQUEST):
|
|
"une feuille excel pour importation utilisateurs"
|
|
data = sco_import_users.generate_excel_sample()
|
|
return sco_excel.sendExcelFile(REQUEST, data, "ImportUtilisateurs.xls")
|
|
|
|
security.declareProtected(ScoUsersAdmin, "create_user")
|
|
|
|
def create_user(self, args, REQUEST=None):
|
|
"creation utilisateur zope"
|
|
cnx = self.GetUsersDBConnexion()
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
passwd = args["passwd"]
|
|
args["passwd"] = "undefined"
|
|
if "passwd2" in args:
|
|
del args["passwd2"]
|
|
log("create_user: args=%s" % args) # log apres supr. du mot de passe !
|
|
_ = self._userEditor.create(cnx, args)
|
|
self.get_userlist_cache().inval_cache() # >
|
|
|
|
# call exUserFolder to set passwd
|
|
roles = args["roles"].split(",")
|
|
self.acl_users.scodoc_editUser(
|
|
cursor, args["user_name"], password=passwd, roles=roles
|
|
)
|
|
|
|
if REQUEST:
|
|
return REQUEST.RESPONSE.redirect(self.UsersURL())
|
|
|
|
security.declareProtected(ScoUsersAdmin, "delete_user_form")
|
|
|
|
def delete_user_form(self, REQUEST, user_name, dialog_confirmed=False):
|
|
"delete user"
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
if not self._can_handle_passwd(authuser, user_name):
|
|
return (
|
|
self.sco_header(REQUEST, user_check=False)
|
|
+ "<p>Vous n'avez pas la permission de supprimer cet utilisateur</p>"
|
|
+ self.sco_footer(REQUEST)
|
|
)
|
|
|
|
r = self._user_list(args={"user_name": user_name})
|
|
if len(r) != 1:
|
|
return ScoValueError("utilisateur %s inexistant" % user_name)
|
|
if not dialog_confirmed:
|
|
return self.confirmDialog(
|
|
"""<h2>Confirmer la suppression de l\'utilisateur %s ?</h2>
|
|
<p class="warning">En général, il est déconseillé de supprimer un utilisateur, son
|
|
identité étant référencé dans les modules de formation. N'utilisez
|
|
cette fonction qu'en cas d'erreur (création de doublons, etc).
|
|
</p>
|
|
"""
|
|
% user_name,
|
|
dest_url="",
|
|
REQUEST=REQUEST,
|
|
cancel_url=self.UsersURL(),
|
|
parameters={"user_name": user_name},
|
|
)
|
|
self._user_delete(user_name)
|
|
return REQUEST.RESPONSE.redirect(
|
|
self.UsersURL() + r"?head_message=Utilisateur%20supprimé"
|
|
)
|
|
|
|
security.declareProtected(ScoView, "list_users")
|
|
|
|
def list_users(
|
|
self,
|
|
dept,
|
|
all=False, # tous les departements
|
|
with_olds=False, # inclue les anciens utilisateurs (status "old")
|
|
format="html",
|
|
with_links=True,
|
|
REQUEST=None,
|
|
):
|
|
"List users"
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
if dept and not all:
|
|
r = self.get_userlist(dept=dept, with_olds=with_olds)
|
|
comm = "dept. %s" % dept
|
|
else:
|
|
r = self.get_userlist(with_olds=with_olds)
|
|
comm = "tous"
|
|
if with_olds:
|
|
comm += ", avec anciens"
|
|
comm = "(" + comm + ")"
|
|
# -- Add some information and links:
|
|
for u in r:
|
|
# Can current user modify this user ?
|
|
can_modify = self._can_handle_passwd(
|
|
authuser, u["user_name"], allow_admindepts=True
|
|
)
|
|
|
|
# Add links
|
|
if with_links and can_modify:
|
|
target = "userinfo?user_name=%(user_name)s" % u
|
|
u["_user_name_target"] = target
|
|
u["_nom_target"] = target
|
|
u["_prenom_target"] = target
|
|
|
|
# Hide passwd modification date (depending on rights wrt user)
|
|
if not can_modify:
|
|
u["date_modif_passwd"] = "(non visible)"
|
|
|
|
# Add spaces between roles to ease line wrap
|
|
if u["roles"]:
|
|
u["roles"] = ", ".join(u["roles"].split(","))
|
|
|
|
# Convert dates to ISO if XML output
|
|
if format == "xml" and u["date_modif_passwd"] != "NA":
|
|
u["date_modif_passwd"] = ndb.DateDMYtoISO(u["date_modif_passwd"]) or ""
|
|
|
|
# Convert date_expiration and date_modif_passwd to ISO to ease sorting
|
|
if u["date_expiration"]:
|
|
u["date_expiration_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
|
|
else:
|
|
u["date_expiration_iso"] = ""
|
|
if u["date_modif_passwd"]:
|
|
u["date_modif_passwd_iso"] = ndb.DateDMYtoISO(u["date_expiration"])
|
|
else:
|
|
u["date_modif_passwd_iso"] = ""
|
|
|
|
title = "Utilisateurs définis dans ScoDoc"
|
|
tab = GenTable(
|
|
rows=r,
|
|
columns_ids=(
|
|
"user_name",
|
|
"nom_fmt",
|
|
"prenom_fmt",
|
|
"email",
|
|
"dept",
|
|
"roles",
|
|
"date_expiration_iso",
|
|
"date_modif_passwd_iso",
|
|
"passwd_temp",
|
|
"status_txt",
|
|
),
|
|
titles={
|
|
"user_name": "Login",
|
|
"nom_fmt": "Nom",
|
|
"prenom_fmt": "Prénom",
|
|
"email": "Mail",
|
|
"dept": "Dept.",
|
|
"roles": "Rôles",
|
|
"date_expiration_iso": "Expiration",
|
|
"date_modif_passwd_iso": "Modif. mot de passe",
|
|
"passwd_temp": "Temp.",
|
|
"status_txt": "Etat",
|
|
},
|
|
caption=title,
|
|
page_title="title",
|
|
html_title="""<h2>%d utilisateurs %s</h2>
|
|
<p class="help">Cliquer sur un nom pour changer son mot de passe</p>"""
|
|
% (len(r), comm),
|
|
html_class="table_leftalign list_users",
|
|
html_with_td_classes=True,
|
|
html_sortable=True,
|
|
base_url="%s?all=%s" % (REQUEST.URL0, all),
|
|
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
|
|
preferences=self.get_preferences(),
|
|
)
|
|
|
|
return tab.make_page(
|
|
self, format=format, with_html_headers=False, REQUEST=REQUEST
|
|
)
|
|
|
|
def get_userlist_cache(self):
|
|
url = self.ScoURL()
|
|
if CACHE_userlist.has_key(url):
|
|
return CACHE_userlist[url]
|
|
else:
|
|
log("get_userlist_cache: new simpleCache")
|
|
CACHE_userlist[url] = sco_cache.expiringCache(max_validity=60)
|
|
return CACHE_userlist[url]
|
|
|
|
security.declareProtected(ScoView, "get_userlist")
|
|
|
|
def get_userlist(self, dept=None, with_olds=False):
|
|
"""Returns list of users.
|
|
If dept, select users from this dept,
|
|
else return all users.
|
|
"""
|
|
# on ne cache que la liste sans les "olds"
|
|
if with_olds:
|
|
r = None
|
|
else:
|
|
cache = self.get_userlist_cache()
|
|
r = cache.get(dept)
|
|
|
|
if r != None:
|
|
return r
|
|
else:
|
|
args = {}
|
|
if not with_olds:
|
|
args["status"] = None
|
|
if dept != None:
|
|
args["dept"] = dept
|
|
|
|
r = self._user_list(args=args)
|
|
|
|
l = [self.user_info(user=user) for user in r]
|
|
|
|
if not with_olds:
|
|
cache.set(dept, l)
|
|
return l
|
|
|
|
security.declareProtected(ScoView, "get_userlist_xml")
|
|
|
|
def get_userlist_xml(self, dept=None, start="", limit=25, REQUEST=None):
|
|
"""Returns XML list of users with name (nomplogin) starting with start.
|
|
Used for forms auto-completion."""
|
|
userlist = self.get_userlist(dept=dept)
|
|
start = scu.suppression_diacritics(unicode(start, "utf-8"))
|
|
start = scu.strlower(str(start))
|
|
|
|
userlist = [user for user in userlist if user["nomnoacc"].startswith(start)]
|
|
if REQUEST:
|
|
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
|
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
|
|
doc.results()
|
|
for user in userlist[:limit]:
|
|
doc._push()
|
|
doc.rs(user["nomplogin"], id=user["user_id"], info="")
|
|
doc._pop()
|
|
return repr(doc)
|
|
|
|
security.declareProtected(ScoView, "get_user_name_from_nomplogin")
|
|
|
|
def get_user_name_from_nomplogin(self, nomplogin):
|
|
"""Returns user_name (login) from nomplogin"""
|
|
m = re.match(r".*\((.*)\)", nomplogin.strip())
|
|
if m:
|
|
return m.group(1)
|
|
else:
|
|
return None
|
|
|
|
security.declareProtected(ScoView, "reset_password_form")
|
|
|
|
def reset_password_form(self, user_name=None, dialog_confirmed=False, REQUEST=None):
|
|
"""Form to reset a password"""
|
|
if not dialog_confirmed:
|
|
return self.confirmDialog(
|
|
"""<h2>Ré-initialiser le mot de passe de %s ?</h2>
|
|
<p>Le mot de passe de %s va être choisi au hasard et lui être envoyé par mail.
|
|
Il devra ensuite se connecter et le changer.
|
|
</p>
|
|
"""
|
|
% (user_name, user_name),
|
|
parameters={"user_name": user_name},
|
|
REQUEST=REQUEST,
|
|
)
|
|
self.reset_password(user_name=user_name, REQUEST=REQUEST)
|
|
return REQUEST.RESPONSE.redirect(
|
|
self.UsersURL()
|
|
+ r"?head_message=mot%20de%20passe%20de%20"
|
|
+ user_name
|
|
+ "%20reinitialise"
|
|
)
|
|
|
|
security.declareProtected(ScoView, "reset_password")
|
|
|
|
def reset_password(self, user_name=None, REQUEST=None):
|
|
"""Reset a password:
|
|
- set user's passwd_temp to 1
|
|
- set roles to 'ScoReset'
|
|
- generate a random password and mail it
|
|
"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
auth_name = str(authuser)
|
|
if not user_name:
|
|
user_name = auth_name
|
|
# Access control
|
|
if not self._can_handle_passwd(authuser, user_name):
|
|
raise AccessDenied(
|
|
"vous n'avez pas la permission de changer ce mot de passe"
|
|
)
|
|
log("reset_password: %s" % user_name)
|
|
# Check that user has valid mail
|
|
info = self.user_info(user_name=user_name)
|
|
if not scu.is_valid_mail(info["email"]):
|
|
raise Exception("pas de mail valide associé à l'utilisateur")
|
|
# Generate random password
|
|
password = sco_import_users.generate_password()
|
|
self.do_change_password(user_name, password)
|
|
# Flag it as temporary:
|
|
cnx = self.GetUsersDBConnexion()
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
ui = {"user_name": user_name}
|
|
cursor.execute(
|
|
"update sco_users set passwd_temp=1 where user_name='%(user_name)s'" % ui
|
|
)
|
|
# Send email
|
|
info["passwd"] = password
|
|
sco_import_users.mail_password(info, context=self, reset=True)
|
|
|
|
|
|
# --------------------------------------------------------------------
|
|
#
|
|
# Zope Product Administration
|
|
#
|
|
# --------------------------------------------------------------------
|
|
def manage_addZScoUsers(
|
|
self, id="id_ZScousers", title="The Title for ZScoUsers Object", REQUEST=None
|
|
):
|
|
"Add a ZScoUsers instance to a folder."
|
|
self._setObject(id, ZScoUsers(id, title))
|
|
if REQUEST is not None:
|
|
return self.manage_main(self, REQUEST)
|