diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session/REST.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session/REST.pm index 0d610cae474f81a39f66c306f150646c417beb45..5ae1cd7f10fef475eae60b46d27eaf301888fc7d 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session/REST.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session/REST.pm @@ -3,6 +3,7 @@ package Lemonldap::NG::Common::Session::REST; use strict; use Mouse; use Lemonldap::NG::Common::Conf::Constants; +use Lemonldap::NG::Common::Util qw/display2F/; use JSON qw(from_json to_json); our $VERSION = '2.0.15'; @@ -46,7 +47,7 @@ sub hAttr { sub delSession { my ( $self, $req ) = @_; my $type = $req->params('sessionType'); - my $mod = $self->getMod($req) + my $mod = $self->getMod($req) or return $self->sendError( $req, undef, 400 ); my $id = $req->params('sessionId') or return $self->sendError( $req, 'sessionId is missing', 400 ); @@ -181,9 +182,16 @@ sub delete2F { $self->logger->debug( "Searching for 2F device to delete -> $type / $epoch ..."); if ( defined $element->{type} && defined $element->{epoch} ) { - push @keep, $element - unless ( ( $element->{type} eq $type ) - and ( $element->{epoch} eq $epoch ) ); + if ( ( $element->{type} eq $type ) + and ( $element->{epoch} eq $epoch ) ) + { + my $uid = $session->data->{_session_uid}; + $self->userLogger->notice( + "2FA deletion for $uid: " . display2F($element) ); + } + else { + push @keep, $element; + } } else { $self->logger->error("Corrupted _2fDevice"); diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Util.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Util.pm index 569bea4a88736b0cabf502777b88a6e1336de4e2..56560684c2eafdd93e607a1ffa0ef8fe481f95f7 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Util.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Util.pm @@ -7,7 +7,7 @@ use MIME::Base64 qw(encode_base64); our $VERSION = '2.0.14'; our @ISA = qw(Exporter); -our @EXPORT_OK = qw(getSameSite getPSessionID genId2F); +our @EXPORT_OK = qw(getSameSite getPSessionID genId2F display2F); sub getPSessionID { my ($uid) = @_; @@ -20,6 +20,11 @@ sub genId2F { "" ); } +sub display2F { + my ($device) = @_; + return sprintf( "[%s]%s", $device->{type}, $device->{epoch} ); +} + sub getSameSite { my ($conf) = @_; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index 918339607dfdf3c818360f86fc4088a759a3c3e7..04d5ca06878cb0659851a0669fb40553a2ffe6a4 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -10,7 +10,7 @@ use Mouse; use JSON; use Lemonldap::NG::Common::Session; -use Lemonldap::NG::Common::Util qw/genId2F/; +use Lemonldap::NG::Common::Util qw/genId2F display2F/; sub getSecondFactors { my ( $self, $req ) = @_; @@ -230,7 +230,7 @@ sub _delete2FFromSessions { push @keep, $element; } else { - $removed->{ genId2F($element) } = "removed"; + $removed->{ genId2F($element) } = $element; } } if ( ( $total - scalar @keep ) > 0 ) { @@ -291,6 +291,13 @@ sub _delete2F { # merge results $removed = { %$removed, %{ $res->{removed} } }; $count = scalar( keys %$removed ); + if ($count) { + my $list_log = join( + ",", map { display2F( $removed->{$_} ) } + keys %$removed + ); + $self->userLogger->notice("[API] 2FA deletion for $uid: $list_log"); + } return { res => 'ok', diff --git a/lemonldap-ng-manager/site/coffee/2ndfa.coffee b/lemonldap-ng-manager/site/coffee/2ndfa.coffee index 9e2198d3dcf2182d86191425069a04a42478f81f..9d8e29d7ae00b3b277c3fb6f203de2a580c9992d 100644 --- a/lemonldap-ng-manager/site/coffee/2ndfa.coffee +++ b/lemonldap-ng-manager/site/coffee/2ndfa.coffee @@ -203,7 +203,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', array = JSON.parse(session[attr]) if array.length > 0 subres.push - title: "type" + title: "2fid" value: "name" epoch: "date" for sfDevice in array @@ -215,7 +215,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', if key == 'epoch' epoch = value subres.push - title: title + title: '[' + title + ']' + epoch value: name epoch: epoch sfrow: true diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js index b023164be33e8ac020fa6730e1f2f8acf19b3d98..741ca0afa32e63f27cc383d3fb93e20cd70f3d0c 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js @@ -207,7 +207,7 @@ array = JSON.parse(session[attr]); if (array.length > 0) { subres.push({ - title: "type", + title: "2fid", value: "name", epoch: "date" }); @@ -226,7 +226,7 @@ } } subres.push({ - title: title, + title: '[' + title + ']' + epoch, value: name, epoch: epoch, sfrow: true diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js index 857e6fcca2a8027191db01de77f4ea57cd6c9533..d031ff9ab6fc9d44eb5d303a0226a9920a3cc128 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.min.js @@ -1 +1 @@ -!function(){var a={_whatToTrace:[function(e,t){return"groupBy=substr("+e+",1)"},function(e,t){return e+"="+t+"*"}]},d={_whatToTrace:function(e,t,n,r){return console.log("overSchema => level",n,"over",r),1===n&&t.length>r?e+"="+t+"*&groupBy=substr("+e+","+(n+r+1)+")":null}},_={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},i={home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(v,t,e,n,p){var f,r,h;return v.links=links,v.menulinks=menulinks,v.staticPrefix=staticPrefix,v.scriptname=scriptname,v.formPrefix=formPrefix,v.availableLanguages=availableLanguages,v.waiting=!0,v.showM=!1,v.showT=!0,v.data=[],v.currentScope=null,v.currentSession=null,v.menu=i,v.searchString="",v.sfatypes={},v.translateP=t.translateP,v.translate=t.translate,v.translateTitle=function(e){return t.translateField(e,"title")},h="persistent",v.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(v.currentNode,v),v[e.action]();break;case"string":v[e.action]();break;default:console.log(typeof e.action)}return v.showM=!1},v.search2FA=function(e){return e&&(v.searchString=""),v.currentSession=null,v.data=[],v.updateTree2("",v.data,0,0)},v.delete2FA=function(e,t){for(var n=document.querySelectorAll(".data-"+t),r=0,a=n.length;r level",n,"over",r),1===n&&t.length>r?e+"="+t+"*&groupBy=substr("+e+","+(n+r+1)+")":null}},_={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},i={home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(v,t,e,n,p){var f,r,h;return v.links=links,v.menulinks=menulinks,v.staticPrefix=staticPrefix,v.scriptname=scriptname,v.formPrefix=formPrefix,v.availableLanguages=availableLanguages,v.waiting=!0,v.showM=!1,v.showT=!0,v.data=[],v.currentScope=null,v.currentSession=null,v.menu=i,v.searchString="",v.sfatypes={},v.translateP=t.translateP,v.translate=t.translate,v.translateTitle=function(e){return t.translateField(e,"title")},h="persistent",v.menuClick=function(e){if(e.popup)window.open(e.popup);else switch(e.action||(e.action=e.title),typeof e.action){case"function":e.action(v.currentNode,v),v[e.action]();break;case"string":v[e.action]();break;default:console.log(typeof e.action)}return v.showM=!1},v.search2FA=function(e){return e&&(v.searchString=""),v.currentSession=null,v.data=[],v.updateTree2("",v.data,0,0)},v.delete2FA=function(e,t){for(var n=document.querySelectorAll(".data-"+t),r=0,a=n.length;r {{translate(node.title)}} {{node.title}} - {{translate(node.value)}} - {{node.value}} - {{translate(node.epoch)}} + {{translate(node.value)}} + {{node.value}} + {{translate(node.epoch)}} {{localeDate(node.epoch)}} diff --git a/lemonldap-ng-manager/t/60-2ndfa.t b/lemonldap-ng-manager/t/60-2ndfa.t index 0ecd12ad87a74a4ac5f19b1c2e5b4da06e61e912..e63b5840e2b9581d465210c6d31e1117c41f675e 100644 --- a/lemonldap-ng-manager/t/60-2ndfa.t +++ b/lemonldap-ng-manager/t/60-2ndfa.t @@ -32,6 +32,7 @@ sub newSession { uid => $uid, _utime => time, _session_kind => $kind, + _session_uid => $uid, _2fDevices => to_json($sfaDevices), } ); 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 f6e7c32d61ae3c5c013b54594e27c94b5f579e92..b0f01c0c2c87e757caf8e3c2a3a57e10f5ec7120 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Password.pm @@ -8,6 +8,7 @@ use strict; use Mouse; use JSON qw(from_json to_json); use Lemonldap::NG::Common::Crypto; +use Lemonldap::NG::Common::Util qw/display2F/; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_ERROR @@ -82,6 +83,7 @@ sub run { sub verify { my ( $self, $req, $session ) = @_; my ( $password, $password_device, $secret ); + my $uid = $session->{ $self->conf->{whatToTrace} }; $self->logger->debug( $self->prefix . '2f: verification' ); @@ -103,13 +105,13 @@ sub verify { } if ( $password eq $self->crypto->decrypt($secret) ) { - $self->userLogger->info( $self->prefix . '2f: correct' ); + + $self->userLogger->info( "User $uid authenticated with 2F device: " + . display2F($password_device) ); return PE_OK; } else { - $self->userLogger->notice( $self->prefix - . '2f: invalid for ' - . $session->{ $self->conf->{whatToTrace} } ); + $self->userLogger->notice( $self->prefix . '2f: invalid for ' . $uid ); return PE_BADCREDENTIALS; } } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm index 224b369caab26f82a4f203c3bf6970885f958ee0..c5e899a3b8c08f652208c650e497811a35ce7927 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm @@ -131,8 +131,6 @@ sub run { ) ) { - $self->userLogger->notice( $self->prefix - . "2f: registration of $genericname succeeds for $user" ); return [ 200, [ @@ -160,8 +158,6 @@ sub run { if ( $self->del2fDevice( $req, $req->userData, $self->prefix, $epoch ) ) { - $self->userLogger->notice( - $self->prefix . "2f: device deleted for $user" ); return [ 200, [ diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm index 02b3173200adead46c507bf870cea6963ed83e83..068a652ec21f9ba0d61358364be3a6679a4cb31a 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm @@ -110,8 +110,6 @@ sub run { ) ) { - $self->userLogger->notice( $self->prefix - . "2f: registration of password succeeds for $user" ); return [ 200, [ diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm index b593162768907df84710cdf6f16a64aa21c6ae8b..e365359827cc170f4f2ea31b89664630bae022d3 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm @@ -119,8 +119,6 @@ sub run { ) ) { - $self->userLogger->notice( $self->prefix - . "2f: registration of $TOTPName succeeds for $user" ); return [ 200, [ @@ -192,8 +190,6 @@ sub run { or return $self->p->sendError( $req, $self->prefix . '2f: "epoch" parameter is missing', 400 ); if ( $self->del2fDevice( $req, $req->userData, $self->type, $epoch ) ) { - $self->userLogger->notice( - $self->prefix . "2f: device deleted for $user" ); return [ 200, [ diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm index 15cf90ee7176f837e4215e5db8c4686d81bce682..1e5edc440e67f1e34401d19112c2afc915695823 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm @@ -39,7 +39,7 @@ sub run { # Read existing 2F device(s) my @alldevices = $self->find2fDevicesByType( $req, $req->userData ); - my $challenge = $self->crypter->registrationChallenge; + my $challenge = $self->crypter->registrationChallenge; $self->logger->debug( $self->prefix . "2f: registration challenge ($challenge)" ); return [ @@ -92,9 +92,6 @@ sub run { ) ) { - $self->userLogger->notice( $self->prefix - . "2f: registration of device $keyName succeeds for $user" - ); return [ 200, [ @@ -210,8 +207,6 @@ sub run { or return $self->p->sendError( $req, $self->prefix . '2f: "epoch" parameter is missing', 400 ); if ( $self->del2fDevice( $req, $req->userData, $self->type, $epoch ) ) { - $self->userLogger->notice( - $self->prefix . "2f: device deleted for $user" ); return [ 200, [ diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm index 135fcd1c3ed702db454527b058581dd33ef4c948..6f4b7d901437ebed1ffd0cb67b8d0bf500912297 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm @@ -192,8 +192,6 @@ sub _registration { ) ) { - $self->userLogger->notice( - $self->prefix . "2f: registration of $keyName succeeds for $user" ); return $self->p->sendJSONresponse( $req, { result => 1 } ); } else { diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Yubikey.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Yubikey.pm index 749aa36e1b22c401d7cb9621e7de47da91b9976f..1d037f71a9b2ce52d605f9cd198ff0bd66fc554b 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Yubikey.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Yubikey.pm @@ -71,9 +71,6 @@ sub run { ) ) { - $self->userLogger->notice( $self->prefix - . "2f: registration of device $UBKName succeeds for $user" - ); return $self->p->sendHtml( $req, 'error', params => { @@ -110,8 +107,6 @@ sub run { or return $self->p->sendError( $req, $self->prefix . '2f: "epoch" parameter is missing', 400 ); if ( $self->del2fDevice( $req, $req->userData, $self->type, $epoch ) ) { - $self->userLogger->notice( - $self->prefix . "2f: device deleted for $user" ); return [ 200, [ 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 3acca045cfb72664d551796a80f6537527cd4e90..c936b711167c8ec834a259a17f08a4ecb2d1003c 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm @@ -14,6 +14,7 @@ use Lemonldap::NG::Portal::Main::Constants qw( PE_FORMEMPTY PE_SENDRESPONSE ); +use Lemonldap::NG::Common::Util qw/display2F/; our $VERSION = '2.0.16'; @@ -65,6 +66,7 @@ sub run { sub verify { my ( $self, $req, $session ) = @_; my ( $code, $secret, @totp2f ); + my $uid = $session->{ $self->conf->{whatToTrace} }; $self->logger->debug( $self->prefix . '2f: verification' ); unless ( $code = $req->param('code') ) { @@ -73,7 +75,13 @@ sub verify { } @totp2f = $self->find2fDevicesByType( $req, $session, $self->type ); - $secret = $_->{_secret} foreach @totp2f; + my $_2fDevice; + foreach (@totp2f) { + if ( $_->{_secret} ) { + $secret = $_->{_secret}; + $_2fDevice = $_; + } + } unless ($secret) { $self->logger->debug( $self->prefix . '2f: no secret found' ); return PE_BADOTP; @@ -88,13 +96,13 @@ sub verify { return PE_ERROR if $r == -1; if ($r) { - $self->userLogger->info( $self->prefix . '2f: succeed' ); + $self->userLogger->info( "User $uid authenticated with 2F device: " + . display2F($_2fDevice) ); return PE_OK; } else { - $self->userLogger->notice( $self->prefix - . '2f: invalid attempt for ' - . $session->{ $self->conf->{whatToTrace} } ); + $self->userLogger->notice( + $self->prefix . '2f: invalid attempt for ' . $uid ); return PE_BADOTP; } } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/U2F.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/U2F.pm index b64fabe3a86de6442b8d1618025b120e1f044a9c..ee788d3fc3b7a695291a616eaa8faed2e0031cca 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/U2F.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/U2F.pm @@ -8,6 +8,7 @@ use strict; use Mouse; use JSON qw(from_json to_json); use MIME::Base64 qw(decode_base64url); +use Lemonldap::NG::Common::Util qw/display2F/; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_ERROR @@ -106,6 +107,7 @@ sub run { sub verify { my ( $self, $req, $session ) = @_; my $crypter; + my $uid = $session->{ $self->conf->{whatToTrace} }; # Check U2F signature if ( my $resp = $req->param('signature') @@ -151,13 +153,17 @@ sub verify { return $self->fail($req); } if ( $crypter->authenticationVerify($resp) ) { - $self->userLogger->info( $self->prefix . '2f: signature verified' ); + + # Lookup 2f device from authenticator's keyHandle + my $device = $req->data->{u2fkhtodev}->{ $data->{keyHandle} }; + $self->userLogger->info( "User $uid authenticated with 2F device: " + . display2F($device) ); return PE_OK; } else { $self->userLogger->notice( $self->prefix . '2f: unvalid signature for ' - . $session->{ $self->conf->{whatToTrace} } . ' (' + . $uid . ' (' . Crypt::U2F::Server::u2fclib_getError() . ')' ); $req->error(PE_U2FFAILED); @@ -165,9 +171,8 @@ sub verify { } } else { - $self->userLogger->notice( $self->prefix - . '2f: no valid response for user ' - . $session->{ $self->conf->{whatToTrace} } ); + $self->userLogger->notice( + $self->prefix . '2f: no valid response for user ' . $uid ); $req->authResult(PE_U2FFAILED); return $self->fail($req); } @@ -202,6 +207,7 @@ sub loadUser { foreach (@u2fs) { $kh = $_->{_keyHandle}; + $req->data->{u2fkhtodev}->{$kh} = $_; $uk = decode_base64url( $_->{_userKey} ); $self->logger->debug( $self->prefix . "2f: append crypter with kh = $kh" ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Yubikey.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Yubikey.pm index c4b85e1c6851b575ea236a5b5fb46d135ec19837..56b487bb0bc3db06e68771795866ab7dae4aa909 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Yubikey.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Yubikey.pm @@ -7,6 +7,7 @@ package Lemonldap::NG::Portal::2F::Yubikey; use strict; use Mouse; use JSON qw(from_json to_json); +use Lemonldap::NG::Common::Util qw/display2F/; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK PE_ERROR @@ -89,44 +90,63 @@ sub init { return $self->SUPER::init(); } -sub _findYubikey { - my ( $self, $req, $session ) = @_; +sub _findYubikeyForCode { + my ( $self, $req, $session, $code ) = @_; my ( $yubikey, @ubk2f ); + my $id_from_code = substr( $code, 0, $self->conf->{yubikey2fPublicIDSize} ); + # First, lookup from session attribute if ( $self->conf->{yubikey2fFromSessionAttribute} ) { my $attr = $self->conf->{yubikey2fFromSessionAttribute}; $yubikey = $session->{$attr}; + if ($yubikey) { + if ( $yubikey eq $id_from_code ) { + return { _yubikey => $yubikey, }; + } + else { + return; + } + } } # If we didn't find a key, lookup psession if ( !$yubikey and $session->{_2fDevices} ) { @ubk2f = $self->find2fDevicesByType( $req, $session, $self->type ); - if ( my $code = $req->param('code') ) { - $yubikey = $_->{_yubikey} foreach grep { - $_->{_yubikey} eq - substr( $code, 0, $self->conf->{yubikey2fPublicIDSize} ) - } @ubk2f; - } - else { - $yubikey = $_->{_yubikey} foreach @ubk2f; - } + my @results = grep { $_->{_yubikey} eq $id_from_code } @ubk2f; + return $results[0]; } + return; +} + +sub _hasYubikey { + my ( $self, $req, $session ) = @_; + my ( $yubikey, @ubk2f ); - return $yubikey || ''; + # Does the user have a session attribute + if ( $self->conf->{yubikey2fFromSessionAttribute} ) { + my $attr = $self->conf->{yubikey2fFromSessionAttribute}; + $yubikey = $session->{$attr}; + } + + # If we didn't find a value, lookup registered devices + if ( !$yubikey and $session->{_2fDevices} ) { + @ubk2f = $self->find2fDevicesByType( $req, $session, $self->type ); + $yubikey = $_->{_yubikey} foreach @ubk2f; + } + + return ( $yubikey ? 1 : 0 ); } sub run { my ( $self, $req, $token ) = @_; - my $yubikey = $self->_findYubikey( $req, $req->sessionInfo ); - unless ($yubikey) { + unless ( $self->_hasYubikey( $req, $req->sessionInfo ) ) { $self->userLogger->warn( $self->prefix . '2f: user ' . $req->{sessionInfo}->{ $self->conf->{whatToTrace} } . ' has no device registered' ); return PE_BADOTP; } - $self->logger->debug( $self->prefix . "2f: found $yubikey" ); # Prepare form my ( $checkLogins, $stayConnected ) = $self->getFormParams($req); @@ -157,12 +177,8 @@ sub verify { } # Verify OTP - my $yubikey = $self->_findYubikey( $req, $session ); - if ( - index( $yubikey, - substr( $code, 0, $self->conf->{yubikey2fPublicIDSize} ) ) == -1 - ) - { + my $yubikey = $self->_findYubikeyForCode( $req, $session, $code ); + unless ($yubikey) { $self->userLogger->warn( $self->prefix . '2f: device not registered' ); return PE_BADOTP; } @@ -173,6 +189,15 @@ sub verify { $self->userLogger->warn( $self->prefix . '2f: verification failed' ); return PE_BADOTP; } + my $uid = $session->{ $self->conf->{whatToTrace} }; + if ( $yubikey->{epoch} ) { + $self->userLogger->info( + "User $uid authenticated with 2F device: " . display2F($yubikey) ); + } + else { + $self->userLogger->info("User $uid authenticated with Yubikey"); + } + return PE_OK; } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/2fDevices.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/2fDevices.pm index b51219c9897e584978c141042d5d36e1dab77b1a..214dc6ef010a24ce2f66d08337de322a28ddb0e8 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/2fDevices.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/2fDevices.pm @@ -22,6 +22,7 @@ of this module use strict; use Mouse::Role; +use Lemonldap::NG::Common::Util qw/display2F/; use JSON; requires qw(p conf logger); @@ -103,9 +104,10 @@ sub add2fDevice { my $_2fDevices = $self->get2fDevices( $req, $info ); push @{$_2fDevices}, $device; - $self->logger->debug( - "Append 2F device: { type => $device->{type}, name => $device->{name} }" - ); + + my $uid = $info->{ $self->conf->{whatToTrace} }; + $self->userLogger->notice( + "User " . $uid . " registered 2F device: " . display2F($device) ); $self->p->updatePersistentSession( $req, { _2fDevices => to_json($_2fDevices) } ); return 1; @@ -156,8 +158,11 @@ sub del2fDevices { @updated_2fDevices; if ( @updated_2fDevices < $size_before ) { $need_update = 1; - $self->logger->debug( - "Deleted 2F device: { type => $type, epoch => $epoch }"); + my $uid = $info->{ $self->conf->{whatToTrace} }; + $self->userLogger->notice( "User " + . $uid + . " deleted 2F device: " + . display2F($device_spec) ); } } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Code2F.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Code2F.pm index b06837dda48378949bf891e72455379c2de75d4c..3a1050c2582a6fea7f220fceaf1b93ab2ceb4acf 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Code2F.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Code2F.pm @@ -10,6 +10,7 @@ package Lemonldap::NG::Portal::Lib::Code2F; use strict; use Mouse; +use Lemonldap::NG::Common::Util qw/display2F/; use Lemonldap::NG::Portal::Main::Constants qw( PE_OK @@ -131,9 +132,9 @@ sub _resend { { # Resend code and update last retry - unless ($self->sendCode( $req, $session, $code )){ + unless ( $self->sendCode( $req, $session, $code ) ) { return $self->p->do( $req, [ sub { PE_ERROR } ] ); - }; + } $self->ott->updateToken( $token, __lastRetry => time ); } else { @@ -151,10 +152,18 @@ sub verify { return PE_FORMEMPTY; } - $self->populateDestAttribute( $req, $req->sessionInfo ); + $self->populateDestAttribute( $req, $session ); - return $self->verify_supplied_code( $req, $session, $usercode ); + my $res = $self->verify_supplied_code( $req, $session, $usercode ); + # If we authenticated the user using a 2F Device, log it + if ( $res == PE_OK && $req->data->{__2fDevice_registrable} ) { + my $uid = $session->{ $self->conf->{whatToTrace} }; + my $device = $req->data->{__2fDevice_registrable}; + $self->userLogger->info( + "User $uid authenticated with 2F device: " . display2F($device) ); + } + return $res; } sub verify_supplied_code { @@ -236,7 +245,9 @@ sub populateDestAttribute { my @registered_devices = $self->find2fDevicesByType( $req, $sessionInfo, $self->prefix ); if (@registered_devices) { + $sessionInfo->{destination} = $registered_devices[0]->{_generic}; + $req->data->{__2fDevice_registrable} = $registered_devices[0]; } } } diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/WebAuthn.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/WebAuthn.pm index 90e98a8dc40ee7ba9414a1ee02ee80f4195690ce..63db100020168b0e652d77faf9a5724814e09b5f 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/WebAuthn.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/WebAuthn.pm @@ -8,6 +8,7 @@ use Digest::SHA qw(sha256); use URI; use Carp; with 'Lemonldap::NG::Portal::Lib::2fDevices'; +use Lemonldap::NG::Common::Util qw/display2F/; our $VERSION = '2.0.16'; @@ -186,9 +187,10 @@ sub validateAssertion { if ( $validation_result->{success} == 1 ) { my $new_signature_count = $validation_result->{signature_count}; - $self->userLogger->info( - "Successfully verified signature with count " - . "$new_signature_count for $user" ); + + $self->userLogger->info( "User $user authenticated with 2F device: " + . display2F($matching_credential) + . " (signature count: $new_signature_count) " ); # Update storedSignCount to be the value of authData.signCount $self->update2fDevice( $req, $data, $self->type, 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 30fe3ec3e4d42ba6b088c74cb88577ca8c9004c8..d19ba6ded13f9921027bdbd9417185251ed83e24 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/SecondFactor.pm @@ -141,7 +141,7 @@ sub _verify { # Else restore session $req->mustRedirect(1); $self->userLogger->notice( $self->prefix - . '2f verification for ' + . '2f verification succeeded for ' . $req->sessionInfo->{ $self->conf->{whatToTrace} } ); if ( my $l = $self->authnLevel ) {