- enregistre date derniere connection.
 - nouvelle permission: ScoUsersChangeCASId
 - améliore affichage infos utilisateur.
This commit is contained in:
Emmanuel Viennet 2023-03-02 14:49:18 +01:00
parent 5ca85a9da9
commit 0c0d43d075
10 changed files with 112 additions and 19 deletions

View File

@ -8,6 +8,7 @@ import flask
from flask import current_app, flash, url_for from flask import current_app, flash, url_for
from flask_login import login_user from flask_login import login_user
from app import db
from app.auth import bp from app.auth import bp
from app.auth.models import User from app.auth.models import User
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig
@ -27,7 +28,7 @@ def after_cas_login():
flask.session.get("CAS_USERNAME"), flask.session.get("CAS_USERNAME"),
) )
if cas_id is not None: if cas_id is not None:
user = User.query.filter_by(cas_id=cas_id).first() user: User = User.query.filter_by(cas_id=cas_id).first()
if user and user.active: if user and user.active:
if user.cas_allow_login: if user.cas_allow_login:
current_app.logger.info(f"CAS: login {user.user_name}") current_app.logger.info(f"CAS: login {user.user_name}")
@ -35,6 +36,9 @@ def after_cas_login():
flask.session[ flask.session[
"scodoc_cas_login_date" "scodoc_cas_login_date"
] = datetime.datetime.now().isoformat() ] = datetime.datetime.now().isoformat()
user.cas_last_login = datetime.datetime.utcnow()
db.session.add(user)
db.session.commit()
return flask.redirect(url_for("scodoc.index")) return flask.redirect(url_for("scodoc.index"))
else: else:
current_app.logger.info( current_app.logger.info(
@ -77,7 +81,7 @@ def set_cas_configuration(app: flask.app.Flask = None):
""" """
app = app or current_app app = app or current_app
if ScoDocSiteConfig.is_cas_enabled(): if ScoDocSiteConfig.is_cas_enabled():
current_app.logger.info("CAS: set_cas_configuration") current_app.logger.debug("CAS: set_cas_configuration")
app.config["CAS_SERVER"] = ScoDocSiteConfig.get("cas_server") app.config["CAS_SERVER"] = ScoDocSiteConfig.get("cas_server")
app.config["CAS_AFTER_LOGIN"] = "auth.after_cas_login" app.config["CAS_AFTER_LOGIN"] = "auth.after_cas_login"
app.config["CAS_AFTER_LOGOUT"] = "auth.after_cas_logout" app.config["CAS_AFTER_LOGOUT"] = "auth.after_cas_logout"

View File

@ -75,6 +75,8 @@ class User(UserMixin, db.Model):
"""Si CAS forcé (cas_force), peut-on se logguer sur ScoDoc directement ? """Si CAS forcé (cas_force), peut-on se logguer sur ScoDoc directement ?
(le rôle ScoSuperAdmin peut toujours, mettre à True pour les utilisateur API) (le rôle ScoSuperAdmin peut toujours, mettre à True pour les utilisateur API)
""" """
cas_last_login = db.Column(db.DateTime, nullable=True)
"""date du dernier login via CAS"""
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
password_scodoc7 = db.Column(db.String(42)) password_scodoc7 = db.Column(db.String(42))
@ -212,6 +214,9 @@ class User(UserMixin, db.Model):
"cas_id": self.cas_id, "cas_id": self.cas_id,
"cas_allow_login": self.cas_allow_login, "cas_allow_login": self.cas_allow_login,
"cas_allow_scodoc_login": self.cas_allow_scodoc_login, "cas_allow_scodoc_login": self.cas_allow_scodoc_login,
"cas_last_login": self.cas_last_login.isoformat() + "Z"
if self.cas_last_login
else None,
"status_txt": "actif" if self.active else "fermé", "status_txt": "actif" if self.active else "fermé",
"last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None, "last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None,
"nom": (self.nom or ""), # sco8 "nom": (self.nom or ""), # sco8
@ -250,7 +255,7 @@ class User(UserMixin, db.Model):
"cas_allow_login", "cas_allow_login",
"cas_allow_scodoc_login", "cas_allow_scodoc_login",
]: ]:
setattr(self, field, data.get(field, False)) setattr(self, field, scu.to_bool(data.get(field, False)))
if new_user: if new_user:
if "user_name" in data: if "user_name" in data:
@ -552,7 +557,7 @@ class UserRole(db.Model):
) )
def __repr__(self): def __repr__(self):
return "<UserRole u={} r={} dept={}>".format(self.user, self.role, self.dept) return f"<UserRole u={self.user} r={self.role} dept={self.dept}>"
@staticmethod @staticmethod
def role_dept_from_string(role_dept: str): def role_dept_from_string(role_dept: str):

View File

@ -225,7 +225,6 @@ _identiteEditor = ndb.EditableTable(
"nom", "nom",
"nom_usuel", "nom_usuel",
"prenom", "prenom",
"cas_id",
"civilite", # 'M", "F", or "X" "civilite", # 'M", "F", or "X"
"date_naissance", "date_naissance",
"lieu_naissance", "lieu_naissance",

View File

@ -5,7 +5,8 @@
used by auth used by auth
""" """
# Définition des permissions: ne pas changer les numéros ou l'ordre des lignes ! # Définition des permissions: NE PAS CHANGER les numéros ou l'ordre des lignes !
# Les permissions sont sur un BigInt en base SQL, donc 64 bits.
_SCO_PERMISSIONS = ( _SCO_PERMISSIONS = (
# permission bit, symbol, description # permission bit, symbol, description
# ScoSuperAdmin est utilisé pour: # ScoSuperAdmin est utilisé pour:
@ -53,8 +54,10 @@ _SCO_PERMISSIONS = (
"RelationsEntreprisesExport", "RelationsEntreprisesExport",
"Exporter les données de l'application relations entreprises", "Exporter les données de l'application relations entreprises",
), ),
# 27 à 39 ... réservé pour "entreprises" (1 << 29, "ScoUsersChangeCASId", "Paramétrer l'id CAS"),
#
(1 << 40, "ScoEtudChangePhoto", "Modifier la photo d'un étudiant"), (1 << 40, "ScoEtudChangePhoto", "Modifier la photo d'un étudiant"),
# Attention: les permissions sont codées sur 64 bits.
) )

View File

@ -162,7 +162,12 @@ def list_users(
if current_user.is_administrator(): if current_user.is_administrator():
columns_ids.append("last_seen") columns_ids.append("last_seen")
if ScoDocSiteConfig.is_cas_enabled(): if ScoDocSiteConfig.is_cas_enabled():
columns_ids += ["cas_id", "cas_allow_login", "cas_allow_scodoc_login"] columns_ids += [
"cas_id",
"cas_allow_login",
"cas_allow_scodoc_login",
"cas_last_login",
]
title = "Utilisateurs définis dans ScoDoc" title = "Utilisateurs définis dans ScoDoc"
tab = GenTable( tab = GenTable(
@ -183,6 +188,7 @@ def list_users(
"cas_id": "Id CAS", "cas_id": "Id CAS",
"cas_allow_login": "CAS autorisé", "cas_allow_login": "CAS autorisé",
"cas_allow_scodoc_login": "Cnx sans CAS", "cas_allow_scodoc_login": "Cnx sans CAS",
"cas_last_login": "Dernier login CAS",
}, },
caption=title, caption=title,
page_title="title", page_title="title",
@ -276,6 +282,7 @@ def check_modif_user(
): ):
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1) """Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
Cherche homonymes. Cherche homonymes.
Ne vérifie PAS que l'on a la permission de faire la modif.
returns (ok, msg) returns (ok, msg)
- ok : si vrai, peut continuer avec ces parametres - ok : si vrai, peut continuer avec ces parametres
(si ok est faux, l'utilisateur peut quand même forcer la creation) (si ok est faux, l'utilisateur peut quand même forcer la creation)

View File

@ -134,6 +134,31 @@ tr.bandeaugtr {
color: rgb(255, 0, 0); color: rgb(255, 0, 0);
} }
div.user_info div {
padding: 8px;
border-radius: 16px;
margin-bottom: 8px;
}
div.user_info ul li {
margin-bottom: 8px;
}
div.user_basics {
border: 1px solid blue;
background-color: #eeeeee;
}
div.user_info_admin {
border: 1px solid red;
background-color: #fdcaca;
}
div.user_info div.permissions {
border: 1px solid rgb(0, 0, 255);
background-color: #dedefd;
}
/* ----- page content ------ */ /* ----- page content ------ */
div.about-logo { div.about-logo {

View File

@ -4,8 +4,9 @@
{% block app_content %} {% block app_content %}
<div class="user_info">
<h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2> <h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2>
<p> <div class="user_basics">
<b>Login :</b> {{user.user_name}}<br> <b>Login :</b> {{user.user_name}}<br>
<b>CAS id:</b> {{user.cas_id or "(aucun)"}} <b>CAS id:</b> {{user.cas_id or "(aucun)"}}
(CAS {{'autorisé' if user.cas_allow_login else 'interdit'}} pour cet utilisateur) (CAS {{'autorisé' if user.cas_allow_login else 'interdit'}} pour cet utilisateur)
@ -17,12 +18,22 @@
<b>Prénom :</b> {{user.prenom or ""}}<br> <b>Prénom :</b> {{user.prenom or ""}}<br>
<b>Mail :</b> {{user.email}}<br> <b>Mail :</b> {{user.email}}<br>
<b>Roles :</b> {{user.get_roles_string()}}<br> <b>Roles :</b> {{user.get_roles_string()}}<br>
<b>Dept :</b> {{user.dept or ""}}<br> <b>Dept :</b> {{user.dept or ""}}
</div>
{% if current_user.is_administrator() %}
<div class="user_info_admin">
<b>Dernière vue :</b> {{user.last_seen or "-"}}<br>
<b>Dernière connexion CAS :</b> {{user.cas_last_login or "-"}}<br>
</div>
{% endif %}
<div class="user_basics">
<b>Dernière modif mot de passe:</b> <b>Dernière modif mot de passe:</b>
{{user.date_modif_passwd.isoformat() if user.date_modif_passwd else ""}}<br> {{user.date_modif_passwd.isoformat() if user.date_modif_passwd else ""}}<br>
<b>Date d'expiration:</b> <b>Date d'expiration:</b>
{{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}} {{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}}
<p> </div>
<div>
<ul> <ul>
{% if ( {% if (
current_user.is_administrator() current_user.is_administrator()
@ -52,6 +63,7 @@
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div>
{% if current_user.id == user.id %} {% if current_user.id == user.id %}
<div class="user_info_session"> <div class="user_info_session">
@ -71,9 +83,9 @@
<ul> <ul>
{% for p in Permission.description %} {% for p in Permission.description %}
<li>{{Permission.description[p]}} : <li>{{Permission.description[p]}} :
{{ <b>{{
"oui" if user.has_permission(Permission.get_by_name(p), dept) else "non" "oui" if user.has_permission(Permission.get_by_name(p), dept) else "non"
}} }}</b>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -84,6 +96,7 @@
{{url_for('users.index_html', scodoc_dept=g.scodoc_dept)}} {{url_for('users.index_html', scodoc_dept=g.scodoc_dept)}}
">Liste de tous les utilisateurs</a></p> ">Liste de tous les utilisateurs</a></p>
</div>
{% endif %} {% endif %}

View File

@ -395,7 +395,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
else "(service CAS non activé)", else "(service CAS non activé)",
"size": 36, "size": 36,
"allow_null": True, "allow_null": True,
"readonly": not cas_enabled, "readonly": not cas_enabled
or not current_user.has_permission(Permission.ScoUsersChangeCASId),
}, },
), ),
( (
@ -403,8 +404,9 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
{ {
"title": "Autorise connexion via CAS", "title": "Autorise connexion via CAS",
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"explanation": " seul le super-administrateur peut changer ce réglage", "explanation": """ si CAS est activé.
"readonly": not current_user.is_administrator(), Seul le super-administrateur peut changer ce réglage.""",
"enabled": current_user.is_administrator(),
}, },
), ),
( (
@ -412,8 +414,9 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
{ {
"title": "Autorise connexion via ScoDoc", "title": "Autorise connexion via ScoDoc",
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"explanation": " seul le super-administrateur peut changer ce réglage", "explanation": """ même si "Forcer l'utilisation de CAS" est activé.
"readonly": not current_user.is_administrator(), Seul le super-administrateur peut changer ce réglage""",
"enabled": current_user.is_administrator(),
}, },
), ),
] ]
@ -552,6 +555,12 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
else: else:
vals = tf[2] vals = tf[2]
roles = set(vals["roles"]).intersection(editable_roles_strings) roles = set(vals["roles"]).intersection(editable_roles_strings)
if not current_user.is_administrator():
# empeche modification des paramètres CAS
vals.pop("cas_allow_login", None)
vals.pop("cas_allow_scodoc_login", None)
if not current_user.has_permission(Permission.ScoUsersChangeCASId):
vals.pop("cas_id", None)
if "edit" in vals: if "edit" in vals:
edit = int(vals["edit"]) edit = int(vals["edit"])
else: else:

View File

@ -0,0 +1,28 @@
"""CAS last login
Revision ID: 5731e904baac
Revises: 4c19fcb42636
Create Date: 2023-03-02 11:36:58.465579
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "5731e904baac"
down_revision = "4c19fcb42636"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("user", sa.Column("cas_last_login", sa.DateTime(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("user", "cas_last_login")
# ### end Alembic commands ###

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.4.58" SCOVERSION = "9.4.59"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"