diff --git a/fastcgi-server/man/llng-fastcgi-server.8p b/fastcgi-server/man/llng-fastcgi-server.8p index 9d7484ba3..3cb300517 100644 --- a/fastcgi-server/man/llng-fastcgi-server.8p +++ b/fastcgi-server/man/llng-fastcgi-server.8p @@ -130,10 +130,14 @@ .\" .IX Title "llng-fastcgi-server 8" <<<<<<< HEAD +<<<<<<< HEAD .TH llng-fastcgi-server 8 "2020-04-03" "perl v5.26.1" "User Contributed Perl Documentation" ======= .TH llng-fastcgi-server 8 "2020-04-01" "perl v5.26.1" "User Contributed Perl Documentation" >>>>>>> v2.0 +======= +.TH llng-fastcgi-server 8 "2020-04-05" "perl v5.26.1" "User Contributed Perl Documentation" +>>>>>>> v2.0 .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/lemonldap-ng-common/lemonldap-ng.ini b/lemonldap-ng-common/lemonldap-ng.ini index 5d07e3664..1e7528ebb 100644 --- a/lemonldap-ng-common/lemonldap-ng.ini +++ b/lemonldap-ng-common/lemonldap-ng.ini @@ -190,7 +190,7 @@ staticPrefix = __PORTALSTATICDIR__ templateDir = __PORTALTEMPLATESDIR__ ; languages: available languages for portal interface -languages = en, fr, vi, it, ar, de, fi, tr +languages = en, fr, vi, it, ar, de, fi, tr, pl ; II - Optional parameters (overwrite configuration) diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/DBI.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/DBI.pm index 87b6a2903..0a62967bc 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/DBI.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/DBI.pm @@ -100,9 +100,7 @@ sub getAccepted { $self->_execute( "SELECT * FROM " . $self->dbiTable - . " WHERE done IS NOT NULL AND uid=?" - . ( $ref ? " AND ref=?" : '' ) - . " ORDER BY date", + . " WHERE uid=? AND ref=? ORDER BY date", $uid, ( $ref ? $ref : () ) ) or return (); diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/File.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/File.pm index 0ed67def8..a32293a46 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/File.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/File.pm @@ -35,8 +35,8 @@ has fileNameSeparator => ( is => 'rw', default => '_' ); # If $ref is set, returns only notification corresponding to this reference. sub get { my ( $self, $uid, $ref ) = @_; - my $ext = $self->extension; return () unless ($uid); + my $ext = $self->extension; my $fns = $self->{fileNameSeparator}; my $identifier = &getIdentifier( $self, $uid, $ref ); @@ -61,11 +61,12 @@ sub get { sub getAccepted { my ( $self, $uid, $ref ) = @_; return () unless ($uid); + my $ext = $self->extension; my $fns = $self->{fileNameSeparator}; my $identifier = &getIdentifier( $self, $uid, $ref ); opendir D, $self->{dirName}; - my @notif = grep /^\d{8}${fns}${identifier}\S*\.done$/, readdir(D); + my @notif = grep /^\d{8}${fns}${identifier}\S*\.(?:done|$ext)$/, readdir(D); closedir D; my $files; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/LDAP.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/LDAP.pm index 221332b0c..6b14df241 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/LDAP.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notifications/LDAP.pm @@ -95,8 +95,7 @@ sub getAccepted { my $filter = '(&(objectClass=applicationProcess)(description={done}*)' - . "(description={uid}$uid)" - . ( $ref ? '(description={ref}' . $ref . ')' : '' ) . ')'; + . "(description={uid}$uid)(description={ref}$ref))"; my @entries = _search( $self, $filter ); my $result; diff --git a/lemonldap-ng-manager/site/coffee/manager.coffee b/lemonldap-ng-manager/site/coffee/manager.coffee index 380871c08..49027245a 100644 --- a/lemonldap-ng-manager/site/coffee/manager.coffee +++ b/lemonldap-ng-manager/site/coffee/manager.coffee @@ -312,6 +312,7 @@ llapp.controller 'TreeCtrl', [ data: description: "New app description" uri: "https://test.example.com/" + tooltip: "New app tooltip" logo: "network.png" display: "auto" diff --git a/lemonldap-ng-manager/site/htdocs/static/forms/menuApp.html b/lemonldap-ng-manager/site/htdocs/static/forms/menuApp.html index 36d27596d..a267f8bdd 100644 --- a/lemonldap-ng-manager/site/htdocs/static/forms/menuApp.html +++ b/lemonldap-ng-manager/site/htdocs/static/forms/menuApp.html @@ -15,6 +15,10 @@ + + + + diff --git a/lemonldap-ng-manager/site/htdocs/static/js/manager.js b/lemonldap-ng-manager/site/htdocs/static/js/manager.js index d75297fb5..e223f107b 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/manager.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/manager.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.8 +// Generated by CoffeeScript 1.12.7 /* LemonLDAP::NG Manager client @@ -362,6 +362,7 @@ This file contains: data: { description: "New app description", uri: "https://test.example.com/", + tooltip: "New app tooltip", logo: "network.png", display: "auto" } diff --git a/lemonldap-ng-manager/site/htdocs/static/js/manager.min.js b/lemonldap-ng-manager/site/htdocs/static/js/manager.min.js index 38f0906ea..47d75f4f2 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/manager.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/manager.min.js @@ -1 +1 @@ -(function(){angular.module("llngManager",["ui.tree","ui.bootstrap","llApp","ngCookies"]).controller("TreeCtrl",["$scope","$http","$location","$q","$uibModal","$translator","$cookies","$htmlParams",function(f,u,a,l,o,n,r,e){var t,s,d,c,i,p,m,g,h;return f.links=window.links,f.menu=e.menu,f.menulinks=window.menulinks,f.staticPrefix=window.staticPrefix,f.formPrefix=window.formPrefix,f.availableLanguages=window.availableLanguages,f.waiting=!0,f.showM=!1,f.showT=!1,f.form="home",f.currentCfg={},f.confPrefix=window.confPrefix,f.message={},f.result="",f.translateTitle=function(e){return n.translateField(e,"title")},f.translateP=n.translateP,f.translate=n.translate,f.helpUrl="start.html#configuration",f.setShowHelp=function(e){var t;return null==e&&(e=!f.showH),f.showH=e,(t=new Date(Date.now())).setFullYear(t.getFullYear()+1),r.put("showhelp",e?"true":"false",{expires:t})},f.showH="false"!==r.get("showhelp"),null==f.showH&&f.setShowHelp(!0),m=function(e){var t,n;return t=e.status,n=e.statusLine,f.waiting=!1,403===t?f.message={title:"forbidden",message:"",items:[]}:401===t?(console.log("Authentication needed"),f.message={title:"authenticationNeeded",message:"__waitOrF5__",items:[]}):f.message=400===t?{title:"badRequest",message:n,items:[]}:0{options}->{name} || $appid; - my $appuri = $apphash->{options}->{uri} || ""; - my $appdesc = $apphash->{options}->{description}; - my $applogo = $apphash->{options}->{logo}; + my $appname = $apphash->{options}->{name} || $appid; + my $appuri = $apphash->{options}->{uri} || ""; + my $appdesc = $apphash->{options}->{description}; + my $applogo = $apphash->{options}->{logo}; + my $apptip = $apphash->{options}->{tooltip} || $appname; my $appIsFavApp = $apphash->{options}->{isFavApp} || "0"; # Detect sub applications @@ -349,6 +350,7 @@ sub _buildApplicationHash { appdesc => $appdesc, applogo => $applogo, appid => $appid, + apptip => $apptip, appisfav => $appIsFavApp, }; $applicationHash->{applications} = $applications if $applications; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm index 1e7524c4d..7954249d4 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm @@ -541,6 +541,7 @@ sub updateSession { # Update sessionInfo data ## sessionInfo updated if $id defined : quite strange !! ## See https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues/430 + $self->logger->debug("Update session $id"); foreach ( keys %$infos ) { $self->logger->debug("Update sessionInfo $_"); $self->_dump( $infos->{$_} ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm index 37c3421c4..863c518ea 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm @@ -78,7 +78,6 @@ sub _redirect { sub _verify { my ( $self, $req ) = @_; - my $checkLogins = $req->param('checkLogins'); $self->logger->debug("checkLogins set") if ($checkLogins); @@ -124,18 +123,44 @@ sub _verify { . $req->sessionInfo->{ $self->conf->{whatToTrace} } ); if ( my $l = $self->conf->{ $self->prefix . '2fAuthnLevel' } ) { - $self->logger->debug("Update sessionInfo with new authenticationLevel: $l"); + $self->logger->debug( + "Update sessionInfo with new authenticationLevel: $l"); $req->sessionInfo->{authenticationLevel} = $l; - delete $req->sessionInfo->{groups}; - # Compute groups & macros again with new authenticationLevel - $req->steps( [ $self->p->groupsAndMacros, 'setLocalGroups'] ); + # Compute macros & local groups again with new authenticationLevel + $self->logger->debug("Compute macros and local groups..."); + $req->steps( [ 'setMacros', 'setLocalGroups' ] ); if ( my $error = $self->p->process($req) ) { $self->logger->debug("SFA: Process returned error: $error"); $req->error($error); return $self->p->do( $req, [ sub { $error } ] ); } - $self->p->updateSession( $req, $req->sessionInfo ); + $self->logger->debug("De-duplicate groups..."); + $req->sessionInfo->{groups} = join $self->conf->{multiValuesSeparator}, + keys %{ { + map { $_ => 1 } split $self->conf->{multiValuesSeparator}, + $req->sessionInfo->{groups} + } + }; + + $self->logger->debug("Filter macros..."); + my %macros = ( + map { $_ => $req->sessionInfo->{$_} } + keys %{ $self->{conf}->{macros} } + ); + + $self->logger->debug( +"Update session with new authenticationLevel, groups, hGroups and macros" + ); + $self->p->updateSession( + $req, + { + authenticationLevel => $l, + groups => $req->sessionInfo->{groups}, + hGroups => $req->sessionInfo->{hGroups}, + %macros + } + ); } $req->authResult(PE_SENDRESPONSE); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/Notifications.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/Notifications.pm index 53fc6f783..695bce378 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/Notifications.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/Notifications.pm @@ -167,7 +167,7 @@ sub myNotifs { my ( $self, $req, $ref ) = @_; if ($ref) { - return $self->sendJSONresponse( $req, { error => 'Missing parameter' } ) + return $self->sendJSONresponse( $req, { error => 'Missing epoch parameter' } ) unless $req->param('epoch'); # Retrieve notification reference=$ref with epoch diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/Demo.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/Demo.pm index cf3a28aec..6a4e4702a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/Demo.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/UserDB/Demo.pm @@ -89,7 +89,7 @@ sub setSessionInfo { # @return Lemonldap::NG::Portal constant sub setGroups { my ( $self, $req ) = @_; - my $user = $req->user || $req->sessionInfo->{ $self->conf->{whatToTrace} }; + my $user = $req->user; my $groups = $req->sessionInfo->{groups} || ''; my $hGroups = $req->sessionInfo->{hGroups} || {}; for my $grp ( keys %demoGroups ) { diff --git a/lemonldap-ng-portal/site/htdocs/static/common/pl.png b/lemonldap-ng-portal/site/htdocs/static/common/pl.png new file mode 100644 index 000000000..894083bca Binary files /dev/null and b/lemonldap-ng-portal/site/htdocs/static/common/pl.png differ diff --git a/lemonldap-ng-portal/site/templates/bootstrap/menu.tpl b/lemonldap-ng-portal/site/templates/bootstrap/menu.tpl index 785f6470c..3b2c89366 100644 --- a/lemonldap-ng-portal/site/templates/bootstrap/menu.tpl +++ b/lemonldap-ng-portal/site/templates/bootstrap/menu.tpl @@ -180,7 +180,7 @@
card border-secondary" id="sort_">
-

+

@@ -204,8 +204,7 @@
card"> - - " title="" > + " title="" >
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%[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%[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" : {