Compare commits
2 Commits
42101addad
...
187fe49c3c
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
Reference in New Issue
Block a user