2ndfa.coffee 10.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
###
# 2ndFA Session explorer
###

setMsg = (msg, level) ->
	$('#msg').html window.translate msg
	$('#color').removeClass 'message-positive message-warning alert-success alert-warning'
	$('#color').addClass "message-#{level}"
	level = 'success' if level == 'positive'
	$('#color').addClass "alert-#{level}"

displayError = (j, status, err) ->
	console.log 'Error', err
	res = JSON.parse j.responseText
	if res and res.error
		res = res.error.replace /.* /, ''
		console.log 'Returned error', res
		setMsg res, 'warning'

# 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) ->
30
			"#{t}=#{v}*"
31 32 33 34
	]

overScheme =
	_whatToTrace: (t,v,level,over) ->
Christophe Maudoux's avatar
Christophe Maudoux committed
35
		if level == 1 and v.length > over
36 37 38 39 40 41 42 43
			"#{t}=#{v}*&groupBy=substr(#{t},#{(level+over+1)})"
		else
			null

hiddenAttributes = '_password'

# Attributes to group in session display
categories =
44
    dateTitle:          ['_utime', '_startTime', '_updateTime']
45
    sfaTitle:			['_2fDevices']
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

# Menu entries
menu =
	home: []

###
# AngularJS applications
###
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
71
	$scope.searchString = ''
72 73 74
	$scope.U2FCheck = "1"
	$scope.TOTPCheck = "1"
	$scope.UBKCheck = "1"
75 76 77 78 79 80

	# Import translations functions
	$scope.translateP = $translator.translateP
	$scope.translate = $translator.translate
	$scope.translateTitle = (node) ->
		$translator.translateField node, 'title'
81
	sessionType = 'persistent'
82 83 84 85 86 87 88 89 90 91

	# 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
92
					$scope[button.action]()
93 94 95 96 97 98
				when 'string'
					$scope[button.action]()
				else
					console.log typeof button.action
		$scope.showM = false

99
	## SESSIONS MANAGEMENT
100
	# Search 2FA sessions
101 102
	$scope.search2FA = (clear) ->
		if clear
103
			$scope.searchString = ''
104 105 106 107
		$scope.currentSession = null
		$scope.data = []
		$scope.updateTree2 '', $scope.data, 0, 0
	
108
	# Delete 2FA device
109
	$scope.delete2FA = (type, epoch) ->
110
		item = angular.element(".data-#{epoch}")
Christophe Maudoux's avatar
Christophe Maudoux committed
111
		item.remove()
112
		$scope.waiting = true
113
		$http['delete']("#{scriptname}sfa/#{sessionType}/#{$scope.currentSession.id}?type=#{type}&epoch=#{epoch}").then (response) ->
114 115 116 117 118
			$scope.waiting = false
		, (resp) ->
			$scope.waiting = false
		$scope.showT = false

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
	## 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 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
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

	# 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)$/
Christophe Maudoux's avatar
Christophe Maudoux committed
191
							value = _stToStr value
192 193
							pattern = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/
							arrayDate = value.match(pattern)
194
							session[key] = "#{arrayDate[3]}/#{arrayDate[2]}/#{arrayDate[1]} à #{arrayDate[4]}:#{arrayDate[5]}:#{arrayDate[6]}"
Christophe Maudoux's avatar
Christophe Maudoux committed
195
			
196 197 198 199 200 201
			res = []

			# 2. Push session keys in result, grouped by categories
			for category, attrs of categories
				subres = []
				for attr in attrs
Christophe Maudoux's avatar
Christophe Maudoux committed
202
					if session[attr]
203
						if session[attr].toString().match(/"type":\s*"(?:TOTP|U2F|UBK)"/)
204 205 206
							subres.push
								title: "type"
								value: "name"
207
								epoch: "date"
208
							array = JSON.parse(session[attr])
209 210 211
							for sfDevice in array
								for key, value of sfDevice
									if key == 'type'
212
										title = value
213 214
									if key == 'name'
										name = value
215
									if key == 'epoch'
216
										epoch = value
217
								subres.push
218
									title: title
219
									value: name
220
									epoch: epoch
221
							delete session[attr]
222
						else if session[attr].toString().match(/\w+/)
223 224 225
							subres.push
								title: attr
								value: session[attr]
Christophe Maudoux's avatar
Christophe Maudoux committed
226 227 228 229 230
							delete session[attr]
						else
							delete session[attr]
					else
						delete session[attr]
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
				if subres.length >0
					res.push
						title: "__#{category}__"
						nodes: subres
			return {
				_utime: time
				id: id
				nodes: res
			}

		$scope.currentScope = scope
		sessionId = scope.$modelValue.session
		$http.get("#{scriptname}sfa/#{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) ->
Xavier Guimard's avatar
Xavier Guimard committed
260
		n = next.match /#!?\/(\w+)/
261
		if n == null or n[1].match /^(persistent)$/
262 263 264 265 266
			$scope.type = '_session_uid'
		$scope.init()

	$scope.$on '$locationChangeSuccess', pathEvent

267
	# Functions to update tree: download value of opened subkey
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
	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]

		#  - 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
Christophe Maudoux's avatar
Christophe Maudoux committed
297
		$http.get("#{scriptname}sfa/#{sessionType}?#{query}&U2FCheck=#{$scope.U2FCheck}&TOTPCheck=#{$scope.TOTPCheck}&UBKCheck=#{$scope.UBKCheck}").then (response) ->
298 299 300 301 302 303 304 305 306 307
			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
308
						
309 310 311 312 313
					node.push n
				$scope.total = data.total if value == ''
			$scope.waiting = false
		, (resp) ->
			$scope.waiting = false
314 315
				
	# Functions to filter U2F sessions tree : download value of opened subkey
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	$scope.updateTree2 = (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

347
		# Launch HTTP
348
		$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) ->
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
			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

					node.push n
				$scope.total = data.total if value == ''
			$scope.waiting = false
		, (resp) ->
364
			$scope.waiting = false
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379

	# 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
Christophe Maudoux's avatar
Christophe Maudoux committed
380
		# Colorized link
381 382
		$scope.activeModule = "2ndFA"
		$scope.myStyle = {color: '#ffb84d'}
383 384 385 386 387

	# Query scheme initialization
	# Default to '_whatToTrace'
	c = $location.path().match /^\/(\w+)/
	$scope.type = if c then c[1] else '_whatToTrace'
388
	
389 390
]

391 392