Compare commits
7 Commits
42101addad
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f6d16b00db | |||
| 705ba49f12 | |||
| e81a317b4b | |||
| ea7f12e8a3 | |||
| 94c663b9aa | |||
| 187fe49c3c | |||
| 4d80af2925 |
@@ -716,9 +716,17 @@ Suggested messages, matching the repo's terse existing style:
|
|||||||
- `docs: update AGENTS.md for the Google OAuth migration`
|
- `docs: update AGENTS.md for the Google OAuth migration`
|
||||||
|
|
||||||
**Restore path for `ctp_blogr.sqlite`** (established in todo 2, required before todo 6):
|
**Restore path for `ctp_blogr.sqlite`** (established in todo 2, required before todo 6):
|
||||||
`<backup path from todo 2>` restores the pre-migration database, and
|
|
||||||
`git checkout -- ctp_blogr.sqlite` restores the committed version. The database diff is a real
|
- Branch: `feat/google-oauth`, created from `master` at `3dcb88e`. No commits, no pushes.
|
||||||
tracked change and must be inspected before any commit.
|
- Verified backup (outside the working tree):
|
||||||
|
`/var/folders/cm/x5cx1bfd749gxd1x_zt6bvp40000gn/T/opencode/ctp_blogr-backup-20261005-202240.sqlite`
|
||||||
|
— `PRAGMA integrity_check` returns `ok`.
|
||||||
|
- Restore the pre-migration database:
|
||||||
|
`cp /var/folders/cm/x5cx1bfd749gxd1x_zt6bvp40000gn/T/opencode/ctp_blogr-backup-20261005-202240.sqlite ctp_blogr.sqlite`
|
||||||
|
- Restore the committed database: `git checkout -- ctp_blogr.sqlite`
|
||||||
|
- Abort the whole migration: `git checkout master`
|
||||||
|
|
||||||
|
The database diff is a real tracked change and must be inspected before any commit.
|
||||||
|
|
||||||
## Success criteria
|
## Success criteria
|
||||||
- Signing in with `phuoc@caotek.fr` yields a session whose `authenticated_userid` is that email
|
- Signing in with `phuoc@caotek.fr` yields a session whose `authenticated_userid` is that email
|
||||||
|
|||||||
Binary file not shown.
+2
-34
@@ -1,6 +1,6 @@
|
|||||||
from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField
|
from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField
|
||||||
from wtforms import IntegerField
|
from wtforms import IntegerField
|
||||||
from wtforms.validators import InputRequired, Length
|
from wtforms.validators import InputRequired, Length, Email
|
||||||
from wtforms.widgets import HiddenInput
|
from wtforms.widgets import HiddenInput
|
||||||
|
|
||||||
strip_filter = lambda x: x.strip() if x else None
|
strip_filter = lambda x: x.strip() if x else None
|
||||||
@@ -26,39 +26,7 @@ class TagForm(Form):
|
|||||||
|
|
||||||
|
|
||||||
class UserEditForm(Form):
|
class UserEditForm(Form):
|
||||||
id = IntegerField(widget=HiddenInput())
|
name = StringField('Email', validators=[Email(), InputRequired(), Length(min=1, max=255)],
|
||||||
name = StringField('Nom', validators=[InputRequired(), Length(min=1, max=255)],
|
|
||||||
filters=[strip_filter])
|
filters=[strip_filter])
|
||||||
|
|
||||||
class HistoForm(Form):
|
|
||||||
no_id = IntegerField(widget=HiddenInput())
|
|
||||||
mvt_cash = DecimalField('Montant à ajouter au portefeuille', places=2, validators=[InputRequired()])
|
|
||||||
|
|
||||||
class AllocationForm(Form):
|
|
||||||
no_cat = IntegerField(widget=HiddenInput())
|
|
||||||
classe = StringField("Classe d'actif", validators=[InputRequired(), Length(min=1, max=25)], filters=[strip_filter])
|
|
||||||
pc_cible = IntegerField(validators=[InputRequired()])
|
|
||||||
type = SelectField('Type', choices=[('ACTION','ACTION'),('AUTRE','AUTRE')])
|
|
||||||
ordre = IntegerField(validators=[InputRequired()])
|
|
||||||
bg_color = SelectField('Couleur de fond', choices=[('info','BLEU'),('danger','ROUGE'),('warning','ORANGE'),('success','VERT')])
|
|
||||||
|
|
||||||
class ActifForm(Form):
|
|
||||||
no_id = IntegerField(widget=HiddenInput())
|
|
||||||
classe = SelectField('Classe')
|
|
||||||
symbole = StringField('Symbole', validators=[InputRequired(), Length(min=1, max=15)], filters=[strip_filter])
|
|
||||||
libelle = StringField('Nom', validators=[InputRequired(), Length(min=1, max=45)], filters=[strip_filter])
|
|
||||||
nombre = IntegerField(validators=[InputRequired()])
|
|
||||||
pru = DecimalField('PRU', places=3, validators=[InputRequired()])
|
|
||||||
ter = DecimalField('TER', places=2, validators=[InputRequired()])
|
|
||||||
pc_rdt = DecimalField('% rendement', places=2, validators=[InputRequired()])
|
|
||||||
website = StringField('Web site', validators=[Length(min=1, max=100)], filters=[strip_filter])
|
|
||||||
|
|
||||||
class Actif2Form(Form):
|
|
||||||
no_id = IntegerField(widget=HiddenInput())
|
|
||||||
classe = SelectField('Classe')
|
|
||||||
symbole = StringField('Symbole', validators=[InputRequired(), Length(min=1, max=15)], filters=[strip_filter])
|
|
||||||
libelle = StringField('Libellé', validators=[InputRequired(), Length(min=1, max=45)], filters=[strip_filter])
|
|
||||||
pru = DecimalField('Total investi', places=3, validators=[InputRequired()])
|
|
||||||
cours = DecimalField('Total valeur', places=3, validators=[InputRequired()])
|
|
||||||
pc_rdt = DecimalField('% rendement', places=2, validators=[InputRequired()])
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-default" href="{{ url_retour }}"><span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form action="{{ url }}" method="post" class="form">
|
<form action="{{ url }}" method="post" class="form">
|
||||||
|
|
||||||
{% for error in form.name.errors %}
|
{% for error in form.name.errors %}
|
||||||
@@ -19,24 +15,30 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="required-field" for="name">{{form.name.label}}</label>
|
<label class="required-field" for="name">{{form.name.label}}</label>
|
||||||
<input class="form-control" name="name" readonly type="text" value="{{form.name.data}}">
|
{% if name == "0" %}
|
||||||
|
<input class="form-control" name="name" type="text" value="{{form.name.data}}">
|
||||||
|
{% else %}
|
||||||
|
<input class="form-control" name="name" readonly type="text" value="{{form.name.data}}">
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="help-block">
|
|
||||||
L'adresse {{ form.name.data }} est votre compte Google autorisé. Elle
|
|
||||||
n'est pas modifiable ici : supprimez-la de la base et ajoutez-la à
|
|
||||||
<code>auth.admin_emails</code> ou à la table <code>users</code> pour
|
|
||||||
changer d'identité.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button class="btn btn-primary" type="submit" name="form.submitted">
|
<a class="btn btn-default" href="{{ url_retour }}"><span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
||||||
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
|
{% if name == "0" %}
|
||||||
{% if 'group:administrators' in request.effective_principals %}
|
<button class="btn btn-primary" type="submit" name="form.submitted">
|
||||||
|
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
|
||||||
|
{% else %}
|
||||||
<button class="btn btn-warning" type="submit" name="form.deleted">
|
<button class="btn btn-warning" type="submit" name="form.deleted">
|
||||||
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
|
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<p class="text-danger">
|
||||||
|
Attention : la suppression est définitive. Pas de retour possible.
|
||||||
|
</p>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,26 +2,30 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ request.route_url('home' ) }}" class="btn btn-default" role="button">
|
<a href="{{ request.route_url('home' ) }}">[ Retour ]</a>
|
||||||
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
<a href="{{ request.route_url('user_edit', name='0') }}">[ Nouvel utilisateur ]</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table id="users_list" class="table table-striped table-bordered table-condensed">
|
<table id="users_list" class="table table-striped table-bordered table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>No Id</th>
|
<th>Email</th>
|
||||||
<th>Nom</th>
|
<th>Google sub</th>
|
||||||
<th>Dernière connexion</th>
|
<th>Dernière connexion</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for entry in users %}
|
{% for entry in users %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ entry.id }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ request.route_url('user_edit', name=entry.name) }}">
|
{% if entry.name == 'phuoc@caotek.fr' %}
|
||||||
{{ entry.name }}
|
{{ entry.name }} (admin)
|
||||||
</a>
|
{% else%}
|
||||||
|
<a href="{{ request.route_url('user_edit', name=entry.name) }}">
|
||||||
|
{{ entry.name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>{{ entry.google_sub }}</td>
|
||||||
<td>{% if entry.last_logged %}{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}{% else %}jamais{% endif %}</td>
|
<td>{% if entry.last_logged %}{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}{% else %}jamais{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+22
-11
@@ -277,7 +277,7 @@ def blog():
|
|||||||
def url_callback(blog, code='code-de-test', state=None):
|
def url_callback(blog, code='code-de-test', state=None):
|
||||||
if state is None:
|
if state is None:
|
||||||
state = blog.google.state
|
state = blog.google.state
|
||||||
return '/login/google/callback?code=%s&state=%s' % (code, quote(state))
|
return '/login_google_callback?code=%s&state=%s' % (code, quote(state))
|
||||||
|
|
||||||
|
|
||||||
def cookie_session(client):
|
def cookie_session(client):
|
||||||
@@ -288,9 +288,12 @@ def cookie_session(client):
|
|||||||
|
|
||||||
|
|
||||||
def demarrer_connexion(blog, client):
|
def demarrer_connexion(blog, client):
|
||||||
# Premier saut : la page de connexion Google et la mise en session de
|
# Premier saut : envoi du formulaire de connexion (POST /login avec
|
||||||
|
# form.submitted), redirection vers Google et mise en session de
|
||||||
# state / nonce / code_verifier.
|
# state / nonce / code_verifier.
|
||||||
res = client.get('/login/google', status=302)
|
res = client.post('/login',
|
||||||
|
{'form.submitted': 'Se connecter avec Google'},
|
||||||
|
status=302)
|
||||||
assert 'accounts.google.com' in res.location
|
assert 'accounts.google.com' in res.location
|
||||||
assert blog.google.authorize_calls == 1
|
assert blog.google.authorize_calls == 1
|
||||||
assert blog.google.nonce and blog.google.code_verifier
|
assert blog.google.nonce and blog.google.code_verifier
|
||||||
@@ -317,7 +320,10 @@ def test_page_de_connexion_ne_propose_qu_un_bouton_google(blog):
|
|||||||
res = blog.client().get('/login')
|
res = blog.client().get('/login')
|
||||||
assert res.status_int == 200
|
assert res.status_int == 200
|
||||||
assert 'Se connecter avec Google' in res.text
|
assert 'Se connecter avec Google' in res.text
|
||||||
assert '/login/google' in res.text
|
# Un seul formulaire : un POST vers /login qui porte form.submitted,
|
||||||
|
# pas de lien vers une route de depart dediee (elle n'existe pas).
|
||||||
|
assert '<form action="http://localhost/login" method="post">' in res.text
|
||||||
|
assert 'name="form.submitted"' in res.text
|
||||||
assert '<input type="password"' not in res.text
|
assert '<input type="password"' not in res.text
|
||||||
assert 'name="password"' not in res.text
|
assert 'name="password"' not in res.text
|
||||||
|
|
||||||
@@ -405,7 +411,7 @@ def test_compte_google_inconnu_refuse_sans_creation_de_fiche(blog):
|
|||||||
|
|
||||||
def test_state_absent_refuse_la_connexion_sans_echange(blog):
|
def test_state_absent_refuse_la_connexion_sans_echange(blog):
|
||||||
client = blog.client()
|
client = blog.client()
|
||||||
res = client.get('/login/google/callback?code=code-de-test', status=302)
|
res = client.get('/login_google_callback?code=code-de-test', status=302)
|
||||||
assert res.location == 'http://localhost/login'
|
assert res.location == 'http://localhost/login'
|
||||||
assert blog.google.fetch_calls == 0
|
assert blog.google.fetch_calls == 0
|
||||||
|
|
||||||
@@ -434,7 +440,7 @@ def test_state_rejoue_refuse_la_connexion_sans_echange(blog):
|
|||||||
|
|
||||||
# Premier passage : le client Google refuse, l'etat est consomme.
|
# Premier passage : le client Google refuse, l'etat est consomme.
|
||||||
refuse = client.get(
|
refuse = client.get(
|
||||||
'/login/google/callback?error=access_denied&state=%s'
|
'/login_google_callback?error=access_denied&state=%s'
|
||||||
% quote(blog.google.state), status=302)
|
% quote(blog.google.state), status=302)
|
||||||
assert refuse.location == 'http://localhost/login'
|
assert refuse.location == 'http://localhost/login'
|
||||||
assert blog.google.fetch_calls == 0
|
assert blog.google.fetch_calls == 0
|
||||||
@@ -487,7 +493,7 @@ def test_code_absent_refuse_la_connexion_sans_echange(blog):
|
|||||||
demarrer_connexion(blog, client)
|
demarrer_connexion(blog, client)
|
||||||
|
|
||||||
res = client.get(
|
res = client.get(
|
||||||
'/login/google/callback?state=%s' % quote(blog.google.state), status=302)
|
'/login_google_callback?state=%s' % quote(blog.google.state), status=302)
|
||||||
assert res.location == 'http://localhost/login'
|
assert res.location == 'http://localhost/login'
|
||||||
assert blog.google.fetch_calls == 0
|
assert blog.google.fetch_calls == 0
|
||||||
|
|
||||||
@@ -544,7 +550,7 @@ def test_refus_de_l_utilisateur_chez_google_affiche_un_message(blog):
|
|||||||
demarrer_connexion(blog, client)
|
demarrer_connexion(blog, client)
|
||||||
|
|
||||||
res = client.get(
|
res = client.get(
|
||||||
'/login/google/callback?error=access_denied&state=%s'
|
'/login_google_callback?error=access_denied&state=%s'
|
||||||
% quote(blog.google.state), status=302)
|
% quote(blog.google.state), status=302)
|
||||||
assert res.location == 'http://localhost/login'
|
assert res.location == 'http://localhost/login'
|
||||||
assert blog.google.fetch_calls == 0
|
assert blog.google.fetch_calls == 0
|
||||||
@@ -770,7 +776,9 @@ def test_session_expiree_est_refusee_avec_une_horloge_simulee(blog, monkeypatch)
|
|||||||
|
|
||||||
def test_cookie_de_session_httponly(blog):
|
def test_cookie_de_session_httponly(blog):
|
||||||
client = blog.client()
|
client = blog.client()
|
||||||
res = client.get('/login/google', status=302)
|
res = client.post('/login',
|
||||||
|
{'form.submitted': 'Se connecter avec Google'},
|
||||||
|
status=302)
|
||||||
entetes = res.headers.getall('Set-Cookie')
|
entetes = res.headers.getall('Set-Cookie')
|
||||||
assert entetes
|
assert entetes
|
||||||
assert any('session=' in entete and 'HttpOnly' in entete for entete in entetes)
|
assert any('session=' in entete and 'HttpOnly' in entete for entete in entetes)
|
||||||
@@ -795,7 +803,8 @@ def test_le_garde_fou_reseau_bloque_les_sockets():
|
|||||||
def test_le_client_google_est_pose_comme_attribut_du_registry():
|
def test_le_client_google_est_pose_comme_attribut_du_registry():
|
||||||
# services/google_oauth.py::includeme ecrit l'attribut, pas la cle :
|
# services/google_oauth.py::includeme ecrit l'attribut, pas la cle :
|
||||||
# request.registry.google_oauth leveait AttributeError sinon, et la page
|
# request.registry.google_oauth leveait AttributeError sinon, et la page
|
||||||
# /login rendait correctement alors que /login/google etait morte.
|
# /login se rendait bien en GET alors que son envoi (POST, qui lance le
|
||||||
|
# flux vers Google) plantait.
|
||||||
wsgi_app, _ = configurer()
|
wsgi_app, _ = configurer()
|
||||||
assert type(wsgi_app.registry.google_oauth).__name__ == 'GoogleOAuth'
|
assert type(wsgi_app.registry.google_oauth).__name__ == 'GoogleOAuth'
|
||||||
|
|
||||||
@@ -803,7 +812,9 @@ def test_le_client_google_est_pose_comme_attribut_du_registry():
|
|||||||
def test_la_redirection_vers_google_est_atteignable():
|
def test_la_redirection_vers_google_est_atteignable():
|
||||||
blog = demarrer()
|
blog = demarrer()
|
||||||
client = blog.client()
|
client = blog.client()
|
||||||
res = client.get('/login/google')
|
res = client.post('/login',
|
||||||
|
{'form.submitted': 'Se connecter avec Google'},
|
||||||
|
status=302)
|
||||||
assert res.status_int == 302
|
assert res.status_int == 302
|
||||||
assert res.location.startswith('https://accounts.google.com/')
|
assert res.location.startswith('https://accounts.google.com/')
|
||||||
assert 'code_challenge_method=S256' in res.location
|
assert 'code_challenge_method=S256' in res.location
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ def blog_edit(request):
|
|||||||
form.populate_obj(entry)
|
form.populate_obj(entry)
|
||||||
# interdire le car '/' dans le titre à cause du slug
|
# interdire le car '/' dans le titre à cause du slug
|
||||||
entry.title = entry.title.replace('/','.')
|
entry.title = entry.title.replace('/','.')
|
||||||
entry.creator = request.authenticated_userid
|
entry.creator = request.authenticated_userid.split('@')[0]
|
||||||
entry.editor = entry.creator
|
entry.editor = entry.creator
|
||||||
request.dbsession.add(entry)
|
request.dbsession.add(entry)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def blog_edit(request):
|
|||||||
# interdire le car '/' dans le titre à cause du slug
|
# interdire le car '/' dans le titre à cause du slug
|
||||||
entry.title = entry.title.replace('/','.')
|
entry.title = entry.title.replace('/','.')
|
||||||
entry.edited = datetime.datetime.now()
|
entry.edited = datetime.datetime.now()
|
||||||
entry.editor = request.authenticated_userid
|
entry.editor = request.authenticated_userid.split('@')[0]
|
||||||
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
|
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
|
||||||
|
|
||||||
if 'form.update_created' in request.params and form.validate():
|
if 'form.update_created' in request.params and form.validate():
|
||||||
|
|||||||
+44
-22
@@ -5,6 +5,7 @@ from pyramid.view import view_config
|
|||||||
from pyramid.httpexceptions import HTTPForbidden, HTTPFound
|
from pyramid.httpexceptions import HTTPForbidden, HTTPFound
|
||||||
from pyramid.security import remember, forget
|
from pyramid.security import remember, forget
|
||||||
from ..services.google_oauth import GoogleOAuthError
|
from ..services.google_oauth import GoogleOAuthError
|
||||||
|
from ..models.user import User
|
||||||
from ..services.user import UserService
|
from ..services.user import UserService
|
||||||
from ..services.blog_record import BlogRecordService
|
from ..services.blog_record import BlogRecordService
|
||||||
from ..forms import UserEditForm
|
from ..forms import UserEditForm
|
||||||
@@ -16,12 +17,19 @@ def home(request):
|
|||||||
last_ten = BlogRecordService.get_last_created(request)
|
last_ten = BlogRecordService.get_last_created(request)
|
||||||
# get Chappatte cartoon
|
# get Chappatte cartoon
|
||||||
entry = BlogRecordService.by_id(request, 95)
|
entry = BlogRecordService.by_id(request, 95)
|
||||||
|
if entry:
|
||||||
|
cartoon_title = entry.title
|
||||||
|
cartoon_url = entry.body
|
||||||
|
else:
|
||||||
|
cartoon_title = "Cartoon non trouvé"
|
||||||
|
cartoon_url = ""
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'page_title': "This is the Way",
|
'page_title': "This is the Way",
|
||||||
'last_ten': last_ten,
|
'last_ten': last_ten,
|
||||||
'cartoon_title': entry.title,
|
'cartoon_title': cartoon_title,
|
||||||
'cartoon_url': entry.body,
|
'cartoon_url': cartoon_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -192,36 +200,50 @@ def users(request):
|
|||||||
renderer='ctp_blogr:templates/user_edit.jinja2',
|
renderer='ctp_blogr:templates/user_edit.jinja2',
|
||||||
permission='manage')
|
permission='manage')
|
||||||
def user_edit(request):
|
def user_edit(request):
|
||||||
# 'manage' et non 'view' : avec 'view', n'importe quel utilisateur
|
|
||||||
# connecte pouvait ouvrir /user_edit/<un-autre-email> et le modifier,
|
|
||||||
# ou /user_edit/0 pour creer une fiche.
|
|
||||||
message = ''
|
message = ''
|
||||||
name = request.matchdict['name']
|
name = request.matchdict['name']
|
||||||
url = request.route_url('user_edit', name=name)
|
url = request.route_url('user_edit', name=name)
|
||||||
if 'group:administrators' in request.effective_principals:
|
url_retour = request.route_url('users')
|
||||||
url_retour = request.route_url('users')
|
|
||||||
|
if name == '0':
|
||||||
|
# nouvel utilisateur
|
||||||
|
user = User()
|
||||||
|
form = UserEditForm(request.POST, user)
|
||||||
|
page_title = "Nouvel utilisateur"
|
||||||
else:
|
else:
|
||||||
url_retour = request.route_url('home')
|
# lire la fiche du user
|
||||||
|
user = UserService.by_name(request, name)
|
||||||
|
if not user:
|
||||||
|
request.session.flash("Utilisateur non trouvé : %s" % name, 'danger')
|
||||||
|
return HTTPFound(location=url_retour)
|
||||||
|
|
||||||
user = UserService.by_name(request, name)
|
form = UserEditForm(request.POST, user)
|
||||||
if not user:
|
page_title = "Modification utilisateur"
|
||||||
# '0' n'existe plus : la création d'un utilisateur a disparu avec
|
|
||||||
# le mot de passe.
|
|
||||||
request.session.flash("Utilisateur non trouvé : %s" % name, 'danger')
|
|
||||||
return HTTPFound(location=url_retour)
|
|
||||||
|
|
||||||
form = UserEditForm(request.POST, user)
|
|
||||||
page_title = "Modification utilisateur"
|
|
||||||
|
|
||||||
if 'form.submitted' in request.params and form.validate():
|
if 'form.submitted' in request.params and form.validate():
|
||||||
del form.name # SECURITY: prevent overwriting of primary key
|
# controle que name est bien un email
|
||||||
form.populate_obj(user)
|
if name == '0':
|
||||||
request.session.flash("La fiche a été modifiée avec succès.", 'success')
|
# It is obviously a user creation.
|
||||||
|
# check if the new user already exists in DB
|
||||||
|
new_user = UserService.by_name(request, form.name.data)
|
||||||
|
if new_user:
|
||||||
|
message = "Utilisateur déjà créé : %s" % form.name.data
|
||||||
|
else:
|
||||||
|
form.populate_obj(user)
|
||||||
|
# créer le nouveau
|
||||||
|
request.dbsession.add(user)
|
||||||
|
request.session.flash("La fiche a été créée avec succès.", 'success')
|
||||||
|
|
||||||
return HTTPFound(location=url_retour)
|
return HTTPFound(location=url_retour)
|
||||||
|
|
||||||
if 'form.deleted' in request.params:
|
if 'form.deleted' in request.params:
|
||||||
UserService.delete(request, user.id)
|
# check whether name is an admin
|
||||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
if name in 'group:administrators' in request.effective_principals:
|
||||||
|
request.session.flash("Il est interdit de supprimer un utisateur 'Admin'.", 'success')
|
||||||
|
else:
|
||||||
|
UserService.delete(request, user.id)
|
||||||
|
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||||
|
|
||||||
return HTTPFound(location=url_retour)
|
return HTTPFound(location=url_retour)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ requires = [
|
|||||||
'transaction',
|
'transaction',
|
||||||
'zope.sqlalchemy',
|
'zope.sqlalchemy',
|
||||||
'wtforms', # form library 2.2.1
|
'wtforms', # form library 2.2.1
|
||||||
|
'email_validator',
|
||||||
'webhelpers2', # various web building related helpers 2.0
|
'webhelpers2', # various web building related helpers 2.0
|
||||||
'markdown',
|
'markdown',
|
||||||
'authlib', # client OAuth 2.0 / OIDC pour l'authentification Google
|
'authlib', # client OAuth 2.0 / OIDC pour l'authentification Google
|
||||||
|
|||||||
Reference in New Issue
Block a user