From 9e805aeb4b5dd9ff8c89e3507a1479de61ea84a0 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 19 Mar 2024 13:47:48 +0100 Subject: [PATCH 01/14] #1848: Checking for 2F authnLevel and ensuring only 2F modules that meet targetAuthnLevel are available. --- .../Lemonldap/NG/Portal/2F/Engines/Default.pm | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index 967d99f55c..839d6082c7 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -913,9 +913,24 @@ sub searchForAuthorized2Fmodules { $session ||= $req->sessionInfo; my @am; foreach ( @{ $self->sfModules } ) { + my $authModuleName = $_->{m}->prefix; $self->logger->debug( - 'Looking if ' . $_->{m}->prefix . '2f is available' ); - if ( $_->{r}->( $req, $session ) ) { + 'Looking if ' . $authModuleName . ' 2f is available' ); + + # Compare 2FA AuthnLevel with TargetAuthnLevel + my $authnLevelVar = $_->{m}->prefix + . ( + substr( $authModuleName, -2 ) eq '2f' + ? 'AuthnLevel' + : '2fAuthnLevel' + ); + $self->logger->debug( 'AuthnLevel: ' . $self->conf->{$authnLevelVar} ); + $self->logger->debug( + ' TargetAuthnLevel: ' . $req->pdata->{targetAuthnLevel} ); + if ( $_->{r}->( $req, $session ) + and $self->conf->{$authnLevelVar} >= + $req->pdata->{targetAuthnLevel} ) + { $self->logger->debug(' -> OK'); push @am, $_->{m}; } -- GitLab From 510164af9672d30cb6bdf13607c5cc781ad10e38 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 19 Mar 2024 15:02:48 +0100 Subject: [PATCH 02/14] #1848: Minor change. --- .../lib/Lemonldap/NG/Portal/2F/Engines/Default.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index 839d6082c7..d4e3925fc6 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -926,7 +926,7 @@ sub searchForAuthorized2Fmodules { ); $self->logger->debug( 'AuthnLevel: ' . $self->conf->{$authnLevelVar} ); $self->logger->debug( - ' TargetAuthnLevel: ' . $req->pdata->{targetAuthnLevel} ); + 'TargetAuthnLevel: ' . $req->pdata->{targetAuthnLevel} ); if ( $_->{r}->( $req, $session ) and $self->conf->{$authnLevelVar} >= $req->pdata->{targetAuthnLevel} ) -- GitLab From 9e908402470f09f5213e73afe7ae0fb0be070595 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Fri, 22 Mar 2024 14:37:36 +0100 Subject: [PATCH 03/14] #1848: Special rule to activate 2F to meet target level. --- .../Lemonldap/NG/Portal/2F/Engines/Default.pm | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index d4e3925fc6..edda221c27 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -916,20 +916,12 @@ sub searchForAuthorized2Fmodules { my $authModuleName = $_->{m}->prefix; $self->logger->debug( 'Looking if ' . $authModuleName . ' 2f is available' ); - - # Compare 2FA AuthnLevel with TargetAuthnLevel - my $authnLevelVar = $_->{m}->prefix - . ( - substr( $authModuleName, -2 ) eq '2f' - ? 'AuthnLevel' - : '2fAuthnLevel' - ); - $self->logger->debug( 'AuthnLevel: ' . $self->conf->{$authnLevelVar} ); - $self->logger->debug( - 'TargetAuthnLevel: ' . $req->pdata->{targetAuthnLevel} ); - if ( $_->{r}->( $req, $session ) - and $self->conf->{$authnLevelVar} >= - $req->pdata->{targetAuthnLevel} ) + + # Adding targetAuthnLevel from req to session; + my $modifiedSession = { %${ session } }; + $modifiedSession->{ targetAuthnLevel } = $req->{pdata}->{ targetAuthnLevel }; + + if ( $_->{r}->( $req, $modifiedSession ) ) { $self->logger->debug(' -> OK'); push @am, $_->{m}; -- GitLab From 9dbcc630435ea3b1808fb6bf10fd7d3a0ca4cbb3 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Fri, 12 Apr 2024 12:32:52 +0200 Subject: [PATCH 04/14] Added info about installation OS. --- lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm | 1 + lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm index caef458343..58c9299509 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm @@ -50,6 +50,7 @@ sub init { # If self registration is enabled and "activation" is just set to # "enabled", replace the rule to detect if user has registered its key + $self->logger->debug("PASSWORD Activation -> " . $self->conf->{password2fSelfRegistration}); $self->conf->{password2fActivation} = 'has2f("Password")' if ( $self->conf->{password2fSelfRegistration} and $self->conf->{password2fActivation} eq '1' ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm index 787e69fc14..c116532206 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm @@ -34,9 +34,10 @@ sub init { # If "activation" is just set to "enabled", # replace the rule to detect if user has registered its key + $self->logger->debug("TOTP Activation -> " . $self->conf->{totp2fActivation}); $self->conf->{totp2fActivation} = 'has2f("TOTP")' if $self->conf->{totp2fActivation} eq '1'; - + $self->logger->debug("TOTP Activation -> " . $self->conf->{totp2fActivation}); return $self->SUPER::init(); } -- GitLab From cddecd3b18301160f40f502a535a4c9befa1dcc2 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 16 Apr 2024 15:24:29 +0200 Subject: [PATCH 05/14] Test for TOTP and 2Password where only appropriate 2F is shown based on auth level. --- ...assword-with-authnLevels-and-UpgradeOnly.t | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t diff --git a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t new file mode 100644 index 0000000000..dc64e1450c --- /dev/null +++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t @@ -0,0 +1,255 @@ +use warnings; +use Test::More; +use strict; +use IO::String; + +require 't/test-lib.pm'; +require Lemonldap::NG::Common::TOTP; + +my $client = LLNG::Manager::Test->new( + { + ini => { + logLevel => 'error', + checkUser => 1, + sfOnlyUpgrade => 1, + totp2fSelfRegistration => 1, + totp2fActivation => 1, + totp2fAuthnLevel => 3, + max2FDevices => 3, + handlerInternalCache => 5, + password2fSelfRegistration => 1, + password2fActivation => 1, + password2fUserCanRemoveKey => 1, + password2fAuthnLevel => 5, + authentication => 'Demo', + userDB => 'Same', + restSessionServer => 1 + + } + } +); +my $res; +my $id; +subtest "Authenticate" => sub { + + # Try to authenticate + # ------------------- + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23 + ), + 'Auth query' + ); + $id = expectCookie($res); +}; + +subtest "Registering TOTP" => sub { + + # Try to register TOTP + ok( + $res = $client->_get( + '/2fregisters/totp', + cookie => "lemonldap=$id", + accept => 'text/html' + ), + 'Form registration' + ); + ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' ); + + # JS query + ok( + $res = $client->_post( + '/2fregisters/totp/getkey', IO::String->new(''), + cookie => "lemonldap=$id", + length => 0, + ), + 'Get new key' + ); + eval { $res = JSON::from_json( $res->[2]->[0] ) }; + ok( not($@), 'Content is JSON' ) + or explain( $res->[2]->[0], 'JSON content' ); + my ( $key, $token, $code ); + ok( $key = $res->{secret}, 'Found secret' ); + ok( $token = $res->{token}, 'Found token' ); + $key = Convert::Base32::decode_base32($key); + + # Post code + ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), + 'Code' ); + ok( $code =~ /^\d{6}$/, 'Code contains 6 digits' ); + my $s = "code=$code&token=$token&TOTPName=MyTOTP"; + ok( + $res = $client->_post( + '/2fregisters/totp/verify', + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", + ), + 'Post code' + ); + eval { $res = JSON::from_json( $res->[2]->[0] ) }; + ok( not($@), 'Content is JSON' ) + or explain( $res->[2]->[0], 'JSON content' ); + ok( $res->{result} == 1, 'TOTP is registered' ); + + Time::Fake->offset("+10s"); +}; + +subtest 'Register Password 2FA' => sub { + + # Try to authenticate + # ------------------- + ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Menu', ); + my ( $host, $url, $query ) = + expectForm( $res, '#', undef, 'user', 'password' ); + + $query =~ s/user=/user=dwho/; + $query =~ s/password=/password=dwho/; + ok( + $res = $client->_post( + '/', + IO::String->new($query), + length => length($query), + accept => 'text/html', + ), + 'Auth query' + ); + my $id = expectCookie($res); + expectRedirection( $res, 'http://auth.example.com/' ); + + # Password form + ok( + $res = $client->_get( + '/2fregisters', + cookie => "lemonldap=$id", + accept => 'text/html', + ), + 'Form registration' + ); + expectRedirection( $res, qr#/2fregisters/password$# ); + ok( + $res = $client->_get( + '/2fregisters/password', + cookie => "lemonldap=$id", + accept => 'text/html', + ), + 'Form registration' + ); + ok( $res->[2]->[0] =~ /password2fregistration\.(?:min\.)?js/, + 'Found password js' ); + + my $s = "password=somethingyouknow&passwordverify=somethingyouknow"; + ok( + $res = expectJSON( + $client->_post( + '/2fregisters/password/verify', + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", + ) + ), + 'Post registration' + ); + is( $res->{error}, 'PE34' ); +}; + +subtest "Upgrade Session with TOTP" => sub { + + # Try to upgrade from 2fManager + # ----------------------------- + ok( + $res = $client->_get( + '/upgradesession', + query => +'forceUpgrade=1&url=aHR0cDovL2F1dGguZXhhbXBsZS5jb20vMmZyZWdpc3RlcnM=', + accept => 'text/html', + cookie => "lemonldap=$id", + ), + 'Upgrade session query from 2fManager' + ); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/upgradesession', 'confirm', 'url', + 'forceUpgrade' ); + + # Accept session upgrade + ok( + $res = $client->_post( + '/upgradesession', + IO::String->new($query), + length => length($query), + accept => 'text/html', + cookie => "lemonldap=$id", + ), + 'Accept session upgrade query' + ); + + # POST TOTP + my ( $key, $token, $code ) = + expectForm( $res, undef, '/totp2fcheck', 'token' ); + ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), + 'Code' ); + $query =~ s/code=/code=$code/; + ok( + $res = $client->_post( + '/totp2fcheck', IO::String->new($query), + length => length($query), + ), + 'Post code' + ); + + # Check authLevel (TOTP -> 3) + ok( + $res = $client->_get( + '/checkuser', cookie => "lemonldap=$id", + ), + 'CheckUser', + ); + ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' ) + or print STDERR "$@\n" . Dumper($res); + my @authLevel = map { $_->{key} eq 'authenticationLevel' ? $_ : () } + @{ $res->{ATTRIBUTES} }; + ok( $authLevel[0]->{value} == 3, 'AuthenticationLevel == 3' ) + or explain( $authLevel[0]->{value}, 'AuthenticationLevel value == 3' ); + + Time::Fake->offset("+20s"); +}; + +subtest "Upgrade Session with 2F" => sub { + + # Try to upgrade from 2fManager + # ----------------------------- + ok( + $res = $client->_get( + '/upgradesession', + query => +'forceUpgrade=1&url=aHR0cDovL2F1dGguZXhhbXBsZS5jb20vMmZyZWdpc3RlcnM=', + accept => 'text/html', + cookie => "lemonldap=$id", + ), + 'Upgrade session query from 2fManager' + ); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/upgradesession', 'confirm', 'url', + 'forceUpgrade' ); + + # Accept session upgrade + ok( + $res = $client->_post( + '/upgradesession', + IO::String->new($query), + length => length($query), + accept => 'text/html', + cookie => "lemonldap=$id", + ), + 'Accept session upgrade query' + ); +}; +$client->logout($id); +count(44); +clean_sessions(); + +done_testing( count() ); -- GitLab From 933380664acac1318e771dc01d1a6b7618ed2614 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Wed, 17 Apr 2024 13:45:25 +0200 Subject: [PATCH 06/14] Test authenticates, registers TOTP and 2Password. --- ...assword-with-authnLevels-and-UpgradeOnly.t | 119 +++++++++++++----- 1 file changed, 89 insertions(+), 30 deletions(-) diff --git a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t index dc64e1450c..eee1fbb3bc 100644 --- a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t +++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t @@ -13,17 +13,23 @@ my $client = LLNG::Manager::Test->new( checkUser => 1, sfOnlyUpgrade => 1, totp2fSelfRegistration => 1, - totp2fActivation => 1, - totp2fAuthnLevel => 3, - max2FDevices => 3, + totp2fActivation => "\$targetAuthnLevel >= 10", + totp2fAuthnLevel => 10, handlerInternalCache => 5, password2fSelfRegistration => 1, - password2fActivation => 1, - password2fUserCanRemoveKey => 1, + password2fActivation => "\$targetAuthnLevel >= 5", password2fAuthnLevel => 5, authentication => 'Demo', userDB => 'Same', - restSessionServer => 1 + restSessionServer => 1, + vhostOptions => { + "test1.example.com" => { + vhostAuthnLevel => 5 + }, + "test2.example.com" => { + vhostAuthnLevel => 10 + } + } } } @@ -32,8 +38,6 @@ my $res; my $id; subtest "Authenticate" => sub { - # Try to authenticate - # ------------------- ok( $res = $client->_post( '/', @@ -43,11 +47,20 @@ subtest "Authenticate" => sub { 'Auth query' ); $id = expectCookie($res); + $client->logout($id); }; subtest "Registering TOTP" => sub { - # Try to register TOTP + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23 + ), + 'Auth query' + ); + $id = expectCookie($res); ok( $res = $client->_get( '/2fregisters/totp', @@ -95,12 +108,20 @@ subtest "Registering TOTP" => sub { ok( $res->{result} == 1, 'TOTP is registered' ); Time::Fake->offset("+10s"); + $client->logout($id); }; subtest 'Register Password 2FA' => sub { - # Try to authenticate - # ------------------- + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23 + ), + 'Auth query' + ); + $id = expectCookie($res); ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Menu', ); my ( $host, $url, $query ) = expectForm( $res, '#', undef, 'user', 'password' ); @@ -128,7 +149,8 @@ subtest 'Register Password 2FA' => sub { ), 'Form registration' ); - expectRedirection( $res, qr#/2fregisters/password$# ); + + #expectRedirection( $res, qr#/2fregisters/password$# ); ok( $res = $client->_get( '/2fregisters/password', @@ -137,28 +159,47 @@ subtest 'Register Password 2FA' => sub { ), 'Form registration' ); + ok( $res->[2]->[0] =~ /password2fregistration\.(?:min\.)?js/, 'Found password js' ); my $s = "password=somethingyouknow&passwordverify=somethingyouknow"; ok( - $res = expectJSON( - $client->_post( - '/2fregisters/password/verify', - IO::String->new($s), - length => length($s), - cookie => "lemonldap=$id", - ) - ), - 'Post registration' + $client->_post( + '/2fregisters/password/verify', + IO::String->new($s), + length => length($s), + cookie => "lemonldap=$id", + ) ); - is( $res->{error}, 'PE34' ); + $client->logout($id); }; -subtest "Upgrade Session with TOTP" => sub { +subtest "Check test2 vhost asks TOTP and 2Password" => sub { + + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + ), + 'Auth query' + ); + $id = expectCookie($res); + + ok( + $res = $client->_get( + '/', + accept => 'text/html', + cookie => "lemonldap=$id", + host => 'test2.example.com', + ), + 'GET http://test2.example.com/' + ); + + use Data::Dumper; + print(Dumper($res)); - # Try to upgrade from 2fManager - # ----------------------------- ok( $res = $client->_get( '/upgradesession', @@ -211,16 +252,34 @@ subtest "Upgrade Session with TOTP" => sub { or print STDERR "$@\n" . Dumper($res); my @authLevel = map { $_->{key} eq 'authenticationLevel' ? $_ : () } @{ $res->{ATTRIBUTES} }; - ok( $authLevel[0]->{value} == 3, 'AuthenticationLevel == 3' ) - or explain( $authLevel[0]->{value}, 'AuthenticationLevel value == 3' ); + ok( $authLevel[0]->{value} == 10, 'AuthenticationLevel == 10' ) + or explain( $authLevel[0]->{value}, 'AuthenticationLevel value == 10' ); Time::Fake->offset("+20s"); + $client->logout($id); }; -subtest "Upgrade Session with 2F" => sub { +subtest "Check test2 vhost asks only TOTP" => sub { + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23 + ), + 'Auth query' + ); + $id = expectCookie($res); + + ok( + $res = $client->_get( + '/AuthWeak', + accept => 'text/html', + cookie => "lemonldap=$id", + host => 'test1.example.com', + ), + 'GET http://test1.example.com/AuthWeak' + ); - # Try to upgrade from 2fManager - # ----------------------------- ok( $res = $client->_get( '/upgradesession', -- GitLab From 92dcf5c5dc855d87894dc1170efe33987e2aa920 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 23 Apr 2024 13:47:00 +0000 Subject: [PATCH 07/14] Modifications to improve logging. Setting modified session targetAuthn when available. --- .../Lemonldap/NG/Portal/2F/Engines/Default.pm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index 12ccad1039..d30a16769a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -923,16 +923,15 @@ sub searchForAuthorized2Fmodules { my @am; foreach ( @{ $self->sfModules } ) { my $authModuleName = $_->{m}->prefix; - $self->logger->debug( - 'Looking if ' . $authModuleName . ' 2f is available' ); - + $self->logger->debug('Looking for $authModuleName 2f'); + # Adding targetAuthnLevel from req to session; - my $modifiedSession = { %${ session } }; - $modifiedSession->{ targetAuthnLevel } = $req->{pdata}->{ targetAuthnLevel }; - - if ( $_->{r}->( $req, $modifiedSession ) ) - { - $self->logger->debug(' -> OK'); + my $modifiedSession = {%$session}; + $modifiedSession->{targetAuthnLevel} = $req->{pdata}->{targetAuthnLevel} + if $req->{pdata}->{targetAuthnLevel}; + + if ( $_->{r}->( $req, $modifiedSession ) ) { + $self->logger->debug(' -> $authnModuleName 2f is available'); push @am, $_->{m}; } } -- GitLab From b77131c2022c43657e81bc457313d421bd086820 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Mon, 29 Apr 2024 12:35:07 +0000 Subject: [PATCH 08/14] Fixing tests. --- ...assword-with-authnLevels-and-UpgradeOnly.t | 101 +++--------------- 1 file changed, 12 insertions(+), 89 deletions(-) diff --git a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t index eee1fbb3bc..b24d0fbac6 100644 --- a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t +++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t @@ -173,20 +173,10 @@ subtest 'Register Password 2FA' => sub { ) ); $client->logout($id); -}; + }; subtest "Check test2 vhost asks TOTP and 2Password" => sub { - ok( - $res = $client->_post( - '/', - IO::String->new('user=dwho&password=dwho'), - length => 23, - ), - 'Auth query' - ); - $id = expectCookie($res); - ok( $res = $client->_get( '/', @@ -196,40 +186,21 @@ subtest "Check test2 vhost asks TOTP and 2Password" => sub { ), 'GET http://test2.example.com/' ); - - use Data::Dumper; - print(Dumper($res)); - - ok( - $res = $client->_get( - '/upgradesession', - query => -'forceUpgrade=1&url=aHR0cDovL2F1dGguZXhhbXBsZS5jb20vMmZyZWdpc3RlcnM=', - accept => 'text/html', - cookie => "lemonldap=$id", - ), - 'Upgrade session query from 2fManager' - ); - - my ( $host, $url, $query ) = - expectForm( $res, undef, '/upgradesession', 'confirm', 'url', - 'forceUpgrade' ); - - # Accept session upgrade + ok( $res = $client->_post( - '/upgradesession', - IO::String->new($query), - length => length($query), - accept => 'text/html', - cookie => "lemonldap=$id", + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, ), - 'Accept session upgrade query' + 'Auth query' ); + $id = expectCookie($res); - # POST TOTP - my ( $key, $token, $code ) = + my ( $host, $url, $query ) = expectForm( $res, undef, '/totp2fcheck', 'token' ); + my $code; + my $key; ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), 'Code' ); $query =~ s/code=/code=$code/; @@ -240,7 +211,8 @@ subtest "Check test2 vhost asks TOTP and 2Password" => sub { ), 'Post code' ); - + $id = expectCookie($res); + # Check authLevel (TOTP -> 3) ok( $res = $client->_get( @@ -259,55 +231,6 @@ subtest "Check test2 vhost asks TOTP and 2Password" => sub { $client->logout($id); }; -subtest "Check test2 vhost asks only TOTP" => sub { - ok( - $res = $client->_post( - '/', - IO::String->new('user=dwho&password=dwho'), - length => 23 - ), - 'Auth query' - ); - $id = expectCookie($res); - - ok( - $res = $client->_get( - '/AuthWeak', - accept => 'text/html', - cookie => "lemonldap=$id", - host => 'test1.example.com', - ), - 'GET http://test1.example.com/AuthWeak' - ); - - ok( - $res = $client->_get( - '/upgradesession', - query => -'forceUpgrade=1&url=aHR0cDovL2F1dGguZXhhbXBsZS5jb20vMmZyZWdpc3RlcnM=', - accept => 'text/html', - cookie => "lemonldap=$id", - ), - 'Upgrade session query from 2fManager' - ); - - my ( $host, $url, $query ) = - expectForm( $res, undef, '/upgradesession', 'confirm', 'url', - 'forceUpgrade' ); - - # Accept session upgrade - ok( - $res = $client->_post( - '/upgradesession', - IO::String->new($query), - length => length($query), - accept => 'text/html', - cookie => "lemonldap=$id", - ), - 'Accept session upgrade query' - ); -}; -$client->logout($id); count(44); clean_sessions(); -- GitLab From 767f056acb252a82ab612fc78f8b91d4b13502ef Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 30 Apr 2024 07:52:26 +0000 Subject: [PATCH 09/14] Tests working to test targetAuthnLevel. Requires refinement. --- .../Lemonldap/NG/Portal/2F/Engines/Default.pm | 4 +- ...assword-with-authnLevels-and-UpgradeOnly.t | 68 ++++++++----------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm index d30a16769a..435f87bd2a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Engines/Default.pm @@ -923,7 +923,7 @@ sub searchForAuthorized2Fmodules { my @am; foreach ( @{ $self->sfModules } ) { my $authModuleName = $_->{m}->prefix; - $self->logger->debug('Looking for $authModuleName 2f'); + $self->logger->debug('Looking for ' . $authModuleName . ' 2f'); # Adding targetAuthnLevel from req to session; my $modifiedSession = {%$session}; @@ -931,7 +931,7 @@ sub searchForAuthorized2Fmodules { if $req->{pdata}->{targetAuthnLevel}; if ( $_->{r}->( $req, $modifiedSession ) ) { - $self->logger->debug(' -> $authnModuleName 2f is available'); + $self->logger->debug(' -> ' . $authModuleName . ' 2f is available'); push @am, $_->{m}; } } diff --git a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t index b24d0fbac6..160e1ab6da 100644 --- a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t +++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t @@ -13,11 +13,11 @@ my $client = LLNG::Manager::Test->new( checkUser => 1, sfOnlyUpgrade => 1, totp2fSelfRegistration => 1, - totp2fActivation => "\$targetAuthnLevel >= 10", + totp2fActivation => "\$targetAuthnLevel <= 10", totp2fAuthnLevel => 10, handlerInternalCache => 5, password2fSelfRegistration => 1, - password2fActivation => "\$targetAuthnLevel >= 5", + password2fActivation => "\$targetAuthnLevel <= 5", password2fAuthnLevel => 5, authentication => 'Demo', userDB => 'Same', @@ -36,6 +36,10 @@ my $client = LLNG::Manager::Test->new( ); my $res; my $id; +my $key; +my $keySecret; +my $token; +my $code; subtest "Authenticate" => sub { ok( @@ -83,10 +87,9 @@ subtest "Registering TOTP" => sub { eval { $res = JSON::from_json( $res->[2]->[0] ) }; ok( not($@), 'Content is JSON' ) or explain( $res->[2]->[0], 'JSON content' ); - my ( $key, $token, $code ); - ok( $key = $res->{secret}, 'Found secret' ); + ok( $keySecret = $res->{secret}, 'Found secret' ); ok( $token = $res->{token}, 'Found token' ); - $key = Convert::Base32::decode_base32($key); + $key = Convert::Base32::decode_base32($keySecret); # Post code ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), @@ -150,7 +153,6 @@ subtest 'Register Password 2FA' => sub { 'Form registration' ); - #expectRedirection( $res, qr#/2fregisters/password$# ); ok( $res = $client->_get( '/2fregisters/password', @@ -173,34 +175,40 @@ subtest 'Register Password 2FA' => sub { ) ); $client->logout($id); - }; +}; -subtest "Check test2 vhost asks TOTP and 2Password" => sub { +subtest "Check test1 vhost asks TOTP and 2Password" => sub { + my $requestParams = 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29t'; ok( - $res = $client->_get( + $res = $client->_post( '/', - accept => 'text/html', - cookie => "lemonldap=$id", - host => 'test2.example.com', + IO::String->new($requestParams), + length => length($requestParams), ), - 'GET http://test2.example.com/' + 'Auth query' ); - + $key = Convert::Base32::decode_base32($keySecret); + + # Post code + my ($host, $url, $query) = expectForm( $res, undef, '/2fchoice', 'token' ); +}; + +subtest "Check test2 vhost asks only TOTP" => sub { + + my $requestParams = 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QyLmV4YW1wbGUuY29t'; ok( $res = $client->_post( '/', - IO::String->new('user=dwho&password=dwho'), - length => 23, + IO::String->new($requestParams), + length => length($requestParams), ), 'Auth query' ); - $id = expectCookie($res); + $key = Convert::Base32::decode_base32($keySecret); - my ( $host, $url, $query ) = - expectForm( $res, undef, '/totp2fcheck', 'token' ); - my $code; - my $key; + # Post code + my ($host, $url, $query) = expectForm( $res, undef, '/totp2fcheck', 'token' ); ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), 'Code' ); $query =~ s/code=/code=$code/; @@ -212,26 +220,10 @@ subtest "Check test2 vhost asks TOTP and 2Password" => sub { 'Post code' ); $id = expectCookie($res); - - # Check authLevel (TOTP -> 3) - ok( - $res = $client->_get( - '/checkuser', cookie => "lemonldap=$id", - ), - 'CheckUser', - ); - ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' ) - or print STDERR "$@\n" . Dumper($res); - my @authLevel = map { $_->{key} eq 'authenticationLevel' ? $_ : () } - @{ $res->{ATTRIBUTES} }; - ok( $authLevel[0]->{value} == 10, 'AuthenticationLevel == 10' ) - or explain( $authLevel[0]->{value}, 'AuthenticationLevel value == 10' ); - - Time::Fake->offset("+20s"); $client->logout($id); }; -count(44); +count(4); clean_sessions(); done_testing( count() ); -- GitLab From 9e452c9ddaac2e22683642520ffa5aeb3d642cdc Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 30 Apr 2024 09:09:29 +0000 Subject: [PATCH 10/14] Tests working. --- ...assword-with-authnLevels-and-UpgradeOnly.t | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t index 160e1ab6da..dae7f38f63 100644 --- a/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t +++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-2Password-with-authnLevels-and-UpgradeOnly.t @@ -87,8 +87,8 @@ subtest "Registering TOTP" => sub { eval { $res = JSON::from_json( $res->[2]->[0] ) }; ok( not($@), 'Content is JSON' ) or explain( $res->[2]->[0], 'JSON content' ); - ok( $keySecret = $res->{secret}, 'Found secret' ); - ok( $token = $res->{token}, 'Found token' ); + ok( $keySecret = $res->{secret}, 'Found secret' ); + ok( $token = $res->{token}, 'Found token' ); $key = Convert::Base32::decode_base32($keySecret); # Post code @@ -179,7 +179,8 @@ subtest 'Register Password 2FA' => sub { subtest "Check test1 vhost asks TOTP and 2Password" => sub { - my $requestParams = 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29t'; + my $requestParams = + 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29t'; ok( $res = $client->_post( '/', @@ -191,12 +192,14 @@ subtest "Check test1 vhost asks TOTP and 2Password" => sub { $key = Convert::Base32::decode_base32($keySecret); # Post code - my ($host, $url, $query) = expectForm( $res, undef, '/2fchoice', 'token' ); + my ( $host, $url, $query ) = + expectForm( $res, undef, '/2fchoice', 'token' ); }; subtest "Check test2 vhost asks only TOTP" => sub { - my $requestParams = 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QyLmV4YW1wbGUuY29t'; + my $requestParams = + 'user=dwho&password=dwho&url=aHR0cDovL3Rlc3QyLmV4YW1wbGUuY29t'; ok( $res = $client->_post( '/', @@ -208,7 +211,8 @@ subtest "Check test2 vhost asks only TOTP" => sub { $key = Convert::Base32::decode_base32($keySecret); # Post code - my ($host, $url, $query) = expectForm( $res, undef, '/totp2fcheck', 'token' ); + my ( $host, $url, $query ) = + expectForm( $res, undef, '/totp2fcheck', 'token' ); ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ), 'Code' ); $query =~ s/code=/code=$code/; @@ -223,7 +227,5 @@ subtest "Check test2 vhost asks only TOTP" => sub { $client->logout($id); }; -count(4); clean_sessions(); - -done_testing( count() ); +done_testing(); -- GitLab From 1923f1d3644c2178378ea103d14b8be348cec14e Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 30 Apr 2024 11:45:16 +0000 Subject: [PATCH 11/14] Removing logging from debugging exercise. --- lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm | 1 - lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm index 58c9299509..caef458343 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm @@ -50,7 +50,6 @@ sub init { # If self registration is enabled and "activation" is just set to # "enabled", replace the rule to detect if user has registered its key - $self->logger->debug("PASSWORD Activation -> " . $self->conf->{password2fSelfRegistration}); $self->conf->{password2fActivation} = 'has2f("Password")' if ( $self->conf->{password2fSelfRegistration} and $self->conf->{password2fActivation} eq '1' ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm index c116532206..2ba8a33114 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm @@ -34,10 +34,9 @@ sub init { # If "activation" is just set to "enabled", # replace the rule to detect if user has registered its key - $self->logger->debug("TOTP Activation -> " . $self->conf->{totp2fActivation}); $self->conf->{totp2fActivation} = 'has2f("TOTP")' if $self->conf->{totp2fActivation} eq '1'; - $self->logger->debug("TOTP Activation -> " . $self->conf->{totp2fActivation}); + return $self->SUPER::init(); } -- GitLab From 8df635e15e28969a0ec39d75084c5e4c80fb1650 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 30 Apr 2024 11:46:06 +0000 Subject: [PATCH 12/14] Removing logging from debugging exercise. --- lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm index 2ba8a33114..787e69fc14 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm @@ -36,7 +36,7 @@ sub init { # replace the rule to detect if user has registered its key $self->conf->{totp2fActivation} = 'has2f("TOTP")' if $self->conf->{totp2fActivation} eq '1'; - + return $self->SUPER::init(); } -- GitLab From e105589cd76f43e9d3d671c386f706f66ead6af7 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Tue, 30 Apr 2024 14:13:05 +0000 Subject: [PATCH 13/14] Added documentation for feature. --- doc/sources/admin/secondfactor.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/sources/admin/secondfactor.rst b/doc/sources/admin/secondfactor.rst index 65f95a5adf..bde9805701 100644 --- a/doc/sources/admin/secondfactor.rst +++ b/doc/sources/admin/secondfactor.rst @@ -77,6 +77,18 @@ of doing a complete reauthentication. .. |beta| image:: /documentation/beta.png +Special Rule Activation +----------------------- + +When activating second factor an additional option is available called "Special Rule". This +option allows expressions to enable the second factor when the conditions of the expression +are met. + +.. tip:: + + Using the following expression ``$targetAuthnLevel <= 10`` enables the second factor when + the application targetAuthnLevel is equal to the required authentication level. + Login timeout ------------- -- GitLab From 7d12c96ad66275bcd4a7c4eb63f61933eb282823 Mon Sep 17 00:00:00 2001 From: Abhishek Pai Date: Thu, 2 May 2024 09:26:44 +0000 Subject: [PATCH 14/14] Minor doc change. --- doc/sources/admin/secondfactor.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/sources/admin/secondfactor.rst b/doc/sources/admin/secondfactor.rst index bde9805701..921f10cd2c 100644 --- a/doc/sources/admin/secondfactor.rst +++ b/doc/sources/admin/secondfactor.rst @@ -81,8 +81,7 @@ Special Rule Activation ----------------------- When activating second factor an additional option is available called "Special Rule". This -option allows expressions to enable the second factor when the conditions of the expression -are met. +option allows the second factor to be enabled only when the conditions of the expression are met. .. tip:: -- GitLab