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