We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[qt-journalist-updater] Update UI properly if Tails Administrator password is not provided
# Feature request

## Description

If the user does not provide the `Tails Administrator` password, the tool should show that in the UI and does not close the application.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `journalist_gui/journalist_gui/SecureDropUpdater.py`
Content:
```
1 #!/usr/bin/python
2 from PyQt5 import QtGui, QtWidgets
3 from PyQt5.QtCore import QThread, pyqtSignal
4 import sys
5 import subprocess
6 import os
7 import pexpect
8 
9 from journalist_gui import updaterUI, strings, resources_rc  # noqa
10 
11 
12 LOCK_LOCATION = "/home/amnesia/Persistent/securedrop/securedrop_update.lock"  # noqa
13 
14 
15 class SetupThread(QThread):
16     signal = pyqtSignal('PyQt_PyObject')
17 
18     def __init__(self):
19         QThread.__init__(self)
20         self.output = ""
21         self.update_success = False
22         self.failure_reason = ""
23 
24     def run(self):
25         sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'
26         update_command = [sdadmin_path, 'setup']
27 
28         # Create lock so we resume failed updates on reboot.
29         # Don't create the lock if it already exists.
30         if not os.path.exists(LOCK_LOCATION):
31             open(LOCK_LOCATION, 'a').close()
32 
33         try:
34             self.output = subprocess.check_output(
35                 update_command,
36                 stderr=subprocess.STDOUT).decode('utf-8')
37             if 'Failed to install' in self.output:
38                 self.update_success = False
39                 self.failure_reason = strings.update_failed_generic_reason
40             else:
41                 self.update_success = True
42         except subprocess.CalledProcessError as e:
43             self.output += e.output.decode('utf-8')
44             self.update_success = False
45             self.failure_reason = strings.update_failed_generic_reason
46         result = {'status': self.update_success,
47                   'output': self.output,
48                   'failure_reason': self.failure_reason}
49         self.signal.emit(result)
50 
51 
52 # This thread will handle the ./securedrop-admin update command
53 class UpdateThread(QThread):
54     signal = pyqtSignal('PyQt_PyObject')
55 
56     def __init__(self):
57         QThread.__init__(self)
58         self.output = ""
59         self.update_success = False
60         self.failure_reason = ""
61 
62     def run(self):
63         sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'
64         update_command = [sdadmin_path, 'update']
65         try:
66             self.output = subprocess.check_output(
67                 update_command,
68                 stderr=subprocess.STDOUT).decode('utf-8')
69             if 'Signature verification failed' in self.output:
70                 self.update_success = False
71                 self.failure_reason = strings.update_failed_sig_failure
72             elif "Signature verification successful" in self.output:
73                 self.update_success = True
74             else:
75                 self.failure_reason = strings.update_failed_generic_reason
76         except subprocess.CalledProcessError as e:
77             self.output += e.output.decode('utf-8')
78             self.update_success = False
79             self.failure_reason = strings.update_failed_generic_reason
80         result = {'status': self.update_success,
81                   'output': self.output,
82                   'failure_reason': self.failure_reason}
83         self.signal.emit(result)
84 
85 
86 # This thread will handle the ./securedrop-admin tailsconfig command
87 class TailsconfigThread(QThread):
88     signal = pyqtSignal('PyQt_PyObject')
89 
90     def __init__(self):
91         QThread.__init__(self)
92         self.output = ""
93         self.update_success = False
94         self.failure_reason = ""
95         self.sudo_password = ""
96 
97     def run(self):
98         tailsconfig_command = ("/home/amnesia/Persistent/"
99                                "securedrop/securedrop-admin "
100                                "tailsconfig")
101         try:
102             child = pexpect.spawn(tailsconfig_command)
103             child.expect('SUDO password:')
104             self.output += child.before.decode('utf-8')
105             child.sendline(self.sudo_password)
106             child.expect(pexpect.EOF)
107             self.output += child.before.decode('utf-8')
108 
109             # For Tailsconfig to be considered a success, we expect no
110             # failures in the Ansible output.
111             if 'failed=0' not in self.output:
112                 self.update_success = False
113                 self.failure_reason = strings.tailsconfig_failed_generic_reason  # noqa
114             else:
115                 self.update_success = True
116         except pexpect.exceptions.TIMEOUT:
117             self.update_success = False
118             self.failure_reason = strings.tailsconfig_failed_sudo_password
119 
120         except subprocess.CalledProcessError:
121             self.update_success = False
122             self.failure_reason = strings.tailsconfig_failed_generic_reason
123         result = {'status': self.update_success,
124                   'output': self.output,
125                   'failure_reason': self.failure_reason}
126         self.signal.emit(result)
127 
128 
129 class UpdaterApp(QtWidgets.QMainWindow, updaterUI.Ui_MainWindow):
130 
131     def __init__(self, parent=None):
132         super(UpdaterApp, self).__init__(parent)
133         self.setupUi(self)
134         self.output = strings.initial_text_box
135         self.plainTextEdit.setPlainText(self.output)
136         self.update_success = False
137 
138         pixmap = QtGui.QPixmap(":/images/static/banner.png")
139         self.label_2.setPixmap(pixmap)
140         self.label_2.setScaledContents(True)
141 
142         self.progressBar.setProperty("value", 0)
143         self.setWindowTitle(strings.window_title)
144         self.setWindowIcon(QtGui.QIcon(':/images/static/securedrop_icon.png'))
145         self.label.setText(strings.update_in_progress)
146 
147         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab),
148                                   strings.main_tab)
149         self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2),
150                                   strings.output_tab)
151 
152         # Connect buttons to their functions.
153         self.pushButton.setText(strings.install_later_button)
154         self.pushButton.setStyleSheet("""background-color: lightgrey;
155                                       min-height: 2em;
156                                       border-radius: 10px""")
157         self.pushButton.clicked.connect(self.close)
158         self.pushButton_2.setText(strings.install_update_button)
159         self.pushButton_2.setStyleSheet("""background-color: #E6FFEB;
160                                         min-height: 2em;
161                                         border-radius: 10px;""")
162         self.pushButton_2.clicked.connect(self.update_securedrop)
163         self.update_thread = UpdateThread()
164         self.update_thread.signal.connect(self.update_status)
165         self.tails_thread = TailsconfigThread()
166         self.tails_thread.signal.connect(self.tails_status)
167         self.setup_thread = SetupThread()
168         self.setup_thread.signal.connect(self.setup_status)
169 
170     # At the end of this function, we will try to do tailsconfig.
171     # A new slot will handle tailsconfig output
172     def setup_status(self, result):
173         "This is the slot for setup thread"
174         self.output += result['output']
175         self.update_success = result['status']
176         self.failure_reason = result['failure_reason']
177         self.progressBar.setProperty("value", 60)
178         self.plainTextEdit.setPlainText(self.output)
179         self.plainTextEdit.setReadOnly = True
180         if not self.update_success:  # Failed to do setup update
181             self.pushButton.setEnabled(True)
182             self.pushButton_2.setEnabled(True)
183             self.update_status_bar_and_output(self.failure_reason)
184             self.progressBar.setProperty("value", 0)
185             self.alert_failure(self.failure_reason)
186             return
187         self.progressBar.setProperty("value", 70)
188         self.call_tailsconfig()
189 
190     # This will update the output text after the git commands.
191     def update_status(self, result):
192         "This is the slot for update thread"
193         self.output += result['output']
194         self.update_success = result['status']
195         self.failure_reason = result['failure_reason']
196         self.progressBar.setProperty("value", 40)
197         self.plainTextEdit.setPlainText(self.output)
198         self.plainTextEdit.setReadOnly = True
199         self.progressBar.setProperty("value", 50)
200         self.update_status_bar_and_output(strings.doing_setup)
201         self.setup_thread.start()
202 
203     def update_status_bar_and_output(self, status_message):
204         """This method updates the status bar and the output window with the
205         status_message."""
206         self.statusbar.showMessage(status_message)
207         self.output += status_message + '\n'
208         self.plainTextEdit.setPlainText(self.output)
209 
210     def call_tailsconfig(self):
211         # Now let us work on tailsconfig part
212         if self.update_success:
213             # Get sudo password and add an enter key as tailsconfig command
214             # expects
215             sudo_password = self.get_sudo_password() + '\n'
216             self.tails_thread.sudo_password = sudo_password
217             self.update_status_bar_and_output(strings.updating_tails_env)
218             self.tails_thread.start()
219         else:
220             self.pushButton.setEnabled(True)
221             self.pushButton_2.setEnabled(True)
222             self.update_status_bar_and_output(self.failure_reason)
223             self.progressBar.setProperty("value", 0)
224             self.alert_failure(self.failure_reason)
225 
226     def tails_status(self, result):
227         "This is the slot for Tailsconfig thread"
228         self.output += result['output']
229         self.update_success = result['status']
230         self.failure_reason = result['failure_reason']
231         self.plainTextEdit.setPlainText(self.output)
232         self.progressBar.setProperty("value", 80)
233         if self.update_success:
234             # Remove lock
235             os.remove(LOCK_LOCATION)
236             self.update_status_bar_and_output(strings.finished)
237             self.progressBar.setProperty("value", 100)
238             self.alert_success()
239         else:
240             self.update_status_bar_and_output(self.failure_reason)
241             self.alert_failure(self.failure_reason)
242             # Now everything is done, enable the button.
243             self.pushButton.setEnabled(True)
244             self.pushButton_2.setEnabled(True)
245             self.progressBar.setProperty("value", 0)
246 
247     def update_securedrop(self):
248         self.pushButton_2.setEnabled(False)
249         self.pushButton.setEnabled(False)
250         self.progressBar.setProperty("value", 10)
251         self.update_status_bar_and_output(strings.fetching_update)
252         self.update_thread.start()
253 
254     def alert_success(self):
255         self.success_dialog = QtWidgets.QMessageBox()
256         self.success_dialog.setIcon(QtWidgets.QMessageBox.Information)
257         self.success_dialog.setText(strings.finished_dialog_message)
258         self.success_dialog.setWindowTitle(strings.finished_dialog_title)
259         self.success_dialog.show()
260 
261     def alert_failure(self, failure_reason):
262         self.error_dialog = QtWidgets.QMessageBox()
263         self.error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
264         self.error_dialog.setText(self.failure_reason)
265         self.error_dialog.setWindowTitle(strings.update_failed_dialog_title)
266         self.error_dialog.show()
267 
268     def get_sudo_password(self):
269         sudo_password, ok_is_pressed = QtWidgets.QInputDialog.getText(
270             self, "Tails Administrator password", strings.sudo_password_text,
271             QtWidgets.QLineEdit.Password, "")
272         if ok_is_pressed and sudo_password:
273             return sudo_password
274         else:
275             sys.exit(0)
276
```

Path: `journalist_gui/journalist_gui/strings.py`
Content:
```
1 window_title = 'SecureDrop Workstation Updater'
2 update_in_progress = ("SecureDrop workstation updates are available! "
3                       "It is recommended to install them now. \n\n"
4                       "If you don\'t want to install them now, "
5                       "you can install them the next time you reboot.\n\n"
6                       "You will need to have set a Tails Administration "
7                       "password in "
8                       "the Tails Greeter on boot to complete the update.\n\n"
9                       "When you start your workstation, this window will "
10                       "automatically appear if you have not "
11                       "completed any required updates.\n")
12 fetching_update = ('Fetching and verifying latest update...'
13                    ' (5 mins remaining)')
14 updating_tails_env = ('Configuring local Tails environment...'
15                       ' (1 min remaining)')
16 finished = 'Update successfully completed!'
17 finished_dialog_message = 'Updates completed successfully. '
18 finished_dialog_title = 'SecureDrop Workstation is up to date!'
19 update_failed_dialog_title = 'Error Updating SecureDrop Workstation'
20 update_failed_generic_reason = ("Update failed. "
21                                 "Please contact your SecureDrop "
22                                 "administrator.")
23 update_failed_sig_failure = ("WARNING: Signature verification failed. "
24                              "Contact your SecureDrop administrator "
25                              "or securedrop@freedom.press immediately.")
26 tailsconfig_failed_sudo_password = ('Administrator password incorrect. '
27                                     'Exiting upgrade - '
28                                     'click Update Now to try again.')
29 tailsconfig_failed_generic_reason = ("Tails workstation configuration failed. "
30                                      "Contact your administrator. "
31                                      "If you are an administrator, contact "
32                                      "securedrop@freedom.press.")
33 install_update_button = 'Update Now'
34 install_later_button = 'Update Later'
35 sudo_password_text = ("Enter the Tails Administrator password you "
36                       "entered in the Tails Greeter.\n If you did not "
37                       "set an Administrator password, click Cancel "
38                       "and reboot. ")
39 main_tab = 'Updates Available'
40 output_tab = 'Detailed Update Progress'
41 initial_text_box = ("When the update begins, this area will populate with "
42                     "output.\n")
43 doing_setup = "Checking dependencies are up to date... (2 mins remaining)"
44
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/journalist_gui/journalist_gui/SecureDropUpdater.py b/journalist_gui/journalist_gui/SecureDropUpdater.py
--- a/journalist_gui/journalist_gui/SecureDropUpdater.py
+++ b/journalist_gui/journalist_gui/SecureDropUpdater.py
@@ -1,7 +1,6 @@
 #!/usr/bin/python
 from PyQt5 import QtGui, QtWidgets
 from PyQt5.QtCore import QThread, pyqtSignal
-import sys
 import subprocess
 import os
 import pexpect
@@ -212,16 +211,17 @@
         if self.update_success:
             # Get sudo password and add an enter key as tailsconfig command
             # expects
-            sudo_password = self.get_sudo_password() + '\n'
-            self.tails_thread.sudo_password = sudo_password
+            sudo_password = self.get_sudo_password()
+            if not sudo_password:
+                self.update_success = False
+                self.failure_reason = strings.missing_sudo_password
+                self.on_failure()
+                return
+            self.tails_thread.sudo_password = sudo_password + '\n'
             self.update_status_bar_and_output(strings.updating_tails_env)
             self.tails_thread.start()
         else:
-            self.pushButton.setEnabled(True)
-            self.pushButton_2.setEnabled(True)
-            self.update_status_bar_and_output(self.failure_reason)
-            self.progressBar.setProperty("value", 0)
-            self.alert_failure(self.failure_reason)
+            self.on_failure()
 
     def tails_status(self, result):
         "This is the slot for Tailsconfig thread"
@@ -237,12 +237,15 @@
             self.progressBar.setProperty("value", 100)
             self.alert_success()
         else:
-            self.update_status_bar_and_output(self.failure_reason)
-            self.alert_failure(self.failure_reason)
-            # Now everything is done, enable the button.
-            self.pushButton.setEnabled(True)
-            self.pushButton_2.setEnabled(True)
-            self.progressBar.setProperty("value", 0)
+            self.on_failure()
+
+    def on_failure(self):
+        self.update_status_bar_and_output(self.failure_reason)
+        self.alert_failure(self.failure_reason)
+        # Now everything is done, enable the button.
+        self.pushButton.setEnabled(True)
+        self.pushButton_2.setEnabled(True)
+        self.progressBar.setProperty("value", 0)
 
     def update_securedrop(self):
         self.pushButton_2.setEnabled(False)
@@ -272,4 +275,4 @@
         if ok_is_pressed and sudo_password:
             return sudo_password
         else:
-            sys.exit(0)
+            return None
diff --git a/journalist_gui/journalist_gui/strings.py b/journalist_gui/journalist_gui/strings.py
--- a/journalist_gui/journalist_gui/strings.py
+++ b/journalist_gui/journalist_gui/strings.py
@@ -16,6 +16,7 @@
 finished = 'Update successfully completed!'
 finished_dialog_message = 'Updates completed successfully. '
 finished_dialog_title = 'SecureDrop Workstation is up to date!'
+missing_sudo_password = 'Missing Tails Administrator password'
 update_failed_dialog_title = 'Error Updating SecureDrop Workstation'
 update_failed_generic_reason = ("Update failed. "
                                 "Please contact your SecureDrop "

{"golden_diff": "diff --git a/journalist_gui/journalist_gui/SecureDropUpdater.py b/journalist_gui/journalist_gui/SecureDropUpdater.py\n--- a/journalist_gui/journalist_gui/SecureDropUpdater.py\n+++ b/journalist_gui/journalist_gui/SecureDropUpdater.py\n@@ -1,7 +1,6 @@\n #!/usr/bin/python\n from PyQt5 import QtGui, QtWidgets\n from PyQt5.QtCore import QThread, pyqtSignal\n-import sys\n import subprocess\n import os\n import pexpect\n@@ -212,16 +211,17 @@\n         if self.update_success:\n             # Get sudo password and add an enter key as tailsconfig command\n             # expects\n-            sudo_password = self.get_sudo_password() + '\\n'\n-            self.tails_thread.sudo_password = sudo_password\n+            sudo_password = self.get_sudo_password()\n+            if not sudo_password:\n+                self.update_success = False\n+                self.failure_reason = strings.missing_sudo_password\n+                self.on_failure()\n+                return\n+            self.tails_thread.sudo_password = sudo_password + '\\n'\n             self.update_status_bar_and_output(strings.updating_tails_env)\n             self.tails_thread.start()\n         else:\n-            self.pushButton.setEnabled(True)\n-            self.pushButton_2.setEnabled(True)\n-            self.update_status_bar_and_output(self.failure_reason)\n-            self.progressBar.setProperty(\"value\", 0)\n-            self.alert_failure(self.failure_reason)\n+            self.on_failure()\n \n     def tails_status(self, result):\n         \"This is the slot for Tailsconfig thread\"\n@@ -237,12 +237,15 @@\n             self.progressBar.setProperty(\"value\", 100)\n             self.alert_success()\n         else:\n-            self.update_status_bar_and_output(self.failure_reason)\n-            self.alert_failure(self.failure_reason)\n-            # Now everything is done, enable the button.\n-            self.pushButton.setEnabled(True)\n-            self.pushButton_2.setEnabled(True)\n-            self.progressBar.setProperty(\"value\", 0)\n+            self.on_failure()\n+\n+    def on_failure(self):\n+        self.update_status_bar_and_output(self.failure_reason)\n+        self.alert_failure(self.failure_reason)\n+        # Now everything is done, enable the button.\n+        self.pushButton.setEnabled(True)\n+        self.pushButton_2.setEnabled(True)\n+        self.progressBar.setProperty(\"value\", 0)\n \n     def update_securedrop(self):\n         self.pushButton_2.setEnabled(False)\n@@ -272,4 +275,4 @@\n         if ok_is_pressed and sudo_password:\n             return sudo_password\n         else:\n-            sys.exit(0)\n+            return None\ndiff --git a/journalist_gui/journalist_gui/strings.py b/journalist_gui/journalist_gui/strings.py\n--- a/journalist_gui/journalist_gui/strings.py\n+++ b/journalist_gui/journalist_gui/strings.py\n@@ -16,6 +16,7 @@\n finished = 'Update successfully completed!'\n finished_dialog_message = 'Updates completed successfully. '\n finished_dialog_title = 'SecureDrop Workstation is up to date!'\n+missing_sudo_password = 'Missing Tails Administrator password'\n update_failed_dialog_title = 'Error Updating SecureDrop Workstation'\n update_failed_generic_reason = (\"Update failed. \"\n                                 \"Please contact your SecureDrop \"\n", "issue": "[qt-journalist-updater] Update UI properly if Tails Administrator password is not provided\n# Feature request\r\n\r\n## Description\r\n\r\nIf the user does not provide the `Tails Administrator` password, the tool should show that in the UI and does not close the application.\r\n\n", "before_files": [{"content": "#!/usr/bin/python\nfrom PyQt5 import QtGui, QtWidgets\nfrom PyQt5.QtCore import QThread, pyqtSignal\nimport sys\nimport subprocess\nimport os\nimport pexpect\n\nfrom journalist_gui import updaterUI, strings, resources_rc  # noqa\n\n\nLOCK_LOCATION = \"/home/amnesia/Persistent/securedrop/securedrop_update.lock\"  # noqa\n\n\nclass SetupThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n\n    def run(self):\n        sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'\n        update_command = [sdadmin_path, 'setup']\n\n        # Create lock so we resume failed updates on reboot.\n        # Don't create the lock if it already exists.\n        if not os.path.exists(LOCK_LOCATION):\n            open(LOCK_LOCATION, 'a').close()\n\n        try:\n            self.output = subprocess.check_output(\n                update_command,\n                stderr=subprocess.STDOUT).decode('utf-8')\n            if 'Failed to install' in self.output:\n                self.update_success = False\n                self.failure_reason = strings.update_failed_generic_reason\n            else:\n                self.update_success = True\n        except subprocess.CalledProcessError as e:\n            self.output += e.output.decode('utf-8')\n            self.update_success = False\n            self.failure_reason = strings.update_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\n# This thread will handle the ./securedrop-admin update command\nclass UpdateThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n\n    def run(self):\n        sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'\n        update_command = [sdadmin_path, 'update']\n        try:\n            self.output = subprocess.check_output(\n                update_command,\n                stderr=subprocess.STDOUT).decode('utf-8')\n            if 'Signature verification failed' in self.output:\n                self.update_success = False\n                self.failure_reason = strings.update_failed_sig_failure\n            elif \"Signature verification successful\" in self.output:\n                self.update_success = True\n            else:\n                self.failure_reason = strings.update_failed_generic_reason\n        except subprocess.CalledProcessError as e:\n            self.output += e.output.decode('utf-8')\n            self.update_success = False\n            self.failure_reason = strings.update_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\n# This thread will handle the ./securedrop-admin tailsconfig command\nclass TailsconfigThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n        self.sudo_password = \"\"\n\n    def run(self):\n        tailsconfig_command = (\"/home/amnesia/Persistent/\"\n                               \"securedrop/securedrop-admin \"\n                               \"tailsconfig\")\n        try:\n            child = pexpect.spawn(tailsconfig_command)\n            child.expect('SUDO password:')\n            self.output += child.before.decode('utf-8')\n            child.sendline(self.sudo_password)\n            child.expect(pexpect.EOF)\n            self.output += child.before.decode('utf-8')\n\n            # For Tailsconfig to be considered a success, we expect no\n            # failures in the Ansible output.\n            if 'failed=0' not in self.output:\n                self.update_success = False\n                self.failure_reason = strings.tailsconfig_failed_generic_reason  # noqa\n            else:\n                self.update_success = True\n        except pexpect.exceptions.TIMEOUT:\n            self.update_success = False\n            self.failure_reason = strings.tailsconfig_failed_sudo_password\n\n        except subprocess.CalledProcessError:\n            self.update_success = False\n            self.failure_reason = strings.tailsconfig_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\nclass UpdaterApp(QtWidgets.QMainWindow, updaterUI.Ui_MainWindow):\n\n    def __init__(self, parent=None):\n        super(UpdaterApp, self).__init__(parent)\n        self.setupUi(self)\n        self.output = strings.initial_text_box\n        self.plainTextEdit.setPlainText(self.output)\n        self.update_success = False\n\n        pixmap = QtGui.QPixmap(\":/images/static/banner.png\")\n        self.label_2.setPixmap(pixmap)\n        self.label_2.setScaledContents(True)\n\n        self.progressBar.setProperty(\"value\", 0)\n        self.setWindowTitle(strings.window_title)\n        self.setWindowIcon(QtGui.QIcon(':/images/static/securedrop_icon.png'))\n        self.label.setText(strings.update_in_progress)\n\n        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab),\n                                  strings.main_tab)\n        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2),\n                                  strings.output_tab)\n\n        # Connect buttons to their functions.\n        self.pushButton.setText(strings.install_later_button)\n        self.pushButton.setStyleSheet(\"\"\"background-color: lightgrey;\n                                      min-height: 2em;\n                                      border-radius: 10px\"\"\")\n        self.pushButton.clicked.connect(self.close)\n        self.pushButton_2.setText(strings.install_update_button)\n        self.pushButton_2.setStyleSheet(\"\"\"background-color: #E6FFEB;\n                                        min-height: 2em;\n                                        border-radius: 10px;\"\"\")\n        self.pushButton_2.clicked.connect(self.update_securedrop)\n        self.update_thread = UpdateThread()\n        self.update_thread.signal.connect(self.update_status)\n        self.tails_thread = TailsconfigThread()\n        self.tails_thread.signal.connect(self.tails_status)\n        self.setup_thread = SetupThread()\n        self.setup_thread.signal.connect(self.setup_status)\n\n    # At the end of this function, we will try to do tailsconfig.\n    # A new slot will handle tailsconfig output\n    def setup_status(self, result):\n        \"This is the slot for setup thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.progressBar.setProperty(\"value\", 60)\n        self.plainTextEdit.setPlainText(self.output)\n        self.plainTextEdit.setReadOnly = True\n        if not self.update_success:  # Failed to do setup update\n            self.pushButton.setEnabled(True)\n            self.pushButton_2.setEnabled(True)\n            self.update_status_bar_and_output(self.failure_reason)\n            self.progressBar.setProperty(\"value\", 0)\n            self.alert_failure(self.failure_reason)\n            return\n        self.progressBar.setProperty(\"value\", 70)\n        self.call_tailsconfig()\n\n    # This will update the output text after the git commands.\n    def update_status(self, result):\n        \"This is the slot for update thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.progressBar.setProperty(\"value\", 40)\n        self.plainTextEdit.setPlainText(self.output)\n        self.plainTextEdit.setReadOnly = True\n        self.progressBar.setProperty(\"value\", 50)\n        self.update_status_bar_and_output(strings.doing_setup)\n        self.setup_thread.start()\n\n    def update_status_bar_and_output(self, status_message):\n        \"\"\"This method updates the status bar and the output window with the\n        status_message.\"\"\"\n        self.statusbar.showMessage(status_message)\n        self.output += status_message + '\\n'\n        self.plainTextEdit.setPlainText(self.output)\n\n    def call_tailsconfig(self):\n        # Now let us work on tailsconfig part\n        if self.update_success:\n            # Get sudo password and add an enter key as tailsconfig command\n            # expects\n            sudo_password = self.get_sudo_password() + '\\n'\n            self.tails_thread.sudo_password = sudo_password\n            self.update_status_bar_and_output(strings.updating_tails_env)\n            self.tails_thread.start()\n        else:\n            self.pushButton.setEnabled(True)\n            self.pushButton_2.setEnabled(True)\n            self.update_status_bar_and_output(self.failure_reason)\n            self.progressBar.setProperty(\"value\", 0)\n            self.alert_failure(self.failure_reason)\n\n    def tails_status(self, result):\n        \"This is the slot for Tailsconfig thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.plainTextEdit.setPlainText(self.output)\n        self.progressBar.setProperty(\"value\", 80)\n        if self.update_success:\n            # Remove lock\n            os.remove(LOCK_LOCATION)\n            self.update_status_bar_and_output(strings.finished)\n            self.progressBar.setProperty(\"value\", 100)\n            self.alert_success()\n        else:\n            self.update_status_bar_and_output(self.failure_reason)\n            self.alert_failure(self.failure_reason)\n            # Now everything is done, enable the button.\n            self.pushButton.setEnabled(True)\n            self.pushButton_2.setEnabled(True)\n            self.progressBar.setProperty(\"value\", 0)\n\n    def update_securedrop(self):\n        self.pushButton_2.setEnabled(False)\n        self.pushButton.setEnabled(False)\n        self.progressBar.setProperty(\"value\", 10)\n        self.update_status_bar_and_output(strings.fetching_update)\n        self.update_thread.start()\n\n    def alert_success(self):\n        self.success_dialog = QtWidgets.QMessageBox()\n        self.success_dialog.setIcon(QtWidgets.QMessageBox.Information)\n        self.success_dialog.setText(strings.finished_dialog_message)\n        self.success_dialog.setWindowTitle(strings.finished_dialog_title)\n        self.success_dialog.show()\n\n    def alert_failure(self, failure_reason):\n        self.error_dialog = QtWidgets.QMessageBox()\n        self.error_dialog.setIcon(QtWidgets.QMessageBox.Critical)\n        self.error_dialog.setText(self.failure_reason)\n        self.error_dialog.setWindowTitle(strings.update_failed_dialog_title)\n        self.error_dialog.show()\n\n    def get_sudo_password(self):\n        sudo_password, ok_is_pressed = QtWidgets.QInputDialog.getText(\n            self, \"Tails Administrator password\", strings.sudo_password_text,\n            QtWidgets.QLineEdit.Password, \"\")\n        if ok_is_pressed and sudo_password:\n            return sudo_password\n        else:\n            sys.exit(0)\n", "path": "journalist_gui/journalist_gui/SecureDropUpdater.py"}, {"content": "window_title = 'SecureDrop Workstation Updater'\nupdate_in_progress = (\"SecureDrop workstation updates are available! \"\n                      \"It is recommended to install them now. \\n\\n\"\n                      \"If you don\\'t want to install them now, \"\n                      \"you can install them the next time you reboot.\\n\\n\"\n                      \"You will need to have set a Tails Administration \"\n                      \"password in \"\n                      \"the Tails Greeter on boot to complete the update.\\n\\n\"\n                      \"When you start your workstation, this window will \"\n                      \"automatically appear if you have not \"\n                      \"completed any required updates.\\n\")\nfetching_update = ('Fetching and verifying latest update...'\n                   ' (5 mins remaining)')\nupdating_tails_env = ('Configuring local Tails environment...'\n                      ' (1 min remaining)')\nfinished = 'Update successfully completed!'\nfinished_dialog_message = 'Updates completed successfully. '\nfinished_dialog_title = 'SecureDrop Workstation is up to date!'\nupdate_failed_dialog_title = 'Error Updating SecureDrop Workstation'\nupdate_failed_generic_reason = (\"Update failed. \"\n                                \"Please contact your SecureDrop \"\n                                \"administrator.\")\nupdate_failed_sig_failure = (\"WARNING: Signature verification failed. \"\n                             \"Contact your SecureDrop administrator \"\n                             \"or securedrop@freedom.press immediately.\")\ntailsconfig_failed_sudo_password = ('Administrator password incorrect. '\n                                    'Exiting upgrade - '\n                                    'click Update Now to try again.')\ntailsconfig_failed_generic_reason = (\"Tails workstation configuration failed. \"\n                                     \"Contact your administrator. \"\n                                     \"If you are an administrator, contact \"\n                                     \"securedrop@freedom.press.\")\ninstall_update_button = 'Update Now'\ninstall_later_button = 'Update Later'\nsudo_password_text = (\"Enter the Tails Administrator password you \"\n                      \"entered in the Tails Greeter.\\n If you did not \"\n                      \"set an Administrator password, click Cancel \"\n                      \"and reboot. \")\nmain_tab = 'Updates Available'\noutput_tab = 'Detailed Update Progress'\ninitial_text_box = (\"When the update begins, this area will populate with \"\n                    \"output.\\n\")\ndoing_setup = \"Checking dependencies are up to date... (2 mins remaining)\"\n", "path": "journalist_gui/journalist_gui/strings.py"}], "after_files": [{"content": "#!/usr/bin/python\nfrom PyQt5 import QtGui, QtWidgets\nfrom PyQt5.QtCore import QThread, pyqtSignal\nimport subprocess\nimport os\nimport pexpect\n\nfrom journalist_gui import updaterUI, strings, resources_rc  # noqa\n\n\nLOCK_LOCATION = \"/home/amnesia/Persistent/securedrop/securedrop_update.lock\"  # noqa\n\n\nclass SetupThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n\n    def run(self):\n        sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'\n        update_command = [sdadmin_path, 'setup']\n\n        # Create lock so we resume failed updates on reboot.\n        # Don't create the lock if it already exists.\n        if not os.path.exists(LOCK_LOCATION):\n            open(LOCK_LOCATION, 'a').close()\n\n        try:\n            self.output = subprocess.check_output(\n                update_command,\n                stderr=subprocess.STDOUT).decode('utf-8')\n            if 'Failed to install' in self.output:\n                self.update_success = False\n                self.failure_reason = strings.update_failed_generic_reason\n            else:\n                self.update_success = True\n        except subprocess.CalledProcessError as e:\n            self.output += e.output.decode('utf-8')\n            self.update_success = False\n            self.failure_reason = strings.update_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\n# This thread will handle the ./securedrop-admin update command\nclass UpdateThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n\n    def run(self):\n        sdadmin_path = '/home/amnesia/Persistent/securedrop/securedrop-admin'\n        update_command = [sdadmin_path, 'update']\n        try:\n            self.output = subprocess.check_output(\n                update_command,\n                stderr=subprocess.STDOUT).decode('utf-8')\n            if 'Signature verification failed' in self.output:\n                self.update_success = False\n                self.failure_reason = strings.update_failed_sig_failure\n            elif \"Signature verification successful\" in self.output:\n                self.update_success = True\n            else:\n                self.failure_reason = strings.update_failed_generic_reason\n        except subprocess.CalledProcessError as e:\n            self.output += e.output.decode('utf-8')\n            self.update_success = False\n            self.failure_reason = strings.update_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\n# This thread will handle the ./securedrop-admin tailsconfig command\nclass TailsconfigThread(QThread):\n    signal = pyqtSignal('PyQt_PyObject')\n\n    def __init__(self):\n        QThread.__init__(self)\n        self.output = \"\"\n        self.update_success = False\n        self.failure_reason = \"\"\n        self.sudo_password = \"\"\n\n    def run(self):\n        tailsconfig_command = (\"/home/amnesia/Persistent/\"\n                               \"securedrop/securedrop-admin \"\n                               \"tailsconfig\")\n        try:\n            child = pexpect.spawn(tailsconfig_command)\n            child.expect('SUDO password:')\n            self.output += child.before.decode('utf-8')\n            child.sendline(self.sudo_password)\n            child.expect(pexpect.EOF)\n            self.output += child.before.decode('utf-8')\n\n            # For Tailsconfig to be considered a success, we expect no\n            # failures in the Ansible output.\n            if 'failed=0' not in self.output:\n                self.update_success = False\n                self.failure_reason = strings.tailsconfig_failed_generic_reason  # noqa\n            else:\n                self.update_success = True\n        except pexpect.exceptions.TIMEOUT:\n            self.update_success = False\n            self.failure_reason = strings.tailsconfig_failed_sudo_password\n\n        except subprocess.CalledProcessError:\n            self.update_success = False\n            self.failure_reason = strings.tailsconfig_failed_generic_reason\n        result = {'status': self.update_success,\n                  'output': self.output,\n                  'failure_reason': self.failure_reason}\n        self.signal.emit(result)\n\n\nclass UpdaterApp(QtWidgets.QMainWindow, updaterUI.Ui_MainWindow):\n\n    def __init__(self, parent=None):\n        super(UpdaterApp, self).__init__(parent)\n        self.setupUi(self)\n        self.output = strings.initial_text_box\n        self.plainTextEdit.setPlainText(self.output)\n        self.update_success = False\n\n        pixmap = QtGui.QPixmap(\":/images/static/banner.png\")\n        self.label_2.setPixmap(pixmap)\n        self.label_2.setScaledContents(True)\n\n        self.progressBar.setProperty(\"value\", 0)\n        self.setWindowTitle(strings.window_title)\n        self.setWindowIcon(QtGui.QIcon(':/images/static/securedrop_icon.png'))\n        self.label.setText(strings.update_in_progress)\n\n        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab),\n                                  strings.main_tab)\n        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2),\n                                  strings.output_tab)\n\n        # Connect buttons to their functions.\n        self.pushButton.setText(strings.install_later_button)\n        self.pushButton.setStyleSheet(\"\"\"background-color: lightgrey;\n                                      min-height: 2em;\n                                      border-radius: 10px\"\"\")\n        self.pushButton.clicked.connect(self.close)\n        self.pushButton_2.setText(strings.install_update_button)\n        self.pushButton_2.setStyleSheet(\"\"\"background-color: #E6FFEB;\n                                        min-height: 2em;\n                                        border-radius: 10px;\"\"\")\n        self.pushButton_2.clicked.connect(self.update_securedrop)\n        self.update_thread = UpdateThread()\n        self.update_thread.signal.connect(self.update_status)\n        self.tails_thread = TailsconfigThread()\n        self.tails_thread.signal.connect(self.tails_status)\n        self.setup_thread = SetupThread()\n        self.setup_thread.signal.connect(self.setup_status)\n\n    # At the end of this function, we will try to do tailsconfig.\n    # A new slot will handle tailsconfig output\n    def setup_status(self, result):\n        \"This is the slot for setup thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.progressBar.setProperty(\"value\", 60)\n        self.plainTextEdit.setPlainText(self.output)\n        self.plainTextEdit.setReadOnly = True\n        if not self.update_success:  # Failed to do setup update\n            self.pushButton.setEnabled(True)\n            self.pushButton_2.setEnabled(True)\n            self.update_status_bar_and_output(self.failure_reason)\n            self.progressBar.setProperty(\"value\", 0)\n            self.alert_failure(self.failure_reason)\n            return\n        self.progressBar.setProperty(\"value\", 70)\n        self.call_tailsconfig()\n\n    # This will update the output text after the git commands.\n    def update_status(self, result):\n        \"This is the slot for update thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.progressBar.setProperty(\"value\", 40)\n        self.plainTextEdit.setPlainText(self.output)\n        self.plainTextEdit.setReadOnly = True\n        self.progressBar.setProperty(\"value\", 50)\n        self.update_status_bar_and_output(strings.doing_setup)\n        self.setup_thread.start()\n\n    def update_status_bar_and_output(self, status_message):\n        \"\"\"This method updates the status bar and the output window with the\n        status_message.\"\"\"\n        self.statusbar.showMessage(status_message)\n        self.output += status_message + '\\n'\n        self.plainTextEdit.setPlainText(self.output)\n\n    def call_tailsconfig(self):\n        # Now let us work on tailsconfig part\n        if self.update_success:\n            # Get sudo password and add an enter key as tailsconfig command\n            # expects\n            sudo_password = self.get_sudo_password()\n            if not sudo_password:\n                self.update_success = False\n                self.failure_reason = strings.missing_sudo_password\n                self.on_failure()\n                return\n            self.tails_thread.sudo_password = sudo_password + '\\n'\n            self.update_status_bar_and_output(strings.updating_tails_env)\n            self.tails_thread.start()\n        else:\n            self.on_failure()\n\n    def tails_status(self, result):\n        \"This is the slot for Tailsconfig thread\"\n        self.output += result['output']\n        self.update_success = result['status']\n        self.failure_reason = result['failure_reason']\n        self.plainTextEdit.setPlainText(self.output)\n        self.progressBar.setProperty(\"value\", 80)\n        if self.update_success:\n            # Remove lock\n            os.remove(LOCK_LOCATION)\n            self.update_status_bar_and_output(strings.finished)\n            self.progressBar.setProperty(\"value\", 100)\n            self.alert_success()\n        else:\n            self.on_failure()\n\n    def on_failure(self):\n        self.update_status_bar_and_output(self.failure_reason)\n        self.alert_failure(self.failure_reason)\n        # Now everything is done, enable the button.\n        self.pushButton.setEnabled(True)\n        self.pushButton_2.setEnabled(True)\n        self.progressBar.setProperty(\"value\", 0)\n\n    def update_securedrop(self):\n        self.pushButton_2.setEnabled(False)\n        self.pushButton.setEnabled(False)\n        self.progressBar.setProperty(\"value\", 10)\n        self.update_status_bar_and_output(strings.fetching_update)\n        self.update_thread.start()\n\n    def alert_success(self):\n        self.success_dialog = QtWidgets.QMessageBox()\n        self.success_dialog.setIcon(QtWidgets.QMessageBox.Information)\n        self.success_dialog.setText(strings.finished_dialog_message)\n        self.success_dialog.setWindowTitle(strings.finished_dialog_title)\n        self.success_dialog.show()\n\n    def alert_failure(self, failure_reason):\n        self.error_dialog = QtWidgets.QMessageBox()\n        self.error_dialog.setIcon(QtWidgets.QMessageBox.Critical)\n        self.error_dialog.setText(self.failure_reason)\n        self.error_dialog.setWindowTitle(strings.update_failed_dialog_title)\n        self.error_dialog.show()\n\n    def get_sudo_password(self):\n        sudo_password, ok_is_pressed = QtWidgets.QInputDialog.getText(\n            self, \"Tails Administrator password\", strings.sudo_password_text,\n            QtWidgets.QLineEdit.Password, \"\")\n        if ok_is_pressed and sudo_password:\n            return sudo_password\n        else:\n            return None\n", "path": "journalist_gui/journalist_gui/SecureDropUpdater.py"}, {"content": "window_title = 'SecureDrop Workstation Updater'\nupdate_in_progress = (\"SecureDrop workstation updates are available! \"\n                      \"It is recommended to install them now. \\n\\n\"\n                      \"If you don\\'t want to install them now, \"\n                      \"you can install them the next time you reboot.\\n\\n\"\n                      \"You will need to have set a Tails Administration \"\n                      \"password in \"\n                      \"the Tails Greeter on boot to complete the update.\\n\\n\"\n                      \"When you start your workstation, this window will \"\n                      \"automatically appear if you have not \"\n                      \"completed any required updates.\\n\")\nfetching_update = ('Fetching and verifying latest update...'\n                   ' (5 mins remaining)')\nupdating_tails_env = ('Configuring local Tails environment...'\n                      ' (1 min remaining)')\nfinished = 'Update successfully completed!'\nfinished_dialog_message = 'Updates completed successfully. '\nfinished_dialog_title = 'SecureDrop Workstation is up to date!'\nmissing_sudo_password = 'Missing Tails Administrator password'\nupdate_failed_dialog_title = 'Error Updating SecureDrop Workstation'\nupdate_failed_generic_reason = (\"Update failed. \"\n                                \"Please contact your SecureDrop \"\n                                \"administrator.\")\nupdate_failed_sig_failure = (\"WARNING: Signature verification failed. \"\n                             \"Contact your SecureDrop administrator \"\n                             \"or securedrop@freedom.press immediately.\")\ntailsconfig_failed_sudo_password = ('Administrator password incorrect. '\n                                    'Exiting upgrade - '\n                                    'click Update Now to try again.')\ntailsconfig_failed_generic_reason = (\"Tails workstation configuration failed. \"\n                                     \"Contact your administrator. \"\n                                     \"If you are an administrator, contact \"\n                                     \"securedrop@freedom.press.\")\ninstall_update_button = 'Update Now'\ninstall_later_button = 'Update Later'\nsudo_password_text = (\"Enter the Tails Administrator password you \"\n                      \"entered in the Tails Greeter.\\n If you did not \"\n                      \"set an Administrator password, click Cancel \"\n                      \"and reboot. \")\nmain_tab = 'Updates Available'\noutput_tab = 'Detailed Update Progress'\ninitial_text_box = (\"When the update begins, this area will populate with \"\n                    \"output.\\n\")\ndoing_setup = \"Checking dependencies are up to date... (2 mins remaining)\"\n", "path": "journalist_gui/journalist_gui/strings.py"}]}