diff --git a/lemonldap-ng-portal/t/40-Notifications-Explorer-JSON-File.t b/lemonldap-ng-portal/t/40-Notifications-Explorer-JSON-File.t
index 76b3d9686..150b5ff9c 100644
--- a/lemonldap-ng-portal/t/40-Notifications-Explorer-JSON-File.t
+++ b/lemonldap-ng-portal/t/40-Notifications-Explorer-JSON-File.t
@@ -6,12 +6,12 @@ use JSON qw(from_json);
require 't/test-lib.pm';
my $res;
-my $file = "$main::tmpDir/20160530_dwho_dGVzdHJlZg==.json";
+my $file = "$main::tmpDir/20160530_allusers_dGVzdHJlZg==.json";
open F, "> $file" or die($!);
print F '[
{
- "uid": "dwho",
+ "uid": "allusers",
"date": "2016-05-30",
"reference": "testref",
"title": "Test title",
@@ -104,8 +104,8 @@ ok(
),
"Accept notification"
);
-$file =~ s/json$/done/;
-ok( -e $file, 'Notification was deleted' );
+
+ok( -e $file, 'Notification was not deleted' );
count(2);
$id = expectCookie($res);
@@ -216,8 +216,8 @@ ok(
ok( $json = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
-ok( $json->{error} eq 'Missing parameter', ' Missing parameter' )
- or explain( $json, "Missing parameter" );
+ok( $json->{error} eq 'Missing epoch parameter', ' Missing epoch parameter' )
+ or explain( $json, "Missing epoch parameter" );
count(3);
# Bad request
diff --git a/lemonldap-ng-portal/t/40-Notifications-Explorer-XML-File.t b/lemonldap-ng-portal/t/40-Notifications-Explorer-XML-File.t
index 0717f23cc..f1d359564 100644
--- a/lemonldap-ng-portal/t/40-Notifications-Explorer-XML-File.t
+++ b/lemonldap-ng-portal/t/40-Notifications-Explorer-XML-File.t
@@ -235,8 +235,8 @@ m%
{error} eq 'Missing parameter', ' Missing parameter' )
- or explain( $json, "Missing parameter" );
+ ok( $json->{error} eq 'Missing epoch parameter', ' Missing epoch parameter' )
+ or explain( $json, "Missing epoch parameter" );
# Bad request
ok(
diff --git a/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t b/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t
index dae421e40..4d441c74f 100644
--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t
+++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t
@@ -36,6 +36,7 @@ my $client = LLNG::Manager::Test->new( {
contextSwitchingStopWithLogout => 0,
checkUser => 1,
notification => 1,
+ tokenUseGlobalStorage => 1,
notificationStorage => 'File',
notificationStorageOptions => { dirName => $main::tmpDir },
}
@@ -392,8 +393,8 @@ ok( $res->[2]->[0] =~ m%%, 'Found trspan="checkUser"' )
or explain( $res->[2]->[0], 'trspan="checkUser"' );
ok( $res->[2]->[0] =~ m%authMode | %, 'Found macro authMode' )
or explain( $res->[2]->[0], 'Macro Key authMode' );
-ok( $res->[2]->[0] =~ m%
TOTP | %, 'Found TOTP' )
- or explain( $res->[2]->[0], 'Macro Value TOTP' );
+ok( $res->[2]->[0] =~ m%
TOTP | %, 'Found macro value "TOTP"' )
+ or explain( $res->[2]->[0], 'Macro value "TOTP"' );
count(4);
# Request not connected user
@@ -411,7 +412,8 @@ ok(
( $host, $url, $query ) =
expectForm( $res, undef, '/checkuser', 'user', 'url' );
-ok( $res->[2]->[0] =~ m%
%, 'Found trspan="checkUserComputeSession"' )
+ok( $res->[2]->[0] =~ m%%,
+ 'Found trspan="checkUserComputeSession"' )
or explain( $res->[2]->[0], 'trspan="checkUserComputeSession"' );
ok( $res->[2]->[0] =~ m%authMode | %, 'Found macro authMode' )
or explain( $res->[2]->[0], 'Macro Key authMode' );
diff --git a/lemonldap-ng-portal/t/68-ContextSwitching.t b/lemonldap-ng-portal/t/68-ContextSwitching.t
index b5faa75d4..2d3d30734 100644
--- a/lemonldap-ng-portal/t/68-ContextSwitching.t
+++ b/lemonldap-ng-portal/t/68-ContextSwitching.t
@@ -61,7 +61,15 @@ ok( $res->[2]->[0] =~ qr%
%, 'Found language flags' )
expectAuthenticatedAs( $res, 'rtyler' );
ok( $res->[2]->[0] !~ m%contextSwitching_ON%, 'Connected as dwho' )
or print STDERR Dumper( $res->[2]->[0] );
-count(3);
+ok( $res->[2]->[0] =~ qr%href="http://test1\.example\.com/" title="Application Test 1"%, 'Found test1 & title' )
+ or print STDERR Dumper( $res->[2]->[0] );
+ok( $res->[2]->[0] =~ qr%href="http://test2\.example\.com/" title="A nice application!"%, 'Found test2 & title' )
+ or print STDERR Dumper( $res->[2]->[0] );
+
+my @appdesc = ($res->[2]->[0] =~ qr%class="appdesc%);
+ok( @appdesc == 1 , 'Found only one description' )
+ or print STDERR Dumper( $res->[2]->[0] );
+count(6);
$client->logout($id);
diff --git 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
new file mode 100644
index 000000000..b110f9bf5
--- /dev/null
+++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-TTL-and-JSON.t
@@ -0,0 +1,311 @@
+use Test::More;
+use strict;
+use IO::String;
+
+require 't/test-lib.pm';
+my $maintests = 39;
+
+SKIP: {
+ eval {
+ require Convert::Base32;
+ require Crypt::U2F::Server;
+ require Authen::U2F::Tester;
+ };
+ if ( $@ or $Crypt::U2F::Server::VERSION < 0.42 ) {
+ skip 'Missing libraries', $maintests;
+ }
+ require Lemonldap::NG::Common::TOTP;
+
+ my $client = LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => 'error',
+ totp2fSelfRegistration => 1,
+ totp2fActivation => 1,
+ totp2fTTL => 180,
+ u2fSelfRegistration => 1,
+ u2fActivation => 1,
+ u2fTTL => 60,
+ sfRemovedMsgRule => 1,
+ sfRemovedUseNotif => 1,
+ portalMainLogo => 'common/logos/logo_llng_old.png',
+ notification => 1,
+ notificationStorage => 'File',
+ notificationStorageOptions => { dirName => 't' },
+ oldNotifFormat => 0,
+ }
+ }
+ );
+ my $res;
+
+ # Try to authenticate
+ # -------------------
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ length => 23
+ ),
+ 'Auth query'
+ );
+ my $id = expectCookie($res);
+ ok(
+ $res = $client->_get(
+ '/2fregisters/totp',
+ cookie => "lemonldap=$id",
+ accept => 'text/html',
+ ),
+ 'Form registration'
+ );
+ ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' );
+ ok(
+ $res->[2]->[0] =~ qr%
![](/static/common/logos/logo_llng_old.png)
[2]->[0] );
+
+ # 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 );
+ ok( $key = $res->{secret}, 'Found secret' );
+ ok( $token = $res->{token}, 'Found token' );
+ $key = Convert::Base32::decode_base32($key);
+
+ # Post code
+ my $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, 'Key is registered' );
+ ok(
+ $res = $client->_get(
+ '/2fregisters/u',
+ cookie => "lemonldap=$id",
+ accept => 'text/html',
+ ),
+ 'Form registration'
+ );
+ ok( $res->[2]->[0] =~ /u2fregistration\.(?:min\.)?js/, 'Found U2F js' );
+ ok(
+ $res->[2]->[0] =~ qr%
![](/static/common/logos/logo_llng_old.png)
[2]->[0] );
+
+ # Ajax registration request
+ ok(
+ $res = $client->_post(
+ '/2fregisters/u/register', IO::String->new(''),
+ accept => 'application/json',
+ cookie => "lemonldap=$id",
+ length => 0,
+ ),
+ 'Get registration challenge'
+ );
+ expectOK($res);
+ my $data;
+ eval { $data = JSON::from_json( $res->[2]->[0] ) };
+ ok( not($@), ' Content is JSON' )
+ or explain( [ $@, $res->[2] ], 'JSON content' );
+ ok( ( $data->{challenge} and $data->{appId} ), ' Get challenge and appId' )
+ or explain( $data, 'challenge and appId' );
+
+ # Build U2F tester
+ my $tester = Authen::U2F::Tester->new(
+ certificate => Crypt::OpenSSL::X509->new_from_string(
+ '-----BEGIN CERTIFICATE-----
+MIIB6DCCAY6gAwIBAgIJAJKuutkN2sAfMAoGCCqGSM49BAMCME8xCzAJBgNVBAYT
+AlVTMQ4wDAYDVQQIDAVUZXhhczEaMBgGA1UECgwRVW50cnVzdGVkIFUyRiBPcmcx
+FDASBgNVBAMMC3ZpcnR1YWwtdTJmMB4XDTE4MDMyODIwMTc1OVoXDTI3MTIyNjIw
+MTc1OVowTzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRowGAYDVQQKDBFV
+bnRydXN0ZWQgVTJGIE9yZzEUMBIGA1UEAwwLdmlydHVhbC11MmYwWTATBgcqhkjO
+PQIBBggqhkjOPQMBBwNCAAQTij+9mI1FJdvKNHLeSQcOW4ob3prvIXuEGJMrQeJF
+6OYcgwxrVqsmNMl5w45L7zx8ryovVOti/mtqkh2pQjtpo1MwUTAdBgNVHQ4EFgQU
+QXKKf+rrZwA4WXDCU/Vebe4gYXEwHwYDVR0jBBgwFoAUQXKKf+rrZwA4WXDCU/Ve
+be4gYXEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAiCdOEmw5
+hknzHR1FoyFZKRrcJu17a1PGcqTFMJHTC70CIHeCZ8KVuuMIPjoofQd1l1E221rv
+RJY1Oz1fUNbrIPsL
+-----END CERTIFICATE-----', Crypt::OpenSSL::X509::FORMAT_PEM()
+ ),
+ key => Crypt::PK::ECC->new(
+ \'-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIOdbZw1swQIL+RZoDQ9zwjWY5UjA1NO81WWjwbmznUbgoAoGCCqGSM49
+AwEHoUQDQgAEE4o/vZiNRSXbyjRy3kkHDluKG96a7yF7hBiTK0HiRejmHIMMa1ar
+JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
+-----END EC PRIVATE KEY-----'
+ ),
+ );
+ my $r = $tester->register( $data->{appId}, $data->{challenge} );
+ ok( $r->is_success, ' Good challenge value' )
+ or diag( $r->error_message );
+
+ my $registrationData = JSON::to_json( {
+ clientData => $r->client_data,
+ errorCode => 0,
+ registrationData => $r->registration_data,
+ version => "U2F_V2"
+ }
+ );
+ my ( $host, $url, $query );
+ $query = Lemonldap::NG::Common::FormEncode::build_urlencoded(
+ registration => $registrationData,
+ challenge => $res->[2]->[0],
+ );
+
+ ok(
+ $res = $client->_post(
+ '/2fregisters/u/registration', IO::String->new($query),
+ length => length($query),
+ accept => 'application/json',
+ cookie => "lemonldap=$id",
+ ),
+ 'Push registration data'
+ );
+ expectOK($res);
+ eval { $data = JSON::from_json( $res->[2]->[0] ) };
+ ok( not($@), ' Content is JSON' )
+ or explain( [ $@, $res->[2] ], 'JSON content' );
+ ok( $data->{result} == 1, 'Key is registered' )
+ or explain( $data, '"result":1' );
+
+ # Try to sign-in
+ $client->logout($id);
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ length => 23,
+ accept => 'text/html',
+ ),
+ 'Auth query'
+ );
+ ( $host, $url, $query ) = expectForm( $res, undef, '/2fchoice', 'token' );
+ $query .= '&sf=totp';
+ ok(
+ $res = $client->_post(
+ '/2fchoice',
+ IO::String->new($query),
+ length => length($query),
+ accept => 'text/html',
+ ),
+ 'Post TOTP choice'
+ );
+ ( $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/;
+ ok(
+ $res = $client->_post(
+ '/totp2fcheck', IO::String->new($query),
+ length => length($query),
+ ),
+ 'Post code'
+ );
+ $id = expectCookie($res);
+ $client->logout($id);
+
+ # Skipping time until TOTP token expiration
+ Time::Fake->offset("+2m");
+
+ # Try to sign-in
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ length => 23,
+ accept => 'text/html',
+ ),
+ 'Auth query'
+ );
+ ( $host, $url, $query ) =
+ expectForm( $res, undef, '/totp2fcheck', 'token' );
+
+ # Generate TOTP with LLNG
+ ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
+ 'LLNG Code' );
+ $query =~ s/code=/code=$code/;
+
+ ok(
+ $res = $client->_post(
+ '/totp2fcheck', IO::String->new($query),
+ length => length($query),
+ accept => 'text/html',
+ ),
+ 'Post code'
+ );
+ ok(
+ $res->[2]->[0] =~
+qr%
%,
+ 'Notification reference found'
+ ) or print STDERR Dumper( $res->[2]->[0] );
+ ok( time() + 120 <= $1 && $1 <= time() + 125, 'Right reference found' )
+ or print STDERR Dumper( $res->[2]->[0] );
+ ok(
+ $res->[2]->[0] =~
+qr%
1 expired second factor\(s\) has/have been removed!
%,
+ 'Notification message found'
+ ) or print STDERR Dumper( $res->[2]->[0] );
+ $id = expectCookie($res);
+ $client->logout($id);
+
+ # Skipping time until TOTP token expiration
+ Time::Fake->offset("+5m");
+
+ # Try to sign-in
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ length => 23,
+ accept => 'text/html',
+ ),
+ 'Auth query'
+ );
+ ok(
+ $res->[2]->[0] =~
+qr%
%,
+ 'Notification reference found'
+ ) or print STDERR Dumper( $res->[2]->[0] );
+ ok( time() + 120 <= $1 && $1 <= time() + 125, 'Right reference found' )
+ or print STDERR Dumper( $res->[2]->[0] );
+ ok(
+ $res->[2]->[0] =~
+qr%
%,
+ 'Notification reference found'
+ ) or print STDERR Dumper( $res->[2]->[0] );
+ ok( time() + 300 <= $1 && $1 <= time() + 305, 'Right reference found' )
+ or print STDERR Dumper( $res->[2]->[0] );
+ my @notifs =
+ ( $res->[2]->[0] =~
+m%
1 expired second factor\(s\) has/have been removed!
%gs
+ );
+ ok( 2 == @notifs, '2 notifications found' )
+ or print STDERR Dumper( $res->[2]->[0] );
+}
+
+count($maintests);
+system 'rm -f t/*_dwho_*.json';
+clean_sessions();
+
+done_testing( count() );
diff --git a/lemonldap-ng-portal/t/lmConf-1.json b/lemonldap-ng-portal/t/lmConf-1.json
index 716daf346..2a9d18662 100644
--- a/lemonldap-ng-portal/t/lmConf-1.json
+++ b/lemonldap-ng-portal/t/lmConf-1.json
@@ -8,17 +8,19 @@
"display" : "auto",
"logo" : "demo.png",
"name" : "Application Test 1",
- "uri" : "http://test1.example.com/"
+ "uri" : "http://test1.example.com/",
+ "tooltip": ""
},
"type" : "application"
},
"test2" : {
"options" : {
- "description" : "The same simple application displaying authenticated user",
+ "description" : "",
"display" : "auto",
"logo" : "thumbnail.png",
"name" : "Application Test 2",
- "uri" : "http://test2.example.com/"
+ "uri" : "http://test2.example.com/",
+ "tooltip": "A nice application!"
},
"type" : "application"
},
@@ -74,7 +76,7 @@
"LockDirectory": "t/sessions/lock",
"generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256"
},
- "groups": { "su":"$uid and $uid eq \"rtyler\"", "test_su": "$uid and $uid eq \"rtyler\"", "su_test": "$uid and $uid eq \"rtyler\"" },
+ "groups": { "su":"$uid and $uid =~ /(?:rtyler|dwho)/", "test_su": "$uid and $uid eq \"rtyler\"", "su_test": "$uid and $uid eq \"rtyler\"" },
"key": "qwertyui",
"locationRules": {
"auth.example.com" : {