88
83
properties and calls a hook function when any of them are
91
def __init__(self, proxy_object=None, properties=None, **kwargs):
86
def __init__(self, proxy_object=None, *args, **kwargs):
92
87
self.proxy = proxy_object # Mandos Client proxy object
93
self.properties = dict() if properties is None else properties
89
self.properties = dict()
94
90
self.property_changed_match = (
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
100
if properties is None:
101
self.properties.update(
102
self.proxy.GetAll(client_interface,
104
= dbus.PROPERTIES_IFACE))
106
super(MandosClientPropertyCache, self).__init__(**kwargs)
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__(
108
def properties_changed(self, interface, properties, invalidated):
109
"""This is called whenever we get a PropertiesChanged signal
110
It updates the changed properties in the "properties" dict.
104
def property_changed(self, property=None, value=None):
105
"""This is called whenever we get a PropertyChanged signal
106
It updates the changed property in the "properties" dict.
112
108
# Update properties dict with new value
113
if interface == client_interface:
114
self.properties.update(properties)
109
self.properties[property] = value
111
def delete(self, *args, **kwargs):
117
112
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
120
117
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
133
130
self.logger = logger
135
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
137
135
# The widget shown normally
138
136
self._text_widget = urwid.Text("")
139
137
# The widget shown when we have focus
140
138
self._focus_text_widget = urwid.Text("")
141
super(MandosClientWidget, self).__init__(**kwargs)
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
143
143
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)
145
154
self.match_objects = (
146
155
self.proxy.connect_to_signal("CheckerCompleted",
147
156
self.checker_completed,
164
173
client_interface,
165
174
byte_arrays=True))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
175
#self.logger('Created client %s' % (self.properties["Name"]))
177
def property_changed(self, property=None, value=None):
178
super(self, MandosClientWidget).property_changed(property,
180
if property == "ApprovalPending":
181
using_timer(bool(value))
182
if property == "LastCheckerStatus":
183
using_timer(value != 0)
184
#self.logger('Checker for client %s (command "%s")'
186
# % (self.properties["Name"], command))
169
188
def using_timer(self, flag):
170
189
"""Call this method with True or False when timer should be
171
190
activated or deactivated.
173
if flag and self._update_timer_callback_tag is None:
192
old = self._update_timer_callback_lock
194
self._update_timer_callback_lock += 1
196
self._update_timer_callback_lock -= 1
197
if old == 0 and self._update_timer_callback_lock:
174
198
# Will update the shown timer value every second
175
self._update_timer_callback_tag = (GObject.timeout_add
199
self._update_timer_callback_tag = (gobject.timeout_add
177
201
self.update_timer))
178
elif not (flag or self._update_timer_callback_tag is None):
179
GObject.source_remove(self._update_timer_callback_tag)
202
elif old and self._update_timer_callback_lock == 0:
203
gobject.source_remove(self._update_timer_callback_tag)
180
204
self._update_timer_callback_tag = None
182
206
def checker_completed(self, exitstatus, condition, command):
183
207
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
190
211
if os.WIFEXITED(condition):
191
self.logger('Checker for client {} (command "{}") failed'
193
.format(self.properties["Name"], command,
194
os.WEXITSTATUS(condition)))
212
self.logger('Checker for client %s (command "%s")'
213
' failed with exit code %s'
214
% (self.properties["Name"], command,
215
os.WEXITSTATUS(condition)))
195
216
elif os.WIFSIGNALED(condition):
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
198
.format(self.properties["Name"], command,
199
os.WTERMSIG(condition)))
217
self.logger('Checker for client %s (command "%s")'
218
' was killed by signal %s'
219
% (self.properties["Name"], command,
220
os.WTERMSIG(condition)))
221
elif os.WCOREDUMP(condition):
222
self.logger('Checker for client %s (command "%s")'
224
% (self.properties["Name"], command))
226
self.logger('Checker for client %s completed'
202
230
def checker_started(self, command):
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
231
"""Server signals that a checker started. This could be useful
232
to log in the future. """
233
#self.logger('Client %s started checker "%s"'
234
# % (self.properties["Name"], unicode(command)))
208
237
def got_secret(self):
209
self.logger('Client {} received its secret'
210
.format(self.properties["Name"]))
238
self.logger('Client %s received its secret'
239
% self.properties["Name"])
212
241
def need_approval(self, timeout, default):
214
message = 'Client {} needs approval within {} seconds'
243
message = 'Client %s needs approval within %s seconds'
216
message = 'Client {} will get its secret in {} seconds'
217
self.logger(message.format(self.properties["Name"],
245
message = 'Client %s will get its secret in %s seconds'
247
% (self.properties["Name"], timeout/1000))
248
self.using_timer(True)
220
250
def rejected(self, reason):
221
self.logger('Client {} was rejected; reason: {}'
222
.format(self.properties["Name"], reason))
251
self.logger('Client %s was rejected; reason: %s'
252
% (self.properties["Name"], reason))
224
254
def selectable(self):
225
255
"""Make this a "selectable" widget.
262
292
last_approval_request = isoformat_to_datetime(
263
293
self.properties["LastApprovalRequest"])
264
294
if last_approval_request is not None:
265
timer = max(timeout - (datetime.datetime.utcnow()
266
- last_approval_request),
267
datetime.timedelta())
295
timer = timeout - (datetime.datetime.utcnow()
296
- last_approval_request)
269
298
timer = datetime.timedelta()
270
299
if self.properties["ApprovedByDefault"]:
271
message = "Approval in {}. (d)eny?"
300
message = "Approval in %s. (d)eny?"
273
message = "Denial in {}. (a)pprove?"
274
message = message.format(str(timer).rsplit(".", 1)[0])
275
self.using_timer(True)
302
message = "Denial in %s. (a)pprove?"
303
message = message % unicode(timer).rsplit(".", 1)[0]
276
304
elif self.properties["LastCheckerStatus"] != 0:
277
# When checker has failed, show timer until client expires
305
# When checker has failed, print a timer until client expires
278
306
expires = self.properties["Expires"]
279
307
if expires == "":
280
308
timer = datetime.timedelta(0)
282
expires = (datetime.datetime.strptime
283
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
284
timer = max(expires - datetime.datetime.utcnow(),
285
datetime.timedelta())
310
expires = datetime.datetime.strptime(expires,
311
'%Y-%m-%dT%H:%M:%S.%f')
312
timer = expires - datetime.datetime.utcnow()
286
313
message = ('A checker has failed! Time until client'
288
.format(str(timer).rsplit(".", 1)[0]))
289
self.using_timer(True)
315
% unicode(timer).rsplit(".", 1)[0])
291
317
message = "enabled"
292
self.using_timer(False)
293
self._text = "{}{}".format(base, message)
318
self._text = "%s%s" % (base, message)
295
320
if not urwid.supports_unicode():
296
321
self._text = self._text.encode("ascii", "replace")
297
322
textlist = [("normal", self._text)]
309
334
self.update_hook()
311
336
def update_timer(self):
312
"""called by GObject. Will indefinitely loop until
313
GObject.source_remove() on tag is called"""
337
"""called by gobject. Will indefinitely loop until
338
gobject.source_remove() on tag is called"""
315
340
return True # Keep calling this
317
def delete(self, **kwargs):
342
def delete(self, *args, **kwargs):
318
343
if self._update_timer_callback_tag is not None:
319
GObject.source_remove(self._update_timer_callback_tag)
344
gobject.source_remove(self._update_timer_callback_tag)
320
345
self._update_timer_callback_tag = None
321
346
for match in self.match_objects:
323
348
self.match_objects = ()
324
349
if self.delete_hook is not None:
325
350
self.delete_hook(self)
326
return super(MandosClientWidget, self).delete(**kwargs)
351
return super(MandosClientWidget, self).delete(*args, **kwargs)
328
353
def render(self, maxcolrow, focus=False):
329
354
"""Render differently if we have focus.
336
361
This overrides the method from urwid.FlowWidget"""
338
self.proxy.Set(client_interface, "Enabled",
339
dbus.Boolean(True), ignore_reply = True,
340
dbus_interface = dbus.PROPERTIES_IFACE)
363
self.proxy.Enable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
366
self.proxy.Disable(dbus_interface = client_interface,
346
369
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
370
dbus_interface = client_interface,
356
379
ignore_reply=True)
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply = True,
360
dbus_interface = dbus.PROPERTIES_IFACE)
381
self.proxy.StartChecker(dbus_interface = client_interface,
362
self.proxy.Set(client_interface, "CheckerRunning",
363
dbus.Boolean(False), ignore_reply = True,
364
dbus_interface = dbus.PROPERTIES_IFACE)
384
self.proxy.StopChecker(dbus_interface = client_interface,
366
387
self.proxy.CheckedOK(dbus_interface = client_interface,
367
388
ignore_reply=True)
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
399
def property_changed(self, property=None, value=None,
401
"""Call self.update() if old value is not new value.
380
402
This overrides the method from MandosClientPropertyCache"""
381
old_values = { key: self.properties.get(key)
382
for key in properties.keys() }
383
super(MandosClientWidget, self).properties_changed(
384
interface, properties, invalidated)
385
if any(old_values[key] != self.properties.get(key)
386
for key in old_values):
403
property_name = unicode(property)
404
old_value = self.properties.get(property_name)
405
super(MandosClientWidget, self).property_changed(
406
property=property, value=value, *args, **kwargs)
407
if self.properties.get(property_name) != old_value:
413
434
"default", "default", None),
415
"bold", "default", "bold"),
436
"default", "default", "bold"),
416
437
("underline-blink",
417
"underline,blink", "default", "underline,blink"),
438
"default", "default", "underline"),
419
"standout", "default", "standout"),
440
"default", "default", "standout"),
420
441
("bold-underline-blink",
421
"bold,underline,blink", "default", "bold,underline,blink"),
442
"default", "default", ("bold", "underline")),
422
443
("bold-standout",
423
"bold,standout", "default", "bold,standout"),
444
"default", "default", ("bold", "standout")),
424
445
("underline-blink-standout",
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
446
"default", "default", ("underline", "standout")),
427
447
("bold-underline-blink-standout",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
448
"default", "default", ("bold", "underline",
432
452
if urwid.supports_unicode():
488
506
self.uilist.append(self.logbox)
489
507
self.topwidget = urwid.Pile(self.uilist)
491
def log_message(self, message, level=1):
492
"""Log message formatted with timestamp"""
493
if level < self.log_level:
509
def log_message(self, message):
495
510
timestamp = datetime.datetime.now().isoformat()
496
self.log_message_raw("{}: {}".format(timestamp, message),
511
self.log_message_raw(timestamp + ": " + message)
499
def log_message_raw(self, markup, level=1):
513
def log_message_raw(self, markup):
500
514
"""Add a log message to the log buffer."""
501
if level < self.log_level:
503
515
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
504
516
if (self.max_log_length
505
517
and len(self.log) > self.max_log_length):
524
536
self.log_wrap = "clip"
525
537
for textwidget in self.log:
526
538
textwidget.set_wrap_mode(self.log_wrap)
527
self.log_message("Wrap mode: {}".format(self.log_wrap),
539
#self.log_message("Wrap mode: " + self.log_wrap)
530
def find_and_remove_client(self, path, interfaces):
541
def find_and_remove_client(self, path, name):
531
542
"""Find a client by its object path and remove it.
533
This is connected to the InterfacesRemoved signal from the
544
This is connected to the ClientRemoved signal from the
534
545
Mandos server object."""
535
if client_interface not in interfaces:
536
# Not a Mandos client object; ignore
539
547
client = self.clients_dict[path]
542
self.log_message("Unknown client {!r} removed"
550
self.log_message("Unknown client %r (%r) removed", name,
547
def add_new_client(self, path, ifs_and_props):
548
"""Find a client by its object path and remove it.
550
This is connected to the InterfacesAdded signal from the
551
Mandos server object.
553
if client_interface not in ifs_and_props:
554
# Not a Mandos client object; ignore
555
def add_new_client(self, path):
556
556
client_proxy_object = self.bus.get_object(self.busname, path)
557
557
self.add_client(MandosClientWidget(server_proxy_object
558
558
=self.mandos_serv,
601
603
mandos_clients = (self.mandos_serv
602
604
.GetAllClientsWithProperties())
603
if not mandos_clients:
604
self.log_message_raw(("bold", "Note: Server has no clients."))
605
605
except dbus.exceptions.DBusException:
606
self.log_message_raw(("bold", "Note: No Mandos server running."))
607
606
mandos_clients = dbus.Dictionary()
609
608
(self.mandos_serv
610
.connect_to_signal("InterfacesRemoved",
609
.connect_to_signal("ClientRemoved",
611
610
self.find_and_remove_client,
613
= dbus.OBJECT_MANAGER_IFACE,
611
dbus_interface=server_interface,
614
612
byte_arrays=True))
615
613
(self.mandos_serv
616
.connect_to_signal("InterfacesAdded",
614
.connect_to_signal("ClientAdded",
617
615
self.add_new_client,
619
= dbus.OBJECT_MANAGER_IFACE,
616
dbus_interface=server_interface,
620
617
byte_arrays=True))
621
618
(self.mandos_serv
622
619
.connect_to_signal("ClientNotFound",
623
620
self.client_not_found,
624
621
dbus_interface=server_interface,
625
622
byte_arrays=True))
626
for path, client in mandos_clients.items():
623
for path, client in mandos_clients.iteritems():
627
624
client_proxy_object = self.bus.get_object(self.busname,
629
626
self.add_client(MandosClientWidget(server_proxy_object