Version in base suite: 2.16.1+ds-deb12u4 Base version: lemonldap-ng_2.16.1+ds-deb12u4 Target version: lemonldap-ng_2.16.1+ds-deb12u5 Base file: /srv/ftp-master.debian.org/ftp/pool/main/l/lemonldap-ng/lemonldap-ng_2.16.1+ds-deb12u4.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/l/lemonldap-ng/lemonldap-ng_2.16.1+ds-deb12u5.dsc changelog | 6 patches/CVE-2024-52948.patch | 1945 +++++++++++++++++++++++++ patches/fix-jwt.patch | 16 patches/fix-test-when-ldap-server-exists.patch | 17 patches/series | 3 5 files changed, 1987 insertions(+) diff -Nru lemonldap-ng-2.16.1+ds/debian/changelog lemonldap-ng-2.16.1+ds/debian/changelog --- lemonldap-ng-2.16.1+ds/debian/changelog 2024-11-19 17:44:18.000000000 +0000 +++ lemonldap-ng-2.16.1+ds/debian/changelog 2025-02-02 11:04:04.000000000 +0000 @@ -1,3 +1,9 @@ +lemonldap-ng (2.16.1+ds-deb12u5) bookworm; urgency=medium + + * Fix CSRF on 2FA registration interface (Closes: CVE-2024-52948) + + -- Yadd Sun, 02 Feb 2025 12:04:04 +0100 + lemonldap-ng (2.16.1+ds-deb12u4) bookworm; urgency=medium * Fix authentication privilege (Closes: CVE-2024-52946) diff -Nru lemonldap-ng-2.16.1+ds/debian/patches/CVE-2024-52948.patch lemonldap-ng-2.16.1+ds/debian/patches/CVE-2024-52948.patch --- lemonldap-ng-2.16.1+ds/debian/patches/CVE-2024-52948.patch 1970-01-01 00:00:00.000000000 +0000 +++ lemonldap-ng-2.16.1+ds/debian/patches/CVE-2024-52948.patch 2025-02-02 10:30:27.000000000 +0000 @@ -0,0 +1,1945 @@ +Description: fix CSRF on 2FA registration +Author: Maxime Besson +Origin: upstream, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/644 +Bug: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/3258 +Forwarded: not-needed +Applied-Upstream: 2.20.2, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/638/diffs +Reviewed-By: Yadd +Last-Update: 2025-01-22 + +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Base.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Base.pm +@@ -72,4 +72,10 @@ + return $name; + } + ++sub checkCsrf { ++ my ( $self, $req ) = @_; ++ ++ return $req->headers->header('X-CSRF-Check'); ++} ++ + 1; +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm +@@ -49,6 +49,10 @@ + + # Send a code to generic + if ( $action eq 'sendcode' ) { ++ ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + my $generic = $req->param('generic'); + + unless ($generic) { +@@ -85,6 +89,10 @@ + + # Verification that user has a valid generic + elsif ( $action eq 'verify' ) { ++ ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + my $generic = $req->param('generic'); + my $tokenid = $req->param("token"); + my $genericcode = $req->param('genericcode'); +@@ -150,6 +158,9 @@ + + elsif ( $action eq 'delete' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check if unregistration is allowed + return $self->p->sendError( $req, 'notAuthorized', 400 ) + unless $self->userCanRemove; +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm +@@ -56,6 +56,9 @@ + # Verification that user has a valid password + if ( $action eq 'verify' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check Password + my $password = $req->param('password'); + my $passwordverify = $req->param('passwordverify'); +@@ -129,6 +132,9 @@ + } + elsif ( $action eq 'delete' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check if unregistration is allowed + return $self->p->sendError( $req, 'notAuthorized', 400 ) + unless $self->conf->{password2fUserCanRemoveKey}; +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm +@@ -41,6 +41,9 @@ + # Verification that user has a valid TOTP app + if ( $action eq 'verify' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Get form token + my $token = $req->param('token'); + unless ($token) { +@@ -140,6 +143,9 @@ + elsif ( $action eq 'getkey' ) { + my ( $nk, $secret, $issuer ) = ( 0, '' ); + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Read existing TOTP 2F + my @totp2f = + $self->find2fDevicesByType( $req, $req->userData, $self->type ); +@@ -184,6 +190,9 @@ + # Delete TOTP + elsif ( $action eq 'delete' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check if unregistration is allowed + return $self->p->sendError( $req, 'notAuthorized', 400 ) + unless $self->userCanRemove; +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm +@@ -202,6 +202,9 @@ + + elsif ( $action eq 'delete' ) { + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check if unregistration is allowed + return $self->p->sendError( $req, 'notAuthorized', 400 ) + unless $self->userCanRemove; +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm +@@ -79,6 +79,10 @@ + + sub _registrationchallenge { + my ( $self, $req, $user ) = @_; ++ ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + my @alldevices = $self->find2fDevicesByType( $req, $req->userData ); + my $challenge_base64 = encode_base64url( Crypt::URandom::urandom(32) ); + +@@ -119,6 +123,9 @@ + sub _registration { + my ( $self, $req, $user ) = @_; + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Recover creation parameters, including challenge + my $state_id = $req->param('state_id'); + unless ($state_id) { +@@ -205,6 +212,9 @@ + sub _verificationchallenge { + my ( $self, $req, $user ) = @_; + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + $self->logger->debug( $self->prefix . '2f: verification challenge req' ); + + my $request = $self->generateChallenge( $req, $req->userData ); +@@ -229,6 +239,9 @@ + sub _verification { + my ( $self, $req, $user ) = @_; + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + my $credential_json = $req->param('credential'); + + unless ($credential_json) { +@@ -274,6 +287,9 @@ + sub _delete { + my ( $self, $req, $user ) = @_; + ++ $self->checkCsrf($req) ++ or return $self->p->sendError( $req, 'csrfError', 400 ); ++ + # Check if unregistration is allowed + return $self->p->sendError( $req, 'notAuthorized', 400 ) + unless $self->userCanRemove; +--- a/lemonldap-ng-portal/site/coffee/2fregistration.coffee ++++ b/lemonldap-ng-portal/site/coffee/2fregistration.coffee +@@ -41,6 +41,8 @@ + url: "#{portal}2fregisters/#{prefix}/delete" + data: + epoch: epoch ++ headers: ++ "X-CSRF-Check": 1 + dataType: 'json' + error: displayError + success: (resp) -> +--- a/lemonldap-ng-portal/site/coffee/generic2fregistration.coffee ++++ b/lemonldap-ng-portal/site/coffee/generic2fregistration.coffee +@@ -32,6 +32,8 @@ + dataType: 'json' + data: + generic: generic ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (data) -> + if data.error +@@ -62,6 +64,8 @@ + genericname: genericname + genericcode: genericcode + token: token ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (data) -> + if data.error +--- a/lemonldap-ng-portal/site/coffee/password2fregistration.coffee ++++ b/lemonldap-ng-portal/site/coffee/password2fregistration.coffee +@@ -33,6 +33,8 @@ + data: + password: password + passwordverify: passwordverify ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (data) -> + if data.error +--- a/lemonldap-ng-portal/site/coffee/totpregistration.coffee ++++ b/lemonldap-ng-portal/site/coffee/totpregistration.coffee +@@ -27,6 +27,8 @@ + type: "POST", + url: "#{portal}/2fregisters/totp/getkey" + dataType: 'json' ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + # Display key and QR code + success: (data) -> +@@ -73,6 +75,8 @@ + token: token + code: val + TOTPName: $('#TOTPName').val() ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (data) -> + if data.error +--- a/lemonldap-ng-portal/site/coffee/webauthnregistration.coffee ++++ b/lemonldap-ng-portal/site/coffee/webauthnregistration.coffee +@@ -31,6 +31,8 @@ + url: "#{portal}2fregisters/webauthn/registrationchallenge" + data: {} + dataType: 'json' ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (ch) -> + # 2 build response +@@ -47,6 +49,8 @@ + credential: JSON.stringify response + keyName: $('#keyName').val() + dataType: 'json' ++ headers: ++ "X-CSRF-Check": 1 + success: (resp) -> + if resp.error + if resp.error.match /badName/ +@@ -67,6 +71,8 @@ + url: "#{portal}2fregisters/webauthn/verificationchallenge" + data: {} + dataType: 'json' ++ headers: ++ "X-CSRF-Check": 1 + error: displayError + success: (ch) -> + # 2 build response +@@ -81,6 +87,8 @@ + state_id: ch.state_id + credential: JSON.stringify response + dataType: 'json' ++ headers: ++ "X-CSRF-Check": 1 + success: (resp) -> + if resp.error + setMsg 'webAuthnFailed', 'danger' +--- a/lemonldap-ng-portal/site/htdocs/static/common/js/2fregistration.js ++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/2fregistration.js +@@ -54,6 +54,9 @@ + data: { + epoch: epoch + }, ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + dataType: 'json', + error: displayError, + success: function(resp) { +--- a/lemonldap-ng-portal/site/htdocs/static/common/js/generic2fregistration.js ++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/generic2fregistration.js +@@ -45,6 +45,9 @@ + data: { + generic: generic + }, ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(data) { + if (data.error) { +@@ -83,6 +86,9 @@ + genericcode: genericcode, + token: token + }, ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(data) { + if (data.error) { +--- a/lemonldap-ng-portal/site/htdocs/static/common/js/password2fregistration.js ++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/password2fregistration.js +@@ -46,6 +46,9 @@ + password: password, + passwordverify: passwordverify + }, ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(data) { + if (data.error) { +--- a/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js ++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js +@@ -38,6 +38,9 @@ + type: "POST", + url: portal + "/2fregisters/totp/getkey", + dataType: 'json', ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(data) { + var qr, s, secret; +@@ -91,6 +94,9 @@ + code: val, + TOTPName: $('#TOTPName').val() + }, ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(data) { + if (data.error) { +--- a/lemonldap-ng-portal/site/htdocs/static/common/js/webauthnregistration.js ++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/webauthnregistration.js +@@ -44,6 +44,9 @@ + url: portal + "2fregisters/webauthn/registrationchallenge", + data: {}, + dataType: 'json', ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(ch) { + var request; +@@ -60,6 +63,9 @@ + keyName: $('#keyName').val() + }, + dataType: 'json', ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + success: function(resp) { + if (resp.error) { + if (resp.error.match(/badName/)) { +@@ -91,6 +97,9 @@ + url: portal + "2fregisters/webauthn/verificationchallenge", + data: {}, + dataType: 'json', ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + error: displayError, + success: function(ch) { + var request; +@@ -105,6 +114,9 @@ + credential: JSON.stringify(response) + }, + dataType: 'json', ++ headers: { ++ "X-CSRF-Check": 1 ++ }, + success: function(resp) { + if (resp.error) { + return setMsg('webAuthnFailed', 'danger'); +--- a/lemonldap-ng-portal/site/htdocs/static/languages/ar.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/ar.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Impersonate another user", + "continue":"استمر", + "createAccount":"انشئ حساب", ++"csrfError":"CSRF check failed", + "current":"Current", + "currentPwd":"كلمة المرور الحالية", + "date":"تاريخ", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/de.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/de.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Impersonate another user", + "continue":"Weiter", + "createAccount":"Konto erstellen", ++"csrfError":"CSRF check failed", + "current":"Current", + "currentPwd":"Aktuelles Passwort", + "date":"Datum", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/en.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/en.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Impersonate another user", + "continue":"Continue", + "createAccount":"Create an account", ++"csrfError":"CSRF check failed", + "current":"Current", + "currentPwd":"Current password", + "date":"Date", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/es.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/es.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Suplantar otro usuario", + "continue":"Continuar", + "createAccount":"Crear una cuenta", ++"csrfError":"CSRF check failed", + "current":"Actual", + "currentPwd":"Contraseña actual", + "date":"Fecha", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/fi.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/fi.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Esiinny toisena käyttäjänä", + "continue":"Jatka", + "createAccount":"Luo käyttäjätili", ++"csrfError":"CSRF check failed", + "current":"Nykyinen", + "currentPwd":"Nykyinen salasana", + "date":"Päivämäärä", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/fr.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/fr.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Endosser l'identité d'un autre utilisateur", + "continue":"Continuer", + "createAccount":"Créer un compte", ++"csrfError":"CSRF check failed", + "current":"Courante", + "currentPwd":"Mot de passe actuel", + "date":"Date", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/he.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/he.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"התחזות למשתמש אחר", + "continue":"להמשיך", + "createAccount":"יצירת חשבון", ++"csrfError":"CSRF check failed", + "current":"נוכחית", + "currentPwd":"סיסמה נוכחית", + "date":"תאריך", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/it.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/it.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Impersonate another user", + "continue":"Procedi", + "createAccount":"Crea un account", ++"csrfError":"CSRF check failed", + "current":"Current", + "currentPwd":"Password attuale", + "date":"Data", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/pl.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pl.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Podszyj się pod innego użytkownika", + "continue":"Kontynuuj", + "createAccount":"Utwórz konto", ++"csrfError":"CSRF check failed", + "current":"Obecny", + "currentPwd":"Aktualne hasło", + "date":"Data", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/pt.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pt.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Personifique outro usuário", + "continue":"Continue", + "createAccount":"Criar uma conta", ++"csrfError":"CSRF check failed", + "current":"Atual", + "currentPwd":"Senha atual", + "date":"Data", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/pt_BR.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pt_BR.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Personifique outro usuário", + "continue":"Continue", + "createAccount":"Criar uma conta", ++"csrfError":"CSRF check failed", + "current":"Atual", + "currentPwd":"Senha atual", + "date":"Data", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/tr.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/tr.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Başka bir kullanıcı gibi görün", + "continue":"Devam Et", + "createAccount":"Hesap oluştur", ++"csrfError":"CSRF check failed", + "current":"Geçerli", + "currentPwd":"Mevcut parola", + "date":"Tarih", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/vi.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/vi.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"Mạo danh người dùng khác", + "continue":"Tiếp tục", + "createAccount":"Tạo một tài khoản", ++"csrfError":"CSRF check failed", + "current":"Hiện tại", + "currentPwd":"Mật khẩu hiện tại", + "date":"Ngày", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/zh.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/zh.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"模擬其他使用者", + "continue":"继续", + "createAccount":"创建账户", ++"csrfError":"CSRF check failed", + "current":"目前", + "currentPwd":"当前密码", + "date":"日期", +--- a/lemonldap-ng-portal/site/htdocs/static/languages/zh_TW.json ++++ b/lemonldap-ng-portal/site/htdocs/static/languages/zh_TW.json +@@ -155,6 +155,7 @@ + "contextSwitching_ON":"模擬其他使用者", + "continue":"繼續", + "createAccount":"建立帳號", ++"csrfError":"CSRF check failed", + "current":"目前", + "currentPwd":"目前的密碼", + "date":"日期", +--- a/lemonldap-ng-portal/t/01-WebAuthn-Registration.t ++++ b/lemonldap-ng-portal/t/01-WebAuthn-Registration.t +@@ -129,6 +129,9 @@ + IO::String->new('{}'), + cookie => "lemonldap=$id", + length => 2, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Registration challenge' + ); +@@ -168,6 +171,9 @@ + IO::String->new($registration_response), + cookie => "lemonldap=$id", + length => length($registration_response), ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Registration challenge' + ); +@@ -198,6 +204,9 @@ + IO::String->new('{}'), + cookie => "lemonldap=$id", + length => 2, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Registration challenge' + ); +@@ -232,6 +241,9 @@ + IO::String->new($verification_response), + cookie => "lemonldap=$id", + length => length($verification_response), ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Registration challenge' + ); +@@ -334,12 +346,29 @@ + ok( $epoch, "Found epoch for $name" ); + + my $delete_query = buildForm( { epoch => $epoch } ); ++ { ++ my $delete_query = buildForm( { epoch => $epoch } ); ++ $res = $client->_post( ++ '/2fregisters/webauthn/delete', ++ $delete_query, ++ length => length($delete_query), ++ cookie => "lemonldap=$id", ++ ); ++ my $json = expectBadRequest($res); ++ ok( ++ $res->[2]->[0] =~ 'csrfError', ++ "Deletion expects valid CSRF token" ++ ); ++ } + ok( + $res = $client->_post( + '/2fregisters/webauthn/delete', + $delete_query, + length => length($delete_query), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete WebAuthn query' + ); +--- a/lemonldap-ng-portal/t/35-REST-sessions-with-AuthBasic-handler-with-2FA.t ++++ b/lemonldap-ng-portal/t/35-REST-sessions-with-AuthBasic-handler-with-2FA.t +@@ -84,9 +84,13 @@ + # JS query + ok( + $res = $p->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -111,6 +115,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -118,22 +125,22 @@ + ok( not($@), 'Content is JSON' ) + or explain( $res->[2]->[0], 'JSON content' ); + ok( $res->{result} == 1, 'Key is registered' ); +- ok( $res = $p->_get( '/', accept => 'text/html' ), 'Get Menu', ); +- ( $host, $url, $query ) = +- expectForm( $res, '#', undef, 'user', 'password' ); +- +- $query =~ s/user=/user=dwho/; +- $query =~ s/password=/password=dwho/; +- ok( +- $res = $p->_post( +- '/', +- IO::String->new($query), +- length => length($query), +- accept => 'text/html', +- ), +- 'Auth query' +- ); +- ( $host, $url, $query ) = expectForm( $res, undef, '/totp2fcheck' ); ++ ok( $res = $p->_get( '/', accept => 'text/html' ), 'Get Menu', ); ++ ( $host, $url, $query ) = ++ expectForm( $res, '#', undef, 'user', 'password' ); ++ ++ $query =~ s/user=/user=dwho/; ++ $query =~ s/password=/password=dwho/; ++ ok( ++ $res = $p->_post( ++ '/', ++ IO::String->new($query), ++ length => length($query), ++ accept => 'text/html', ++ ), ++ 'Auth query' ++ ); ++ ( $host, $url, $query ) = expectForm( $res, undef, '/totp2fcheck' ); + + ok( + $res = handler( +@@ -166,12 +173,11 @@ + ), + 'AuthBasic request' + ); +- ok( $res->[0] == 401, "Authentication rejected"); ++ ok( $res->[0] == 401, "Authentication rejected" ); + } + ok( $subtest == 1, 'REST requests were done by handler' ); + +- +- $subtest=0; ++ $subtest = 0; + foreach my $user (qw(dwho)) { + ok( + $res = handler( +@@ -204,8 +210,8 @@ + ), + 'New AuthBasic request' + ); +- ok( $subtest == 1, 'Handler used its local cache' ); +- ok( $res->[0] == 401, 'Authentication rejected a second time'); ++ ok( $subtest == 1, 'Handler used its local cache' ); ++ ok( $res->[0] == 401, 'Authentication rejected a second time' ); + } + + foreach my $user (qw(rtyler)) { +--- a/lemonldap-ng-portal/t/36-Combination-with-TOTP.t ++++ b/lemonldap-ng-portal/t/36-Combination-with-TOTP.t +@@ -13,8 +13,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + loginHistoryEnabled => 0, +@@ -80,9 +79,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -106,6 +109,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/38-No-persistent-session.t ++++ b/lemonldap-ng-portal/t/38-No-persistent-session.t +@@ -17,8 +17,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -99,9 +98,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -125,6 +128,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t ++++ b/lemonldap-ng-portal/t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t +@@ -14,8 +14,7 @@ + } + my $res; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + authentication => 'Demo', +@@ -50,9 +49,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -77,6 +80,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/64-StayConnected-with-2F-and-History.t ++++ b/lemonldap-ng-portal/t/64-StayConnected-with-2F-and-History.t +@@ -159,9 +159,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -187,6 +191,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/67-CheckUser.t ++++ b/lemonldap-ng-portal/t/67-CheckUser.t +@@ -8,8 +8,7 @@ + + my $res; + +-my $client = LLNG::Manager::Test->new( +- { ++my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + authentication => 'Demo', +@@ -123,9 +122,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -149,6 +152,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-2F-allowed.t ++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-2F-allowed.t +@@ -100,9 +100,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -126,6 +130,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -369,9 +376,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id2", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -396,6 +407,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -431,6 +445,9 @@ + IO::String->new("epoch=$epoch"), + length => 16, + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete TOTP query' + ); +@@ -563,6 +580,9 @@ + IO::String->new("epoch=$epoch"), + length => 16, + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete TOTP query' + ); +--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-2F.t ++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-2F.t +@@ -99,9 +99,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -125,6 +129,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -361,9 +368,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id2", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -380,6 +391,9 @@ + IO::String->new("epoch=1234567890"), + length => 16, + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete TOTP query' + ); +@@ -399,6 +413,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -449,6 +466,9 @@ + IO::String->new("epoch=1234567890"), + length => 16, + cookie => "lemonldap=$id2", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete U2F key query' + ); +--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t ++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t +@@ -22,8 +22,7 @@ + ]'; + close F; + +-my $client = LLNG::Manager::Test->new( +- { ++my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + authentication => 'Demo', +@@ -63,9 +62,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -90,6 +93,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -144,9 +150,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -170,6 +180,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/68-Impersonation-with-2F.t ++++ b/lemonldap-ng-portal/t/68-Impersonation-with-2F.t +@@ -80,9 +80,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -108,6 +112,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -303,9 +310,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -322,6 +333,9 @@ + IO::String->new("epoch=1234567890"), + length => 16, + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete TOTP query' + ); +@@ -341,6 +355,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -391,6 +408,9 @@ + IO::String->new("epoch=1234567890"), + length => 16, + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete U2F key query' + ); +--- a/lemonldap-ng-portal/t/68-Impersonation-with-TOTP.t ++++ b/lemonldap-ng-portal/t/68-Impersonation-with-TOTP.t +@@ -106,9 +106,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -133,6 +137,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-Password.t ++++ b/lemonldap-ng-portal/t/70-2F-Password.t +@@ -5,8 +5,7 @@ + + require 't/test-lib.pm'; + +-my $client = LLNG::Manager::Test->new( +- { ++my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + password2fSelfRegistration => 1, +@@ -72,6 +71,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ) + ), + 'Post registration (mismatched)' +@@ -86,6 +88,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ) + ), + 'Post registration (mismatched)' +@@ -100,6 +105,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ) + ), + 'Post registration (mismatched)' +--- a/lemonldap-ng-portal/t/70-2F-TOTP-8-with-global-storage.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-8-with-global-storage.t +@@ -17,8 +17,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -76,9 +75,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -102,6 +105,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-TTL-and-JSON.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-TTL-and-JSON.t +@@ -69,9 +69,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -95,6 +99,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -119,10 +126,14 @@ + # Ajax registration request + ok( + $res = $client->_post( +- '/2fregisters/u/register', IO::String->new(''), ++ '/2fregisters/u/register', ++ IO::String->new(''), + accept => 'application/json', + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get registration challenge' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-authnLevels-and-UpgradeOnly.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-authnLevels-and-UpgradeOnly.t +@@ -62,9 +62,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -87,6 +91,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-encryption.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-encryption.t +@@ -18,8 +18,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -77,9 +76,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -103,6 +106,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-History-and-Refresh.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-History-and-Refresh.t +@@ -14,8 +14,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -65,9 +64,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -91,6 +94,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-Range.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-Range.t +@@ -19,8 +19,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -74,9 +73,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -101,6 +104,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-JSON.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-JSON.t +@@ -15,8 +15,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -75,9 +74,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -101,6 +104,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-XML.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-XML.t +@@ -19,8 +19,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -78,9 +77,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -104,6 +107,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL.t ++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL.t +@@ -13,8 +13,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => '$uid eq "dwho"', +@@ -79,9 +78,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -109,6 +112,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -129,6 +135,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F-with-History.t ++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F-with-History.t +@@ -69,9 +69,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -95,6 +99,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F.t ++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F.t +@@ -72,9 +72,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -98,6 +102,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -137,6 +144,9 @@ + '/2fregisters', + cookie => "lemonldap=$id", + accept => 'text/html', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Form registration' + ); +--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only-with-History.t ++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only-with-History.t +@@ -62,9 +62,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -88,6 +92,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only.t ++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only.t +@@ -61,9 +61,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -87,6 +91,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t ++++ b/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t +@@ -13,8 +13,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + totp2fSelfRegistration => 1, +@@ -35,8 +34,7 @@ + ok( + $res = $client->_get( + '/cas/login', +- query => buildForm( +- { ++ query => buildForm( { + service => "http://cas.example.com/", + } + ), +@@ -80,9 +78,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => $pdata, + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -109,6 +111,9 @@ + IO::String->new($s), + length => length($s), + cookie => $pdata, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/74-2F-Required.t ++++ b/lemonldap-ng-portal/t/74-2F-Required.t +@@ -56,9 +56,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => $pdata, + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -82,6 +86,9 @@ + IO::String->new($s), + length => length($s), + cookie => $pdata, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +--- a/lemonldap-ng-portal/t/75-2F-Registers.t ++++ b/lemonldap-ng-portal/t/75-2F-Registers.t +@@ -98,9 +98,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -124,6 +128,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); +@@ -316,6 +323,9 @@ + IO::String->new("epoch=$1"), + length => 16, + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete TOTP query' + ); +@@ -361,6 +371,9 @@ + IO::String->new("epoch=$1"), + length => 16, + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete U2F key query' + ); +@@ -477,6 +490,9 @@ + IO::String->new("epoch=$1"), + length => 16, + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Delete U2F key query' + ); +--- a/lemonldap-ng-portal/t/77-2F-Extra-Register.t ++++ b/lemonldap-ng-portal/t/77-2F-Extra-Register.t +@@ -132,6 +132,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + + $res = expectJSON($res); +@@ -156,6 +159,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + + $res = expectJSON($res); +@@ -234,6 +240,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + + $res = expectJSON($res); +@@ -258,6 +267,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + expectReject( $res, 400, 'PE96' ); + ok( !getPSession('dwho')->data->{_2fDevices}, +@@ -294,6 +306,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + + $res = expectJSON($res); +@@ -316,6 +331,9 @@ + length => length $query, + cookie => "lemonldap=$id", + accept => 'application/json', ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ); + + $res = expectJSON($res); +@@ -374,13 +392,35 @@ + ok( $epoch, "Found epoch on delete button" ); + ok( $prefix, "Found prefix on delete button" ); + +- $query = buildForm( { epoch => $epoch, } ); ++ { ++ my $delete_query = buildForm( { epoch => $epoch } ); ++ $res = $client->_post( ++ "/2fregisters/$prefix/delete", ++ $delete_query, ++ length => length($delete_query), ++ cookie => "lemonldap=$id", ++ ); ++ my $json = expectBadRequest($res); ++ ok( $res->[2]->[0] =~ 'csrfError', ++ "Deletion expects valid CSRF token" ); ++ } ++ ++ $res = $client->_get( ++ '/2fregisters', ++ cookie => "lemonldap=$id", ++ accept => "test/html", ++ ); ++ ++ $query = buildForm( { epoch => $epoch } ); + ok( + $res = $client->_post( + "/2fregisters/$prefix/delete", + IO::String->new($query), + cookie => "lemonldap=$id", + length => length($query), ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post deletion' + ); +--- a/lemonldap-ng-portal/t/78-2F-UpgradeOnly-with-forceFlag.t ++++ b/lemonldap-ng-portal/t/78-2F-UpgradeOnly-with-forceFlag.t +@@ -14,8 +14,7 @@ + } + require Lemonldap::NG::Common::TOTP; + +- my $client = LLNG::Manager::Test->new( +- { ++ my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + checkUser => 1, +@@ -82,9 +81,13 @@ + # JS query + ok( + $res = $client->_post( +- '/2fregisters/totp/getkey', IO::String->new(''), ++ '/2fregisters/totp/getkey', ++ IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Get new key' + ); +@@ -112,6 +115,9 @@ + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", ++ custom => { ++ HTTP_X_CSRF_CHECK => 1, ++ }, + ), + 'Post code' + ); diff -Nru lemonldap-ng-2.16.1+ds/debian/patches/fix-jwt.patch lemonldap-ng-2.16.1+ds/debian/patches/fix-jwt.patch --- lemonldap-ng-2.16.1+ds/debian/patches/fix-jwt.patch 1970-01-01 00:00:00.000000000 +0000 +++ lemonldap-ng-2.16.1+ds/debian/patches/fix-jwt.patch 2025-02-02 10:30:27.000000000 +0000 @@ -0,0 +1,16 @@ +Description: fix bad JWT header +Author: Yadd +Forwarded: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/643 +Last-Update: 2025-01-20 + +--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm ++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm +@@ -1142,7 +1142,7 @@ + ->{oidcRPMetaDataOptionsAccessTokenSignAlg} || "RS256"; + $self->logger->debug("Access Token signature algorithm: $alg"); + +- my $jwt = $self->createJWT( $access_token_payload, $alg, $rp, "at+JWT" ); ++ my $jwt = $self->createJWT( $access_token_payload, $alg, $rp, "JWT" ); + + return $jwt; + } diff -Nru lemonldap-ng-2.16.1+ds/debian/patches/fix-test-when-ldap-server-exists.patch lemonldap-ng-2.16.1+ds/debian/patches/fix-test-when-ldap-server-exists.patch --- lemonldap-ng-2.16.1+ds/debian/patches/fix-test-when-ldap-server-exists.patch 1970-01-01 00:00:00.000000000 +0000 +++ lemonldap-ng-2.16.1+ds/debian/patches/fix-test-when-ldap-server-exists.patch 2025-02-02 10:58:24.000000000 +0000 @@ -0,0 +1,17 @@ +Description: fix test when a LDAP server is run on build machine +Author: Christophe Maudoux +Forwarded: not-needed +Applied-Upstream: 2.19.2, commit:710b28a6 +Reviewed-By: Yadd +Last-Update: 2025-02-02 + +--- a/lemonldap-ng-portal/t/41-Captcha-with-LDAP.t ++++ b/lemonldap-ng-portal/t/41-Captcha-with-LDAP.t +@@ -20,6 +20,7 @@ + ini => { + logLevel => 'error', + authentication => 'LDAP', ++ ldapServer => 'ldap://ldap.failed.xzx', + captcha_login_enabled => 1 + } + } diff -Nru lemonldap-ng-2.16.1+ds/debian/patches/series lemonldap-ng-2.16.1+ds/debian/patches/series --- lemonldap-ng-2.16.1+ds/debian/patches/series 2024-11-18 17:32:35.000000000 +0000 +++ lemonldap-ng-2.16.1+ds/debian/patches/series 2025-02-02 10:57:52.000000000 +0000 @@ -3,6 +3,7 @@ fix-for-pod2man.diff replace-api-doc-by-link.diff drop-network-test.patch +fix-jwt.patch fix-OP-acr-parsing.patch fix-viewer-endpoint.patch apply-user-control-to-authslave.patch @@ -12,3 +13,5 @@ CVE-2024-48933.patch fix-auth-level-escalation.patch fix-xss-in-upgrade-plugin.patch +CVE-2024-52948.patch +fix-test-when-ldap-server-exists.patch