Merge branch 'portal-2f-improvement' into 'master'
Portal 2f improvement See merge request lemonldap-ng/lemonldap-ng!26
This commit is contained in:
commit
0d72ad10df
|
@ -3,6 +3,7 @@ package Lemonldap::NG::Common::Session::REST;
|
|||
use strict;
|
||||
use Mouse;
|
||||
use Lemonldap::NG::Common::Conf::Constants;
|
||||
use JSON qw(from_json to_json);
|
||||
|
||||
our $VERSION = '2.0.0';
|
||||
|
||||
|
@ -44,15 +45,17 @@ sub delSession {
|
|||
my $id = $req->params('sessionId')
|
||||
or return $self->sendError( $req, 'sessionId is missing', 400 );
|
||||
my $session = $self->getApacheSession( $mod, $id );
|
||||
$self->logger->debug("Delete session : $id");
|
||||
$session->remove;
|
||||
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 deleteU2FKey {
|
||||
sub delete2F {
|
||||
my ( $self, $req ) = @_;
|
||||
return $self->sendJSONresponse( $req, { result => 1 } )
|
||||
if ( $self->{demoMode} );
|
||||
|
@ -62,12 +65,51 @@ sub deleteU2FKey {
|
|||
or return $self->sendError( $req, 'sessionId is missing', 400 );
|
||||
|
||||
# Try to read session
|
||||
$self->logger->debug("Loading session : $id");
|
||||
my $session = $self->getApacheSession( $mod, $id )
|
||||
or return $self->sendError( $req, undef, 400 );
|
||||
|
||||
# Delete U2F key attributs and update session
|
||||
$session->data->{_u2fKeyHandle} = '';
|
||||
$session->data->{_u2fUserKey} = '';
|
||||
# Try to read 2F parameters
|
||||
$self->logger->debug("Reading parameters ...");
|
||||
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 } );
|
||||
|
||||
Lemonldap::NG::Handler::PSGI::Main->localUnlog( $req, $id );
|
||||
|
@ -77,79 +119,55 @@ sub deleteU2FKey {
|
|||
return $self->sendJSONresponse( $req, { result => 1 } );
|
||||
}
|
||||
|
||||
sub deleteTOTPKey {
|
||||
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 );
|
||||
#sub add2F {
|
||||
#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 );
|
||||
## 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->{_totp2fSecret} = '';
|
||||
$session->update( \%{ $session->data } );
|
||||
## Delete U2F key attributs and update session
|
||||
#$session->data->{_u2fKeyHandle} = 'TOF';
|
||||
#$session->data->{_u2fUserKey} = 'TOF';
|
||||
#$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 } );
|
||||
}
|
||||
#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 addU2FKey {
|
||||
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 );
|
||||
#sub verify2F {
|
||||
#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 );
|
||||
## 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} = 'TOF';
|
||||
$session->data->{_u2fUserKey} = 'TOF';
|
||||
$session->update( \%{ $session->data } );
|
||||
## 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 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 } );
|
||||
}
|
||||
#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 {
|
||||
my ( $self, $req, $id, $skey ) = @_;
|
||||
|
|
|
@ -27,7 +27,7 @@ use constant defaultRoute => '2ndfa.html#/persistent';
|
|||
sub addRoutes {
|
||||
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
|
||||
$self->addRoute( '2ndfa.html', undef, ['GET'] )
|
||||
|
||||
|
@ -36,24 +36,24 @@ sub addRoutes {
|
|||
['GET']
|
||||
)
|
||||
|
||||
# DELETE 2FA KEY
|
||||
# DELETE 2FA DEVICE
|
||||
->addRoute(
|
||||
sfa => { ':sessionType' => { ':sessionId' => 'delete2FAKey' } },
|
||||
sfa => { ':sessionType' => { ':sessionId' => 'delete2FA' } },
|
||||
['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->{ipField} ||= 'ipAddr';
|
||||
|
@ -61,54 +61,60 @@ sub addRoutes {
|
|||
$self->{hiddenAttributes} //= "_password";
|
||||
$self->{TOTPCheck} = '1';
|
||||
$self->{U2FCheck} = '1';
|
||||
$self->{UBKCheck} = '1';
|
||||
}
|
||||
|
||||
###################
|
||||
# II. 2FA METHODS #
|
||||
###################
|
||||
|
||||
sub delete2FAKey {
|
||||
sub delete2FA {
|
||||
|
||||
my ( $self, $req, $session, $skey ) = @_;
|
||||
|
||||
my $mod = $self->getMod($req)
|
||||
or return $self->sendError( $req, undef, 400 );
|
||||
|
||||
my $params = $req->parameters();
|
||||
my $Key = $params->{Key};
|
||||
my $params = $req->parameters();
|
||||
my $type = $params->{type};
|
||||
my $epoch = $params->{epoch};
|
||||
|
||||
if ( $Key =~ /\bU2F\b/ ) {
|
||||
$self->logger->debug("Call procedure deleteU2FKey");
|
||||
return $self->deleteU2FKey( $req, $session, $skey );
|
||||
}
|
||||
elsif ( $Key =~ /\bTOTP\b/ ) {
|
||||
$self->logger->debug("Call procedure deleteTOTPKey");
|
||||
return $self->deleteTOTPKey( $req, $session, $skey );
|
||||
if ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ and $epoch ) {
|
||||
$self->logger->debug("Call procedure delete2F with type=$type and epoch=$epoch");
|
||||
return $self->delete2F( $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 {
|
||||
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';
|
||||
if ($@) {
|
||||
$self->error("Can't load U2F library: $@");
|
||||
return 0;
|
||||
}
|
||||
#eval 'use Crypt::U2F::Server::Simple';
|
||||
#if ($@) {
|
||||
#$self->error("Can't load U2F library: $@");
|
||||
#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 #
|
||||
|
@ -137,8 +143,7 @@ sub sfa {
|
|||
# 2.1 Get fields to require
|
||||
my @fields = (
|
||||
'_httpSessionType', $self->{ipField},
|
||||
$whatToTrace, '_u2fKeyHandle',
|
||||
'_totp2fSecret'
|
||||
$whatToTrace, '_2fDevices'
|
||||
);
|
||||
if ( my $groupBy = $params->{groupBy} ) {
|
||||
$groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/;
|
||||
|
@ -155,9 +160,10 @@ sub sfa {
|
|||
$moduleOptions->{backend} = $mod->{module};
|
||||
|
||||
# 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->{U2FCheck} = delete $params->{U2FCheck};
|
||||
$self->{UBKCheck} = delete $params->{UBKCheck};
|
||||
}
|
||||
|
||||
my %filters = map {
|
||||
|
@ -236,15 +242,22 @@ sub sfa {
|
|||
if ( $self->{U2FCheck} eq '2' ) {
|
||||
foreach my $session ( keys %$res ) {
|
||||
delete $res->{$session}
|
||||
unless ( defined $res->{$session}->{_u2fKeyHandle}
|
||||
and length $res->{$session}->{_u2fKeyHandle} );
|
||||
unless ( defined $res->{$session}->{_2fDevices}
|
||||
and $res->{$session}->{_2fDevices} =~ /"type":\s*"U2F"/s );
|
||||
}
|
||||
}
|
||||
if ( $self->{TOTPCheck} eq '2' ) {
|
||||
foreach my $session ( keys %$res ) {
|
||||
delete $res->{$session}
|
||||
unless ( defined $res->{$session}->{_totp2fSecret}
|
||||
and length $res->{$session}->{_totp2fSecret} );
|
||||
unless ( defined $res->{$session}->{_2fDevices}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,6 @@ displayError = (j, status, err) ->
|
|||
setMsg res, 'warning'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Max number of session to display (see overScheme)
|
||||
max = 25
|
||||
|
||||
|
@ -72,40 +69,34 @@ hiddenAttributes = '_password'
|
|||
categories =
|
||||
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']
|
||||
BrowserID: ['_browserIdAnswer', '_browserIdAnswerRaw']
|
||||
OpenIDConnect: ['_oidc_id_token', '_oidc_OP', '_oidc_access_token']
|
||||
sfaTitle: ['_2fDevices']
|
||||
|
||||
# Menu entries
|
||||
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'
|
||||
]
|
||||
#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: []
|
||||
|
||||
###
|
||||
|
@ -146,6 +137,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
switch typeof button.action
|
||||
when 'function'
|
||||
button.action $scope.currentNode, $scope
|
||||
$scope[button.action]()
|
||||
when 'string'
|
||||
$scope[button.action]()
|
||||
else
|
||||
|
@ -161,83 +153,49 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
$scope.data = []
|
||||
$scope.updateTree2 '', $scope.data, 0, 0
|
||||
|
||||
# Delete U2F key
|
||||
$scope.deleteU2FKey = ->
|
||||
# Delete 2FA device
|
||||
$scope.delete2FA = (type, epoch) ->
|
||||
item = angular.element("#data-#{epoch}")
|
||||
item.remove()
|
||||
$scope.waiting = true
|
||||
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?Key=U2F").then (response) ->
|
||||
$scope.currentSession = null
|
||||
$scope.currentScope.remove()
|
||||
$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?type=#{type}&epoch=#{epoch}").then (response) ->
|
||||
#$scope.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
#$scope.data = []
|
||||
$scope.waiting = false
|
||||
, (resp) ->
|
||||
$scope.currentSession = null
|
||||
$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.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
$scope.waiting = false
|
||||
$scope.showT = false
|
||||
#$scope.data = []
|
||||
|
||||
# Add U2F key
|
||||
$scope.addU2FKey = ->
|
||||
$scope.waiting = true
|
||||
$http['put']("#{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 = 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
|
||||
## Add 2FA device
|
||||
#$scope.add2FA (type) = ->
|
||||
#$scope.waiting = true
|
||||
#$http['put']("#{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 = 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
|
||||
$scope.verifyTOTPKey = ->
|
||||
$scope.waiting = true
|
||||
$http['post']("#{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 = true
|
||||
## Verify 2FA device
|
||||
#$scope.verify2FA (epoch) = ->
|
||||
#$scope.waiting = true
|
||||
#$http['post']("#{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 = true
|
||||
|
||||
# Open node
|
||||
$scope.stoggle = (scope) ->
|
||||
|
@ -284,66 +242,46 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
else if key.match /^(_utime|_lastAuthnUTime|_lastSeen|notification)$/
|
||||
session[key] = $scope.localeDate value
|
||||
else if key.match /^(_startTime|_updateTime)$/
|
||||
session[key] = _stToStr value
|
||||
#else if key.match /^(_u2fKeyHandle|_u2fUserKey|_totp2fSecret)$/
|
||||
# session[key] = '##########'
|
||||
|
||||
session[key] = _stToStr value
|
||||
res = []
|
||||
|
||||
# 2. Push session keys in result, grouped by categories
|
||||
for category, attrs of categories
|
||||
subres = []
|
||||
for attr in attrs
|
||||
if session[attr]
|
||||
subres.push
|
||||
title: attr
|
||||
value: session[attr]
|
||||
delete session[attr]
|
||||
if session[attr] and session[attr].match(/\w+/)
|
||||
if session[attr].match(/"type":\s*"(?:TOTP|U2F|UBK)"/)
|
||||
subres.push
|
||||
title: "type"
|
||||
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
|
||||
res.push
|
||||
title: "__#{category}__"
|
||||
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 {
|
||||
_utime: time
|
||||
id: id
|
||||
|
@ -478,7 +416,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}").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
|
||||
if data.result
|
||||
for n in data.values
|
||||
|
@ -528,3 +466,5 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
$scope.type = if c then c[1] else '_whatToTrace'
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ hiddenAttributes = '_password'
|
|||
categories =
|
||||
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen']
|
||||
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']
|
||||
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump']
|
||||
groups: ['groups', 'hGroups']
|
||||
|
@ -196,8 +196,7 @@ llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location',
|
|||
session[key] = $scope.localeDate value
|
||||
else if key.match /^(_startTime|_updateTime)$/
|
||||
session[key] = _stToStr value
|
||||
#else if key.match /^(_u2fKeyHandle|_u2fUserKey|_totp2fSecret)$/
|
||||
# session[key] = '##########'
|
||||
|
||||
res = []
|
||||
|
||||
# 2. Push session keys in result, grouped by categories
|
||||
|
|
|
@ -82,52 +82,10 @@
|
|||
categories = {
|
||||
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'],
|
||||
BrowserID: ['_browserIdAnswer', '_browserIdAnswerRaw'],
|
||||
OpenIDConnect: ['_oidc_id_token', '_oidc_OP', '_oidc_access_token']
|
||||
sfaTitle: ['_2fDevices']
|
||||
};
|
||||
|
||||
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: []
|
||||
};
|
||||
|
||||
|
@ -171,6 +129,7 @@
|
|||
switch (typeof button.action) {
|
||||
case 'function':
|
||||
button.action($scope.currentNode, $scope);
|
||||
$scope[button.action]();
|
||||
break;
|
||||
case 'string':
|
||||
$scope[button.action]();
|
||||
|
@ -189,84 +148,18 @@
|
|||
$scope.data = [];
|
||||
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;
|
||||
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?Key=U2F").then(function(response) {
|
||||
$scope.currentSession = null;
|
||||
$scope.currentScope.remove();
|
||||
$http['delete'](scriptname + "sfa/" + sessionType + "/" + $scope.currentSession.id + "?type=" + type + "&epoch=" + epoch).then(function(response) {
|
||||
return $scope.waiting = false;
|
||||
}, function(resp) {
|
||||
$scope.currentSession = null;
|
||||
$scope.currentScope.remove();
|
||||
return $scope.waiting = 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) {
|
||||
var node;
|
||||
node = scope.$modelValue;
|
||||
|
@ -278,7 +171,7 @@
|
|||
$scope.displaySession = function(scope) {
|
||||
var sessionId, transformSession;
|
||||
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) {
|
||||
return s;
|
||||
};
|
||||
|
@ -330,12 +223,45 @@
|
|||
subres = [];
|
||||
for (i = 0, len = attrs.length; i < len; i++) {
|
||||
attr = attrs[i];
|
||||
if (session[attr]) {
|
||||
subres.push({
|
||||
title: attr,
|
||||
value: session[attr]
|
||||
});
|
||||
delete session[attr];
|
||||
if (session[attr] && session[attr].match(/\w+/)) {
|
||||
if (session[attr].match(/"type":\s*"(?:TOTP|U2F|UBK)"/)) {
|
||||
subres.push({
|
||||
title: "type",
|
||||
value: "name",
|
||||
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) {
|
||||
|
@ -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 {
|
||||
_utime: time,
|
||||
id: id,
|
||||
|
@ -502,7 +372,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).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;
|
||||
data = response.data;
|
||||
if (data.result) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -94,7 +94,7 @@
|
|||
categories = {
|
||||
dateTitle: ['_utime', '_startTime', '_updateTime', '_lastAuthnUTime', '_lastSeen'],
|
||||
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'],
|
||||
saml: ['_idp', '_idpConfKey', '_samlToken', '_lassoSessionDump', '_lassoIdentityDump'],
|
||||
groups: ['groups', 'hGroups'],
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -672,6 +672,7 @@
|
|||
"sessionStartedAt":"بدأت الجلسة",
|
||||
"sessionStorage":"تخزين الجلسات",
|
||||
"sessionTitle":"محتوى الجلسة",
|
||||
"sfaTitle":"Seconds Factors Authentication",
|
||||
"show":"عرض",
|
||||
"showHelp":"عرض المساعدة",
|
||||
"singleIP":"عنوان آي بي واحد لكل مستخدم",
|
||||
|
|
|
@ -664,7 +664,7 @@
|
|||
"security":"Security",
|
||||
"serverError":"Server error",
|
||||
"session":"session",
|
||||
"sessions":"Sessions",
|
||||
"sessions":"sessions",
|
||||
"session_s":"session(s)",
|
||||
"sessionDataToRemember":"Session data to store",
|
||||
"sessionDeleted":"The session was deleted",
|
||||
|
@ -672,6 +672,7 @@
|
|||
"sessionStartedAt":"Session started on",
|
||||
"sessionStorage":"Sessions Storage",
|
||||
"sessionTitle":"Session content",
|
||||
"sfaTitle":"Seconds Factors Authentication",
|
||||
"show":"Show",
|
||||
"showHelp":"Show help",
|
||||
"singleIP":"One IP only by user",
|
||||
|
|
|
@ -664,7 +664,7 @@
|
|||
"security":"Sécurité",
|
||||
"serverError":"Erreur du serveur",
|
||||
"session":"session",
|
||||
"sessions":"Sessions",
|
||||
"sessions":"sessions",
|
||||
"session_s":"session(s)",
|
||||
"sessionDataToRemember":"Données de session à conserver",
|
||||
"sessionDeleted":"La session a été supprimée",
|
||||
|
@ -672,6 +672,7 @@
|
|||
"sessionStartedAt":"Session démarrée le ",
|
||||
"sessionStorage":"Stockage des sessions",
|
||||
"sessionTitle":"Contenu de la session",
|
||||
"sfaTitle":"Seconds Facteurs d'Authentification",
|
||||
"show":"Montrer",
|
||||
"showHelp":"Montrer l'aide",
|
||||
"singleIP":"Une seule session par couple utilisateur/IP",
|
||||
|
|
|
@ -672,6 +672,7 @@
|
|||
"sessionStartedAt":"La sessione è stata avviata",
|
||||
"sessionStorage":"Conservazione di sessioni",
|
||||
"sessionTitle":"Contenuto della sessione",
|
||||
"sfaTitle":"Seconds Factors Authentication",
|
||||
"show":"Mostra",
|
||||
"showHelp":"Mostra aiuto",
|
||||
"singleIP":"Solo un IP per utente",
|
||||
|
|
|
@ -665,13 +665,14 @@
|
|||
"serverError":"Lỗi máy chủ",
|
||||
"session":"phiên",
|
||||
"sessions":"Phiên",
|
||||
"session_s":"session (s)",
|
||||
"session_s":"session(s)",
|
||||
"sessionDataToRemember":"Dữ liệu phiên để lưu trữ",
|
||||
"sessionDeleted":"Phiên đã bị xóa",
|
||||
"sessionParams":"Phiên",
|
||||
"sessionStartedAt":"Phiên bắt đầu lúc",
|
||||
"sessionStorage":"Sessions lưu trữ",
|
||||
"sessionTitle":"Nội dung phiên",
|
||||
"sfaTitle":"Seconds Factors Authentication",
|
||||
"show":"Hiển thị",
|
||||
"showHelp":"Hiển thị trợ giúp",
|
||||
"singleIP":"Chỉ một địa chỉ IP bởi người dùng",
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
<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>
|
||||
<label class="form-check-label" for="U2FCheck">U2F</label>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</ul>
|
||||
|
@ -54,49 +58,7 @@
|
|||
</aside>
|
||||
|
||||
<!-- 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}">
|
||||
<!-- 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 id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}">
|
||||
<div class="panel panel-default" ng-hide="currentSession===null">
|
||||
<div class="panel-heading">
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
<div ng-if="!node.nodes">
|
||||
<th>{{translate(node.title)}}</th>
|
||||
<td><tt>${{node.title}}</tt></td>
|
||||
<td><span id="v-{{node.title}}">{{node.value}}</td>
|
||||
<div ng-if="!node.nodes" id="data-{{node.epoch}}" ng-hide="isHidden">
|
||||
<th ng-if="node.title=='type'">{{translate(node.title)}}</th>
|
||||
<td ng-if="node.title!='type'">{{node.title}}</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>
|
||||
</script>
|
||||
|
||||
|
@ -138,12 +109,14 @@
|
|||
</a>
|
||||
<span id="s-{{node.value}}" ng-click="stoggle(this)">{{node.title || node.value}} <span class="badge">{{node.count}}</span></span>
|
||||
</span>
|
||||
|
||||
<span ng-if="node.session">
|
||||
<a class="btn btn-node btn-sm" ng-click="displaySession(this)">
|
||||
<span class="glyphicon glyphicon-eye-open"></span>
|
||||
</a>
|
||||
<span id="s-{{node.session}}" ng-click="displaySession(this)">{{localeDate(node.date)}}</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<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>
|
||||
|
|
|
@ -162,14 +162,43 @@ sub run {
|
|||
elsif ( $err == 0 ) {
|
||||
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 [
|
||||
200,
|
||||
[
|
||||
'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);
|
||||
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 ) {
|
||||
return $self->p->sendError( $req, "noU2FKeyFound" );
|
||||
}
|
||||
|
||||
$self->logger->debug("Get verify response $resp");
|
||||
$req->datas->{crypter}->setChallenge($challenge);
|
||||
my $res =
|
||||
( $req->datas->{crypter}->authenticationVerify($resp) ? 1 : 0 );
|
||||
my $data = eval { JSON::from_json($resp) };
|
||||
if ($@) {
|
||||
$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 [
|
||||
200,
|
||||
[ 'Content-Type' => 'application/json', 'Content-Length' => 12, ],
|
||||
|
@ -216,7 +265,6 @@ sub run {
|
|||
[$challenge]
|
||||
];
|
||||
}
|
||||
|
||||
elsif ( $action eq 'delete' ) {
|
||||
my $epoch = $req->param('epoch');
|
||||
|
||||
|
@ -266,14 +314,13 @@ sub run {
|
|||
sub loadUser {
|
||||
my ( $self, $req ) = @_;
|
||||
$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
|
||||
$self->logger->debug("Looking for 2F Devices ...");
|
||||
my $_2fDevices;
|
||||
my ( $kh, $uk, $_2fDevices );
|
||||
|
||||
my @u2fs = ();
|
||||
|
||||
if ( $req->userData->{_2fDevices} ) {
|
||||
$_2fDevices = eval {
|
||||
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
|
||||
|
@ -283,32 +330,56 @@ sub loadUser {
|
|||
return $self->p->sendError( $req, "Corrupted session", 500 );
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
}
|
||||
|
||||
# Reading existing U2F keys
|
||||
$self->logger->debug("Reading U2F keys if exists ...");
|
||||
my @U2Fs = grep { $_->{type} =~ /U2F/s } @$_2fDevices;
|
||||
my $kh = $U2Fs[0]{_keyHandle};
|
||||
my $uk = $self->decode_base64url( $U2Fs[0]{_userKey} );
|
||||
unless ( $kh and $uk ) {
|
||||
$self->logger->debug("UTOP -> No U2F key found !!!");
|
||||
foreach (@$_2fDevices) {
|
||||
$self->logger->debug("Looking for registered U2F key(s) ...");
|
||||
if ( $_->{type} eq 'U2F' ) {
|
||||
unless ( $_->{_userKey} and $_->{_keyHandle} ) {
|
||||
$self->logger->error(
|
||||
'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;
|
||||
}
|
||||
$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;
|
||||
|
|
|
@ -203,10 +203,12 @@ sub loadUser {
|
|||
}
|
||||
$self->logger->debug("2F Device(s) found");
|
||||
|
||||
|
||||
|
||||
foreach (@$_2fDevices) {
|
||||
$self->logger->debug("Looking for registered U2F key(s) ...");
|
||||
if ( $_->{type} eq 'U2F' ) {
|
||||
unless ( $_->{_userKey} and $_->{_userKey} ) {
|
||||
unless ( $_->{_userKey} and $_->{_keyHandle} ) {
|
||||
$self->logger->error(
|
||||
"Missing required U2F attributes in storage ($session->{_2fDevices})"
|
||||
);
|
||||
|
|
|
@ -67,12 +67,8 @@ verify = ->
|
|||
error: displayError
|
||||
success: (ch) ->
|
||||
# 2 build response
|
||||
request = [
|
||||
keyHandle: ch.keyHandle
|
||||
version: ch.version
|
||||
]
|
||||
setMsg 'touchU2fDevice', 'positive'
|
||||
u2f.sign ch.appId, ch.challenge, request, (data) ->
|
||||
u2f.sign ch.appId, ch.challenge, ch.registeredKeys, (data) ->
|
||||
# Handle errors
|
||||
if data.errorCode
|
||||
setMsg 'unableToGetKey', 'warning'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by CoffeeScript 1.9.3
|
||||
// Generated by CoffeeScript 1.10.0
|
||||
|
||||
/*
|
||||
LemonLDAP::NG U2F registration script
|
||||
|
@ -82,15 +82,8 @@ LemonLDAP::NG U2F registration script
|
|||
dataType: 'json',
|
||||
error: displayError,
|
||||
success: function(ch) {
|
||||
var request;
|
||||
request = [
|
||||
{
|
||||
keyHandle: ch.keyHandle,
|
||||
version: ch.version
|
||||
}
|
||||
];
|
||||
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) {
|
||||
return setMsg('unableToGetKey', 'warning');
|
||||
} else {
|
||||
|
|
|
@ -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);
|
|
@ -15,7 +15,7 @@
|
|||
</form>
|
||||
</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>
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
</TMPL_IF>
|
||||
<br>
|
||||
<div class="buttons">
|
||||
<TMPL_LOOP NAME="MODULES">
|
||||
|
||||
|
|
|
@ -20,9 +20,14 @@
|
|||
<span trspan="connect">Connect</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="buttons">
|
||||
<a href="<TMPL_VAR NAME="PORTAL_URL">" class="btn btn-primary" role="button">
|
||||
<span class="glyphicon glyphicon-home"></span>
|
||||
<span trspan="goToPortal">Go to portal</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<TMPL_INCLUDE NAME="footer.tpl">
|
||||
|
|
|
@ -49,6 +49,6 @@
|
|||
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/totpregistration.min.js"></script>
|
||||
//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">/common/js/totpregistration.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">common/js/totpregistration.js"></script>
|
||||
<!-- //endif -->
|
||||
<TMPL_INCLUDE NAME="footer.tpl">
|
||||
|
|
Loading…
Reference in New Issue
Block a user