You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
15 KiB
534 lines
15 KiB
###
|
|
# Sessions 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: [
|
|
# First level: display 1 letter
|
|
(t,v) ->
|
|
"groupBy=substr(#{t},1)"
|
|
# Second level (if no overScheme), display usernames
|
|
(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}"
|
|
]
|
|
_session_uid: [
|
|
# First level: display 1 letter
|
|
(t,v) ->
|
|
"groupBy=substr(#{t},1)"
|
|
# Second level (if no overScheme), display usernames
|
|
(t,v) ->
|
|
"#{t}=#{v}*&groupBy=#{t}"
|
|
(t,v) ->
|
|
"#{t}=#{v}"
|
|
]
|
|
|
|
# When number of children nodes exceeds "max" value and if "overScheme.<type>"
|
|
# is available and does not return "null", a level is added. See
|
|
# "$scope.updateTree" method
|
|
overScheme =
|
|
_whatToTrace: (t,v,level,over) ->
|
|
# "v.length > over" avoids a loop if one user opened more than "max"
|
|
# sessions
|
|
console.log 'overScheme => level', level, 'over', over
|
|
if level == 1 and v.length > over
|
|
"#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})"
|
|
else
|
|
null
|
|
# Note: IPv4 only
|
|
ipAddr: (t,v,level,over) ->
|
|
console.log 'overScheme => level', level, 'over', over
|
|
if level > 0 and level < 4 and !v.match(/^\d+\.\d/) and over < 2
|
|
"#{t}=#{v}*&groupBy=net(#{t},#{16*level+4*(over+1)},#{1+level+over})"
|
|
else
|
|
null
|
|
_startTime: (t,v,level,over) ->
|
|
console.log 'overScheme => level', level, 'over', over
|
|
if level > 3
|
|
"#{t}=#{v}*&groupBy=substr(#{t},#{(10+level+over)})"
|
|
else
|
|
null
|
|
_session_uid: (t,v,level,over) ->
|
|
console.log 'overScheme => level', level, 'over', over
|
|
if level == 1 and v.length > over
|
|
"#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})"
|
|
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']
|
|
sfaTitle: ['_2fDevices']
|
|
oidcConsents: ['_oidcConsents']
|
|
|
|
# Menu entries
|
|
menu =
|
|
session: [
|
|
title: 'deleteSession'
|
|
icon: 'trash'
|
|
]
|
|
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.impPrefix = impPrefix
|
|
$scope.sessionTTL = sessionTTL
|
|
$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 RP Consent
|
|
$scope.deleteOIDCConsent = (rp, epoch) ->
|
|
items = document.querySelectorAll(".data-#{epoch}")
|
|
for e in items
|
|
e.remove()
|
|
$scope.waiting = true
|
|
$http['delete']("#{scriptname}sessions/OIDCConsent/#{sessionType}/#{$scope.currentSession.id}?rp=#{rp}&epoch=#{epoch}").then (response) ->
|
|
$scope.waiting = false
|
|
, (resp) ->
|
|
$scope.waiting = false
|
|
$scope.showT = false
|
|
|
|
# Delete
|
|
$scope.deleteSession = ->
|
|
$scope.waiting = true
|
|
$http['delete']("#{scriptname}sessions/#{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
|
|
|
|
# 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) ->
|
|
_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
|
|
|
|
# 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] = $scope.strToLocaleDate value
|
|
|
|
res = []
|
|
|
|
# 2. Push session keys in result, grouped by categories
|
|
for category, attrs of categories
|
|
subres = []
|
|
for attr in attrs
|
|
if session[attr]
|
|
if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK)"/)
|
|
subres.push
|
|
title: "type"
|
|
value: "name"
|
|
epoch: "date"
|
|
td: "0"
|
|
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
|
|
subres.push
|
|
title: title
|
|
value: name
|
|
epoch: epoch
|
|
td: "1"
|
|
delete session[attr]
|
|
else if session[attr].toString().match(/"rp":\s*"[\w-]+"/)
|
|
subres.push
|
|
title: "RP"
|
|
value: "scope"
|
|
epoch: "date"
|
|
td: "0"
|
|
array = JSON.parse(session[attr])
|
|
for oidcConsent in array
|
|
for key, value of oidcConsent
|
|
if key == 'rp'
|
|
title = value
|
|
if key == 'scope'
|
|
name = value
|
|
if key == 'epoch'
|
|
epoch = value
|
|
subres.push
|
|
title: title
|
|
value: name
|
|
epoch: epoch
|
|
td: "2"
|
|
delete session[attr]
|
|
else if session[attr].toString().match(/\w+/)
|
|
subres.push
|
|
title: attr
|
|
value: session[attr]
|
|
epoch: ''
|
|
delete session[attr]
|
|
else
|
|
delete session[attr]
|
|
else
|
|
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
|
|
# History custom values
|
|
cv = ""
|
|
for key, value of l
|
|
if !key.match /^(_utime|ipAddr|error)$/
|
|
cv += ", #{key} : #{value}"
|
|
tab = cv.split ', '
|
|
tab.sort()
|
|
cv = tab.join ', '
|
|
tmp.push
|
|
t: l._utime
|
|
title: $scope.localeDate l._utime
|
|
value: "Success (IP #{l.ipAddr})" + cv
|
|
if session._loginHistory.failedLogin
|
|
for l in session._loginHistory.failedLogin
|
|
# History custom values
|
|
cv = ""
|
|
for key, value of l
|
|
if !key.match /^(_utime|ipAddr|error)$/
|
|
cv += ", #{key} : #{value}"
|
|
tab = cv.split ', '
|
|
tab.sort()
|
|
cv = tab.join ', '
|
|
tmp.push
|
|
t: l._utime
|
|
title: $scope.localeDate l._utime
|
|
value: "Error #{l.error} (IP #{l.ipAddr})" + cv
|
|
delete session._loginHistory
|
|
tmp.sort (a,b) ->
|
|
b.t - a.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
|
|
# Sort by real and spoofed attributes
|
|
real = []
|
|
spoof = []
|
|
for element in tmp
|
|
if element.title.match(new RegExp('^' + $scope.impPrefix + '.+$'))
|
|
console.log element, '-> real attribute'
|
|
real.push element
|
|
else
|
|
#console.log element, '-> spoofed attribute'
|
|
spoof.push element
|
|
tmp = spoof.concat real
|
|
|
|
res.push
|
|
title: '__attributesAndMacros__'
|
|
nodes: tmp
|
|
return {
|
|
_utime: time
|
|
nodes: res
|
|
}
|
|
|
|
$scope.currentScope = scope
|
|
sessionId = scope.$modelValue.session
|
|
$http.get("#{scriptname}sessions/#{sessionType}/#{sessionId}").then (response) ->
|
|
$scope.currentSession = transformSession response.data
|
|
$scope.currentSession.id = sessionId
|
|
$scope.showT = false
|
|
|
|
$scope.localeDate = (s) ->
|
|
d = new Date(s * 1000)
|
|
return d.toLocaleString()
|
|
|
|
$scope.isValid = (epoch, type) ->
|
|
path = $location.path()
|
|
now = Date.now() / 1000
|
|
console.log "Path", path
|
|
console.log "Session epoch", epoch
|
|
console.log "Current date", now
|
|
console.log "Session TTL", sessionTTL
|
|
isValid = now - epoch < sessionTTL || $location.path().match(/^\/persistent/)
|
|
if type == 'msg'
|
|
console.log "Return msg"
|
|
if isValid then return "info" else return "warning"
|
|
else if type == 'style'
|
|
console.log "Return style"
|
|
if isValid then return {} else return {'color': '#627990', 'font-style': 'italic'}
|
|
else
|
|
console.log "Return isValid"
|
|
return isValid
|
|
|
|
$scope.strToLocaleDate = (s) ->
|
|
arrayDate = s.match /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/
|
|
return s unless arrayDate.length
|
|
d = new Date "#{arrayDate[1]}-#{arrayDate[2]}-#{arrayDate[3]}T#{arrayDate[4]}:#{arrayDate[5]}:#{arrayDate[6]}"
|
|
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|offline)$/
|
|
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 startTime
|
|
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}sessions/#{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
|
|
|
|
# Highlight current selection
|
|
console.log "Selection", sessionType
|
|
$scope.navssoStyle = {color: '#777'}
|
|
$scope.offlineStyle = {color: '#777'}
|
|
$scope.persistentStyle = {color: '#777'}
|
|
$scope.navssoStyle = {color: '#333'} if sessionType == 'global'
|
|
$scope.offlineStyle = {color: '#333'} if sessionType == 'offline'
|
|
$scope.persistentStyle = {color: '#333'} if sessionType == 'persistent'
|
|
|
|
# Intialization function
|
|
# Simply set $scope.waiting to false during $translator and tree root
|
|
# initialization
|
|
$scope.init = ->
|
|
$scope.waiting = true
|
|
$scope.data = []
|
|
$scope.currentScope = null
|
|
$scope.currentSession = null
|
|
$q.all [
|
|
$translator.init $scope.lang
|
|
$scope.updateTree '', $scope.data, 0, 0
|
|
]
|
|
.then ->
|
|
$scope.waiting = false
|
|
, (resp) ->
|
|
$scope.waiting = false
|
|
# Colorized link
|
|
$scope.activeModule = "sessions"
|
|
$scope.myStyle = {color: '#ffb84d'}
|
|
|
|
# Query scheme initialization
|
|
# Default to '_whatToTrace'
|
|
c = $location.path().match /^\/(\w+)/
|
|
$scope.type = if c then c[1] else '_whatToTrace'
|
|
]
|
|
|