Merge branch 'portal-2f-improvement' into 'master'

Portal 2f improvement

See merge request lemonldap-ng/lemonldap-ng!26
This commit is contained in:
Xavier Guimard 2018-04-20 19:57:49 +02:00
commit 0d72ad10df
23 changed files with 441 additions and 556 deletions

View File

@ -3,6 +3,7 @@ package Lemonldap::NG::Common::Session::REST;
use strict; use strict;
use Mouse; use Mouse;
use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::Conf::Constants;
use JSON qw(from_json to_json);
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
@ -44,15 +45,17 @@ sub delSession {
my $id = $req->params('sessionId') my $id = $req->params('sessionId')
or return $self->sendError( $req, 'sessionId is missing', 400 ); or return $self->sendError( $req, 'sessionId is missing', 400 );
my $session = $self->getApacheSession( $mod, $id ); my $session = $self->getApacheSession( $mod, $id );
$self->logger->debug("Delete session : $id");
$session->remove; $session->remove;
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id ); Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
if ( $session->error ) { if ( $session->error ) {
return $self->sendError( $req, $session->error, 200 ); return $self->sendError( $req, $session->error, 200 );
} }
return $self->sendJSONresponse( $req, { result => 1 } ); return $self->sendJSONresponse( $req, { result => 1 } );
} }
sub deleteU2FKey { sub delete2F {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } ) return $self->sendJSONresponse( $req, { result => 1 } )
if ( $self->{demoMode} ); if ( $self->{demoMode} );
@ -62,12 +65,51 @@ sub deleteU2FKey {
or return $self->sendError( $req, 'sessionId is missing', 400 ); or return $self->sendError( $req, 'sessionId is missing', 400 );
# Try to read session # Try to read session
$self->logger->debug("Loading session : $id");
my $session = $self->getApacheSession( $mod, $id ) my $session = $self->getApacheSession( $mod, $id )
or return $self->sendError( $req, undef, 400 ); or return $self->sendError( $req, undef, 400 );
# Delete U2F key attributs and update session # Try to read 2F parameters
$session->data->{_u2fKeyHandle} = ''; $self->logger->debug("Reading parameters ...");
$session->data->{_u2fUserKey} = ''; my $params = $req->parameters();
my $type = $params->{type}
or return $self->sendError( $req, '2F device Type is missing', 400 );
my $epoch = $params->{epoch}
or return $self->sendError( $req, '2F device Epoch is missing', 400 );
# Try to load 2F Device(s) from session
$self->logger->debug("Looking for 2F Device(s) ...");
my $_2fDevices;
if ( $session->data->{_2fDevices} ) {
$_2fDevices = eval {
from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } );
};
if ($@) {
$self->logger->error("Corrupted session (_2fDevices) : $@");
return $self->p->sendError( $req, "Corrupted session", 500 );
}
}
else {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
}
# Delete 2F device
$self->logger->debug("Reading 2F device(s) ...");
my @keep = ();
while (@$_2fDevices) {
my $element = shift @$_2fDevices;
$self->logger->debug(
"Searching 2F device to delete -> $type / $epoch ...");
push @keep, $element
unless ( ( $element->{type} eq $type )
and ( $element->{epoch} eq $epoch ) );
}
# Update session
$self->logger->debug("Saving 2F Devices ...");
$session->data->{_2fDevices} = to_json( \@keep );
$self->logger->debug("Updating session ...");
$session->update( \%{ $session->data } ); $session->update( \%{ $session->data } );
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id ); Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
@ -77,79 +119,55 @@ sub deleteU2FKey {
return $self->sendJSONresponse( $req, { result => 1 } ); return $self->sendJSONresponse( $req, { result => 1 } );
} }
sub deleteTOTPKey { #sub add2F {
my ( $self, $req ) = @_; #my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } ) #return $self->sendJSONresponse( $req, { result => 1 } )
if ( $self->{demoMode} ); #if ( $self->{demoMode} );
my $mod = $self->getMod($req) #my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 ); #or return $self->sendError( $req, undef, 400 );
my $id = $req->params('sessionId') #my $id = $req->params('sessionId')
or return $self->sendError( $req, 'sessionId is missing', 400 ); #or return $self->sendError( $req, 'sessionId is missing', 400 );
# Try to read session ## Try to read session
my $session = $self->getApacheSession( $mod, $id ) #my $session = $self->getApacheSession( $mod, $id )
or return $self->sendError( $req, undef, 400 ); #or return $self->sendError( $req, undef, 400 );
# Delete U2F key attributs and update session ## Delete U2F key attributs and update session
$session->data->{_totp2fSecret} = ''; #$session->data->{_u2fKeyHandle} = 'TOF';
$session->update( \%{ $session->data } ); #$session->data->{_u2fUserKey} = 'TOF';
#$session->update( \%{ $session->data } );
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id ); #Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
if ( $session->error ) { #if ( $session->error ) {
return $self->sendError( $req, $session->error, 200 ); #return $self->sendError( $req, $session->error, 200 );
} #}
return $self->sendJSONresponse( $req, { result => 1 } ); #return $self->sendJSONresponse( $req, { result => 1 } );
} #}
sub addU2FKey { #sub verify2F {
my ( $self, $req ) = @_; #my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } ) #return $self->sendJSONresponse( $req, { result => 1 } )
if ( $self->{demoMode} ); #if ( $self->{demoMode} );
my $mod = $self->getMod($req) #my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 ); #or return $self->sendError( $req, undef, 400 );
my $id = $req->params('sessionId') #my $id = $req->params('sessionId')
or return $self->sendError( $req, 'sessionId is missing', 400 ); #or return $self->sendError( $req, 'sessionId is missing', 400 );
# Try to read session ## Try to read session
my $session = $self->getApacheSession( $mod, $id ) #my $session = $self->getApacheSession( $mod, $id )
or return $self->sendError( $req, undef, 400 ); #or return $self->sendError( $req, undef, 400 );
# Delete U2F key attributs and update session ## Delete U2F key attributs and update session
$session->data->{_u2fKeyHandle} = 'TOF'; #$session->data->{_u2fKeyHandle} = 'OK';
$session->data->{_u2fUserKey} = 'TOF'; #$session->data->{_u2fUserKey} = 'OK';
$session->update( \%{ $session->data } ); #$session->update( \%{ $session->data } );
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id ); #Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
if ( $session->error ) { #if ( $session->error ) {
return $self->sendError( $req, $session->error, 200 ); #return $self->sendError( $req, $session->error, 200 );
} #}
return $self->sendJSONresponse( $req, { result => 1 } ); #return $self->sendJSONresponse( $req, { result => 1 } );
} #}
sub verifyU2FKey {
my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { result => 1 } )
if ( $self->{demoMode} );
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 );
# Try to read session
my $session = $self->getApacheSession( $mod, $id )
or return $self->sendError( $req, undef, 400 );
# Delete U2F key attributs and update session
$session->data->{_u2fKeyHandle} = 'OK';
$session->data->{_u2fUserKey} = 'OK';
$session->update( \%{ $session->data } );
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
if ( $session->error ) {
return $self->sendError( $req, $session->error, 200 );
}
return $self->sendJSONresponse( $req, { result => 1 } );
}
sub session { sub session {
my ( $self, $req, $id, $skey ) = @_; my ( $self, $req, $id, $skey ) = @_;

View File

@ -27,7 +27,7 @@ use constant defaultRoute => '2ndfa.html#/persistent';
sub addRoutes { sub addRoutes {
my ( $self, $conf ) = @_; my ( $self, $conf ) = @_;
# Remote Procedure Call are defined in Lemonldap::NG::Common::Session::REST # Remote Procedure are defined in Lemonldap::NG::Common::Session::REST
# HTML template # HTML template
$self->addRoute( '2ndfa.html', undef, ['GET'] ) $self->addRoute( '2ndfa.html', undef, ['GET'] )
@ -36,24 +36,24 @@ sub addRoutes {
['GET'] ['GET']
) )
# DELETE 2FA KEY # DELETE 2FA DEVICE
->addRoute( ->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'delete2FAKey' } }, sfa => { ':sessionType' => { ':sessionId' => 'delete2FA' } },
['DELETE'] ['DELETE']
)
# ADD 2FA KEY
->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'add2FAKey' } },
['PUT']
)
# VERIFY 2FA KEY
->addRoute(
sfa => { ':sessionType' => { ':sessionId' => 'verify2FAKey' } },
['POST']
); );
## ADD 2FA DEVICE
#->addRoute(
#sfa => { ':sessionType' => { ':sessionId' => 'add2FA' } },
#['PUT']
#)
## VERIFY 2FA DEVICE
#->addRoute(
#sfa => { ':sessionType' => { ':sessionId' => 'verify2FA' } },
#['POST']
#);
$self->setTypes($conf); $self->setTypes($conf);
$self->{ipField} ||= 'ipAddr'; $self->{ipField} ||= 'ipAddr';
@ -61,54 +61,60 @@ sub addRoutes {
$self->{hiddenAttributes} //= "_password"; $self->{hiddenAttributes} //= "_password";
$self->{TOTPCheck} = '1'; $self->{TOTPCheck} = '1';
$self->{U2FCheck} = '1'; $self->{U2FCheck} = '1';
$self->{UBKCheck} = '1';
} }
################### ###################
# II. 2FA METHODS # # II. 2FA METHODS #
################### ###################
sub delete2FAKey { sub delete2FA {
my ( $self, $req, $session, $skey ) = @_; my ( $self, $req, $session, $skey ) = @_;
my $mod = $self->getMod($req) my $mod = $self->getMod($req)
or return $self->sendError( $req, undef, 400 ); or return $self->sendError( $req, undef, 400 );
my $params = $req->parameters(); my $params = $req->parameters();
my $Key = $params->{Key}; my $type = $params->{type};
my $epoch = $params->{epoch};
if ( $Key =~ /\bU2F\b/ ) { if ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ and $epoch ) {
$self->logger->debug("Call procedure deleteU2FKey"); $self->logger->debug("Call procedure delete2F with type=$type and epoch=$epoch");
return $self->deleteU2FKey( $req, $session, $skey ); return $self->delete2F( $req, $session, $skey );
}
elsif ( $Key =~ /\bTOTP\b/ ) {
$self->logger->debug("Call procedure deleteTOTPKey");
return $self->deleteTOTPKey( $req, $session, $skey );
} }
#elsif ( $type =~ /\bTOTP\b/ ) {
#$self->logger->debug("Call procedure deleteTOTP");
#return $self->deleteTOTP( $req, $session, $skey );
#}
#elsif ( $type =~ /\bUBK\b/ ) {
#$self->logger->debug("Call procedure deleteUBK");
#return $self->deleteUBK( $req, $session, $skey );
#}
else { else {
return $self->sendError( $req, undef, 400 ); return $self->sendError( $req, undef, 400 );
} }
} }
sub add2FAKey { #sub add2FA {
my ( $self, $req, $session, $skey ) = @_; #my ( $self, $req, $session, $skey ) = @_;
eval 'use Crypt::U2F::Server::Simple'; #eval 'use Crypt::U2F::Server::Simple';
if ($@) { #if ($@) {
$self->error("Can't load U2F library: $@"); #$self->error("Can't load U2F library: $@");
return 0; #return 0;
} #}
return $self->addU2FKey( $req, $session, $skey ); #return $self->addU2FKey( $req, $session, $skey );
} #}
sub verify2FAKey { #sub verify2FA {
my ( $self, $req, $session, $skey ) = @_; #my ( $self, $req, $session, $skey ) = @_;
return $self->addU2FKey( $req, $session, $skey ); #return $self->addU2FKey( $req, $session, $skey );
} #}
######################## ########################
# III. DISPLAY METHODS # # III. DISPLAY METHODS #
@ -137,8 +143,7 @@ sub sfa {
# 2.1 Get fields to require # 2.1 Get fields to require
my @fields = ( my @fields = (
'_httpSessionType', $self->{ipField}, '_httpSessionType', $self->{ipField},
$whatToTrace, '_u2fKeyHandle', $whatToTrace, '_2fDevices'
'_totp2fSecret'
); );
if ( my $groupBy = $params->{groupBy} ) { if ( my $groupBy = $params->{groupBy} ) {
$groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/; $groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/;
@ -155,9 +160,10 @@ sub sfa {
$moduleOptions->{backend} = $mod->{module}; $moduleOptions->{backend} = $mod->{module};
# Select 2FA sessions to display # Select 2FA sessions to display
if ( defined $params->{TOTPCheck} and defined $params->{TOTPCheck} ) { if ( defined $params->{TOTPCheck} or defined $params->{U2FCheck} or defined $params->{UBKCheck}) {
$self->{TOTPCheck} = delete $params->{TOTPCheck}; $self->{TOTPCheck} = delete $params->{TOTPCheck};
$self->{U2FCheck} = delete $params->{U2FCheck}; $self->{U2FCheck} = delete $params->{U2FCheck};
$self->{UBKCheck} = delete $params->{UBKCheck};
} }
my %filters = map { my %filters = map {
@ -236,15 +242,22 @@ sub sfa {
if ( $self->{U2FCheck} eq '2' ) { if ( $self->{U2FCheck} eq '2' ) {
foreach my $session ( keys %$res ) { foreach my $session ( keys %$res ) {
delete $res->{$session} delete $res->{$session}
unless ( defined $res->{$session}->{_u2fKeyHandle} unless ( defined $res->{$session}->{_2fDevices}
and length $res->{$session}->{_u2fKeyHandle} ); and $res->{$session}->{_2fDevices} =~ /"type":\s*"U2F"/s );
} }
} }
if ( $self->{TOTPCheck} eq '2' ) { if ( $self->{TOTPCheck} eq '2' ) {
foreach my $session ( keys %$res ) { foreach my $session ( keys %$res ) {
delete $res->{$session} delete $res->{$session}
unless ( defined $res->{$session}->{_totp2fSecret} unless ( defined $res->{$session}->{_2fDevices}
and length $res->{$session}->{_totp2fSecret} ); and $res->{$session}->{_2fDevices} =~ /"type":\s*"TOTP"/s );
}
}
if ( $self->{UBKCheck} eq '2' ) {
foreach my $session ( keys %$res ) {
delete $res->{$session}
unless ( defined $res->{$session}->{_2fDevices}
and $res->{$session}->{_2fDevices} =~ /"type":\s*"UBK"/s );
} }
} }

View File

@ -19,9 +19,6 @@ displayError = (j, status, err) ->
setMsg res, 'warning' setMsg res, 'warning'
# Max number of session to display (see overScheme) # Max number of session to display (see overScheme)
max = 25 max = 25
@ -72,40 +69,34 @@ hiddenAttributes = '_password'
categories = categories =
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'] dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen']
connectionTitle: ['ipAddr', '_timezone', '_url'] connectionTitle: ['ipAddr', '_timezone', '_url']
authenticationTitle:['_session_id', '_user', '_password', 'authenticationLevel'] sfaTitle: ['_2fDevices']
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti']
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump']
groups: ['groups', 'hGroups']
ldap: ['dn']
BrowserID: ['_browserIdAnswer', '_browserIdAnswerRaw']
OpenIDConnect: ['_oidc_id_token', '_oidc_OP', '_oidc_access_token']
# Menu entries # Menu entries
menu = menu =
delU2FKey: [ #delU2FKey: [
title: 'deleteU2FKey' #title: 'deleteU2FKey'
icon: 'trash' #icon: 'trash'
] #]
addU2FKey: [ #addU2FKey: [
title: 'addU2FKey' #title: 'addU2FKey'
icon: 'plus' #icon: 'plus'
] #]
verifyU2FKey: [ #verifyU2FKey: [
title: 'verifyU2FKey' #title: 'verifyU2FKey'
icon: 'check' #icon: 'check'
] #]
delTOTPKey: [ #delTOTPKey: [
title: 'deleteTOTPKey' #title: 'deleteTOTPKey'
icon: 'trash' #icon: 'trash'
] #]
addTOTPKey: [ #addTOTPKey: [
title: 'addTOTPKey' #title: 'addTOTPKey'
icon: 'plus' #icon: 'plus'
] #]
verifyTOTPKey: [ #verifyTOTPKey: [
title: 'verifyTOTPKey' #title: 'verifyTOTPKey'
icon: 'check' #icon: 'check'
] #]
home: [] home: []
### ###
@ -146,6 +137,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
switch typeof button.action switch typeof button.action
when 'function' when 'function'
button.action $scope.currentNode, $scope button.action $scope.currentNode, $scope
$scope[button.action]()
when 'string' when 'string'
$scope[button.action]() $scope[button.action]()
else else
@ -161,83 +153,49 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
$scope.data = [] $scope.data = []
$scope.updateTree2 '', $scope.data, 0, 0 $scope.updateTree2 '', $scope.data, 0, 0
# Delete U2F key # Delete 2FA device
$scope.deleteU2FKey = -> $scope.delete2FA = (type, epoch) ->
item = angular.element("#data-#{epoch}")
item.remove()
$scope.waiting = true $scope.waiting = true
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) -> $http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?type=#{type}&epoch=#{epoch}").then (response) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
#$scope.data = []
$scope.waiting = false $scope.waiting = false
, (resp) -> , (resp) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = false
# Delete TOTP key
$scope.deleteTOTPKey = ->
$scope.waiting = true
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false $scope.waiting = false
$scope.showT = false $scope.showT = false
#$scope.data = []
# Add U2F key ## Add 2FA device
$scope.addU2FKey = -> #$scope.add2FA (type) = ->
$scope.waiting = true #$scope.waiting = true
$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) -> #$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
$scope.waiting = false #$scope.waiting = false
, (resp) -> #, (resp) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
$scope.waiting = false #$scope.waiting = false
$scope.showT = false #$scope.showT = false
# Add TOTP key
$scope.addTOTPKey = ->
$scope.waiting = true
$http['put']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = false
# Verify U2F key
$scope.verifyU2FKey = ->
$scope.waiting = true
$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
, (resp) ->
$scope.currentSession = null
$scope.currentScope.remove()
$scope.waiting = false
$scope.showT = true
# Verify TOTP key ## Verify 2FA device
$scope.verifyTOTPKey = -> #$scope.verify2FA (epoch) = ->
$scope.waiting = true #$scope.waiting = true
$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) -> #$http['post']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=TOTP").then (response) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
$scope.waiting = false #$scope.waiting = false
, (resp) -> #, (resp) ->
$scope.currentSession = null #$scope.currentSession = null
$scope.currentScope.remove() #$scope.currentScope.remove()
$scope.waiting = false #$scope.waiting = false
$scope.showT = true #$scope.showT = true
# Open node # Open node
$scope.stoggle = (scope) -> $scope.stoggle = (scope) ->
@ -284,66 +242,46 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
else if key.match /^(_utime|_lastAuthnUTime|_lastSeen|notification)$/ else if key.match /^(_utime|_lastAuthnUTime|_lastSeen|notification)$/
session[key] = $scope.localeDate value session[key] = $scope.localeDate value
else if key.match /^(_startTime|_updateTime)$/ else if key.match /^(_startTime|_updateTime)$/
session[key] = _stToStr value session[key] = _stToStr value
#else if key.match /^(_u2fKeyHandle|_u2fUserKey|_totp2fSecret)$/
# session[key] = '##########'
res = [] res = []
# 2. Push session keys in result, grouped by categories # 2. Push session keys in result, grouped by categories
for category, attrs of categories for category, attrs of categories
subres = [] subres = []
for attr in attrs for attr in attrs
if session[attr] if session[attr] and session[attr].match(/\w+/)
subres.push if session[attr].match(/"type":\s*"(?:TOTP|U2F|UBK)"/)
title: attr subres.push
value: session[attr] title: "type"
delete session[attr] value: "name"
sdate: "date"
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
newDate = new Date(value * 1000)
myDate = newDate.toLocaleString()
subres.push
title: title
value: name
epoch: epoch
sdate: myDate
delete session[attr]
else
subres.push
title: attr
value: session[attr]
delete session[attr]
if subres.length >0 if subres.length >0
res.push res.push
title: "__#{category}__" title: "__#{category}__"
nodes: subres nodes: subres
# 3. Add OpenID and notifications already notified
_insert '^openid', 'OpenID'
_insert '^notification_(.+)', '__notificationsDone__'
# 4. Add session history if exists
if session._loginHistory
tmp = []
if session._loginHistory.successLogin
for l in session._loginHistory.successLogin
tmp.push
t: l._utime
title: $scope.localeDate l._utime
value: "Success (IP #{l.ipAddr})"
if session._loginHistory.failedLogin
for l in session._loginHistory.failedLogin
tmp.push
t: l._utime
title: $scope.localeDate l._utime
value: "#{l.error} (IP #{l.ipAddr})"
delete session._loginHistory
tmp.sort (a,b) ->
a.t - b.t
res.push
title: '__loginHistory__'
nodes: tmp
# 5. Other keys (attributes and macros)
tmp = []
for key, value of session
tmp.push
title: key
value: value
tmp.sort (a,b) ->
if a.title > b.title then 1
else if a.title < b.title then -1
else 0
res.push
title: '__attributesAndMacros__'
nodes: tmp
return { return {
_utime: time _utime: time
id: id id: id
@ -478,7 +416,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
over = 0 over = 0
# Launch HTTP # Launch HTTP
$http.get("#{scriptname}sfa/#{sessionType}?_session_uid=#{$scope.searchString}*&groupBy=substr(_session_uid,#{$scope.searchString.length})&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}").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}").then (response) ->
data = response.data data = response.data
if data.result if data.result
for n in data.values for n in data.values
@ -528,3 +466,5 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
$scope.type = if c then c[1] else '_whatToTrace' $scope.type = if c then c[1] else '_whatToTrace'
] ]

View File

@ -77,7 +77,7 @@ hiddenAttributes = '_password'
categories = categories =
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'] dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen']
connectionTitle: ['ipAddr', '_timezone', '_url'] connectionTitle: ['ipAddr', '_timezone', '_url']
authenticationTitle:['_session_id', '_user', '_password', 'authenticationLevel'] authenticationTitle:['_session_id', '_user', '_password', 'authenticationLevel', '_2fDevices']
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'] modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti']
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'] saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump']
groups: ['groups', 'hGroups'] groups: ['groups', 'hGroups']
@ -196,8 +196,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
session[key] = $scope.localeDate value session[key] = $scope.localeDate value
else if key.match /^(_startTime|_updateTime)$/ else if key.match /^(_startTime|_updateTime)$/
session[key] = _stToStr value session[key] = _stToStr value
#else if key.match /^(_u2fKeyHandle|_u2fUserKey|_totp2fSecret)$/
# session[key] = '##########'
res = [] res = []
# 2. Push session keys in result, grouped by categories # 2. Push session keys in result, grouped by categories

View File

@ -82,52 +82,10 @@
categories = { categories = {
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'], dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'],
connectionTitle: ['ipAddr', '_timezone', '_url'], connectionTitle: ['ipAddr', '_timezone', '_url'],
authenticationTitle: ['_session_id', '_user', '_password', 'authenticationLevel'], sfaTitle: ['_2fDevices']
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'],
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'],
groups: ['groups', 'hGroups'],
ldap: ['dn'],
BrowserID: ['_browserIdAnswer', '_browserIdAnswerRaw'],
OpenIDConnect: ['_oidc_id_token', '_oidc_OP', '_oidc_access_token']
}; };
menu = { menu = {
delU2FKey: [
{
title: 'deleteU2FKey',
icon: 'trash'
}
],
addU2FKey: [
{
title: 'addU2FKey',
icon: 'plus'
}
],
verifyU2FKey: [
{
title: 'verifyU2FKey',
icon: 'check'
}
],
delTOTPKey: [
{
title: 'deleteTOTPKey',
icon: 'trash'
}
],
addTOTPKey: [
{
title: 'addTOTPKey',
icon: 'plus'
}
],
verifyTOTPKey: [
{
title: 'verifyTOTPKey',
icon: 'check'
}
],
home: [] home: []
}; };
@ -171,6 +129,7 @@
switch (typeof button.action) { switch (typeof button.action) {
case 'function': case 'function':
button.action($scope.currentNode, $scope); button.action($scope.currentNode, $scope);
$scope[button.action]();
break; break;
case 'string': case 'string':
$scope[button.action](); $scope[button.action]();
@ -189,84 +148,18 @@
$scope.data = []; $scope.data = [];
return $scope.updateTree2('', $scope.data, 0, 0); return $scope.updateTree2('', $scope.data, 0, 0);
}; };
$scope.deleteU2FKey = function() { $scope.delete2FA = function(type, epoch) {
var item;
item = angular.element("#data-" + epoch);
item.remove();
$scope.waiting = true; $scope.waiting = true;
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) { $http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?type=" + type + "&epoch=" + epoch).then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false; return $scope.waiting = false;
}, function(resp) { }, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false; return $scope.waiting = false;
}); });
return $scope.showT = false; return $scope.showT = false;
}; };
$scope.deleteTOTPKey = function() {
$scope.waiting = true;
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
};
$scope.addU2FKey = function() {
$scope.waiting = true;
$http['put'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
};
$scope.addTOTPKey = function() {
$scope.waiting = true;
$http['put'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = false;
};
$scope.verifyU2FKey = function() {
$scope.waiting = true;
$http['post'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = true;
};
$scope.verifyTOTPKey = function() {
$scope.waiting = true;
$http['post'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=TOTP").then(function(response) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
}, function(resp) {
$scope.currentSession = null;
$scope.currentScope.remove();
return $scope.waiting = false;
});
return $scope.showT = true;
};
$scope.stoggle = function(scope) { $scope.stoggle = function(scope) {
var node; var node;
node = scope.$modelValue; node = scope.$modelValue;
@ -278,7 +171,7 @@
$scope.displaySession = function(scope) { $scope.displaySession = function(scope) {
var sessionId, transformSession; var sessionId, transformSession;
transformSession = function(session) { transformSession = function(session) {
var _insert, _stToStr, attr, attrs, category, i, id, k, key, l, len, len1, len2, m, ref, ref1, res, subres, time, tmp, value; var _insert, _stToStr, array, attr, attrs, category, epoch, i, id, k, key, len, len1, myDate, name, newDate, res, sfDevice, subres, time, title, value;
_stToStr = function(s) { _stToStr = function(s) {
return s; return s;
}; };
@ -330,12 +223,45 @@
subres = []; subres = [];
for (i = 0, len = attrs.length; i < len; i++) { for (i = 0, len = attrs.length; i < len; i++) {
attr = attrs[i]; attr = attrs[i];
if (session[attr]) { if (session[attr] && session[attr].match(/\w+/)) {
subres.push({ if (session[attr].match(/"type":\s*"(?:TOTP|U2F|UBK)"/)) {
title: attr, subres.push({
value: session[attr] title: "type",
}); value: "name",
delete session[attr]; sdate: "date"
});
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;
newDate = new Date(value * 1000);
myDate = newDate.toLocaleString();
}
}
subres.push({
title: title,
value: name,
epoch: epoch,
sdate: myDate
});
}
delete session[attr];
} else {
subres.push({
title: attr,
value: session[attr]
});
delete session[attr];
}
} }
} }
if (subres.length > 0) { if (subres.length > 0) {
@ -345,62 +271,6 @@
}); });
} }
} }
_insert('^openid', 'OpenID');
_insert('^notification_(.+)', '__notificationsDone__');
if (session._loginHistory) {
tmp = [];
if (session._loginHistory.successLogin) {
ref = session._loginHistory.successLogin;
for (k = 0, len1 = ref.length; k < len1; k++) {
l = ref[k];
tmp.push({
t: l._utime,
title: $scope.localeDate(l._utime),
value: "Success (IP " + l.ipAddr + ")"
});
}
}
if (session._loginHistory.failedLogin) {
ref1 = session._loginHistory.failedLogin;
for (m = 0, len2 = ref1.length; m < len2; m++) {
l = ref1[m];
tmp.push({
t: l._utime,
title: $scope.localeDate(l._utime),
value: l.error + " (IP " + l.ipAddr + ")"
});
}
}
delete session._loginHistory;
tmp.sort(function(a, b) {
return a.t - b.t;
});
res.push({
title: '__loginHistory__',
nodes: tmp
});
}
tmp = [];
for (key in session) {
value = session[key];
tmp.push({
title: key,
value: value
});
}
tmp.sort(function(a, b) {
if (a.title > b.title) {
return 1;
} else if (a.title < b.title) {
return -1;
} else {
return 0;
}
});
res.push({
title: '__attributesAndMacros__',
nodes: tmp
});
return { return {
_utime: time, _utime: time,
id: id, id: id,
@ -502,7 +372,7 @@
} else { } else {
over = 0; 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).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).then(function(response) {
var data, i, len, n, ref; var data, i, len, n, ref;
data = response.data; data = response.data;
if (data.result) { if (data.result) {

File diff suppressed because one or more lines are too long

View File

@ -94,7 +94,7 @@
categories = { categories = {
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'], dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'],
connectionTitle: ['ipAddr', '_timezone', '_url'], connectionTitle: ['ipAddr', '_timezone', '_url'],
authenticationTitle: ['_session_id', '_user', '_password', 'authenticationLevel'], authenticationTitle: ['_session_id', '_user', '_password', 'authenticationLevel', '_2fDevices'],
modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'], modulesTitle: ['_auth', '_userDB', '_passwordDB', '_issuerDB', '_authChoice', '_authMulti', '_userDBMulti'],
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'], saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'],
groups: ['groups', 'hGroups'], groups: ['groups', 'hGroups'],

File diff suppressed because one or more lines are too long

View File

@ -672,6 +672,7 @@
"sessionStartedAt":"بدأت الجلسة", "sessionStartedAt":"بدأت الجلسة",
"sessionStorage":"تخزين الجلسات", "sessionStorage":"تخزين الجلسات",
"sessionTitle":"محتوى الجلسة", "sessionTitle":"محتوى الجلسة",
"sfaTitle":"Seconds Factors Authentication",
"show":"عرض", "show":"عرض",
"showHelp":"عرض المساعدة", "showHelp":"عرض المساعدة",
"singleIP":"عنوان آي بي واحد لكل مستخدم", "singleIP":"عنوان آي بي واحد لكل مستخدم",

View File

@ -664,7 +664,7 @@
"security":"Security", "security":"Security",
"serverError":"Server error", "serverError":"Server error",
"session":"session", "session":"session",
"sessions":"Sessions", "sessions":"sessions",
"session_s":"session(s)", "session_s":"session(s)",
"sessionDataToRemember":"Session data to store", "sessionDataToRemember":"Session data to store",
"sessionDeleted":"The session was deleted", "sessionDeleted":"The session was deleted",
@ -672,6 +672,7 @@
"sessionStartedAt":"Session started on", "sessionStartedAt":"Session started on",
"sessionStorage":"Sessions Storage", "sessionStorage":"Sessions Storage",
"sessionTitle":"Session content", "sessionTitle":"Session content",
"sfaTitle":"Seconds Factors Authentication",
"show":"Show", "show":"Show",
"showHelp":"Show help", "showHelp":"Show help",
"singleIP":"One IP only by user", "singleIP":"One IP only by user",

View File

@ -664,7 +664,7 @@
"security":"Sécurité", "security":"Sécurité",
"serverError":"Erreur du serveur", "serverError":"Erreur du serveur",
"session":"session", "session":"session",
"sessions":"Sessions", "sessions":"sessions",
"session_s":"session(s)", "session_s":"session(s)",
"sessionDataToRemember":"Données de session à conserver", "sessionDataToRemember":"Données de session à conserver",
"sessionDeleted":"La session a été supprimée", "sessionDeleted":"La session a été supprimée",
@ -672,6 +672,7 @@
"sessionStartedAt":"Session démarrée le ", "sessionStartedAt":"Session démarrée le ",
"sessionStorage":"Stockage des sessions", "sessionStorage":"Stockage des sessions",
"sessionTitle":"Contenu de la session", "sessionTitle":"Contenu de la session",
"sfaTitle":"Seconds Facteurs d'Authentification",
"show":"Montrer", "show":"Montrer",
"showHelp":"Montrer l'aide", "showHelp":"Montrer l'aide",
"singleIP":"Une seule session par couple utilisateur/IP", "singleIP":"Une seule session par couple utilisateur/IP",

View File

@ -672,6 +672,7 @@
"sessionStartedAt":"La sessione è stata avviata", "sessionStartedAt":"La sessione è stata avviata",
"sessionStorage":"Conservazione di sessioni", "sessionStorage":"Conservazione di sessioni",
"sessionTitle":"Contenuto della sessione", "sessionTitle":"Contenuto della sessione",
"sfaTitle":"Seconds Factors Authentication",
"show":"Mostra", "show":"Mostra",
"showHelp":"Mostra aiuto", "showHelp":"Mostra aiuto",
"singleIP":"Solo un IP per utente", "singleIP":"Solo un IP per utente",

View File

@ -665,13 +665,14 @@
"serverError":"Lỗi máy chủ", "serverError":"Lỗi máy chủ",
"session":"phiên", "session":"phiên",
"sessions":"Phiên", "sessions":"Phiên",
"session_s":"session (s)", "session_s":"session(s)",
"sessionDataToRemember":"Dữ liệu phiên để lưu trữ", "sessionDataToRemember":"Dữ liệu phiên để lưu trữ",
"sessionDeleted":"Phiên đã bị xóa", "sessionDeleted":"Phiên đã bị xóa",
"sessionParams":"Phiên", "sessionParams":"Phiên",
"sessionStartedAt":"Phiên bắt đầu lúc", "sessionStartedAt":"Phiên bắt đầu lúc",
"sessionStorage":"Sessions lưu trữ", "sessionStorage":"Sessions lưu trữ",
"sessionTitle":"Nội dung phiên", "sessionTitle":"Nội dung phiên",
"sfaTitle":"Seconds Factors Authentication",
"show":"Hiển thị", "show":"Hiển thị",
"showHelp":"Hiển thị trợ giúp", "showHelp":"Hiển thị trợ giúp",
"singleIP":"Chỉ một địa chỉ IP bởi người dùng", "singleIP":"Chỉ một địa chỉ IP bởi người dùng",

View File

@ -16,9 +16,13 @@
<form name="filterForm"> <form name="filterForm">
<div class="form-check ">&nbsp;&nbsp;&nbsp; <div class="form-check ">&nbsp;&nbsp;&nbsp;
<input type="checkbox" ng-model="U2FCheck" class="form-check-input" ng-true-value="'2'" ng-false-value="'1'" ng-change="search2FA()"/> <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; <label class="form-check-label" for="U2FCheck">U2F</label>
&nbsp;&nbsp;
<input type="checkbox" ng-model="TOTPCheck" class="form-check-input" ng-true-value="'2'" ng-false-value="'1'" ng-change="search2FA()"/> <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> <label class="form-check-label" for="TOTPCheck">TOTP</label>
&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>
</div> </div>
</form> </form>
</ul> </ul>
@ -54,49 +58,7 @@
</aside> </aside>
<!-- Right(main) div --> <!-- Right(main) div -->
<div id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}"> <div id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}">
<!-- Menu buttons -->
<div ng-if="currentSession && U2FCheck=='2' || currentSession && U2FCheck!='2' && TOTPCheck!='2'" class="lmmenu navbar navbar-default" ng-class="{'hidden-xs':!showM}">
<div class="navbar-collapse" ng-class="{'collapse':!showM}" id="formmenu">
<ul ng-if="currentSession" class="nav navbar-nav">
<li ng-if="currentSession" ng-repeat="button in menu.delU2FKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession" ng-repeat="button in menu.verifyU2FKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession" ng-repeat="button in menu.addU2FKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession===null" ng-repeat="button in menu.home" ng-include="'menubutton.html'"></li>
<li uib-dropdown class="visible-xs">
<a id="langmenu" name="menu" uib-dropdown-toggle data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Menu <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelled-by="langmenu" role="grid">
<li ng-repeat="link in links"><a href="{{link.target}}" role="row"><i ng-if="link.icon" class="glyphicon glyphicon-{{link.icon}}"></i> {{translate(link.title)}}</a></li>
<li ng-repeat="menulink in menulinks"><a href="{{menulink.target}}" role="row"><i ng-if="menulink.icon" class="glyphicon glyphicon-{{menulink.icon}}"></i> {{translate(menulink.title)}}</a></li>
<li ng-include="'languages.html'"></li>
</ul>
</li>
</ul>
</div>
</div>
<div ng-if="currentSession && TOTPCheck=='2'|| currentSession && U2FCheck!='2' && TOTPCheck!='2'" class="lmmenu navbar navbar-default" ng-class="{'hidden-xs':!showM}">
<div class="navbar-collapse" ng-class="{'collapse':!showM}" id="formmenu">
<ul ng-if="currentSession" class="nav navbar-nav">
<li ng-if="currentSession" ng-repeat="button in menu.delTOTPKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession" ng-repeat="button in menu.verifyTOTPKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession" ng-repeat="button in menu.addTOTPKey" ng-include="'menubutton.html'"></li>
<li ng-if="currentSession===null" ng-repeat="button in menu.home" ng-include="'menubutton.html'"></li>
<li uib-dropdown class="visible-xs">
<a id="langmenu" name="menu" uib-dropdown-toggle data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Menu <span class="caret"></span></a>
<ul uib-dropdown-menu aria-labelled-by="langmenu" role="grid">
<li ng-repeat="link in links"><a href="{{link.target}}" role="row"><i ng-if="link.icon" class="glyphicon glyphicon-{{link.icon}}"></i> {{translate(link.title)}}</a></li>
<li ng-repeat="menulink in menulinks"><a href="{{menulink.target}}" role="row"><i ng-if="menulink.icon" class="glyphicon glyphicon-{{menulink.icon}}"></i> {{translate(menulink.title)}}</a></li>
<li ng-include="'languages.html'"></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="panel panel-default" ng-hide="currentSession===null"> <div class="panel panel-default" ng-hide="currentSession===null">
<div class="panel-heading"> <div class="panel-heading">
<h1 class="panel-title text-center">{{translate("sessionTitle")}} {{currentSession.id}}</h1> <h1 class="panel-title text-center">{{translate("sessionTitle")}} {{currentSession.id}}</h1>
@ -123,10 +85,19 @@
<tr ng-repeat="node in node.nodes" ng-include="'session_attr.html'"></tr> <tr ng-repeat="node in node.nodes" ng-include="'session_attr.html'"></tr>
</table> </table>
</div> </div>
<div ng-if="!node.nodes"> <div ng-if="!node.nodes" id="data-{{node.epoch}}" ng-hide="isHidden">
<th>{{translate(node.title)}}</th> <th ng-if="node.title=='type'">{{translate(node.title)}}</th>
<td><tt>${{node.title}}</tt></td> <td ng-if="node.title!='type'">{{node.title}}</td>
<td><span id="v-{{node.title}}">{{node.value}}</td> <th ng-if="node.title=='type'"><span id="v-{{node.value}}">{{translate(node.value)}}</span></th>
<td ng-if="node.title!='type'"><span id="v-{{node.value}}">{{node.value}}</span></td>
<th ng-if="node.title=='type'"><span id="v-{{node.sdate}}">{{translate(node.sdate)}}</span></th>
<td ng-if="node.title!='type'" class="data-epoch"><span id="v-{{node.sdate}}">{{node.sdate}}</span></td>
<td>
<span ng-if="node.title=='TOTP' || node.title=='UBK' || node.title=='U2F'" 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' )" class="link text-success glyphicon glyphicon-plus-sign" ng-click="menuClick({title:'newRule'})"></span>
-->
</td>
</div> </div>
</script> </script>
@ -138,12 +109,14 @@
</a> </a>
<span id="s-{{node.value}}" ng-click="stoggle(this)">{{node.title || node.value}} <span class="badge">{{node.count}}</span></span> <span id="s-{{node.value}}" ng-click="stoggle(this)">{{node.title || node.value}} <span class="badge">{{node.count}}</span></span>
</span> </span>
<span ng-if="node.session"> <span ng-if="node.session">
<a class="btn btn-node btn-sm" ng-click="displaySession(this)"> <a class="btn btn-node btn-sm" ng-click="displaySession(this)">
<span class="glyphicon glyphicon-eye-open"></span> <span class="glyphicon glyphicon-eye-open"></span>
</a> </a>
<span id="s-{{node.session}}" ng-click="displaySession(this)">{{localeDate(node.date)}}</span> <span id="s-{{node.session}}" ng-click="displaySession(this)">{{localeDate(node.date)}}</span>
</span> </span>
</div> </div>
<ol ui-tree-nodes="" ng-model="node.nodes" ng-class="{hidden: collapsed}"> <ol ui-tree-nodes="" ng-model="node.nodes" ng-class="{hidden: collapsed}">
<li ng-repeat="node in node.nodes track by node.id" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li> <li ng-repeat="node in node.nodes track by node.id" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>

View File

@ -162,14 +162,43 @@ sub run {
elsif ( $err == 0 ) { elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" ); return $self->p->sendError( $req, "noU2FKeyFound" );
} }
my $challenge = $req->datas->{crypter}->authenticationChallenge;
# Get a challenge (from first key)
my $data = eval {
from_json( $req->datas->{crypter}->[0]->authenticationChallenge );
};
if ($@) {
$self->logger->error( Crypt::U2F::Server::u2fclib_getError() );
return $self->p->sendError( $req, "U2F error: $error", 200 );
}
# Get registered keys
my @rk;
foreach ( @{ $req->datas->{crypter} } ) {
my $k = push @rk,
{ keyHandle => $_->{keyHandle}, version => $data->{version} };
#{ keyHandle => $_->{keyHandle}, version => $challenge->{version} };
}
# Serialize datas
$data = to_json(
{
challenge => $data->{challenge},
appId => $data->{appId},
registeredKeys => \@rk
}
);
return [ return [
200, 200,
[ [
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Content-Length' => length($challenge), 'Content-Length' => length($data),
], ],
[$challenge] [$data]
]; ];
} }
@ -184,15 +213,35 @@ sub run {
} }
my ( $err, $error ) = $self->loadUser($req); my ( $err, $error ) = $self->loadUser($req);
if ( $err == -1 ) { if ( $err == -1 ) {
return $self->p->sendError( $req, "U2F error: $error", 200 ); return $self->p->sendError( $req, "U2F loading error: $error", 500 );
} }
elsif ( $err == 0 ) { elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" ); return $self->p->sendError( $req, "noU2FKeyFound" );
} }
$self->logger->debug("Get verify response $resp"); $self->logger->debug("Get verify response $resp");
$req->datas->{crypter}->setChallenge($challenge); my $data = eval { JSON::from_json($resp) };
my $res = if ($@) {
( $req->datas->{crypter}->authenticationVerify($resp) ? 1 : 0 ); $self->logger->error("U2F response error: $@");
return $self->p->sendError( $req, "U2FAnswerError" );
}
my $crypter;
foreach ( @{ $req->datas->{crypter} } ) {
$crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
}
unless ($crypter) {
$self->userLogger->error("Unregistered U2F key");
return $self->p->sendError( $req, "U2FKeyUnregistered" );
}
if ( not $crypter->setChallenge($challenge) ) {
$self->logger->error(
$@ ? $@ : Crypt::U2F::Server::Simple::lastError() );
return $self->p->sendError( $req, "U2FServerError" );
}
my $res = ( $crypter->authenticationVerify($resp) ? 1 : 0 );
return [ return [
200, 200,
[ 'Content-Type' => 'application/json', 'Content-Length' => 12, ], [ 'Content-Type' => 'application/json', 'Content-Length' => 12, ],
@ -216,7 +265,6 @@ sub run {
[$challenge] [$challenge]
]; ];
} }
elsif ( $action eq 'delete' ) { elsif ( $action eq 'delete' ) {
my $epoch = $req->param('epoch'); my $epoch = $req->param('epoch');
@ -266,14 +314,13 @@ sub run {
sub loadUser { sub loadUser {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
$self->logger->debug("Loading user U2F Devices ..."); $self->logger->debug("Loading user U2F Devices ...");
my $uid = $req->userData->{ $self->conf->{whatToTrace} };
my $session = $self->p->getPersistentSession($uid);
my $secret = '';
# Read existing 2FDevices # Read existing 2FDevices
$self->logger->debug("Looking for 2F Devices ..."); $self->logger->debug("Looking for 2F Devices ...");
my $_2fDevices; my ( $kh, $uk, $_2fDevices );
my @u2fs = ();
if ( $req->userData->{_2fDevices} ) { if ( $req->userData->{_2fDevices} ) {
$_2fDevices = eval { $_2fDevices = eval {
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } ); from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
@ -283,32 +330,56 @@ sub loadUser {
return $self->p->sendError( $req, "Corrupted session", 500 ); return $self->p->sendError( $req, "Corrupted session", 500 );
} }
} }
else { else {
$self->logger->debug("No 2F Device found"); $self->logger->debug("No 2F Device found");
$_2fDevices = []; $_2fDevices = [];
} }
# Reading existing U2F keys # Reading existing U2F keys
$self->logger->debug("Reading U2F keys if exists ..."); foreach (@$_2fDevices) {
my @U2Fs = grep { $_->{type} =~ /U2F/s } @$_2fDevices; $self->logger->debug("Looking for registered U2F key(s) ...");
my $kh = $U2Fs[0]{_keyHandle}; if ( $_->{type} eq 'U2F' ) {
my $uk = $self->decode_base64url( $U2Fs[0]{_userKey} ); unless ( $_->{_userKey} and $_->{_keyHandle} ) {
unless ( $kh and $uk ) { $self->logger->error(
$self->logger->debug("UTOP -> No U2F key found !!!"); 'Missing required U2F attributes in storage ($session->{_2fDevices})'
);
next;
}
$self->logger->debug( "Found U2F key -> _userKey = "
. $_->{_userKey}
. "/ _keyHandle = "
. $_->{_keyHandle} );
$_->{_userKey} = $self->decode_base64url( $_->{_userKey} );
push @u2fs, $_;
}
}
# Manage multi u2f keys
my @crypters;
if (@u2fs) {
foreach (@u2fs) {
$kh = $_->{_keyHandle};
$uk = $_->{_userKey};
my $c = $self->crypter( keyHandle => $kh, publicKey => $uk );
if ($c) {
$self->logger->debug("kh & uk -> OK");
push @crypters, $c;
}
else {
$self->logger->error(
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
}
}
unless (@crypters) {
return -1;
}
$req->datas->{crypter} = \@crypters;
return 1;
}
else {
$self->userLogger->info("U2F : user not registered");
return 0; return 0;
} }
$self->logger->debug( "_userKey = " . $uk );
$req->datas->{crypter} = $self->crypter(
keyHandle => $kh,
publicKey => $uk
);
unless ( $req->datas->{crypter} ) {
my $error = Crypt::U2F::Server::Simple::lastError();
return ( -1, $error );
}
return 1;
} }
1; 1;

View File

@ -203,10 +203,12 @@ sub loadUser {
} }
$self->logger->debug("2F Device(s) found"); $self->logger->debug("2F Device(s) found");
foreach (@$_2fDevices) { foreach (@$_2fDevices) {
$self->logger->debug("Looking for registered U2F key(s) ..."); $self->logger->debug("Looking for registered U2F key(s) ...");
if ( $_->{type} eq 'U2F' ) { if ( $_->{type} eq 'U2F' ) {
unless ( $_->{_userKey} and $_->{_userKey} ) { unless ( $_->{_userKey} and $_->{_keyHandle} ) {
$self->logger->error( $self->logger->error(
"Missing required U2F attributes in storage ($session->{_2fDevices})" "Missing required U2F attributes in storage ($session->{_2fDevices})"
); );

View File

@ -67,12 +67,8 @@ verify = ->
error: displayError error: displayError
success: (ch) -> success: (ch) ->
# 2 build response # 2 build response
request = [
keyHandle: ch.keyHandle
version: ch.version
]
setMsg 'touchU2fDevice', 'positive' setMsg 'touchU2fDevice', 'positive'
u2f.sign ch.appId, ch.challenge, request, (data) -> u2f.sign ch.appId, ch.challenge, ch.registeredKeys, (data) ->
# Handle errors # Handle errors
if data.errorCode if data.errorCode
setMsg 'unableToGetKey', 'warning' setMsg 'unableToGetKey', 'warning'

View File

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.9.3 // Generated by CoffeeScript 1.10.0
/* /*
LemonLDAP::NG U2F registration script LemonLDAP::NG U2F registration script
@ -82,15 +82,8 @@ LemonLDAP::NG U2F registration script
dataType: 'json', dataType: 'json',
error: displayError, error: displayError,
success: function(ch) { success: function(ch) {
var request;
request = [
{
keyHandle: ch.keyHandle,
version: ch.version
}
];
setMsg('touchU2fDevice', 'positive'); setMsg('touchU2fDevice', 'positive');
return u2f.sign(ch.appId, ch.challenge, request, function(data) { return u2f.sign(ch.appId, ch.challenge, ch.registeredKeys, function(data) {
if (data.errorCode) { if (data.errorCode) {
return setMsg('unableToGetKey', 'warning'); return setMsg('unableToGetKey', 'warning');
} else { } else {

View File

@ -1 +1 @@
(function(){var a,b,c,d;c=function(e,f){$("#msg").html(window.translate(e));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+f);if(f==="positive"){f="success"}return $("#color").addClass("alert-"+f)};a=function(f,e,h){var g;console.log("Error",h);g=JSON.parse(f.responseText);if(g&&g.error){g=g.error.replace(/.* /,"");console.log("Returned error",g);return c(g,"warning")}};b=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/register",data:{},dataType:"json",error:a,success:function(e){var f;f=[{challenge:e.challenge,version:e.version}];c("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(e.appId,f,[],function(g){$("#u2fPermission").hide();if(g.errorCode){return c(g.error,"warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/registration",data:{registration:JSON.stringify(g),challenge:JSON.stringify(e),keyName:$("#keyName").val()},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsRegistered","positive")}}},error:a})}})}})};d=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/verify",data:{},dataType:"json",error:a,success:function(e){var f;f=[{keyHandle:e.keyHandle,version:e.version}];c("touchU2fDevice","positive");return u2f.sign(e.appId,e.challenge,f,function(g){if(g.errorCode){return c("unableToGetKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/signature",data:{signature:JSON.stringify(g),challenge:e.challenge},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsVerified","positive")}}},error:function(i,h,k){return console.log("error",k)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",b);$("#verify").on("click",d);return $("#goback").attr("href",portal)})}).call(this); (function(){var a,b,c,d;c=function(e,f){$("#msg").html(window.translate(e));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+f);if(f==="positive"){f="success"}return $("#color").addClass("alert-"+f)};a=function(f,e,h){var g;console.log("Error",h);g=JSON.parse(f.responseText);if(g&&g.error){g=g.error.replace(/.* /,"");console.log("Returned error",g);return c(g,"warning")}};b=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/register",data:{},dataType:"json",error:a,success:function(e){var f;f=[{challenge:e.challenge,version:e.version}];c("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(e.appId,f,[],function(g){$("#u2fPermission").hide();if(g.errorCode){return c(g.error,"warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/registration",data:{registration:JSON.stringify(g),challenge:JSON.stringify(e),keyName:$("#keyName").val()},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsRegistered","positive")}}},error:a})}})}})};d=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/verify",data:{},dataType:"json",error:a,success:function(e){c("touchU2fDevice","positive");return u2f.sign(e.appId,e.challenge,e.registeredKeys,function(f){if(f.errorCode){return c("unableToGetKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/signature",data:{signature:JSON.stringify(f),challenge:e.challenge},dataType:"json",success:function(g){if(g.error){return c("u2fFailed","warning")}else{if(g.result){return c("yourKeyIsVerified","positive")}}},error:function(h,g,i){return console.log("error",i)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",b);$("#verify").on("click",d);return $("#goback").attr("href",portal)})}).call(this);

View File

@ -15,7 +15,7 @@
</form> </form>
</div> </div>
</div> </div>
<br>
<div class="buttons"> <div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button"> <a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp; <span class="glyphicon glyphicon-home"></span>&nbsp;

View File

@ -28,8 +28,8 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<br>
</TMPL_IF> </TMPL_IF>
<br>
<div class="buttons"> <div class="buttons">
<TMPL_LOOP NAME="MODULES"> <TMPL_LOOP NAME="MODULES">
&nbsp;&nbsp; &nbsp;&nbsp;

View File

@ -20,9 +20,14 @@
<span trspan="connect">Connect</span> <span trspan="connect">Connect</span>
</button> </button>
</div> </div>
<br/>
<div class="buttons">
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-home"></span>&nbsp;
<span trspan="goToPortal">Go to portal</span>
</a>
</div>
</div> </div>
</main> </main>
<TMPL_INCLUDE NAME="footer.tpl"> <TMPL_INCLUDE NAME="footer.tpl">

View File

@ -49,6 +49,6 @@
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.min.js"></script> <script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.min.js"></script>
//else --> //else -->
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">bwr/qrious/dist/qrious.js"></script> <script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">bwr/qrious/dist/qrious.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.js"></script> <script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">common/js/totpregistration.js"></script>
<!-- //endif --> <!-- //endif -->
<TMPL_INCLUDE NAME="footer.tpl"> <TMPL_INCLUDE NAME="footer.tpl">