Rename U2F manager module to SFA
This commit is contained in:
parent
f808292b84
commit
395826f8b2
|
@ -20,7 +20,7 @@ lib/Lemonldap/NG/Manager/Conf/Tests.pm
|
|||
lib/Lemonldap/NG/Manager/Conf/Zero.pm
|
||||
lib/Lemonldap/NG/Manager/Notifications.pm
|
||||
lib/Lemonldap/NG/Manager/Sessions.pm
|
||||
lib/Lemonldap/NG/Manager/U2F.pm
|
||||
lib/Lemonldap/NG/Manager/SFA.pm
|
||||
Makefile.PL
|
||||
MANIFEST This list of files
|
||||
META.yml
|
||||
|
@ -34,7 +34,7 @@ site/coffee/llApp.coffee
|
|||
site/coffee/manager.coffee
|
||||
site/coffee/notifications.coffee
|
||||
site/coffee/sessions.coffee
|
||||
site/coffee/u2f.coffee
|
||||
site/coffee/sfa.coffee
|
||||
site/htdocs/manager.fcgi
|
||||
site/htdocs/manager.psgi
|
||||
site/htdocs/static/bwr/angular-animate/angular-animate.js
|
||||
|
@ -150,8 +150,8 @@ site/htdocs/static/js/notifications.js
|
|||
site/htdocs/static/js/notifications.min.js
|
||||
site/htdocs/static/js/sessions.js
|
||||
site/htdocs/static/js/sessions.min.js
|
||||
site/htdocs/static/js/u2f.js
|
||||
site/htdocs/static/js/u2f.min.js
|
||||
site/htdocs/static/js/sfa.js
|
||||
site/htdocs/static/js/sfa.min.js
|
||||
site/htdocs/static/languages/ar.json
|
||||
site/htdocs/static/languages/en.json
|
||||
site/htdocs/static/languages/fr.json
|
||||
|
|
|
@ -91,7 +91,7 @@ sub init {
|
|||
|
||||
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
|
||||
my $linksIcons =
|
||||
{ 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell', 'U2F' => 'wrench' };
|
||||
{ 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell', 'SFA' => 'wrench' };
|
||||
|
||||
$self->links( [] );
|
||||
for ( my $i = 0 ; $i < @links ; $i++ ) {
|
||||
|
|
|
@ -1,274 +0,0 @@
|
|||
package Lemonldap::NG::Manager::U2F;
|
||||
|
||||
use 5.10.0;
|
||||
use utf8;
|
||||
use strict;
|
||||
use Mouse;
|
||||
use MIME::Base64 qw(encode_base64 decode_base64);
|
||||
#use Crypt::U2F::Server::Simple;
|
||||
|
||||
use Lemonldap::NG::Common::Session;
|
||||
use Lemonldap::NG::Common::Conf::Constants;
|
||||
use Lemonldap::NG::Common::PSGI::Constants;
|
||||
use Lemonldap::NG::Common::Conf::ReConstants;
|
||||
|
||||
use feature 'state';
|
||||
|
||||
extends 'Lemonldap::NG::Common::Conf::AccessLib',
|
||||
'Lemonldap::NG::Common::Session::REST';
|
||||
|
||||
our $VERSION = '2.0.0';
|
||||
|
||||
#############################
|
||||
# I. INITIALIZATION METHODS #
|
||||
#############################
|
||||
|
||||
use constant defaultRoute => 'u2f.html#/persistent';
|
||||
|
||||
sub addRoutes {
|
||||
my ( $self, $conf ) = @_;
|
||||
|
||||
# Remote Procedure Call are defined in Lemonldap::NG::Common::Session::REST
|
||||
# HTML template
|
||||
$self->addRoute( 'u2f.html', undef, ['GET'] )
|
||||
|
||||
# READ
|
||||
->addRoute(
|
||||
u2f => { ':sessionType' => 'u2f' },
|
||||
['GET']
|
||||
)
|
||||
|
||||
# DELETE U2F KEY
|
||||
->addRoute(
|
||||
u2f => { ':sessionType' => { ':sessionId' => 'deleteU2FKey' } },
|
||||
['DELETE']
|
||||
)
|
||||
|
||||
# ADD U2F KEY
|
||||
->addRoute(
|
||||
u2f => { ':sessionType' => { ':sessionId' => 'registerU2FKey' } },
|
||||
['PUT']
|
||||
)
|
||||
|
||||
# VERIFY U2F KEY
|
||||
->addRoute(
|
||||
u2f => { ':sessionType' => { ':sessionId' => 'verifyU2FKey' } },
|
||||
['POST']
|
||||
);
|
||||
|
||||
$self->setTypes($conf);
|
||||
|
||||
$self->{ipField} ||= 'ipAddr';
|
||||
$self->{multiValuesSeparator} ||= '; ';
|
||||
$self->{hiddenAttributes} //= "_password";
|
||||
}
|
||||
|
||||
|
||||
############################
|
||||
# II. REGISTRATION METHODS #
|
||||
############################
|
||||
|
||||
sub registerU2FKey {
|
||||
|
||||
my ( $self, $req, $session, $skey ) = @_;
|
||||
|
||||
eval 'use Crypt::U2F::Server::Simple';
|
||||
if ($@) {
|
||||
$self->error("Can't load U2F library: $@");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $self->addU2FKey( $req, $session, $skey );
|
||||
}
|
||||
|
||||
|
||||
########################
|
||||
# III. DISPLAY METHODS #
|
||||
########################
|
||||
|
||||
sub u2f {
|
||||
my ( $self, $req, $session, $skey ) = @_;
|
||||
|
||||
# Case 1: only one session is required
|
||||
if ($session) {
|
||||
return $self->session( $req, $session, $skey );
|
||||
}
|
||||
|
||||
my $mod = $self->getMod($req)
|
||||
or return $self->sendError( $req, undef, 400 );
|
||||
my $params = $req->parameters();
|
||||
my $type = delete $params->{sessionType};
|
||||
$type = ucfirst($type);
|
||||
|
||||
my $res;
|
||||
|
||||
# Case 2: list of sessions
|
||||
|
||||
my $whatToTrace = Lemonldap::NG::Handler::PSGI::Main->tsv->{whatToTrace};
|
||||
|
||||
# 2.1 Get fields to require
|
||||
my @fields = ( '_httpSessionType', $self->{ipField}, $whatToTrace, '_u2fKeyHandle' );
|
||||
if ( my $groupBy = $params->{groupBy} ) {
|
||||
$groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/;
|
||||
$groupBy =~ s/^_whatToTrace$/$whatToTrace/o
|
||||
or push @fields, $groupBy;
|
||||
}
|
||||
else {
|
||||
push @fields, '_utime';
|
||||
}
|
||||
|
||||
# 2.2 Restrict query if possible: search for filters (any query arg that is
|
||||
# not a keyword)
|
||||
my $moduleOptions = $mod->{options};
|
||||
$moduleOptions->{backend} = $mod->{module};
|
||||
my %filters = map {
|
||||
my $s = $_;
|
||||
$s =~ s/\b_whatToTrace\b/$whatToTrace/o;
|
||||
/^(?:(?:group|order)By|doubleIp)$/
|
||||
? ()
|
||||
: ( $s => $params->{$_} );
|
||||
} keys %$params;
|
||||
$filters{_session_kind} = $type;
|
||||
|
||||
push @fields, keys(%filters);
|
||||
{
|
||||
my %seen;
|
||||
@fields = grep { !$seen{$_}++ } @fields;
|
||||
}
|
||||
|
||||
# For now, only one argument can be passed to
|
||||
# Lemonldap::NG::Common::Apache::Session so just the first filter is
|
||||
# used
|
||||
my ($firstFilter) = sort {
|
||||
$filters{$a} =~ m#^[\w:]+/\d+\*?$# ? 1
|
||||
: $filters{$b} =~ m#^[\w:]+/\d+\*?$# ? -1
|
||||
: $a eq '_session_kind' ? 1
|
||||
: $b eq '_session_kind' ? -1
|
||||
: $a cmp $b
|
||||
} keys %filters;
|
||||
|
||||
# Check if a '*' is required
|
||||
my $function = 'searchOn';
|
||||
$function = 'searchOnExpr'
|
||||
if ( grep { /\*/ and not m#^[\w:]+/\d+\*?$# }
|
||||
( $filters{$firstFilter} ) );
|
||||
$self->logger->debug(
|
||||
"First filter: $firstFilter = $filters{$firstFilter} ($function)");
|
||||
|
||||
$res =
|
||||
Lemonldap::NG::Common::Apache::Session->$function( $moduleOptions,
|
||||
$firstFilter, $filters{$firstFilter}, @fields );
|
||||
|
||||
return $self->sendJSONresponse(
|
||||
$req,
|
||||
{
|
||||
result => 1,
|
||||
count => 0,
|
||||
total => 0,
|
||||
values => []
|
||||
}
|
||||
) unless ( $res and %$res );
|
||||
|
||||
delete $filters{$firstFilter}
|
||||
unless ( grep { /\*/ and not m#^[\w:]+/\d+\*?$# }
|
||||
( $filters{$firstFilter} ) );
|
||||
foreach my $k ( keys %filters ) {
|
||||
$self->logger->debug("Removing unless $k =~ /^$filters{$k}\$/");
|
||||
if ( $filters{$k} =~ m#^([\w:]+)/(\d+)\*?$# ) {
|
||||
my ( $net, $bits ) = ( $1, $2 );
|
||||
foreach my $session ( keys %$res ) {
|
||||
delete $res->{$session}
|
||||
unless ( net6( $res->{$session}->{$k}, $bits ) eq $net );
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filters{$k} =~ s/\./\\./g;
|
||||
$filters{$k} =~ s/\*/\.\*/g;
|
||||
foreach my $session ( keys %$res ) {
|
||||
if ( $res->{$session}->{$k} ) {
|
||||
delete $res->{$session}
|
||||
unless ( $res->{$session}->{$k} =~ /^$filters{$k}$/ );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Display sessions with registered U2F key only
|
||||
foreach my $session ( keys %$res ) {
|
||||
delete $res->{$session}
|
||||
unless ( defined $res->{$session}->{_u2fKeyHandle} and length $res->{$session}->{_u2fKeyHandle} )
|
||||
}
|
||||
|
||||
my $total = ( keys %$res );
|
||||
|
||||
if ( my $group = $req->params('groupBy') ) {
|
||||
my $r;
|
||||
$group =~ s/\b_whatToTrace\b/$whatToTrace/o;
|
||||
|
||||
# Substrings
|
||||
if ( $group =~ /^substr\((\w+)(?:,(\d+)(?:,(\d+))?)?\)$/ ) {
|
||||
my ( $field, $length, $start ) = ( $1, $2, $3 );
|
||||
$start ||= 0;
|
||||
$length = 1 if ( $length < 1 );
|
||||
foreach my $k ( keys %$res ) {
|
||||
$r->{ substr $res->{$k}->{$field}, $start, $length }++
|
||||
if ( $res->{$k}->{$field} );
|
||||
}
|
||||
$group = $field;
|
||||
}
|
||||
|
||||
# Simple field groupBy query
|
||||
elsif ( $group =~ /^\w+$/ ) {
|
||||
eval {
|
||||
foreach my $k ( keys %$res ) {
|
||||
$r->{ $res->{$k}->{$group} }++;
|
||||
}
|
||||
};
|
||||
return $self->sendError(
|
||||
$req,
|
||||
qq{Use of an uninitialized attribute "$group" to group sessions},
|
||||
400
|
||||
) if ($@);
|
||||
}
|
||||
else {
|
||||
return $self->sendError( $req, 'Syntax error in groupBy', 400 );
|
||||
}
|
||||
|
||||
# Build result
|
||||
$res = [
|
||||
sort {
|
||||
my @a = ( $a->{value} =~ /^(\d+)(?:\.(\d+))*$/ );
|
||||
my @b = ( $b->{value} =~ /^(\d+)(?:\.(\d+))*$/ );
|
||||
( @a and @b )
|
||||
? ( $a[0] <=> $b[0]
|
||||
or $a[1] <=> $b[1]
|
||||
or $a[2] <=> $b[2]
|
||||
or $a[3] <=> $b[3] )
|
||||
: $a->{value} cmp $b->{value}
|
||||
}
|
||||
map { { value => $_, count => $r->{$_} } } keys %$r
|
||||
];
|
||||
}
|
||||
|
||||
# Else, $res elements will be like:
|
||||
# { session => <sessionId>, date => <timestamp> }
|
||||
else {
|
||||
$res = [
|
||||
sort { $a->{date} <=> $b->{date} }
|
||||
map { { session => $_, date => $res->{$_}->{_utime} } }
|
||||
keys %$res
|
||||
];
|
||||
}
|
||||
|
||||
return $self->sendJSONresponse(
|
||||
$req,
|
||||
{
|
||||
result => 1,
|
||||
count => scalar(@$res),
|
||||
total => $total,
|
||||
values => $res
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,419 +0,0 @@
|
|||
###
|
||||
# Session explorer
|
||||
###
|
||||
|
||||
# Max number of session to display (see overScheme)
|
||||
max = 25
|
||||
|
||||
# Queries to do each type of display: each array item corresponds to the depth
|
||||
# of opened nodes in the tree
|
||||
schemes =
|
||||
_whatToTrace: [
|
||||
(t,v) ->
|
||||
"groupBy=substr(#{t},1)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}*&groupBy=#{t}"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}"
|
||||
]
|
||||
ipAddr: [
|
||||
(t,v) ->
|
||||
"groupBy=net(#{t},16,1)"
|
||||
(t,v) ->
|
||||
v = v + '.' unless v.match /:/
|
||||
"#{t}=#{v}*&groupBy=net(#{t},32,2)"
|
||||
(t,v) ->
|
||||
v = v + '.' unless v.match /:/
|
||||
"#{t}=#{v}*&groupBy=net(#{t},48,3)"
|
||||
(t,v) ->
|
||||
v = v + '.' unless v.match /:/
|
||||
"#{t}=#{v}*&groupBy=net(#{t},128,4)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}&groupBy=_whatToTrace"
|
||||
(t,v,q) ->
|
||||
q.replace(/\&groupBy.*$/, '') + "&_whatToTrace=#{v}"
|
||||
]
|
||||
_startTime: [
|
||||
(t,v) ->
|
||||
"groupBy=substr(#{t},8)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}*&groupBy=substr(#{t},10)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}*&groupBy=substr(#{t},11)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}*&groupBy=substr(#{t},12)"
|
||||
(t,v) ->
|
||||
"#{t}=#{v}*&groupBy=_whatToTrace"
|
||||
(t,v,q) ->
|
||||
console.log t
|
||||
console.log v
|
||||
console.log q
|
||||
q.replace(/\&groupBy.*$/, '') + "&_whatToTrace=#{v}"
|
||||
]
|
||||
doubleIp: [
|
||||
(t,v) ->
|
||||
t
|
||||
(t,v) ->
|
||||
"_whatToTrace=#{v}&groupBy=ipAddr"
|
||||
(t,v,q) ->
|
||||
q.replace(/\&groupBy.*$/, '') + "&ipAddr=#{v}"
|
||||
]
|
||||
|
||||
overScheme =
|
||||
_whatToTrace: (t,v,level,over) ->
|
||||
if level == 1
|
||||
"#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})"
|
||||
else
|
||||
null
|
||||
ipAddr: (t,v,level,over) ->
|
||||
if level > 0 and level < 4
|
||||
"#{t}=#{v}*&groupBy=net(#{t},#{16*level+4*(over+1)},2)"
|
||||
else
|
||||
null
|
||||
|
||||
hiddenAttributes = '_password'
|
||||
|
||||
# Attributes to group in session display
|
||||
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']
|
||||
|
||||
# Menu entries
|
||||
menu =
|
||||
delU2FKey: [
|
||||
title: 'deleteU2FKey'
|
||||
icon: 'trash'
|
||||
]
|
||||
addU2FKey: [
|
||||
title: 'addU2FKey'
|
||||
icon: 'plus'
|
||||
]
|
||||
verifyU2FKey: [
|
||||
title: 'verifyU2FKey'
|
||||
icon: 'check'
|
||||
]
|
||||
home: []
|
||||
|
||||
###
|
||||
# AngularJS application
|
||||
###
|
||||
llapp = angular.module 'llngSessionsExplorer', ['ui.tree', 'ui.bootstrap', 'llApp']
|
||||
|
||||
# Main controller
|
||||
llapp.controller 'SessionsExplorerCtrl', ['$scope', '$translator', '$location', '$q', '$http', ($scope, $translator, $location, $q, $http) ->
|
||||
$scope.links = links
|
||||
$scope.menulinks = menulinks
|
||||
$scope.staticPrefix = staticPrefix
|
||||
$scope.scriptname = scriptname
|
||||
$scope.formPrefix = formPrefix
|
||||
$scope.availableLanguages = availableLanguages
|
||||
$scope.waiting = true
|
||||
$scope.showM = false
|
||||
$scope.showT = true
|
||||
$scope.data = []
|
||||
$scope.currentScope = null
|
||||
$scope.currentSession = null
|
||||
$scope.menu = menu
|
||||
|
||||
# Import translations functions
|
||||
$scope.translateP = $translator.translateP
|
||||
$scope.translate = $translator.translate
|
||||
$scope.translateTitle = (node) ->
|
||||
$translator.translateField node, 'title'
|
||||
sessionType = 'global'
|
||||
|
||||
# Handle menu items
|
||||
$scope.menuClick = (button) ->
|
||||
if button.popup
|
||||
window.open button.popup
|
||||
else
|
||||
button.action = button.title unless button.action
|
||||
switch typeof button.action
|
||||
when 'function'
|
||||
button.action $scope.currentNode, $scope
|
||||
when 'string'
|
||||
$scope[button.action]()
|
||||
else
|
||||
console.log typeof button.action
|
||||
$scope.showM = false
|
||||
|
||||
# SESSION MANAGEMENT
|
||||
|
||||
# Delete U2F key
|
||||
$scope.deleteU2FKey = ->
|
||||
$scope.waiting = true
|
||||
$http['delete']("#{scriptname}u2f/#{sessionType}/#{$scope.currentSession.id}").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 U2F key
|
||||
$scope.addU2FKey = ->
|
||||
$scope.waiting = true
|
||||
$http['put']("#{scriptname}u2f/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
|
||||
#$scope.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
$scope.waiting = false
|
||||
, (resp) ->
|
||||
#$scope.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
$scope.waiting = false
|
||||
$scope.showT = false
|
||||
$http.get("#{scriptname}u2f/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
|
||||
$scope.currentSession = transformSession response.data
|
||||
$scope.showT = false
|
||||
|
||||
# Verify U2F key
|
||||
$scope.verifyU2FKey = ->
|
||||
$scope.waiting = true
|
||||
$http['post']("#{scriptname}u2f/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
|
||||
#$scope.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
$scope.waiting = false
|
||||
, (resp) ->
|
||||
#$scope.currentSession = null
|
||||
#$scope.currentScope.remove()
|
||||
$scope.waiting = false
|
||||
$scope.showT = true
|
||||
$http.get("#{scriptname}u2f/#{sessionType}/#{$scope.currentSession.id}").then (response) ->
|
||||
$scope.currentSession = transformSession response.data
|
||||
$scope.showT = false
|
||||
|
||||
# Open node
|
||||
$scope.stoggle = (scope) ->
|
||||
node = scope.$modelValue
|
||||
if node.nodes.length == 0
|
||||
$scope.updateTree node.value, node.nodes, node.level, node.over, node.query, node.count
|
||||
scope.toggle()
|
||||
|
||||
# Display selected session
|
||||
$scope.displaySession = (scope) ->
|
||||
|
||||
# Private functions
|
||||
|
||||
# Session preparation
|
||||
transformSession = (session) ->
|
||||
_stToStr = (s) ->
|
||||
s
|
||||
_insert = (re, title) ->
|
||||
tmp = []
|
||||
reg = new RegExp(re)
|
||||
for key,value of session
|
||||
if key.match(reg) and value
|
||||
tmp.push
|
||||
title: key
|
||||
value: value
|
||||
delete session[key]
|
||||
if tmp.length > 0
|
||||
res.push
|
||||
title: title
|
||||
nodes: tmp
|
||||
time = session._utime
|
||||
id = session._session_id
|
||||
|
||||
# 1. Replace values if needed
|
||||
for key, value of session
|
||||
unless value
|
||||
delete session[key]
|
||||
else
|
||||
if typeof session == 'string' and value.match(/; /)
|
||||
session[key] = value.split '; '
|
||||
if typeof session[key] != 'object'
|
||||
if hiddenAttributes.match(new RegExp('\b' + key + '\b'))
|
||||
session[key] = '********'
|
||||
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)$/
|
||||
# session[key] = '########'
|
||||
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 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
|
||||
nodes: res
|
||||
}
|
||||
|
||||
$scope.currentScope = scope
|
||||
sessionId = scope.$modelValue.session
|
||||
$http.get("#{scriptname}u2f/#{sessionType}/#{sessionId}").then (response) ->
|
||||
$scope.currentSession = transformSession response.data
|
||||
$scope.showT = false
|
||||
|
||||
$scope.localeDate = (s) ->
|
||||
d = new Date(s * 1000)
|
||||
return d.toLocaleString()
|
||||
|
||||
# Function to change interface language
|
||||
$scope.getLanguage = (lang) ->
|
||||
$scope.lang = lang
|
||||
$scope.form = 'white'
|
||||
$scope.init()
|
||||
$scope.showM = false
|
||||
|
||||
# URI local path management
|
||||
pathEvent = (event, next, current) ->
|
||||
n = next.match /#\/(\w+)/
|
||||
sessionType = 'global'
|
||||
if n == null
|
||||
$scope.type = '_whatToTrace'
|
||||
else if n[1].match /^(persistent)$/
|
||||
sessionType = RegExp.$1
|
||||
$scope.type = '_session_uid'
|
||||
else
|
||||
$scope.type = n[1]
|
||||
$scope.init()
|
||||
|
||||
$scope.$on '$locationChangeSuccess', pathEvent
|
||||
|
||||
# Function to update tree: download value of opened subkey
|
||||
autoId = 0
|
||||
$scope.updateTree = (value, node, level, over, currentQuery, count) ->
|
||||
$scope.waiting = true
|
||||
|
||||
# Query scheme selection:
|
||||
|
||||
# - if defined above
|
||||
scheme = if schemes[$scope.type]
|
||||
schemes[$scope.type]
|
||||
|
||||
# - _updateTime must be displayed as startDate
|
||||
else if $scope.type == '_updateTime'
|
||||
schemes._startTime
|
||||
|
||||
# - default to _whatToTrace scheme
|
||||
else
|
||||
schemes._whatToTrace
|
||||
|
||||
# Build query using schemes
|
||||
query = scheme[level] $scope.type, value, currentQuery
|
||||
|
||||
# If number of session exceeds "max" and overScheme exists, call it
|
||||
if count > max and overScheme[$scope.type]
|
||||
if tmp = overScheme[$scope.type] $scope.type, value, level, over, currentQuery
|
||||
over++
|
||||
query = tmp
|
||||
level = level - 1
|
||||
else
|
||||
over = 0
|
||||
else
|
||||
over = 0
|
||||
|
||||
# Launch HTTP query
|
||||
$http.get("#{scriptname}u2f/#{sessionType}?#{query}").then (response) ->
|
||||
data = response.data
|
||||
if data.result
|
||||
for n in data.values
|
||||
autoId++
|
||||
n.id = "node#{autoId}"
|
||||
if level < scheme.length - 1
|
||||
n.nodes = []
|
||||
n.level = level + 1
|
||||
n.query = query
|
||||
n.over = over
|
||||
|
||||
# Date display in tree
|
||||
if $scope.type.match /^(?:start|update)Time$/
|
||||
n.title = n.value
|
||||
# 12 digits -> 12:34
|
||||
.replace(/^(\d{8})(\d{2})(\d{2})$/,'$2:$3')
|
||||
# 11 digits -> 12:30
|
||||
.replace(/^(\d{8})(\d{2})(\d)$/,'$2:$30')
|
||||
# 10 digits -> 12h
|
||||
.replace(/^(\d{8})(\d{2})$/,'$2h')
|
||||
# 8 digits -> 2016-03-15
|
||||
.replace(/^(\d{4})(\d{2})(\d{2})/,'$1-$2-$3')
|
||||
node.push n
|
||||
$scope.total = data.total if value == ''
|
||||
$scope.waiting = false
|
||||
, (resp) ->
|
||||
$scope.waiting = false
|
||||
|
||||
# Intialization function
|
||||
# Simply set $scope.waiting to false during $translator and tree root
|
||||
# initialization
|
||||
$scope.init = ->
|
||||
$scope.waiting = true
|
||||
$scope.data = []
|
||||
$q.all [
|
||||
$translator.init $scope.lang
|
||||
$scope.updateTree '', $scope.data, 0, 0
|
||||
]
|
||||
.then ->
|
||||
$scope.waiting = false
|
||||
, (resp) ->
|
||||
$scope.waiting = false
|
||||
|
||||
# Query scheme initialization
|
||||
# Default to '_whatToTrace'
|
||||
c = $location.path().match /^\/(\w+)/
|
||||
$scope.type = if c then c[1] else '_whatToTrace'
|
||||
]
|
|
@ -1,449 +0,0 @@
|
|||
// Generated by CoffeeScript 1.9.3
|
||||
|
||||
/*
|
||||
* Session explorer
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var categories, hiddenAttributes, llapp, max, menu, overScheme, schemes;
|
||||
|
||||
max = 25;
|
||||
|
||||
schemes = {
|
||||
_whatToTrace: [
|
||||
function(t, v) {
|
||||
return "groupBy=substr(" + t + ",1)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "*&groupBy=" + t;
|
||||
}, function(t, v) {
|
||||
return t + "=" + v;
|
||||
}
|
||||
],
|
||||
ipAddr: [
|
||||
function(t, v) {
|
||||
return "groupBy=net(" + t + ",16,1)";
|
||||
}, function(t, v) {
|
||||
if (!v.match(/:/)) {
|
||||
v = v + '.';
|
||||
}
|
||||
return t + "=" + v + "*&groupBy=net(" + t + ",32,2)";
|
||||
}, function(t, v) {
|
||||
if (!v.match(/:/)) {
|
||||
v = v + '.';
|
||||
}
|
||||
return t + "=" + v + "*&groupBy=net(" + t + ",48,3)";
|
||||
}, function(t, v) {
|
||||
if (!v.match(/:/)) {
|
||||
v = v + '.';
|
||||
}
|
||||
return t + "=" + v + "*&groupBy=net(" + t + ",128,4)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "&groupBy=_whatToTrace";
|
||||
}, function(t, v, q) {
|
||||
return q.replace(/\&groupBy.*$/, '') + ("&_whatToTrace=" + v);
|
||||
}
|
||||
],
|
||||
_startTime: [
|
||||
function(t, v) {
|
||||
return "groupBy=substr(" + t + ",8)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "*&groupBy=substr(" + t + ",10)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "*&groupBy=substr(" + t + ",11)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "*&groupBy=substr(" + t + ",12)";
|
||||
}, function(t, v) {
|
||||
return t + "=" + v + "*&groupBy=_whatToTrace";
|
||||
}, function(t, v, q) {
|
||||
console.log(t);
|
||||
console.log(v);
|
||||
console.log(q);
|
||||
return q.replace(/\&groupBy.*$/, '') + ("&_whatToTrace=" + v);
|
||||
}
|
||||
],
|
||||
doubleIp: [
|
||||
function(t, v) {
|
||||
return t;
|
||||
}, function(t, v) {
|
||||
return "_whatToTrace=" + v + "&groupBy=ipAddr";
|
||||
}, function(t, v, q) {
|
||||
return q.replace(/\&groupBy.*$/, '') + ("&ipAddr=" + v);
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
overScheme = {
|
||||
_whatToTrace: function(t, v, level, over) {
|
||||
if (level === 1) {
|
||||
return t + "=" + v + "*&groupBy=substr(" + t + "," + (level + over + 1) + ")";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
ipAddr: function(t, v, level, over) {
|
||||
if (level > 0 && level < 4) {
|
||||
return t + "=" + v + "*&groupBy=net(" + t + "," + (16 * level + 4 * (over + 1)) + ",2)";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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']
|
||||
};
|
||||
|
||||
menu = {
|
||||
delU2FKey: [
|
||||
{
|
||||
title: 'deleteU2FKey',
|
||||
icon: 'trash'
|
||||
}
|
||||
],
|
||||
addU2FKey: [
|
||||
{
|
||||
title: 'addU2FKey',
|
||||
icon: 'plus'
|
||||
}
|
||||
],
|
||||
verifyU2FKey: [
|
||||
{
|
||||
title: 'verifyU2FKey',
|
||||
icon: 'check'
|
||||
}
|
||||
],
|
||||
home: []
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* AngularJS application
|
||||
*/
|
||||
|
||||
llapp = angular.module('llngSessionsExplorer', ['ui.tree', 'ui.bootstrap', 'llApp']);
|
||||
|
||||
llapp.controller('SessionsExplorerCtrl', [
|
||||
'$scope', '$translator', '$location', '$q', '$http', function($scope, $translator, $location, $q, $http) {
|
||||
var autoId, c, pathEvent, sessionType;
|
||||
$scope.links = links;
|
||||
$scope.menulinks = menulinks;
|
||||
$scope.staticPrefix = staticPrefix;
|
||||
$scope.scriptname = scriptname;
|
||||
$scope.formPrefix = formPrefix;
|
||||
$scope.availableLanguages = availableLanguages;
|
||||
$scope.waiting = true;
|
||||
$scope.showM = false;
|
||||
$scope.showT = true;
|
||||
$scope.data = [];
|
||||
$scope.currentScope = null;
|
||||
$scope.currentSession = null;
|
||||
$scope.menu = menu;
|
||||
$scope.translateP = $translator.translateP;
|
||||
$scope.translate = $translator.translate;
|
||||
$scope.translateTitle = function(node) {
|
||||
return $translator.translateField(node, 'title');
|
||||
};
|
||||
sessionType = 'global';
|
||||
$scope.menuClick = function(button) {
|
||||
if (button.popup) {
|
||||
window.open(button.popup);
|
||||
} else {
|
||||
if (!button.action) {
|
||||
button.action = button.title;
|
||||
}
|
||||
switch (typeof button.action) {
|
||||
case 'function':
|
||||
button.action($scope.currentNode, $scope);
|
||||
break;
|
||||
case 'string':
|
||||
$scope[button.action]();
|
||||
break;
|
||||
default:
|
||||
console.log(typeof button.action);
|
||||
}
|
||||
}
|
||||
return $scope.showM = false;
|
||||
};
|
||||
$scope.deleteU2FKey = function() {
|
||||
$scope.waiting = true;
|
||||
$http['delete'](scriptname + "u2f/" + sessionType + "/" + $scope.currentSession.id).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 + "u2f/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
|
||||
return $scope.waiting = false;
|
||||
}, function(resp) {
|
||||
return $scope.waiting = false;
|
||||
});
|
||||
$scope.showT = false;
|
||||
$http.get(scriptname + "u2f/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
|
||||
return $scope.currentSession = transformSession(response.data);
|
||||
});
|
||||
return $scope.showT = false;
|
||||
};
|
||||
$scope.verifyU2FKey = function() {
|
||||
$scope.waiting = true;
|
||||
$http['post'](scriptname + "u2f/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
|
||||
return $scope.waiting = false;
|
||||
}, function(resp) {
|
||||
return $scope.waiting = false;
|
||||
});
|
||||
$scope.showT = true;
|
||||
$http.get(scriptname + "u2f/" + sessionType + "/" + $scope.currentSession.id).then(function(response) {
|
||||
return $scope.currentSession = transformSession(response.data);
|
||||
});
|
||||
return $scope.showT = false;
|
||||
};
|
||||
$scope.stoggle = function(scope) {
|
||||
var node;
|
||||
node = scope.$modelValue;
|
||||
if (node.nodes.length === 0) {
|
||||
$scope.updateTree(node.value, node.nodes, node.level, node.over, node.query, node.count);
|
||||
}
|
||||
return scope.toggle();
|
||||
};
|
||||
$scope.displaySession = function(scope) {
|
||||
var sessionId, transformSession;
|
||||
transformSession = function(session) {
|
||||
var _insert, _stToStr, attr, attrs, category, i, id, j, k, key, l, len, len1, len2, ref, ref1, res, subres, time, tmp, value;
|
||||
_stToStr = function(s) {
|
||||
return s;
|
||||
};
|
||||
_insert = function(re, title) {
|
||||
var key, reg, tmp, value;
|
||||
tmp = [];
|
||||
reg = new RegExp(re);
|
||||
for (key in session) {
|
||||
value = session[key];
|
||||
if (key.match(reg) && value) {
|
||||
tmp.push({
|
||||
title: key,
|
||||
value: value
|
||||
});
|
||||
delete session[key];
|
||||
}
|
||||
}
|
||||
if (tmp.length > 0) {
|
||||
return res.push({
|
||||
title: title,
|
||||
nodes: tmp
|
||||
});
|
||||
}
|
||||
};
|
||||
time = session._utime;
|
||||
id = session._session_id;
|
||||
for (key in session) {
|
||||
value = session[key];
|
||||
if (!value) {
|
||||
delete session[key];
|
||||
} else {
|
||||
if (typeof session === 'string' && value.match(/; /)) {
|
||||
session[key] = value.split('; ');
|
||||
}
|
||||
if (typeof session[key] !== 'object') {
|
||||
if (hiddenAttributes.match(new RegExp('\b' + key + '\b'))) {
|
||||
session[key] = '********';
|
||||
} else if (key.match(/^(_utime|_lastAuthnUTime|_lastSeen|notification)$/)) {
|
||||
session[key] = $scope.localeDate(value);
|
||||
} else if (key.match(/^(_startTime|_updateTime)$/)) {
|
||||
session[key] = _stToStr(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res = [];
|
||||
for (category in categories) {
|
||||
attrs = categories[category];
|
||||
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 (subres.length > 0) {
|
||||
res.push({
|
||||
title: "__" + category + "__",
|
||||
nodes: subres
|
||||
});
|
||||
}
|
||||
}
|
||||
_insert('^openid', 'OpenID');
|
||||
_insert('^notification_(.+)', '__notificationsDone__');
|
||||
if (session._loginHistory) {
|
||||
tmp = [];
|
||||
if (session._loginHistory.successLogin) {
|
||||
ref = session._loginHistory.successLogin;
|
||||
for (j = 0, len1 = ref.length; j < len1; j++) {
|
||||
l = ref[j];
|
||||
tmp.push({
|
||||
t: l._utime,
|
||||
title: $scope.localeDate(l._utime),
|
||||
value: "Success (IP " + l.ipAddr + ")"
|
||||
});
|
||||
}
|
||||
}
|
||||
if (session._loginHistory.failedLogin) {
|
||||
ref1 = session._loginHistory.failedLogin;
|
||||
for (k = 0, len2 = ref1.length; k < len2; k++) {
|
||||
l = ref1[k];
|
||||
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,
|
||||
nodes: res
|
||||
};
|
||||
};
|
||||
$scope.currentScope = scope;
|
||||
sessionId = scope.$modelValue.session;
|
||||
$http.get(scriptname + "u2f/" + sessionType + "/" + sessionId).then(function(response) {
|
||||
return $scope.currentSession = transformSession(response.data);
|
||||
});
|
||||
return $scope.showT = false;
|
||||
};
|
||||
$scope.localeDate = function(s) {
|
||||
var d;
|
||||
d = new Date(s * 1000);
|
||||
return d.toLocaleString();
|
||||
};
|
||||
$scope.getLanguage = function(lang) {
|
||||
$scope.lang = lang;
|
||||
$scope.form = 'white';
|
||||
$scope.init();
|
||||
return $scope.showM = false;
|
||||
};
|
||||
pathEvent = function(event, next, current) {
|
||||
var n;
|
||||
n = next.match(/#\/(\w+)/);
|
||||
sessionType = 'global';
|
||||
if (n === null) {
|
||||
$scope.type = '_whatToTrace';
|
||||
} else if (n[1].match(/^(persistent)$/)) {
|
||||
sessionType = RegExp.$1;
|
||||
$scope.type = '_session_uid';
|
||||
} else {
|
||||
$scope.type = n[1];
|
||||
}
|
||||
return $scope.init();
|
||||
};
|
||||
$scope.$on('$locationChangeSuccess', pathEvent);
|
||||
autoId = 0;
|
||||
$scope.updateTree = function(value, node, level, over, currentQuery, count) {
|
||||
var query, scheme, tmp;
|
||||
$scope.waiting = true;
|
||||
scheme = schemes[$scope.type] ? schemes[$scope.type] : $scope.type === '_updateTime' ? schemes._startTime : schemes._whatToTrace;
|
||||
query = scheme[level]($scope.type, value, currentQuery);
|
||||
if (count > max && overScheme[$scope.type]) {
|
||||
if (tmp = overScheme[$scope.type]($scope.type, value, level, over, currentQuery)) {
|
||||
over++;
|
||||
query = tmp;
|
||||
level = level - 1;
|
||||
} else {
|
||||
over = 0;
|
||||
}
|
||||
} else {
|
||||
over = 0;
|
||||
}
|
||||
return $http.get(scriptname + "u2f/" + sessionType + "?" + query).then(function(response) {
|
||||
var data, i, len, n, ref;
|
||||
data = response.data;
|
||||
if (data.result) {
|
||||
ref = data.values;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
n = ref[i];
|
||||
autoId++;
|
||||
n.id = "node" + autoId;
|
||||
if (level < scheme.length - 1) {
|
||||
n.nodes = [];
|
||||
n.level = level + 1;
|
||||
n.query = query;
|
||||
n.over = over;
|
||||
if ($scope.type.match(/^(?:start|update)Time$/)) {
|
||||
n.title = n.value.replace(/^(\d{8})(\d{2})(\d{2})$/, '$2:$3').replace(/^(\d{8})(\d{2})(\d)$/, '$2:$30').replace(/^(\d{8})(\d{2})$/, '$2h').replace(/^(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||
}
|
||||
}
|
||||
node.push(n);
|
||||
}
|
||||
if (value === '') {
|
||||
$scope.total = data.total;
|
||||
}
|
||||
}
|
||||
return $scope.waiting = false;
|
||||
}, function(resp) {
|
||||
return $scope.waiting = false;
|
||||
});
|
||||
};
|
||||
$scope.init = function() {
|
||||
$scope.waiting = true;
|
||||
$scope.data = [];
|
||||
return $q.all([$translator.init($scope.lang), $scope.updateTree('', $scope.data, 0, 0)]).then(function() {
|
||||
return $scope.waiting = false;
|
||||
}, function(resp) {
|
||||
return $scope.waiting = false;
|
||||
});
|
||||
};
|
||||
c = $location.path().match(/^\/(\w+)/);
|
||||
return $scope.type = c ? c[1] : '_whatToTrace';
|
||||
}
|
||||
]);
|
||||
|
||||
}).call(this);
|
File diff suppressed because one or more lines are too long
|
@ -1,123 +0,0 @@
|
|||
<TMPL_INCLUDE NAME="header.tpl">
|
||||
|
||||
<title>LemonLDAP::NG sessions explorer</title>
|
||||
</head>
|
||||
|
||||
<body ng-app="llngSessionsExplorer" ng-controller="SessionsExplorerCtrl" ng-csp>
|
||||
|
||||
<TMPL_INCLUDE NAME="menubar.tpl">
|
||||
|
||||
<div id="content" class="row container-fluid">
|
||||
<div id="pleaseWait" ng-show="waiting"><span trspan="waitingForDatas"></span></div>
|
||||
|
||||
<!-- Tree -->
|
||||
<aside id="left" class="col-lg-4 col-md-4 col-sm-5 col-xs-12 scrollable " ng-class="{'hidden-xs':!showT}" role="complementary">
|
||||
<div class="navbar navbar-default">
|
||||
<div class="navbar-collapse">
|
||||
<ul class="nav navbar-nav" role="grid">
|
||||
<li><a id="a-persistent" role="row"><i class="glyphicon glyphicon-exclamation-sign"></i> {{translate('u2fSessions')}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center"><p class="badge">{{total}} <span trspan="session_s"></span></p></div>
|
||||
<div class="region region-sidebar-first">
|
||||
<section id="block-superfish-1" class="block block-superfish clearfix">
|
||||
<div ui-tree data-drag-enabled="false" id="tree-root">
|
||||
<div ng-show="data.length==0" class="center">
|
||||
<span class="label label-warning" trspan="noDatas"></span>
|
||||
</div>
|
||||
<ol ui-tree-nodes="" ng-model="data">
|
||||
<li ng-repeat="node in data track by node.id" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="hresizer hidden-xs" resizer="vertical" resizer-left="#left" resizer-right="#right"></div>
|
||||
</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 class="lmmenu navbar navbar-default" ng-class="{'hidden-xs':!showM}">
|
||||
<div class="navbar-collapse" ng-class="{'collapse':!showM}" id="formmenu">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
<li ng-if="currentSession" ng-repeat="button in menu.addU2FKey" 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.delU2FKey" 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-heading">
|
||||
<h1 class="panel-title text-center">{{translate("sessionTitle")}} {{currentSession.id}}</h1>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{translate("sessionStartedAt")}}</strong>
|
||||
{{localeDate(currentSession._utime)}}
|
||||
</div>
|
||||
<div ng-model="currentSession.nodes">
|
||||
<div ng-repeat="node in currentSession.nodes" ng-include="'session_attr.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="session_attr.html">
|
||||
<div class="panel panel-default" ng-if="node.nodes">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title text-center">{{translateP(node.title)}}</h2>
|
||||
</div>
|
||||
<table class="table table-striped" ng-model="node.nodes">
|
||||
<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>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="nodes_renderer.html">
|
||||
<div ui-tree-handle class="tree-node tree-node-content panel-info" ng-class="{'bg-info':this.$modelValue===currentScope.$modelValue,'tree-node-default':this.$modelValue!==currentScope.$modelValue}">
|
||||
<span ng-if="node.value">
|
||||
<a id="a-{{node.value}}" class="btn btn-node btn-sm" ng-click="stoggle(this)">
|
||||
<span class="glyphicon" ng-class="{'glyphicon-chevron-right': collapsed,'glyphicon-chevron-down': !collapsed}"></span>
|
||||
</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>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
<TMPL_INCLUDE NAME="scripts.tpl">
|
||||
|
||||
<!-- //if:jsminified
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/u2f.min.js"></script>
|
||||
//else -->
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/u2f.js"></script>
|
||||
<!-- //endif -->
|
||||
|
||||
<TMPL_INCLUDE NAME="footer.tpl">
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
Loading…
Reference in New Issue
Block a user