Use the new SudoDialog

This commit is contained in:
Sean Davis 2014-08-24 11:46:34 -04:00
parent beab6c6611
commit 67513c2357
4 changed files with 215 additions and 126 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.16.1 -->
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.6"/>
<!-- interface-requires mugshot_window 1.0 -->
<requires lib="mugshot_window" version="1.0"/>
<!-- interface-local-resource-path ../media -->
<object class="GtkBox" id="box8">
<property name="visible">True</property>

View File

@ -192,7 +192,7 @@ class MugshotWindow(Window):
self.fax_entry = builder.get_object('fax')
# Users without sudo rights cannot change their name.
self.set_name_editable(SudoDialog.check_sudo())
self.set_name_editable(SudoDialog.check_dependencies(['chfn']))
# Stock photo browser
self.stock_browser = builder.get_object('stock_browser')
@ -311,13 +311,24 @@ class MugshotWindow(Window):
changes."""
logger.debug('Applying changes...')
if self.get_chfn_details_updated():
if not self.save_chfn_details():
success, response = self.save_chfn_details()
if not success:
# Password was incorrect, complain.
primary = _("Authentication Failed")
if response in [Gtk.ResponseType.NONE,
Gtk.ResponseType.CANCEL,
Gtk.ResponseType.DELETE_EVENT]:
msg_type = Gtk.MessageType.WARNING
primary = _("Authentication cancelled.")
elif response == Gtk.ResponseType.REJECT:
msg_type = Gtk.MessageType.WARNING
primary = _("Authentication failed.")
else:
msg_type = Gtk.MessageType.ERROR
primary = _("An error occurred when saving changes.")
secondary = _("User details were not updated.")
dialog = Gtk.MessageDialog(transient_for=self, flags=0,
message_type=
Gtk.MessageType.WARNING,
message_type=msg_type,
buttons=Gtk.ButtonsType.OK,
text=primary)
dialog.format_secondary_text(secondary)
@ -555,8 +566,7 @@ class MugshotWindow(Window):
"""Handle password prompts from the interactive chfn commands."""
# Force the C language for guaranteed english strings in the script.
logger.debug('Executing: %s' % command)
child = pexpect.spawn(command, env={"LANG": "C"})
child.timeout = 5
child = SudoDialog.env_spawn(command, 5)
child.write_to_stdout = True
try:
child.expect([".*ssword.*", pexpect.EOF])
@ -582,13 +592,13 @@ class MugshotWindow(Window):
sudo_dialog.format_secondary_text(_("This is a security measure to "
"prevent unwanted updates\n"
"to your personal information."))
sudo_dialog.run()
response = sudo_dialog.run()
sudo_dialog.hide()
password = sudo_dialog.get_password()
sudo_dialog.destroy()
if not password:
return False
return (False, response)
sudo = which('sudo')
chfn = which('chfn')
@ -606,7 +616,7 @@ class MugshotWindow(Window):
home_phone = 'none'
# Full name can only be modified by root. Try using sudo to modify.
if SudoDialog.check_sudo():
if SudoDialog.check_dependencies(['chfn']):
logger.debug('Updating Full Name...')
command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name, username)
if self.process_terminal_password(command, password):
@ -629,7 +639,7 @@ class MugshotWindow(Window):
else:
success = False
return success
return (success, response)
# = LibreOffice ========================================================= #
def get_libreoffice_details_updated(self):

View File

@ -5,7 +5,7 @@
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
@ -23,22 +23,70 @@ from locale import gettext as _
import pexpect
gtk_version = (Gtk.get_major_version(),
Gtk.get_minor_version(),
Gtk.get_micro_version())
def check_sudo():
"""Return True if user has permission to use sudo."""
child = pexpect.spawn('sudo -v', env={"LANG": "C"})
child.timeout = 1
# Check for failure message.
try:
child.expect(["Sorry", pexpect.EOF])
child.close()
def check_gtk_version(major_version, minor_version, micro=0):
"""Return true if running gtk >= requested version"""
return gtk_version >= (major_version, minor_version, micro)
# Check if the LANG variable needs to be set
use_env = False
def check_dependencies(commands=[]):
"""Check for the existence of required commands, and sudo access"""
# Check for sudo
if pexpect.which("sudo") is None:
return False
except:
# Check for required commands
for command in commands:
if pexpect.which(command) is None:
return False
# Check for LANG requirements
child = env_spawn('sudo -v', 1)
if child.expect([".*ssword.*", "Sorry",
pexpect.EOF,
pexpect.TIMEOUT]) == 3:
global use_env
use_env = True
child.close()
# Check for sudo rights
child = env_spawn('sudo -v', 1)
try:
index = child.expect([".*ssword.*", "Sorry",
pexpect.EOF, pexpect.TIMEOUT])
child.close()
return True
if index == 0 or index == 2:
# User in sudoers, or already admin
return True
elif index == 1 or index == 3:
# User not in sudoers
return False
except:
# Something else went wrong.
child.close()
return False
class SudoDialog(Gtk.MessageDialog):
def env_spawn(command, timeout):
"""Use pexpect.spawn, adapt for timeout and env requirements."""
if use_env:
child = pexpect.spawn(command, env={"LANG": "C"})
else:
child = pexpect.spawn(command)
child.timeout = timeout
return child
class SudoDialog(Gtk.Dialog):
'''
Creates a new SudoDialog. This is a replacement for using gksudo which
provides additional flexibility when performing sudo commands.
@ -58,33 +106,93 @@ class SudoDialog(Gtk.MessageDialog):
- REJECT: Password invalid.
- ACCEPT: Password valid.
'''
def __init__(self, parent=None, icon=None, message=None, name=None,
retries=-1):
def __init__(self, title=None, parent=None, icon=None, message=None,
name=None, retries=-1):
"""Initialize the SudoDialog."""
# default dialog parameters
message_type = Gtk.MessageType.QUESTION
buttons = Gtk.ButtonsType.NONE
# initialize the dialog
super(SudoDialog, self).__init__(transient_for=parent,
super(SudoDialog, self).__init__(title=title,
transient_for=parent,
modal=True,
destroy_with_parent=True,
message_type=message_type,
buttons=buttons,
text='')
self.set_dialog_icon(icon)
destroy_with_parent=True)
#
self.connect("show", self.on_show)
if title is None:
title = _("Password Required")
self.set_title(title)
# add buttons
button_box = self.get_children()[0].get_children()[1]
self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
self.set_border_width(5)
# Content Area
content_area = self.get_content_area()
grid = Gtk.Grid.new()
grid.set_row_spacing(6)
grid.set_column_spacing(12)
grid.set_margin_left(5)
grid.set_margin_right(5)
content_area.add(grid)
# Icon
self.dialog_icon = Gtk.Image.new_from_icon_name("dialog-password",
Gtk.IconSize.DIALOG)
grid.attach(self.dialog_icon, 0, 0, 1, 2)
# Text
self.primary_text = Gtk.Label.new("")
self.primary_text.set_use_markup(True)
self.primary_text.set_halign(Gtk.Align.START)
self.secondary_text = Gtk.Label.new("")
self.secondary_text.set_use_markup(True)
self.secondary_text.set_halign(Gtk.Align.START)
self.secondary_text.set_margin_top(6)
grid.attach(self.primary_text, 1, 0, 1, 1)
grid.attach(self.secondary_text, 1, 1, 1, 1)
# Infobar
self.infobar = Gtk.InfoBar.new()
self.infobar.set_margin_top(12)
self.infobar.set_message_type(Gtk.MessageType.WARNING)
content_area = self.infobar.get_content_area()
infobar_icon = Gtk.Image.new_from_icon_name("dialog-warning",
Gtk.IconSize.BUTTON)
label = Gtk.Label.new(_("Incorrect password... try again."))
content_area.add(infobar_icon)
content_area.add(label)
grid.attach(self.infobar, 0, 2, 2, 1)
content_area.show_all()
self.infobar.set_no_show_all(True)
# Password
label = Gtk.Label.new("")
label.set_use_markup(True)
label.set_markup("<b>%s</b>" % _("Password:"))
label.set_halign(Gtk.Align.START)
label.set_margin_top(12)
self.password_entry = Gtk.Entry()
self.password_entry.set_visibility(False)
self.password_entry.set_activates_default(True)
self.password_entry.set_margin_top(12)
grid.attach(label, 0, 3, 1, 1)
grid.attach(self.password_entry, 1, 3, 1, 1)
# Buttons
button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
button_box = button.get_parent()
button_box.set_margin_top(24)
ok_button = Gtk.Button.new_with_label(_("OK"))
ok_button.connect("clicked", self.on_ok_clicked)
ok_button.set_receives_default(True)
ok_button.set_can_default(True)
ok_button.set_sensitive(False)
self.set_default(ok_button)
button_box.pack_start(ok_button, False, False, 0)
if check_gtk_version(3, 12):
button_box.pack_start(ok_button, True, True, 0)
else:
button_box.pack_start(ok_button, False, False, 0)
self.password_entry.connect("changed", self.on_password_changed,
ok_button)
self.set_dialog_icon(icon)
# add primary and secondary text
if message:
@ -98,47 +206,11 @@ class SudoDialog(Gtk.MessageDialog):
self.format_primary_text(primary_text)
self.format_secondary_text(secondary_text)
# Pack the content area with password-related widgets.
content_area = self.get_content_area()
# Use an alignment to move align the password widgets with the text.
self.password_alignment = Gtk.Alignment()
# Make an educated guess about how for to align.
left_align = Gtk.icon_size_lookup(Gtk.IconSize.DIALOG)[1] + 16
self.password_alignment.set_padding(12, 12, left_align, 0)
# Outer password box for incorrect password label and inner widgets.
password_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
spacing=12)
password_outer.set_orientation(Gtk.Orientation.VERTICAL)
# Password error label, only displayed when unsuccessful.
self.password_info = Gtk.Label(label="")
self.password_info.set_markup("<b>%s</b>" %
_("Incorrect password... try again."))
# Inner password box for Password: label and password entry.
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
spacing=12)
password_label = Gtk.Label(label=_("Password:"))
self.password_entry = Gtk.Entry()
self.password_entry.set_visibility(False)
self.password_entry.set_activates_default(True)
self.password_entry.connect("changed", self.on_password_changed,
ok_button)
# Pack all the widgets.
password_box.pack_start(password_label, False, False, 0)
password_box.pack_start(self.password_entry, True, True, 0)
password_outer.pack_start(self.password_info, True, True, 0)
password_outer.pack_start(password_box, True, True, 0)
self.password_alignment.add(password_outer)
content_area.pack_start(self.password_alignment, True, True, 0)
content_area.show_all()
self.password_info.set_visible(False)
self.attempted_logins = 0
self.max_attempted_logins = retries
self.show_all()
def on_password_changed(self, widget, button):
"""Set the apply button sensitivity based on password input."""
button.set_sensitive(len(widget.get_text()) > 0)
@ -146,11 +218,14 @@ class SudoDialog(Gtk.MessageDialog):
def format_primary_text(self, message_format):
'''
Format the primary text widget.
API extension to match with format_secondary_text.
'''
label = self.get_message_area().get_children()[0]
label.set_text(message_format)
self.primary_text.set_markup("<big><b>%s</b></big>" % message_format)
def format_secondary_text(self, message_format):
'''
Format the secondary text widget.
'''
self.secondary_text.set_markup(message_format)
def set_dialog_icon(self, icon=None):
'''
@ -166,24 +241,21 @@ class SudoDialog(Gtk.MessageDialog):
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon,
icon_size,
icon_size)
image = Gtk.Image.new_from_pixbuf(pixbuf)
self.dialog_icon.set_from_pixbuf(pixbuf)
self.set_icon_from_file(icon)
else:
# icon is an named icon, so load it directly to an image
image = Gtk.Image.new_from_icon_name(icon, icon_size)
self.dialog_icon.set_from_icon_name(icon, icon_size)
self.set_icon_name(icon)
else:
# fallback on password icon
image = Gtk.Image.new_from_icon_name('dialog-password', icon_size)
self.dialog_icon.set_from_icon_name('dialog-password', icon_size)
self.set_icon_name('dialog-password')
# align, show, and set the image.
image.set_alignment(Gtk.Align.CENTER, Gtk.Align.FILL)
image.show()
self.set_image(image)
def on_show(self, widget):
'''When the dialog is displayed, clear the password.'''
self.set_password('')
self.password_valid = False
def on_ok_clicked(self, widget):
'''
@ -193,26 +265,22 @@ class SudoDialog(Gtk.MessageDialog):
If unsuccessful, try again until reaching maximum attempted logins,
then emit the response signal with REJECT.
'''
top, bottom, left, right = self.password_alignment.get_padding()
# Password cannot be validated without sudo
if (not check_sudo()) or self.attempt_login():
if self.attempt_login():
self.password_valid = True
# Adjust the dialog for attactiveness.
self.password_alignment.set_padding(12, bottom, left, right)
self.password_info.hide()
self.emit("response", Gtk.ResponseType.ACCEPT)
else:
self.password_valid = False
# Adjust the dialog for attactiveness.
self.password_alignment.set_padding(0, bottom, left, right)
self.password_info.show()
self.set_password('')
self.infobar.show()
self.password_entry.grab_focus()
if self.attempted_logins == self.max_attempted_logins:
self.attempted_logins = 0
self.emit("response", Gtk.ResponseType.REJECT)
def get_password(self):
'''Return the currently entered password, or None if blank.'''
if not self.password_valid:
return None
password = self.password_entry.get_text()
if password == '':
return None
@ -232,12 +300,11 @@ class SudoDialog(Gtk.MessageDialog):
Return True if successful.
'''
# Set the pexpect variables and spawn the process.
child = pexpect.spawn('sudo /bin/true', env={"LANG": "C"})
child.timeout = 1
child = env_spawn('sudo /bin/true', 1)
try:
# Check for password prompt or program exit.
child.expect([".*ssword.*", pexpect.EOF])
child.sendline(self.get_password())
child.sendline(self.password_entry.get_text())
child.expect(pexpect.EOF)
except pexpect.TIMEOUT:
# If we timeout, that means the password was unsuccessful.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-08-24 10:25-0400\n"
"POT-Creation-Date: 2014-08-24 11:44-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -61,7 +61,7 @@ msgstr ""
msgid "Browse…"
msgstr ""
#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:579
#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:589
msgid "Mugshot"
msgstr ""
@ -130,70 +130,82 @@ msgstr ""
msgid "Retry"
msgstr ""
#. Password was incorrect, complain.
#: ../mugshot/MugshotWindow.py:316
msgid "Authentication Failed"
#: ../mugshot/MugshotWindow.py:321
msgid "Authentication cancelled."
msgstr ""
#: ../mugshot/MugshotWindow.py:317
#: ../mugshot/MugshotWindow.py:324
msgid "Authentication failed."
msgstr ""
#: ../mugshot/MugshotWindow.py:327
msgid "An error occurred when saving changes."
msgstr ""
#: ../mugshot/MugshotWindow.py:329
msgid "User details were not updated."
msgstr ""
#: ../mugshot/MugshotWindow.py:488
#: ../mugshot/MugshotWindow.py:499
msgid "Update Pidgin buddy icon?"
msgstr ""
#: ../mugshot/MugshotWindow.py:489
#: ../mugshot/MugshotWindow.py:500
msgid "Would you also like to update your Pidgin buddy icon?"
msgstr ""
#: ../mugshot/MugshotWindow.py:580
#: ../mugshot/MugshotWindow.py:590
msgid "Enter your password to change user details."
msgstr ""
#: ../mugshot/MugshotWindow.py:582
#: ../mugshot/MugshotWindow.py:592
msgid ""
"This is a security measure to prevent unwanted updates\n"
"to your personal information."
msgstr ""
#: ../mugshot/MugshotWindow.py:787
#: ../mugshot/MugshotWindow.py:797
msgid "Update LibreOffice user details?"
msgstr ""
#: ../mugshot/MugshotWindow.py:788
#: ../mugshot/MugshotWindow.py:798
msgid "Would you also like to update your user details in LibreOffice?"
msgstr ""
#: ../mugshot_lib/SudoDialog.py:80
#: ../mugshot_lib/SudoDialog.py:120
msgid "Password Required"
msgstr ""
#: ../mugshot_lib/SudoDialog.py:157
msgid "Incorrect password... try again."
msgstr ""
#: ../mugshot_lib/SudoDialog.py:167
msgid "Password:"
msgstr ""
#. Buttons
#: ../mugshot_lib/SudoDialog.py:178
msgid "Cancel"
msgstr ""
#: ../mugshot_lib/SudoDialog.py:81
#: ../mugshot_lib/SudoDialog.py:181
msgid "OK"
msgstr ""
#: ../mugshot_lib/SudoDialog.py:94
#: ../mugshot_lib/SudoDialog.py:202
msgid ""
"Enter your password to\n"
"perform administrative tasks."
msgstr ""
#: ../mugshot_lib/SudoDialog.py:96
#: ../mugshot_lib/SudoDialog.py:204
#, python-format
msgid ""
"The application '%s' lets you\n"
"modify essential parts of your system."
msgstr ""
#: ../mugshot_lib/SudoDialog.py:117
msgid "Incorrect password... try again."
msgstr ""
#: ../mugshot_lib/SudoDialog.py:122
msgid "Password:"
msgstr ""
#: ../data/appdata/mugshot.appdata.xml.in.h:1
msgid "Lightweight user configuration"
msgstr ""