Merge branch 'fix-manager-hardcoded-sfa' into 'v2.0'

Remove hardcoded 2FA types from manager

See merge request lemonldap-ng/lemonldap-ng!288
This commit is contained in:
Maxime Besson 2022-09-06 12:57:43 +00:00
commit a9faf589c1
16 changed files with 173 additions and 197 deletions

View File

@ -56,7 +56,7 @@ sub genRoute {
else {
$dest ||= $word;
}
if ( $dest =~ /^(.+)\.html$/ ) {
if ( $word =~ /^(.+)\.html$/ and $word eq $dest ) {
my $tpl = $1 or die;
$self->logger->debug("route $dest will use $tpl");
$routes->{$word} = sub { $self->sendHtml( $_[1], $tpl ) };
@ -305,8 +305,8 @@ the subroutine will be called with the word of path_info as second argument
=item 'something.html':
if $word finishes with '.html', then sendHtml() will be called with
'something.tpl' as template name. In this case, $dest is not used.
if $word finishes with '.html', and $dest is undef, then sendHtml() will be
called with 'something.tpl' as template name.
=back

View File

@ -15,7 +15,6 @@ extends qw(
Lemonldap::NG::Common::Conf::AccessLib
);
use constant _2FTYPES => [ "UBK", "U2F", "TOTP", "WebAuthn" ];
our $VERSION = '2.0.10';
#############################
@ -29,7 +28,7 @@ sub init {
# Remote Procedure are defined in Lemonldap::NG::Common::Session::REST
# HTML template
$self->addRoute( '2ndfa.html', undef, ['GET'] )
$self->addRoute( '2ndfa.html', 'sfaView', ['GET'] )
->addRoute(
sfa => { ':sessionType' => 'sfa' },
@ -47,8 +46,11 @@ sub init {
$self->{hiddenAttributes} //= "_password";
$self->{hiddenAttributes} .= ' _session_id'
unless $conf->{displaySessionId};
$self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} =
$self->{WebAuthnCheck} = '1';
$self->{regSfaTypes} = [
sort map { s/^Yubikey$/UBK/r } split /[\s,]+/,
$conf->{available2FSelfRegistration}
];
return 1;
}
@ -69,14 +71,9 @@ sub del2F {
my $epoch = $params->{epoch}
or return $self->sendError( $req, 'Missing "epoch" parameter', 400 );
if ( grep { $_ eq $type } @{ _2FTYPES() } ) {
$self->logger->debug(
"Call procedure delete2F with type=$type and epoch=$epoch");
return $self->delete2F( $req, $session, $skey );
}
else {
return $self->sendError( $req, 'Bad value "type" parameter', 400 );
}
$self->logger->debug(
"Call procedure delete2F with type=$type and epoch=$epoch");
return $self->delete2F( $req, $session, $skey );
}
########################
@ -118,11 +115,8 @@ sub sfa {
my $moduleOptions = $mod->{options};
$moduleOptions->{backend} = $mod->{module};
# Select 2FA sessions to display
foreach ( @{ _2FTYPES() } ) {
$self->{ $_ . 'Check' } = delete $params->{ $_ . 'Check' }
if ( defined $params->{ $_ . 'Check' } );
}
my @display_types = $params->get_all('type');
$params->remove('type');
my %filters = map {
my $s = $_;
@ -190,19 +184,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*"(?:$_2f_types_re)"/s );
and $res->{$session}->{_2fDevices} =~ /"type"/s );
}
# Filter 2FA sessions if needed
$self->logger->debug("Filtering 2F sessions...");
my $all = ( keys %$res );
foreach ( @{ _2FTYPES() } ) {
if ( $self->{ $_ . 'Check' } eq '2' ) {
# Filter 2FA sessions if needed
if (@display_types) {
$self->logger->debug("Filtering 2F sessions...");
foreach (@display_types) {
foreach my $session ( keys %$res ) {
delete $res->{$session}
unless ( defined $res->{$session}->{_2fDevices}
@ -286,4 +279,14 @@ qq{Use of an uninitialized attribute "$group" to group sessions},
);
}
sub sfaView {
my ( $self, $req ) = @_;
return $self->p->sendHtml(
$req, "2ndfa",
params => {
SFATYPES => [ map { { SFATYPE => $_ } } @{ $self->{regSfaTypes} } ],
}
);
}
1;

View File

@ -228,7 +228,6 @@ sub init {
$self->setTypes($conf);
$self->{multiValuesSeparator} ||= '; ';
$self->{hiddenAttributes} //= "_password";
$self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1';
return 1;
}

View File

@ -139,11 +139,6 @@ sub _get2F {
my ( $self, $uid, $type, $id ) = @_;
my ( $res, $psessions, @secondFactors );
if ( defined $type ) {
$res = $self->_checkType($type);
return $res if ( $res->{res} ne 'ok' );
}
$psessions = $self->_getSessions2F( $self->_getPersistentMod, 'Persistent',
'_session_uid', $uid );
@ -279,10 +274,6 @@ sub _delete2FFromSessions {
sub _delete2F {
my ( $self, $uid, $type, $id ) = @_;
my ( $res, $removed, $count );
if ( defined $type ) {
$res = $self->_checkType($type);
return $res if ( $res->{res} ne 'ok' );
}
$res =
$self->_delete2FFromSessions( $uid, $type, $id, $self->_getPersistentMod,
@ -331,18 +322,4 @@ sub _getDevicesFromSessionData {
return [];
}
sub _checkType {
my ( $self, $type ) = @_;
return {
res => "ko",
code => 400,
msg =>
"Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\", \"WebAuthn\" or \"UBK\""
}
unless ( $type =~ /\b(?:U2F|TOTP|UBK|WebAuthn)\b/i );
return { res => "ok" };
}
1;

View File

@ -70,10 +70,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
$scope.currentSession = null
$scope.menu = menu
$scope.searchString = ''
$scope.U2FCheck = "1"
$scope.TOTPCheck = "1"
$scope.UBKCheck = "1"
$scope.WebAuthnCheck = "1"
$scope.sfatypes = {}
# Import translations functions
$scope.translateP = $translator.translateP
@ -202,24 +199,26 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
subres = []
for attr in attrs
if session[attr]
if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/)
subres.push
title: "type"
value: "name"
epoch: "date"
if attr == "_2fDevices" && session[attr]
array = JSON.parse(session[attr])
for sfDevice in array
for key, value of sfDevice
if key == 'type'
title = value
if key == 'name'
name = value
if key == 'epoch'
epoch = value
if array.length > 0
subres.push
title: title
value: name
epoch: epoch
title: "type"
value: "name"
epoch: "date"
for sfDevice in array
for key, value of sfDevice
if key == 'type'
title = value
if key == 'name'
name = value
if key == 'epoch'
epoch = value
subres.push
title: title
value: name
epoch: epoch
sfrow: true
delete session[attr]
else if session[attr].toString().match(/\w+/)
subres.push
@ -296,7 +295,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}&WebAuthnCheck=#{$scope.WebAuthnCheck}").then (response) ->
$http.get("#{scriptname}sfa/#{sessionType}?#{query}"+Object.entries($scope.sfatypes).map((x) -> if x[1] then "&type=" + x[0] else "").join("")).then (response) ->
data = response.data
if data.result
for n in data.values
@ -347,7 +346,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}&WebAuthnCheck=#{$scope.WebAuthnCheck}").then (response) ->
$http.get("#{scriptname}sfa/#{sessionType}?_session_uid=#{$scope.searchString}*&groupBy=substr(_session_uid,#{$scope.searchString.length})"+Object.entries($scope.sfatypes).map((x) -> if x[1] then "&type=" + x[0] else "").join("")).then (response) ->
data = response.data
if data.result
for n in data.values

View File

@ -254,26 +254,27 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
subres = []
for attr in attrs
if session[attr]
if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/)
subres.push
title: "type"
value: "name"
epoch: "date"
td: "0"
if attr == "_2fDevices" && session[attr]
array = JSON.parse(session[attr])
for sfDevice in array
for key, value of sfDevice
if key == 'type'
title = value
if key == 'name'
name = value
if key == 'epoch'
epoch = value
if array.length > 0
subres.push
title: title
value: name
epoch: epoch
td: "1"
title: "type"
value: "name"
epoch: "date"
td: "0"
for sfDevice in array
for key, value of sfDevice
if key == 'type'
title = value
if key == 'name'
name = value
if key == 'epoch'
epoch = value
subres.push
title: title
value: name
epoch: epoch
td: "1"
delete session[attr]
else if session[attr].toString().match(/"rp":\s*"[\w-]+"/)
subres.push

View File

@ -86,10 +86,7 @@
$scope.currentSession = null;
$scope.menu = menu;
$scope.searchString = '';
$scope.U2FCheck = "1";
$scope.TOTPCheck = "1";
$scope.UBKCheck = "1";
$scope.WebAuthnCheck = "1";
$scope.sfatypes = {};
$scope.translateP = $translator.translateP;
$scope.translate = $translator.translate;
$scope.translateTitle = function(node) {
@ -206,32 +203,35 @@
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|WebAuthn)"/)) {
subres.push({
title: "type",
value: "name",
epoch: "date"
});
if (attr === "_2fDevices" && session[attr]) {
array = JSON.parse(session[attr]);
for (k = 0, len1 = array.length; k < len1; k++) {
sfDevice = array[k];
for (key in sfDevice) {
value = sfDevice[key];
if (key === 'type') {
title = value;
}
if (key === 'name') {
name = value;
}
if (key === 'epoch') {
epoch = value;
}
}
if (array.length > 0) {
subres.push({
title: title,
value: name,
epoch: epoch
title: "type",
value: "name",
epoch: "date"
});
for (k = 0, len1 = array.length; k < len1; k++) {
sfDevice = array[k];
for (key in sfDevice) {
value = sfDevice[key];
if (key === 'type') {
title = value;
}
if (key === 'name') {
name = value;
}
if (key === 'epoch') {
epoch = value;
}
}
subres.push({
title: title,
value: name,
epoch: epoch,
sfrow: true
});
}
}
delete session[attr];
} else if (session[attr].toString().match(/\w+/)) {
@ -304,7 +304,13 @@
} else {
over = 0;
}
return $http.get(scriptname + "sfa/" + sessionType + "?" + query + "&U2FCheck=" + $scope.U2FCheck + "&TOTPCheck=" + $scope.TOTPCheck + "&UBKCheck=" + $scope.UBKCheck + "&WebAuthnCheck=" + $scope.WebAuthnCheck).then(function(response) {
return $http.get((scriptname + "sfa/" + sessionType + "?" + query) + Object.entries($scope.sfatypes).map(function(x) {
if (x[1]) {
return "&type=" + x[0];
} else {
return "";
}
}).join("")).then(function(response) {
var data, i, len, n, ref;
data = response.data;
if (data.result) {
@ -346,7 +352,13 @@
} 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 + "&WebAuthnCheck=" + $scope.WebAuthnCheck).then(function(response) {
return $http.get((scriptname + "sfa/" + sessionType + "?_session_uid=" + $scope.searchString + "*&groupBy=substr(_session_uid," + $scope.searchString.length + ")") + Object.entries($scope.sfatypes).map(function(x) {
if (x[1]) {
return "&type=" + x[0];
} else {
return "";
}
}).join("")).then(function(response) {
var data, i, len, n, ref;
data = response.data;
if (data.result) {

View File

@ -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}},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<a;r++)n[r].remove();return y.waiting=!0,h.delete(scriptname+"sfa/"+f+"/"+y.currentSession.id+"?type="+e+"&epoch="+t).then(function(e){return y.waiting=!1},function(e){return y.waiting=!1}),y.showT=!1},y.stoggle=function(e){var t=e.$modelValue;return 0===t.nodes.length&&y.updateTree(t.value,t.nodes,t.level,t.over,t.query,t.count),e.toggle()},y.displaySession=function(e){var t,n=function(e){var t,n,r,a,i,o,u,c,l,s,h,p,f,d,g,m,T,w=e._utime;for(c in e)(T=e[c])?("string"==typeof e&&T.match(/; /)&&(e[c]=T.split("; ")),"object"!=typeof e[c]&&("_password".match(new RegExp("\b"+c+"\b"))?e[c]="********":c.match(/^(_utime|_lastAuthnUTime|_lastSeen|notification)$/)?e[c]=y.localeDate(T):c.match(/^(_startTime|_updateTime)$/)&&(p=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/,p=T.match(p),e[c]=p[3]+"/"+p[2]+"/"+p[1]+" à "+p[4]+":"+p[5]+":"+p[6]))):delete e[c];for(a in f=[],v){for(g=[],o=0,l=(r=v[a]).length;o<l;o++)if(n=r[o],e[n])if(e[n].toString().match(/"type":\s*"(?:TOTP|U2F|UBK|WebAuthn)"/)){for(g.push({title:"type",value:"name",epoch:"date"}),u=0,s=(t=JSON.parse(e[n])).length;u<s;u++){for(c in d=t[u])T=d[c],"type"===c&&(m=T),"name"===c&&(h=T),"epoch"===c&&(i=T);g.push({title:m,value:h,epoch:i})}delete e[n]}else e[n].toString().match(/\w+/)&&g.push({title:n,value:e[n]}),delete e[n];else delete e[n];0<g.length&&f.push({title:"__"+a+"__",nodes:g})}return{_utime:w,nodes:f}};return y.currentScope=e,t=e.$modelValue.session,h.get(scriptname+"sfa/"+f+"/"+t).then(function(e){return y.currentSession=n(e.data),y.currentSession.id=t}),y.showT=!1},y.localeDate=function(e){return new Date(1e3*e).toLocaleString()},y.getLanguage=function(e){return y.lang=e,y.form="white",y.init(),y.showM=!1},r=function(e,t,n){t=t.match(/#!?\/(\w+)/);return null!==t&&!t[1].match(/^(persistent)$/)||(y.type="_session_uid"),y.init()},y.$on("$locationChangeSuccess",r),p=0,y.updateTree=function(i,o,u,c,e,t){var l,s,n;return y.waiting=!0,s=a[y.type]||a._whatToTrace,l=s[u](y.type,i,e),25<t&&d[y.type]&&(n=d[y.type](y.type,i,u,c,e))?(c++,l=n,u-=1):c=0,h.get(scriptname+"sfa/"+f+"?"+l+"&U2FCheck="+y.U2FCheck+"&TOTPCheck="+y.TOTPCheck+"&UBKCheck="+y.UBKCheck+"&WebAuthnCheck="+y.WebAuthnCheck).then(function(e){var t,n,r,a,e=e.data;if(e.result){for(t=0,n=(a=e.values).length;t<n;t++)r=a[t],p++,r.id="node"+p,u<s.length-1&&(r.nodes=[],r.level=u+1,r.query=l,r.over=c),o.push(r);""===i&&(y.total=e.total)}return y.waiting=!1},function(e){return y.waiting=!1})},y.updateTree2=function(i,o,u,c,e,t){var l,s,n;return y.waiting=!0,s=a[y.type]||("_updateTime"===y.type?a._startTime:a._whatToTrace),l=s[u](y.type,i,e),25<t&&d[y.type]&&(n=d[y.type](y.type,i,u,c,e))?(c++,l=n,u-=1):c=0,h.get(scriptname+"sfa/"+f+"?_session_uid="+y.searchString+"*&groupBy=substr(_session_uid,"+y.searchString.length+")&U2FCheck="+y.U2FCheck+"&TOTPCheck="+y.TOTPCheck+"&UBKCheck="+y.UBKCheck+"&WebAuthnCheck="+y.WebAuthnCheck).then(function(e){var t,n,r,a,e=e.data;if(e.result){for(t=0,n=(a=e.values).length;t<n;t++)r=a[t],p++,r.id="node"+p,u<s.length-1&&(r.nodes=[],r.level=u+1,r.query=l,r.over=c),o.push(r);""===i&&(y.total=e.total)}return y.waiting=!1},function(e){return y.waiting=!1})},y.init=function(){return y.waiting=!0,y.data=[],n.all([t.init(y.lang),y.updateTree("",y.data,0,0)]).then(function(){return y.waiting=!1},function(e){return y.waiting=!1}),y.activeModule="2ndFA",y.myStyle={color:"#ffb84d"}},e=e.path().match(/^\/(\w+)/),y.type=e?e[1]:"_whatToTrace"}])}.call(this);
!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<a;r++)n[r].remove();return v.waiting=!0,p.delete(scriptname+"sfa/"+h+"/"+v.currentSession.id+"?type="+e+"&epoch="+t).then(function(e){return v.waiting=!1},function(e){return v.waiting=!1}),v.showT=!1},v.stoggle=function(e){var t=e.$modelValue;return 0===t.nodes.length&&v.updateTree(t.value,t.nodes,t.level,t.over,t.query,t.count),e.toggle()},v.displaySession=function(e){var t,n=function(e){var t,n,r,a,i,o,u,s,l,c,p,f,h,d,g,m,w,y=e._utime;for(s in e)(w=e[s])?("string"==typeof e&&w.match(/; /)&&(e[s]=w.split("; ")),"object"!=typeof e[s]&&("_password".match(new RegExp("\b"+s+"\b"))?e[s]="********":s.match(/^(_utime|_lastAuthnUTime|_lastSeen|notification)$/)?e[s]=v.localeDate(w):s.match(/^(_startTime|_updateTime)$/)&&(f=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/,f=w.match(f),e[s]=f[3]+"/"+f[2]+"/"+f[1]+" à "+f[4]+":"+f[5]+":"+f[6]))):delete e[s];for(a in h=[],_){for(g=[],o=0,l=(r=_[a]).length;o<l;o++)if(n=r[o],e[n])if("_2fDevices"===n&&e[n]){if(0<(t=JSON.parse(e[n])).length)for(g.push({title:"type",value:"name",epoch:"date"}),u=0,c=t.length;u<c;u++){for(s in d=t[u])w=d[s],"type"===s&&(m=w),"name"===s&&(p=w),"epoch"===s&&(i=w);g.push({title:m,value:p,epoch:i,sfrow:!0})}delete e[n]}else e[n].toString().match(/\w+/)&&g.push({title:n,value:e[n]}),delete e[n];else delete e[n];0<g.length&&h.push({title:"__"+a+"__",nodes:g})}return{_utime:y,nodes:h}};return v.currentScope=e,t=e.$modelValue.session,p.get(scriptname+"sfa/"+h+"/"+t).then(function(e){return v.currentSession=n(e.data),v.currentSession.id=t}),v.showT=!1},v.localeDate=function(e){return new Date(1e3*e).toLocaleString()},v.getLanguage=function(e){return v.lang=e,v.form="white",v.init(),v.showM=!1},r=function(e,t,n){t=t.match(/#!?\/(\w+)/);return null!==t&&!t[1].match(/^(persistent)$/)||(v.type="_session_uid"),v.init()},v.$on("$locationChangeSuccess",r),f=0,v.updateTree=function(i,o,u,s,e,t){var l,c,n;return v.waiting=!0,c=a[v.type]||a._whatToTrace,l=c[u](v.type,i,e),25<t&&d[v.type]&&(n=d[v.type](v.type,i,u,s,e))?(s++,l=n,u-=1):s=0,p.get(scriptname+"sfa/"+h+"?"+l+Object.entries(v.sfatypes).map(function(e){return e[1]?"&type="+e[0]:""}).join("")).then(function(e){var t,n,r,a,e=e.data;if(e.result){for(t=0,n=(a=e.values).length;t<n;t++)r=a[t],f++,r.id="node"+f,u<c.length-1&&(r.nodes=[],r.level=u+1,r.query=l,r.over=s),o.push(r);""===i&&(v.total=e.total)}return v.waiting=!1},function(e){return v.waiting=!1})},v.updateTree2=function(i,o,u,s,e,t){var l,c,n;return v.waiting=!0,c=a[v.type]||("_updateTime"===v.type?a._startTime:a._whatToTrace),l=c[u](v.type,i,e),25<t&&d[v.type]&&(n=d[v.type](v.type,i,u,s,e))?(s++,l=n,u-=1):s=0,p.get(scriptname+"sfa/"+h+"?_session_uid="+v.searchString+"*&groupBy=substr(_session_uid,"+v.searchString.length+")"+Object.entries(v.sfatypes).map(function(e){return e[1]?"&type="+e[0]:""}).join("")).then(function(e){var t,n,r,a,e=e.data;if(e.result){for(t=0,n=(a=e.values).length;t<n;t++)r=a[t],f++,r.id="node"+f,u<c.length-1&&(r.nodes=[],r.level=u+1,r.query=l,r.over=s),o.push(r);""===i&&(v.total=e.total)}return v.waiting=!1},function(e){return v.waiting=!1})},v.init=function(){return v.waiting=!0,v.data=[],n.all([t.init(v.lang),v.updateTree("",v.data,0,0)]).then(function(){return v.waiting=!1},function(e){return v.waiting=!1}),v.activeModule="2ndFA",v.myStyle={color:"#ffb84d"}},e=e.path().match(/^\/(\w+)/),v.type=e?e[1]:"_whatToTrace"}])}.call(this);

File diff suppressed because one or more lines are too long

View File

@ -290,34 +290,36 @@
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|WebAuthn)"/)) {
subres.push({
title: "type",
value: "name",
epoch: "date",
td: "0"
});
if (attr === "_2fDevices" && session[attr]) {
array = JSON.parse(session[attr]);
for (j = 0, len1 = array.length; j < len1; j++) {
sfDevice = array[j];
for (key in sfDevice) {
value = sfDevice[key];
if (key === 'type') {
title = value;
}
if (key === 'name') {
name = value;
}
if (key === 'epoch') {
epoch = value;
}
}
if (array.length > 0) {
subres.push({
title: title,
value: name,
epoch: epoch,
td: "1"
title: "type",
value: "name",
epoch: "date",
td: "0"
});
for (j = 0, len1 = array.length; j < len1; j++) {
sfDevice = array[j];
for (key in sfDevice) {
value = sfDevice[key];
if (key === 'type') {
title = value;
}
if (key === 'name') {
name = value;
}
if (key === 'epoch') {
epoch = value;
}
}
subres.push({
title: title,
value: name,
epoch: epoch,
td: "1"
});
}
}
delete session[attr];
} else if (session[attr].toString().match(/"rp":\s*"[\w-]+"/)) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,17 +14,11 @@
<ul class="nav navbar-nav" role="grid">
<form class="navbar-form" name="filterForm">
<div class="form-check ">
<input type="checkbox" ng-model="U2FCheck" class="form-check-input" ng-true-value="2" ng-false-value="1" ng-change="search2FA()"/>
<label class="form-check-label" for="U2FCheck">U2F</label>
&nbsp;&nbsp;&&nbsp;&nbsp;
<input type="checkbox" ng-model="TOTPCheck" class="form-check-input" ng-true-value="2" ng-false-value="1" ng-change="search2FA()"/>
<label class="form-check-label" for="TOTPCheck">TOTP</label>
&nbsp;&nbsp;&&nbsp;&nbsp;
<input type="checkbox" ng-model="UBKCheck" class="form-check-input" ng-true-value="2" ng-false-value="1" ng-change="search2FA()"/>
<label class="form-check-label" for="UBKCheck">UBK</label>
&nbsp;&nbsp;&&nbsp;&nbsp;
<input type="checkbox" ng-model="WebAuthnCheck" class="form-check-input" ng-true-value="2" ng-false-value="1" ng-change="search2FA()"/>
<label class="form-check-label" for="WebAuthnCheck">WebAuthn</label>
<TMPL_LOOP NAME=SFATYPES>
<input type="checkbox" ng-model="sfatypes.<TMPL_VAR NAME=SFATYPE>" class="form-check-input" ng-change="search2FA()"/>
<label class="form-check-label" for="<TMPL_VAR NAME=SFATYPE>Check"><TMPL_VAR NAME=SFATYPE></label>
<TMPL_UNLESS NAME="__last__">&nbsp;&nbsp;&amp;&nbsp;&nbsp;</TMPL_UNLESS>
</TMPL_LOOP>
</div>
</form>
</ul>
@ -107,17 +101,14 @@
</table>
</div>
<div ng-if="!node.nodes" >
<th class="col-md-3" ng-if="node.title!='UBK' && node.title!='TOTP' && node.title!='U2F' && node.title!='WebAuthn'">{{translate(node.title)}}</th>
<td class="data-{{node.epoch}}" ng-if="node.title=='TOTP' || node.title=='UBK' || node.title=='U2F' || node.title=='WebAuthn'" >{{node.title}}</td>
<th class="col-md-3" ng-if="node.title=='type'">{{translate(node.value)}}</th>
<td class="col-md-3 data-{{node.epoch}}" ng-if="node.title!='type'" >{{node.value}}</td>
<th class="col-md-3" ng-if="node.title=='type'">{{translate(node.epoch)}}</th>
<td class="col-md-3 data-{{node.epoch}}" ng-if="node.title=='TOTP' || node.title=='UBK' || node.title=='U2F' || node.title=='WebAuthn'">{{localeDate(node.epoch)}}</td>
<th class="col-md-3" ng-if="!node.sfrow">{{translate(node.title)}}</th>
<td class="data-{{node.epoch}}" ng-if="node.sfrow" >{{node.title}}</td>
<th class="col-md-3" ng-if="node.title=='type'">{{translate(node.value)}}</th>
<td class="col-md-3 data-{{node.epoch}}" ng-if="node.title!='type'" >{{node.value}}</td>
<th class="col-md-3" ng-if="node.title=='type'">{{translate(node.epoch)}}</th>
<td class="col-md-3 data-{{node.epoch}}" ng-if="node.sfrow">{{localeDate(node.epoch)}}</td>
<td class="data-{{node.epoch}}">
<span ng-if="node.title=='TOTP' || node.title=='UBK' || node.title=='U2F' || node.title=='WebAuthn'" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="delete2FA(node.title, node.epoch)"></span>
<!--
<span ng-if="$last && ( node.title=='TOTP' || node.title=='UBK' || node.title=='U2F' || node.title=='WebAuthn' )" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"></span>
-->
<span ng-if="node.sfrow" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="delete2FA(node.title, node.epoch)"></span>
</td>
</div>
</script>

View File

@ -107,9 +107,6 @@
<td class="col-md-4 data-{{node.epoch}}" ng-if="node.epoch > 1500000000">{{localeDate(node.epoch)}}</td>
<td class="data-{{node.epoch}}">
<span ng-if="node.td=='2'" class="link text-danger glyphicon glyphicon-minus-sign" ng-click="deleteOIDCConsent(node.title, node.epoch)"></span>
<!--
<span ng-if="$last && ( node.title=='TOTP' || node.title=='UBK' || node.title=='U2F' || node.title=='WebAuthn' )" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"></span>
-->
</td>
</div>
</script>

View File

@ -135,14 +135,6 @@ sub checkGetList {
return $ret;
}
sub checkGetBadType {
my ( $uid, $type ) = splice @_;
my ( $test, $res );
$test = "Get for uid $uid and type \"$type\" should get rejected.";
$res = get( $test, $uid, $type );
check400( $test, $res );
}
sub checkGetOnIds {
my ( $uid, $ret ) = splice @_;
foreach (@$ret) {
@ -313,7 +305,7 @@ checkGetList( 1, 'dwho', 'U2F' );
checkGetList( 1, 'dwho', 'TOTP' );
checkGetList( 1, 'dwho', 'UBK' );
checkGetList( 1, 'dwho', 'WebAuthn' );
checkGetBadType( 'dwho', 'UBKIKI' );
checkGetList( 0, 'dwho', 'UBKIKI' );
$ret = checkGetList( 4, 'dwho' );
checkGetOnIds( 'dwho', $ret );
checkDelete( 'dwho', @$ret[0]->{id} );

View File

@ -147,8 +147,7 @@ ok( ( $res->{_2fDevices} and $res->{_2fDevices} =~ /"type":\s*"UBK"/s ),
count(5);
## "All" query
$res = &client->jsonResponse( '/sfa/persistent',
'groupBy=substr(uid,1)&U2FCheck=1&TOTPCheck=1&UBKCheck=1' );
$res = &client->jsonResponse( '/sfa/persistent', 'groupBy=substr(uid,1)' );
ok( $res->{result} == 1, 'Search * - Result code = 1' );
ok( $res->{count} == 3, 'Found 3 results' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 3, 'List 3 results' );
@ -168,8 +167,8 @@ count(9);
## "Search by UID" query
# uid=d*
$res = &client->jsonResponse( '/sfa/persistent',
'uid=d*&groupBy=substr(uid,1)&U2FCheck=1&TOTPCheck=1&UBKCheck=1' );
$res =
&client->jsonResponse( '/sfa/persistent', 'uid=d*&groupBy=substr(uid,1)' );
ok( $res->{result} == 1, 'Search "uid"=d* - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 1, 'List 1 result' );
@ -180,8 +179,8 @@ ok( $res->{values}->[0]->{count} == 2, 'Found 2 sessions starting with "d"' );
count(5);
# uid=dw*
$res = &client->jsonResponse( '/sfa/persistent',
'uid=dw*&groupBy=substr(uid,2)&U2FCheck=1&TOTPCheck=1&UBKCheck=1' );
$res =
&client->jsonResponse( '/sfa/persistent', 'uid=dw*&groupBy=substr(uid,2)' );
ok( $res->{result} == 1, 'Search "uid"=dw* - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 1, 'List 1 result' );
@ -193,7 +192,7 @@ count(5);
# uid=d* & UBK
$res = &client->jsonResponse( '/sfa/persistent',
'uid=d*&groupBy=substr(uid,1)&U2FCheck=1&TOTPCheck=1&UBKCheck=2' );
'uid=d*&groupBy=substr(uid,1)&type=UBK' );
ok( $res->{result} == 1, 'Search "uid"=d* & UBK - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 1, 'List 1 result' );
@ -208,7 +207,7 @@ count(5);
# uid=dw* & UBK
$res = &client->jsonResponse( '/sfa/persistent',
'uid=dw*&groupBy=substr(uid,2)&U2FCheck=1&TOTPCheck=1&UBKCheck=2' );
'uid=dw*&groupBy=substr(uid,2)&type=UBK' );
ok( $res->{result} == 1, 'Search "uid"=dw* & UBK - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 1, 'List 1 result' );
@ -223,7 +222,7 @@ count(5);
# uid=da* & UBK
$res = &client->jsonResponse( '/sfa/persistent',
'uid=da*&groupBy=substr(uid,2)&U2FCheck=1&TOTPCheck=1&UBKCheck=2' );
'uid=da*&groupBy=substr(uid,2)&type=UBK' );
ok( $res->{result} == 1, 'Search "uid"=da* & UBK - Result code = 1' );
ok( $res->{count} == 0, 'Found 0 session with "da" & UBK' )
or print STDERR Dumper($res);
@ -232,7 +231,7 @@ count(3);
## "Filtered by U2F" query
$res = &client->jsonResponse( '/sfa/persistent',
'uid=*&groupBy=substr(uid,0)&U2FCheck=2&TOTPCheck=1&UBKCheck=1' );
'uid=*&groupBy=substr(uid,0)&type=U2F' );
ok( $res->{result} == 1, 'Search "uid"=* & UBK - Result code = 1' );
ok( $res->{count} == 3, 'Found 3 results' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 3, 'List 3 results' );
@ -261,7 +260,7 @@ count(9);
## "Filtered by U2F & TOTP" query
$res = &client->jsonResponse( '/sfa/persistent',
'uid=*&groupBy=substr(uid,0)&U2FCheck=2&TOTPCheck=2&UBKCheck=1' );
'uid=*&groupBy=substr(uid,0)&type=U2F&type=TOTP' );
ok( $res->{result} == 1, 'Search "uid"=* & UBK & TOTP - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 1, 'List 1 result' );
@ -274,7 +273,7 @@ count(5);
## "Filtered by U2F & TOTP & UBK" query
$res = &client->jsonResponse( '/sfa/persistent',
'uid=*&groupBy=substr(uid,0)&U2FCheck=2&TOTPCheck=2&UBKCheck=2' );
'uid=*&groupBy=substr(uid,0)&type=U2F&type=TOTP&type=UBK' );
ok( $res->{result} == 1,
'Search "uid"=* & UBK & TOTP & UBK - Result code = 1' );
ok( $res->{count} == 1, 'Found 1 result' ) or print STDERR Dumper($res);
@ -288,7 +287,7 @@ count(5);
## "Filtered by U2F & UBK" query
$res = &client->jsonResponse( '/sfa/persistent',
'uid=*&groupBy=substr(uid,0)&U2FCheck=2&TOTPCheck=1&UBKCheck=2' );
'uid=*&groupBy=substr(uid,0)&type=U2F&type=UBK' );
ok( $res->{result} == 1, 'Search "uid"=* & UBK & UBK - Result code = 1' );
ok( $res->{count} == 2, 'Found 2 results' ) or print STDERR Dumper($res);
ok( @{ $res->{values} } == 2, 'List 2 results' );
@ -345,14 +344,18 @@ foreach ( 2 .. 3 ) {
}
## Check than all devices have been deleted with "All" query
$res = &client->jsonResponse( '/sfa/persistent',
'groupBy=substr(uid,1)&U2FCheck=1&TOTPCheck=1&UBKCheck=1' );
$res = &client->jsonResponse( '/sfa/persistent', 'groupBy=substr(uid,1)' );
ok( $res->{result} == 1, 'Result code = 1' );
ok( $res->{count} == 0, 'Found 0 session with 2F device' )
or print STDERR Dumper($res);
ok( @{ $res->{values} } == 0, 'List 0 result' );
count(3);
ok( $res = &client->_get('/2ndfa.html'), 'Succeed to get /2ndfa.html' );
like( $res->[2]->[0],
qr,<label class="form-check-label" for="TOTPCheck">TOTP</label>, );
count(2);
done_testing( count() );
# Remove sessions directory