17
17
# GNU General Public License for more details.
19
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
# along with this program. If not, see
21
# <http://www.gnu.org/licenses/>.
22
23
# Contact the authors at <mandos@recompile.se>.
25
26
from __future__ import (division, absolute_import, print_function,
29
from future_builtins import *
52
61
domain = 'se.recompile'
53
62
server_interface = domain + '.Mandos'
54
63
client_interface = domain + '.Mandos.Client'
57
# Always run in monochrome mode
58
urwid.curses_display.curses.has_colors = lambda : False
60
# Urwid doesn't support blinking, but we want it. Since we have no
61
# use for underline on its own, we make underline also always blink.
62
urwid.curses_display.curses.A_UNDERLINE |= (
63
urwid.curses_display.curses.A_BLINK)
65
66
def isoformat_to_datetime(iso):
66
67
"Parse an ISO 8601 date string to a datetime.datetime()"
83
84
properties and calls a hook function when any of them are
86
def __init__(self, proxy_object=None, *args, **kwargs):
87
def __init__(self, proxy_object=None, properties=None, **kwargs):
87
88
self.proxy = proxy_object # Mandos Client proxy object
89
self.properties = dict()
89
self.properties = dict() if properties is None else properties
90
90
self.property_changed_match = (
91
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
92
self._property_changed,
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
98
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
101
# super(MandosClientPropertyCache, self).__init__(
96
if properties is None:
97
self.properties.update(
98
self.proxy.GetAll(client_interface,
100
= dbus.PROPERTIES_IFACE))
102
super(MandosClientPropertyCache, self).__init__(**kwargs)
104
def _property_changed(self, property, value):
105
"""Helper which takes positional arguments"""
106
return self.property_changed(property=property, value=value)
104
108
def property_changed(self, property=None, value=None):
105
109
"""This is called whenever we get a PropertyChanged signal
108
112
# Update properties dict with new value
109
113
self.properties[property] = value
111
def delete(self, *args, **kwargs):
112
116
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
117
119
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
121
123
def __init__(self, server_proxy_object=None, update_hook=None,
122
delete_hook=None, logger=None, *args, **kwargs):
124
delete_hook=None, logger=None, **kwargs):
123
125
# Called on update
124
126
self.update_hook = update_hook
125
127
# Called on delete
130
132
self.logger = logger
132
134
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
135
136
# The widget shown normally
136
137
self._text_widget = urwid.Text("")
137
138
# The widget shown when we have focus
138
139
self._focus_text_widget = urwid.Text("")
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
140
super(MandosClientWidget, self).__init__(**kwargs)
143
142
self.opened = False
145
last_checked_ok = isoformat_to_datetime(self.properties
148
if self.properties ["LastCheckerStatus"] != 0:
149
self.using_timer(True)
151
if self.need_approval:
152
self.using_timer(True)
154
144
self.match_objects = (
155
145
self.proxy.connect_to_signal("CheckerCompleted",
156
146
self.checker_completed,
175
165
#self.logger('Created client {0}'
176
166
# .format(self.properties["Name"]))
178
def property_changed(self, property=None, value=None):
179
super(self, MandosClientWidget).property_changed(property,
181
if property == "ApprovalPending":
182
using_timer(bool(value))
183
if property == "LastCheckerStatus":
184
using_timer(value != 0)
185
#self.logger('Checker for client {0} (command "{1}") was '
186
# ' successful'.format(self.properties["Name"],
189
168
def using_timer(self, flag):
190
169
"""Call this method with True or False when timer should be
191
170
activated or deactivated.
193
old = self._update_timer_callback_lock
195
self._update_timer_callback_lock += 1
197
self._update_timer_callback_lock -= 1
198
if old == 0 and self._update_timer_callback_lock:
172
if flag and self._update_timer_callback_tag is None:
199
173
# Will update the shown timer value every second
200
174
self._update_timer_callback_tag = (gobject.timeout_add
202
176
self.update_timer))
203
elif old and self._update_timer_callback_lock == 0:
177
elif not (flag or self._update_timer_callback_tag is None):
204
178
gobject.source_remove(self._update_timer_callback_tag)
205
179
self._update_timer_callback_tag = None
280
253
"bold-underline-blink":
281
254
"bold-underline-blink-standout",
284
257
# Rebuild focus and non-focus widgets using current properties
286
259
# Base part of a client. Name!
287
260
base = '{name}: '.format(name=self.properties["Name"])
288
261
if not self.properties["Enabled"]:
289
262
message = "DISABLED"
263
self.using_timer(False)
290
264
elif self.properties["ApprovalPending"]:
291
265
timeout = datetime.timedelta(milliseconds
292
266
= self.properties
294
268
last_approval_request = isoformat_to_datetime(
295
269
self.properties["LastApprovalRequest"])
296
270
if last_approval_request is not None:
297
timer = timeout - (datetime.datetime.utcnow()
298
- last_approval_request)
271
timer = max(timeout - (datetime.datetime.utcnow()
272
- last_approval_request),
273
datetime.timedelta())
300
275
timer = datetime.timedelta()
301
276
if self.properties["ApprovedByDefault"]:
302
277
message = "Approval in {0}. (d)eny?"
304
279
message = "Denial in {0}. (a)pprove?"
305
message = message.format(unicode(timer).rsplit(".", 1)[0])
280
message = message.format(str(timer).rsplit(".", 1)[0])
281
self.using_timer(True)
306
282
elif self.properties["LastCheckerStatus"] != 0:
307
# When checker has failed, print a timer until client expires
283
# When checker has failed, show timer until client expires
308
284
expires = self.properties["Expires"]
309
285
if expires == "":
310
286
timer = datetime.timedelta(0)
312
expires = datetime.datetime.strptime(expires,
313
'%Y-%m-%dT%H:%M:%S.%f')
314
timer = expires - datetime.datetime.utcnow()
288
expires = (datetime.datetime.strptime
289
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
290
timer = max(expires - datetime.datetime.utcnow(),
291
datetime.timedelta())
315
292
message = ('A checker has failed! Time until client'
316
293
' gets disabled: {0}'
317
.format(unicode(timer).rsplit(".", 1)[0]))
294
.format(str(timer).rsplit(".", 1)[0]))
295
self.using_timer(True)
319
297
message = "enabled"
298
self.using_timer(False)
320
299
self._text = "{0}{1}".format(base, message)
322
301
if not urwid.supports_unicode():
323
302
self._text = self._text.encode("ascii", "replace")
324
303
textlist = [("normal", self._text)]
342
321
return True # Keep calling this
344
def delete(self, *args, **kwargs):
323
def delete(self, **kwargs):
345
324
if self._update_timer_callback_tag is not None:
346
325
gobject.source_remove(self._update_timer_callback_tag)
347
326
self._update_timer_callback_tag = None
350
329
self.match_objects = ()
351
330
if self.delete_hook is not None:
352
331
self.delete_hook(self)
353
return super(MandosClientWidget, self).delete(*args, **kwargs)
332
return super(MandosClientWidget, self).delete(**kwargs)
355
334
def render(self, maxcolrow, focus=False):
356
335
"""Render differently if we have focus.
401
def property_changed(self, property=None, value=None,
380
def property_changed(self, property=None, **kwargs):
403
381
"""Call self.update() if old value is not new value.
404
382
This overrides the method from MandosClientPropertyCache"""
405
property_name = unicode(property)
383
property_name = str(property)
406
384
old_value = self.properties.get(property_name)
407
385
super(MandosClientWidget, self).property_changed(
408
property=property, value=value, *args, **kwargs)
386
property=property, **kwargs)
409
387
if self.properties.get(property_name) != old_value:
415
393
"down" key presses, thus not allowing any containing widgets to
416
394
use them as an excuse to shift focus away from this widget.
418
def keypress(self, maxcolrow, key):
419
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
396
def keypress(self, *args, **kwargs):
397
ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
420
398
if ret in ("up", "down"):
436
414
"default", "default", None),
438
"default", "default", "bold"),
416
"bold", "default", "bold"),
439
417
("underline-blink",
440
"default", "default", "underline"),
418
"underline,blink", "default", "underline,blink"),
442
"default", "default", "standout"),
420
"standout", "default", "standout"),
443
421
("bold-underline-blink",
444
"default", "default", ("bold", "underline")),
422
"bold,underline,blink", "default", "bold,underline,blink"),
445
423
("bold-standout",
446
"default", "default", ("bold", "standout")),
424
"bold,standout", "default", "bold,standout"),
447
425
("underline-blink-standout",
448
"default", "default", ("underline", "standout")),
426
"underline,blink,standout", "default",
427
"underline,blink,standout"),
449
428
("bold-underline-blink-standout",
450
"default", "default", ("bold", "underline",
429
"bold,underline,blink,standout", "default",
430
"bold,underline,blink,standout"),
454
433
if urwid.supports_unicode():
605
580
mandos_clients = (self.mandos_serv
606
581
.GetAllClientsWithProperties())
582
if not mandos_clients:
583
self.log_message_raw(("bold", "Note: Server has no clients."))
607
584
except dbus.exceptions.DBusException:
585
self.log_message_raw(("bold", "Note: No Mandos server running."))
608
586
mandos_clients = dbus.Dictionary()
610
588
(self.mandos_serv
622
600
self.client_not_found,
623
601
dbus_interface=server_interface,
624
602
byte_arrays=True))
625
for path, client in mandos_clients.iteritems():
603
for path, client in mandos_clients.items():
626
604
client_proxy_object = self.bus.get_object(self.busname,
628
606
self.add_client(MandosClientWidget(server_proxy_object