From 38a100f6f6181af9af365b0dd8f52d319ded338b Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Mon, 13 Dec 2021 18:52:00 +0100 Subject: [PATCH] Add WebAuthn to manager 2FA (#1411) --- .../lib/Lemonldap/NG/Manager/2ndFA.pm | 12 +++++++----- .../lib/Lemonldap/NG/Manager/Conf/Tests.pm | 2 +- lemonldap-ng-manager/site/coffee/2ndfa.coffee | 7 ++++--- lemonldap-ng-manager/site/coffee/sessions.coffee | 2 +- lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js | 7 ++++--- .../site/htdocs/static/js/2ndfa.min.js | 2 +- .../site/htdocs/static/js/2ndfa.min.js.map | 2 +- .../site/htdocs/static/js/sessions.js | 2 +- .../site/htdocs/static/js/sessions.min.js | 2 +- .../site/htdocs/static/js/sessions.min.js.map | 2 +- lemonldap-ng-manager/site/templates/2ndfa.tpl | 13 ++++++++----- lemonldap-ng-manager/site/templates/sessions.tpl | 2 +- 12 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/2ndFA.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/2ndFA.pm index 192d7f413..214350864 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/2ndFA.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/2ndFA.pm @@ -15,6 +15,7 @@ extends qw( Lemonldap::NG::Common::Conf::AccessLib ); +use constant _2FTYPES => [ "UBK", "U2F", "TOTP", "WebAuthn" ]; our $VERSION = '2.0.10'; ############################# @@ -46,7 +47,7 @@ sub init { $self->{hiddenAttributes} //= "_password"; $self->{hiddenAttributes} .= ' _session_id' unless $conf->{displaySessionId}; - $self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1'; + $self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = $self->{WebAuthnCheck} = '1'; return 1; } @@ -67,7 +68,7 @@ sub del2F { my $epoch = $params->{epoch} or return $self->sendError( $req, 'Missing "epoch" parameter', 400 ); - if ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ ) { + if ( grep { $_ eq $type } @{_2FTYPES()} ) { $self->logger->debug( "Call procedure delete2F with type=$type and epoch=$epoch"); return $self->delete2F( $req, $session, $skey ); @@ -117,7 +118,7 @@ sub sfa { $moduleOptions->{backend} = $mod->{module}; # Select 2FA sessions to display - foreach (qw(U2F TOTP UBK)) { + foreach (@{_2FTYPES()}) { $self->{ $_ . 'Check' } = delete $params->{ $_ . 'Check' } if ( defined $params->{ $_ . 'Check' } ); } @@ -188,17 +189,18 @@ sub sfa { # Remove sessions without at least one 2F device(s) $self->logger->debug( "Removing sessions without at least one 2F device(s)..."); + my $_2f_types_re = join ('|', @{_2FTYPES()}); foreach my $session ( keys %$res ) { delete $res->{$session} unless ( defined $res->{$session}->{_2fDevices} and $res->{$session}->{_2fDevices} =~ - /"type":\s*"(?:U2F|TOTP|UBK)"/s ); + /"type":\s*"(?:$_2f_types_re)"/s ); } # Filter 2FA sessions if needed $self->logger->debug("Filtering 2F sessions..."); my $all = ( keys %$res ); - foreach (qw(U2F TOTP UBK)) { + foreach (@{_2FTYPES()}) { if ( $self->{ $_ . 'Check' } eq '2' ) { foreach my $session ( keys %$res ) { delete $res->{$session} diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm index 909a6c619..ce955a35a 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm @@ -630,7 +630,7 @@ sub tests { my $msg = ''; my $ok = 0; - foreach (qw(u totp yubikey)) { + foreach (qw(u totp yubikey webauthn)) { $ok ||= $conf->{ $_ . '2fActivation' } && $conf->{ $_ . '2fSelfRegistration' }; last if ($ok); diff --git a/lemonldap-ng-manager/site/coffee/2ndfa.coffee b/lemonldap-ng-manager/site/coffee/2ndfa.coffee index 3f721f49b..f1ba0ac2a 100644 --- a/lemonldap-ng-manager/site/coffee/2ndfa.coffee +++ b/lemonldap-ng-manager/site/coffee/2ndfa.coffee @@ -73,6 +73,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', $scope.U2FCheck = "1" $scope.TOTPCheck = "1" $scope.UBKCheck = "1" + $scope.WebAuthnCheck = "1" # Import translations functions $scope.translateP = $translator.translateP @@ -201,7 +202,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', subres = [] for attr in attrs if session[attr] - if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK)"/) + if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/) subres.push title: "type" value: "name" @@ -295,7 +296,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', over = 0 # Launch HTTP query - $http.get("#{scriptname}sfa/#{sessionType}?#{query}&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}&UBKCheck=#{$scope.UBKCheck}").then (response) -> + $http.get("#{scriptname}sfa/#{sessionType}?#{query}&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}&UBKCheck=#{$scope.UBKCheck}&WebAuthnCheck=#{$scope.WebAuthnCheck}").then (response) -> data = response.data if data.result for n in data.values @@ -346,7 +347,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', over = 0 # Launch HTTP - $http.get("#{scriptname}sfa/#{sessionType}?_session_uid=#{$scope.searchString}*&groupBy=substr(_session_uid,#{$scope.searchString.length})&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}&UBKCheck=#{$scope.UBKCheck}").then (response) -> + $http.get("#{scriptname}sfa/#{sessionType}?_session_uid=#{$scope.searchString}*&groupBy=substr(_session_uid,#{$scope.searchString.length})&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}&UBKCheck=#{$scope.UBKCheck}&WebAuthnCheck=#{$scope.WebAuthnCheck}").then (response) -> data = response.data if data.result for n in data.values diff --git a/lemonldap-ng-manager/site/coffee/sessions.coffee b/lemonldap-ng-manager/site/coffee/sessions.coffee index ec2d6a599..30fb474ff 100644 --- a/lemonldap-ng-manager/site/coffee/sessions.coffee +++ b/lemonldap-ng-manager/site/coffee/sessions.coffee @@ -246,7 +246,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', subres = [] for attr in attrs if session[attr] - if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK)"/) + if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/) subres.push title: "type" value: "name" diff --git a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js index 4f1190200..06857b174 100644 --- a/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js +++ b/lemonldap-ng-manager/site/htdocs/static/js/2ndfa.js @@ -89,6 +89,7 @@ $scope.U2FCheck = "1"; $scope.TOTPCheck = "1"; $scope.UBKCheck = "1"; + $scope.WebAuthnCheck = "1"; $scope.translateP = $translator.translateP; $scope.translate = $translator.translate; $scope.translateTitle = function(node) { @@ -205,7 +206,7 @@ for (i = 0, len = attrs.length; i < len; i++) { attr = attrs[i]; if (session[attr]) { - if (session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK)"/)) { + if (session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/)) { subres.push({ title: "type", value: "name", @@ -303,7 +304,7 @@ } else { over = 0; } - return $http.get(scriptname + "sfa/" + sessionType + "?" + query + "&U2FCheck=" + $scope.U2FCheck + "&TOTPCheck=" + $scope.TOTPCheck + "&UBKCheck=" + $scope.UBKCheck).then(function(response) { + return $http.get(scriptname + "sfa/" + sessionType + "?" + query + "&U2FCheck=" + $scope.U2FCheck + "&TOTPCheck=" + $scope.TOTPCheck + "&UBKCheck=" + $scope.UBKCheck + "&WebAuthnCheck=" + $scope.WebAuthnCheck).then(function(response) { var data, i, len, n, ref; data = response.data; if (data.result) { @@ -345,7 +346,7 @@ } else { over = 0; } - return $http.get(scriptname + "sfa/" + sessionType + "?_session_uid=" + $scope.searchString + "*&groupBy=substr(_session_uid," + $scope.searchString.length + ")&U2FCheck=" + $scope.U2FCheck + "&TOTPCheck=" + $scope.TOTPCheck + "&UBKCheck=" + $scope.UBKCheck).then(function(response) { + return $http.get(scriptname + "sfa/" + sessionType + "?_session_uid=" + $scope.searchString + "*&groupBy=substr(_session_uid," + $scope.searchString.length + ")&U2FCheck=" + $scope.U2FCheck + "&TOTPCheck=" + $scope.TOTPCheck + "&UBKCheck=" + $scope.UBKCheck + "&WebAuthnCheck=" + $scope.WebAuthnCheck).then(function(response) { var data, i, len, n, ref; data = response.data; if (data.result) { 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 b6dc245e1..32a45b1b7 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 S,o,d,g,e;e=function(e,t){return $("#msg").html(window.translate(e)),$("#color").removeClass("message-positive message-warning alert-success alert-warning"),$("#color").addClass("message-"+t),"positive"===t&&(t="success"),$("#color").addClass("alert-"+t)},g={_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}},S={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},o={home:[]},angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(_,t,e,n,i){var p,r,a,f;return _.links=links,_.menulinks=menulinks,_.staticPrefix=staticPrefix,_.scriptname=scriptname,_.formPrefix=formPrefix,_.availableLanguages=availableLanguages,_.waiting=!0,_.showM=!1,_.showT=!0,_.data=[],_.currentScope=null,_.currentSession=null,_.menu=o,_.searchString="",_.U2FCheck="1",_.TOTPCheck="1",_.UBKCheck="1",_.translateP=t.translateP,_.translate=t.translate,_.translateTitle=function(e){return t.translateField(e,"title")},f="persistent",_.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(_.currentNode,_),_[e.action]();break;case"string":_[e.action]();break;default:console.log(typeof e.action)}return _.showM=!1},_.search2FA=function(e){return e&&(_.searchString=""),_.currentSession=null,_.data=[],_.updateTree2("",_.data,0,0)},_.delete2FA=function(e,t){var n,r,a;for(n=0,a=(r=document.querySelectorAll(".data-"+t)).length;n level",n,"over",r),1===n&&t.length>r?e+"="+t+"*&groupBy=substr("+e+","+(n+r+1)+")":null}},v={dateTitle:["_utime","_startTime","_updateTime"],sfaTitle:["_2fDevices"]},i={home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(y,t,e,n,h){var p,r,f;return y.links=links,y.menulinks=menulinks,y.staticPrefix=staticPrefix,y.scriptname=scriptname,y.formPrefix=formPrefix,y.availableLanguages=availableLanguages,y.waiting=!0,y.showM=!1,y.showT=!0,y.data=[],y.currentScope=null,y.currentSession=null,y.menu=i,y.searchString="",y.U2FCheck="1",y.TOTPCheck="1",y.UBKCheck="1",y.WebAuthnCheck="1",y.translateP=t.translateP,y.translate=t.translate,y.translateTitle=function(e){return t.translateField(e,"title")},f="persistent",y.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(y.currentNode,y),y[e.action]();break;case"string":y[e.action]();break;default:console.log(typeof e.action)}return y.showM=!1},y.search2FA=function(e){return e&&(y.searchString=""),y.currentSession=null,y.data=[],y.updateTree2("",y.data,0,0)},y.delete2FA=function(e,t){for(var n=document.querySelectorAll(".data-"+t),r=0,a=n.length;r level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overScheme => level",n,"over",o),0 level",n,"over",o),3 level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null}},M={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]},i={session:[{title:"deleteSession",icon:"trash"}],home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(H,t,r,e,o){var p,n,d;return H.links=links,H.menulinks=menulinks,H.staticPrefix=staticPrefix,H.scriptname=scriptname,H.formPrefix=formPrefix,H.impPrefix=impPrefix,H.sessionTTL=sessionTTL,H.availableLanguages=availableLanguages,H.waiting=!0,H.showM=!1,H.showT=!0,H.data=[],H.currentScope=null,H.currentSession=null,H.menu=i,H.translateP=t.translateP,H.translate=t.translate,H.translateTitle=function(e){return t.translateField(e,"title")},d="global",H.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(H.currentNode,H);break;case"string":H[e.action]();break;default:console.log(typeof e.action)}return H.showM=!1},H.deleteOIDCConsent=function(e,t){var i=document.querySelectorAll(".data-"+t);return H.waiting=!0,o.delete(scriptname+"sessions/OIDCConsent/"+d+"/"+H.currentSession.id+"?rp="+e+"&epoch="+t).then(function(e){var t,n,o,r;for(H.waiting=!1,r=[],n=0,o=i.length;nt.title?1:e.title real attribute"),B.push(i)):A.push(i);return I=A.concat(B),L.push({title:"__attributesAndMacros__",nodes:I}),{_utime:E,nodes:L}};return H.currentScope=e,t=e.$modelValue.session,o.get(scriptname+"sessions/"+d+"/"+t).then(function(e){return H.currentSession=n(e.data),H.currentSession.id=t}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n=r.path(),o=Date.now()/1e3;return console.log("Path",n),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),e=o-e level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null},ipAddr:function(e,t,n,o){return console.log("overScheme => level",n,"over",o),0 level",n,"over",o),3 level",n,"over",o),1===n&&t.length>o?e+"="+t+"*&groupBy=substr("+e+","+(n+o+1)+")":null}},M={dateTitle:["_utime","_startTime","_updateTime","_lastAuthnUTime","_lastSeen"],connectionTitle:["ipAddr","_timezone","_url"],authenticationTitle:["_session_id","_user","_password","authenticationLevel"],modulesTitle:["_auth","_userDB","_passwordDB","_issuerDB","_authChoice","_authMulti","_userDBMulti"],saml:["_idp","_idpConfKey","_samlToken","_lassoSessionDump","_lassoIdentityDump"],groups:["groups","hGroups"],ldap:["dn"],OpenIDConnect:["_oidc_id_token","_oidc_OP","_oidc_access_token"],sfaTitle:["_2fDevices"],oidcConsents:["_oidcConsents"]},i={session:[{title:"deleteSession",icon:"trash"}],home:[]};angular.module("llngSessionsExplorer",["ui.tree","ui.bootstrap","llApp"]).controller("SessionsExplorerCtrl",["$scope","$translator","$location","$q","$http",function(H,t,r,e,o){var p,n,d;return H.links=links,H.menulinks=menulinks,H.staticPrefix=staticPrefix,H.scriptname=scriptname,H.formPrefix=formPrefix,H.impPrefix=impPrefix,H.sessionTTL=sessionTTL,H.availableLanguages=availableLanguages,H.waiting=!0,H.showM=!1,H.showT=!0,H.data=[],H.currentScope=null,H.currentSession=null,H.menu=i,H.translateP=t.translateP,H.translate=t.translate,H.translateTitle=function(e){return t.translateField(e,"title")},d="global",H.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(H.currentNode,H);break;case"string":H[e.action]();break;default:console.log(typeof e.action)}return H.showM=!1},H.deleteOIDCConsent=function(e,t){var i=document.querySelectorAll(".data-"+t);return H.waiting=!0,o.delete(scriptname+"sessions/OIDCConsent/"+d+"/"+H.currentSession.id+"?rp="+e+"&epoch="+t).then(function(e){var t,n,o,r;for(H.waiting=!1,r=[],n=0,o=i.length;nt.title?1:e.title real attribute"),B.push(i)):P.push(i);return I=P.concat(B),L.push({title:"__attributesAndMacros__",nodes:I}),{_utime:E,nodes:L}};return H.currentScope=e,t=e.$modelValue.session,o.get(scriptname+"sessions/"+d+"/"+t).then(function(e){return H.currentSession=n(e.data),H.currentSession.id=t}),H.showT=!1},H.localeDate=function(e){return new Date(1e3*e).toLocaleString()},H.isValid=function(e,t){var n=r.path(),o=Date.now()/1e3;return console.log("Path",n),console.log("Session epoch",e),console.log("Current date",o),console.log("Session TTL",sessionTTL),e=o-e +   &   + + @@ -104,16 +107,16 @@
- {{translate(node.title)}} - {{node.title}} + {{translate(node.title)}} + {{node.title}} {{translate(node.value)}} {{node.value}} {{translate(node.epoch)}} - {{localeDate(node.epoch)}} + {{localeDate(node.epoch)}} - +
diff --git a/lemonldap-ng-manager/site/templates/sessions.tpl b/lemonldap-ng-manager/site/templates/sessions.tpl index 7488ff756..db561bb10 100644 --- a/lemonldap-ng-manager/site/templates/sessions.tpl +++ b/lemonldap-ng-manager/site/templates/sessions.tpl @@ -108,7 +108,7 @@