~ubuntu-branches/debian/sid/mugshot/sid

« back to all changes in this revision

Viewing changes to mugshot_lib/SudoDialog.py

  • Committer: Package Import Robot
  • Author(s): Jackson Doak
  • Date: 2014-09-02 17:10:26 UTC
  • mfrom: (1.1.5)
  • Revision ID: package-import@ubuntu.com-20140902171026-i73qjo7lzi0hhp15
Tags: 0.2.5-1
* New upstream release.
  - Fix: mugshot fails to start for some users (LP: #1353530)

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
#
6
6
#   This program is free software: you can redistribute it and/or modify it
7
7
#   under the terms of the GNU General Public License as published by
8
 
#   the Free Software Foundation, either version 3 of the License, or 
 
8
#   the Free Software Foundation, either version 3 of the License, or
9
9
#   (at your option) any later version.
10
10
#
11
11
#   This program is distributed in the hope that it will be useful, but
23
23
 
24
24
import pexpect
25
25
 
26
 
 
27
 
def check_sudo():
28
 
    """Return True if user has permission to use sudo."""
29
 
    child = pexpect.spawn('sudo -v', env={"LANG": "C"})
30
 
    child.timeout = 1
31
 
    # Check for failure message.
 
26
gtk_version = (Gtk.get_major_version(),
 
27
               Gtk.get_minor_version(),
 
28
               Gtk.get_micro_version())
 
29
 
 
30
 
 
31
def check_gtk_version(major_version, minor_version, micro=0):
 
32
    """Return true if running gtk >= requested version"""
 
33
    return gtk_version >= (major_version, minor_version, micro)
 
34
 
 
35
# Check if the LANG variable needs to be set
 
36
use_env = False
 
37
 
 
38
 
 
39
def check_dependencies(commands=[]):
 
40
    """Check for the existence of required commands, and sudo access"""
 
41
    # Check for sudo
 
42
    if pexpect.which("sudo") is None:
 
43
        return False
 
44
 
 
45
    # Check for required commands
 
46
    for command in commands:
 
47
        if pexpect.which(command) is None:
 
48
            return False
 
49
 
 
50
    # Check for LANG requirements
 
51
    child = env_spawn('sudo -v', 1)
 
52
    if child.expect([".*ssword.*", "Sorry",
 
53
                     pexpect.EOF,
 
54
                     pexpect.TIMEOUT]) == 3:
 
55
        global use_env
 
56
        use_env = True
 
57
    child.close()
 
58
 
 
59
    # Check for sudo rights
 
60
    child = env_spawn('sudo -v', 1)
32
61
    try:
33
 
        child.expect(["Sorry", pexpect.EOF])
 
62
        index = child.expect([".*ssword.*", "Sorry",
 
63
                              pexpect.EOF, pexpect.TIMEOUT])
34
64
        child.close()
35
 
        return False
 
65
        if index == 0 or index == 2:
 
66
            # User in sudoers, or already admin
 
67
            return True
 
68
        elif index == 1 or index == 3:
 
69
            # User not in sudoers
 
70
            return False
 
71
 
36
72
    except:
 
73
        # Something else went wrong.
37
74
        child.close()
38
 
        return True
39
 
 
40
 
 
41
 
class SudoDialog(Gtk.MessageDialog):
 
75
 
 
76
    return False
 
77
 
 
78
 
 
79
def env_spawn(command, timeout):
 
80
    """Use pexpect.spawn, adapt for timeout and env requirements."""
 
81
    env = os.environ
 
82
    env["LANG"] = "C"
 
83
    if use_env:
 
84
        child = pexpect.spawn(command, env)
 
85
    else:
 
86
        child = pexpect.spawn(command)
 
87
    child.timeout = timeout
 
88
    return child
 
89
 
 
90
 
 
91
class SudoDialog(Gtk.Dialog):
42
92
    '''
43
93
    Creates a new SudoDialog. This is a replacement for using gksudo which
44
94
    provides additional flexibility when performing sudo commands.
58
108
    - REJECT:   Password invalid.
59
109
    - ACCEPT:   Password valid.
60
110
    '''
61
 
    def __init__(self, parent=None, icon=None, message=None, name=None,
62
 
                 retries=-1):
 
111
    def __init__(self, title=None, parent=None, icon=None, message=None,
 
112
                 name=None, retries=-1):
63
113
        """Initialize the SudoDialog."""
64
 
        # default dialog parameters
65
 
        message_type = Gtk.MessageType.QUESTION
66
 
        buttons = Gtk.ButtonsType.NONE
67
 
 
68
114
        # initialize the dialog
69
 
        super(SudoDialog, self).__init__(transient_for=parent,
 
115
        super(SudoDialog, self).__init__(title=title,
 
116
                                         transient_for=parent,
70
117
                                         modal=True,
71
 
                                         destroy_with_parent=True,
72
 
                                         message_type=message_type,
73
 
                                         buttons=buttons,
74
 
                                         text='')
75
 
        self.set_dialog_icon(icon)
 
118
                                         destroy_with_parent=True)
 
119
        #
76
120
        self.connect("show", self.on_show)
77
 
 
78
 
        # add buttons
79
 
        button_box = self.get_children()[0].get_children()[1]
80
 
        self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
 
121
        if title is None:
 
122
            title = _("Password Required")
 
123
        self.set_title(title)
 
124
 
 
125
        self.set_border_width(5)
 
126
 
 
127
        # Content Area
 
128
        content_area = self.get_content_area()
 
129
        grid = Gtk.Grid.new()
 
130
        grid.set_row_spacing(6)
 
131
        grid.set_column_spacing(12)
 
132
        grid.set_margin_left(5)
 
133
        grid.set_margin_right(5)
 
134
        content_area.add(grid)
 
135
 
 
136
        # Icon
 
137
        self.dialog_icon = Gtk.Image.new_from_icon_name("dialog-password",
 
138
                                                        Gtk.IconSize.DIALOG)
 
139
        grid.attach(self.dialog_icon, 0, 0, 1, 2)
 
140
 
 
141
        # Text
 
142
        self.primary_text = Gtk.Label.new("")
 
143
        self.primary_text.set_use_markup(True)
 
144
        self.primary_text.set_halign(Gtk.Align.START)
 
145
        self.secondary_text = Gtk.Label.new("")
 
146
        self.secondary_text.set_use_markup(True)
 
147
        self.secondary_text.set_halign(Gtk.Align.START)
 
148
        self.secondary_text.set_margin_top(6)
 
149
        grid.attach(self.primary_text, 1, 0, 1, 1)
 
150
        grid.attach(self.secondary_text, 1, 1, 1, 1)
 
151
 
 
152
        # Infobar
 
153
        self.infobar = Gtk.InfoBar.new()
 
154
        self.infobar.set_margin_top(12)
 
155
        self.infobar.set_message_type(Gtk.MessageType.WARNING)
 
156
        content_area = self.infobar.get_content_area()
 
157
        infobar_icon = Gtk.Image.new_from_icon_name("dialog-warning",
 
158
                                                    Gtk.IconSize.BUTTON)
 
159
        label = Gtk.Label.new(_("Incorrect password... try again."))
 
160
        content_area.add(infobar_icon)
 
161
        content_area.add(label)
 
162
        grid.attach(self.infobar, 0, 2, 2, 1)
 
163
        content_area.show_all()
 
164
        self.infobar.set_no_show_all(True)
 
165
 
 
166
        # Password
 
167
        label = Gtk.Label.new("")
 
168
        label.set_use_markup(True)
 
169
        label.set_markup("<b>%s</b>" % _("Password:"))
 
170
        label.set_halign(Gtk.Align.START)
 
171
        label.set_margin_top(12)
 
172
        self.password_entry = Gtk.Entry()
 
173
        self.password_entry.set_visibility(False)
 
174
        self.password_entry.set_activates_default(True)
 
175
        self.password_entry.set_margin_top(12)
 
176
        grid.attach(label, 0, 3, 1, 1)
 
177
        grid.attach(self.password_entry, 1, 3, 1, 1)
 
178
 
 
179
        # Buttons
 
180
        button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
 
181
        button_box = button.get_parent()
 
182
        button_box.set_margin_top(24)
81
183
        ok_button = Gtk.Button.new_with_label(_("OK"))
82
184
        ok_button.connect("clicked", self.on_ok_clicked)
83
185
        ok_button.set_receives_default(True)
84
186
        ok_button.set_can_default(True)
85
187
        ok_button.set_sensitive(False)
86
188
        self.set_default(ok_button)
87
 
        button_box.pack_start(ok_button, False, False, 0)
 
189
        if check_gtk_version(3, 12):
 
190
            button_box.pack_start(ok_button, True, True, 0)
 
191
        else:
 
192
            button_box.pack_start(ok_button, False, False, 0)
 
193
 
 
194
        self.password_entry.connect("changed", self.on_password_changed,
 
195
                                    ok_button)
 
196
 
 
197
        self.set_dialog_icon(icon)
88
198
 
89
199
        # add primary and secondary text
90
200
        if message:
98
208
        self.format_primary_text(primary_text)
99
209
        self.format_secondary_text(secondary_text)
100
210
 
101
 
        # Pack the content area with password-related widgets.
102
 
        content_area = self.get_content_area()
103
 
 
104
 
        # Use an alignment to move align the password widgets with the text.
105
 
        self.password_alignment = Gtk.Alignment()
106
 
        # Make an educated guess about how for to align.
107
 
        left_align = Gtk.icon_size_lookup(Gtk.IconSize.DIALOG)[1] + 16
108
 
        self.password_alignment.set_padding(12, 12, left_align, 0)
109
 
 
110
 
        # Outer password box for incorrect password label and inner widgets.
111
 
        password_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
112
 
                                 spacing=12)
113
 
        password_outer.set_orientation(Gtk.Orientation.VERTICAL)
114
 
        # Password error label, only displayed when unsuccessful.
115
 
        self.password_info = Gtk.Label(label="")
116
 
        self.password_info.set_markup("<b>%s</b>" %
117
 
                                      _("Incorrect password... try again."))
118
 
 
119
 
        # Inner password box for Password: label and password entry.
120
 
        password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
121
 
                               spacing=12)
122
 
        password_label = Gtk.Label(label=_("Password:"))
123
 
        self.password_entry = Gtk.Entry()
124
 
        self.password_entry.set_visibility(False)
125
 
        self.password_entry.set_activates_default(True)
126
 
        self.password_entry.connect("changed", self.on_password_changed,
127
 
                                    ok_button)
128
 
 
129
 
        # Pack all the widgets.
130
 
        password_box.pack_start(password_label, False, False, 0)
131
 
        password_box.pack_start(self.password_entry, True, True, 0)
132
 
        password_outer.pack_start(self.password_info, True, True, 0)
133
 
        password_outer.pack_start(password_box, True, True, 0)
134
 
        self.password_alignment.add(password_outer)
135
 
        content_area.pack_start(self.password_alignment, True, True, 0)
136
 
        content_area.show_all()
137
 
        self.password_info.set_visible(False)
138
 
 
139
211
        self.attempted_logins = 0
140
212
        self.max_attempted_logins = retries
141
213
 
 
214
        self.show_all()
 
215
 
142
216
    def on_password_changed(self, widget, button):
143
217
        """Set the apply button sensitivity based on password input."""
144
218
        button.set_sensitive(len(widget.get_text()) > 0)
146
220
    def format_primary_text(self, message_format):
147
221
        '''
148
222
        Format the primary text widget.
 
223
        '''
 
224
        self.primary_text.set_markup("<big><b>%s</b></big>" % message_format)
149
225
 
150
 
        API extension to match with format_secondary_text.
151
 
        '''
152
 
        label = self.get_message_area().get_children()[0]
153
 
        label.set_text(message_format)
 
226
    def format_secondary_text(self, message_format):
 
227
        '''
 
228
        Format the secondary text widget.
 
229
        '''
 
230
        self.secondary_text.set_markup(message_format)
154
231
 
155
232
    def set_dialog_icon(self, icon=None):
156
233
        '''
166
243
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon,
167
244
                                                                icon_size,
168
245
                                                                icon_size)
169
 
                image = Gtk.Image.new_from_pixbuf(pixbuf)
 
246
                self.dialog_icon.set_from_pixbuf(pixbuf)
170
247
                self.set_icon_from_file(icon)
171
248
            else:
172
249
                # icon is an named icon, so load it directly to an image
173
 
                image = Gtk.Image.new_from_icon_name(icon, icon_size)
 
250
                self.dialog_icon.set_from_icon_name(icon, icon_size)
174
251
                self.set_icon_name(icon)
175
252
        else:
176
253
            # fallback on password icon
177
 
            image = Gtk.Image.new_from_icon_name('dialog-password', icon_size)
 
254
            self.dialog_icon.set_from_icon_name('dialog-password', icon_size)
178
255
            self.set_icon_name('dialog-password')
179
 
        # align, show, and set the image.
180
 
        image.set_alignment(Gtk.Align.CENTER, Gtk.Align.FILL)
181
 
        image.show()
182
 
        self.set_image(image)
183
256
 
184
257
    def on_show(self, widget):
185
258
        '''When the dialog is displayed, clear the password.'''
186
259
        self.set_password('')
 
260
        self.password_valid = False
187
261
 
188
262
    def on_ok_clicked(self, widget):
189
263
        '''
193
267
        If unsuccessful, try again until reaching maximum attempted logins,
194
268
        then emit the response signal with REJECT.
195
269
        '''
196
 
        top, bottom, left, right = self.password_alignment.get_padding()
197
 
        # Password cannot be validated without sudo
198
 
        if (not check_sudo()) or self.attempt_login():
 
270
        if self.attempt_login():
199
271
            self.password_valid = True
200
 
            # Adjust the dialog for attactiveness.
201
 
            self.password_alignment.set_padding(12, bottom, left, right)
202
 
            self.password_info.hide()
203
272
            self.emit("response", Gtk.ResponseType.ACCEPT)
204
273
        else:
205
274
            self.password_valid = False
206
275
            # Adjust the dialog for attactiveness.
207
 
            self.password_alignment.set_padding(0, bottom, left, right)
208
 
            self.password_info.show()
209
 
            self.set_password('')
 
276
            self.infobar.show()
 
277
            self.password_entry.grab_focus()
210
278
            if self.attempted_logins == self.max_attempted_logins:
211
279
                self.attempted_logins = 0
212
280
                self.emit("response", Gtk.ResponseType.REJECT)
213
281
 
214
282
    def get_password(self):
215
283
        '''Return the currently entered password, or None if blank.'''
 
284
        if not self.password_valid:
 
285
            return None
216
286
        password = self.password_entry.get_text()
217
287
        if password == '':
218
288
            return None
232
302
        Return True if successful.
233
303
        '''
234
304
        # Set the pexpect variables and spawn the process.
235
 
        child = pexpect.spawn('sudo /bin/true', env={"LANG": "C"})
236
 
        child.timeout = 1
 
305
        child = env_spawn('sudo /bin/true', 1)
237
306
        try:
238
307
            # Check for password prompt or program exit.
239
308
            child.expect([".*ssword.*", pexpect.EOF])
240
 
            child.sendline(self.get_password())
 
309
            child.sendline(self.password_entry.get_text())
241
310
            child.expect(pexpect.EOF)
242
311
        except pexpect.TIMEOUT:
243
312
            # If we timeout, that means the password was unsuccessful.