Merge lp:~azzar1/software-properties/canonical-livepatch into lp:software-properties

Proposed by Andrea Azzarone
Status: Merged
Merged at revision: 1011
Proposed branch: lp:~azzar1/software-properties/canonical-livepatch
Merge into: lp:software-properties
Diff against target: 1630 lines (+1255/-30)
10 files modified
data/gtkbuilder/dialog-livepatch-error.ui (+58/-0)
data/gtkbuilder/main.ui (+90/-21)
data/gtkbuilder/ubuntuone-dialog.ui (+372/-0)
debian/control (+5/-1)
softwareproperties/SoftwareProperties.py (+131/-0)
softwareproperties/dbus/SoftwarePropertiesDBus.py (+11/-0)
softwareproperties/gtk/DialogLivepatchError.py (+56/-0)
softwareproperties/gtk/DialogUbuntuOne.py (+162/-0)
softwareproperties/gtk/LivePatchTokenGetter.py (+192/-0)
softwareproperties/gtk/SoftwarePropertiesGtk.py (+178/-8)
To merge this branch: bzr merge lp:~azzar1/software-properties/canonical-livepatch
Reviewer Review Type Date Requested Status
Ubuntu Core Development Team Pending
Review via email: mp+335219@code.launchpad.net

Commit message

Implement the GUI to enable canonical-livepatch from software-properties. Right now the gui is hidden as canonical-livepatch is not yet enabled on bionic.

To post a comment you must log in.
Revision history for this message
Andrea Azzarone (azzar1) wrote :

Marking as "Working in progress" until we have figured out how to avoid the double sign-in (Ubuntu Store and the one in software-properties)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/gtkbuilder/dialog-livepatch-error.ui'
2--- data/gtkbuilder/dialog-livepatch-error.ui 1970-01-01 00:00:00 +0000
3+++ data/gtkbuilder/dialog-livepatch-error.ui 2017-12-14 14:42:25 +0000
4@@ -0,0 +1,58 @@
5+<?xml version="1.0" encoding="UTF-8"?>
6+<!-- Generated with glade 3.18.3 -->
7+<interface>
8+ <requires lib="gtk+" version="3.12"/>
9+ <object class="GtkMessageDialog" id="messagedialog_livepatch">
10+ <property name="can_focus">False</property>
11+ <property name="type_hint">dialog</property>
12+ <property name="message_type">error</property>
13+ <property name="text" translatable="yes">Sorry, there’s been a problem in setting up Canonical Livepatch.</property>
14+ <child internal-child="vbox">
15+ <object class="GtkBox" id="messagedialog-vbox1">
16+ <property name="can_focus">False</property>
17+ <property name="orientation">vertical</property>
18+ <property name="spacing">2</property>
19+ <child internal-child="action_area">
20+ <object class="GtkButtonBox" id="messagedialog-action_area1">
21+ <property name="can_focus">False</property>
22+ <property name="layout_style">end</property>
23+ <child>
24+ <object class="GtkButton" id="button_settings">
25+ <property name="label" translatable="yes">Settings…</property>
26+ <property name="visible">True</property>
27+ <property name="can_focus">True</property>
28+ <property name="receives_default">True</property>
29+ <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/>
30+ </object>
31+ <packing>
32+ <property name="expand">True</property>
33+ <property name="fill">True</property>
34+ <property name="position">0</property>
35+ </packing>
36+ </child>
37+ <child>
38+ <object class="GtkButton" id="button_ignore">
39+ <property name="label" translatable="yes">Ignore</property>
40+ <property name="visible">True</property>
41+ <property name="can_focus">True</property>
42+ <property name="receives_default">True</property>
43+ <property name="yalign">0.51999998092651367</property>
44+ <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/>
45+ </object>
46+ <packing>
47+ <property name="expand">True</property>
48+ <property name="fill">True</property>
49+ <property name="position">1</property>
50+ </packing>
51+ </child>
52+ </object>
53+ <packing>
54+ <property name="expand">False</property>
55+ <property name="fill">False</property>
56+ <property name="position">0</property>
57+ </packing>
58+ </child>
59+ </object>
60+ </child>
61+ </object>
62+</interface>
63
64=== modified file 'data/gtkbuilder/main.ui'
65--- data/gtkbuilder/main.ui 2016-08-17 09:34:22 +0000
66+++ data/gtkbuilder/main.ui 2017-12-14 14:42:25 +0000
67@@ -1,6 +1,7 @@
68 <?xml version="1.0" encoding="UTF-8"?>
69+<!-- Generated with glade 3.18.3 -->
70 <interface>
71- <!-- interface-requires gtk+ 3.0 -->
72+ <requires lib="gtk+" version="3.0"/>
73 <object class="GtkListStore" id="model_normal_updates_display">
74 <columns>
75 <!-- column-name text -->
76@@ -160,7 +161,6 @@
77 <property name="visible">True</property>
78 <property name="can_focus">True</property>
79 <property name="receives_default">False</property>
80- <property name="use_action_appearance">False</property>
81 <property name="use_underline">True</property>
82 <property name="xalign">0</property>
83 <property name="draw_indicator">True</property>
84@@ -396,7 +396,6 @@
85 <property name="can_focus">True</property>
86 <property name="can_default">True</property>
87 <property name="receives_default">True</property>
88- <property name="use_action_appearance">False</property>
89 <signal name="clicked" handler="on_add_clicked" swapped="no"/>
90 </object>
91 <packing>
92@@ -413,7 +412,6 @@
93 <property name="can_focus">True</property>
94 <property name="can_default">True</property>
95 <property name="receives_default">True</property>
96- <property name="use_action_appearance">False</property>
97 <signal name="clicked" handler="on_edit_clicked" swapped="no"/>
98 </object>
99 <packing>
100@@ -430,7 +428,6 @@
101 <property name="can_focus">True</property>
102 <property name="can_default">True</property>
103 <property name="receives_default">True</property>
104- <property name="use_action_appearance">False</property>
105 <property name="use_stock">True</property>
106 <signal name="clicked" handler="on_remove_clicked" swapped="no"/>
107 </object>
108@@ -459,7 +456,6 @@
109 <property name="visible">True</property>
110 <property name="can_focus">True</property>
111 <property name="receives_default">True</property>
112- <property name="use_action_appearance">False</property>
113 <signal name="clicked" handler="on_button_add_cdrom_clicked" swapped="no"/>
114 </object>
115 <packing>
116@@ -570,8 +566,8 @@
117 <object class="GtkLabel" id="label3">
118 <property name="visible">True</property>
119 <property name="can_focus">False</property>
120- <property name="xalign">1</property>
121 <property name="label" translatable="yes">Automatically check for updates:</property>
122+ <property name="xalign">1</property>
123 </object>
124 <packing>
125 <property name="expand">False</property>
126@@ -613,8 +609,8 @@
127 <object class="GtkLabel" id="label4">
128 <property name="visible">True</property>
129 <property name="can_focus">False</property>
130- <property name="xalign">1</property>
131 <property name="label" translatable="yes">When there are security updates:</property>
132+ <property name="xalign">1</property>
133 </object>
134 <packing>
135 <property name="expand">False</property>
136@@ -656,8 +652,8 @@
137 <object class="GtkLabel" id="label5">
138 <property name="visible">True</property>
139 <property name="can_focus">False</property>
140- <property name="xalign">1</property>
141 <property name="label" translatable="yes">When there are other updates:</property>
142+ <property name="xalign">1</property>
143 </object>
144 <packing>
145 <property name="expand">False</property>
146@@ -700,6 +696,84 @@
147 </packing>
148 </child>
149 <child>
150+ <object class="GtkAlignment" id="alignment3">
151+ <property name="visible">True</property>
152+ <property name="can_focus">False</property>
153+ <property name="left_padding">12</property>
154+ <child>
155+ <object class="GtkGrid" id="grid_livepatch">
156+ <property name="visible">True</property>
157+ <property name="can_focus">False</property>
158+ <property name="halign">center</property>
159+ <property name="hexpand">False</property>
160+ <property name="vexpand">False</property>
161+ <property name="row_spacing">6</property>
162+ <property name="column_spacing">6</property>
163+ <child>
164+ <object class="GtkBox" id="hbox_livepatch">
165+ <property name="visible">True</property>
166+ <property name="can_focus">False</property>
167+ <property name="halign">center</property>
168+ <property name="spacing">6</property>
169+ <child>
170+ <object class="GtkLabel" id="label_livepatch_login">
171+ <property name="visible">True</property>
172+ <property name="can_focus">False</property>
173+ </object>
174+ <packing>
175+ <property name="expand">False</property>
176+ <property name="fill">True</property>
177+ <property name="position">0</property>
178+ </packing>
179+ </child>
180+ <child>
181+ <object class="GtkButton" id="button_ubuntuone">
182+ <property name="visible">True</property>
183+ <property name="can_focus">True</property>
184+ <property name="receives_default">True</property>
185+ <property name="xalign">0</property>
186+ </object>
187+ <packing>
188+ <property name="expand">False</property>
189+ <property name="fill">True</property>
190+ <property name="position">2</property>
191+ </packing>
192+ </child>
193+ </object>
194+ <packing>
195+ <property name="left_attach">1</property>
196+ <property name="top_attach">1</property>
197+ </packing>
198+ </child>
199+ <child>
200+ <object class="GtkCheckButton" id="checkbutton_livepatch">
201+ <property name="label" translatable="yes">Use Canonical Livepatch to increase security between restarts</property>
202+ <property name="use_action_appearance">False</property>
203+ <property name="visible">True</property>
204+ <property name="can_focus">True</property>
205+ <property name="receives_default">False</property>
206+ <property name="use_underline">True</property>
207+ <property name="xalign">0</property>
208+ <property name="draw_indicator">True</property>
209+ </object>
210+ <packing>
211+ <property name="left_attach">1</property>
212+ <property name="top_attach">0</property>
213+ </packing>
214+ </child>
215+ <child>
216+ <placeholder/>
217+ </child>
218+ </object>
219+ </child>
220+ </object>
221+ <packing>
222+ <property name="expand">True</property>
223+ <property name="fill">True</property>
224+ <property name="position">2</property>
225+ </packing>
226+ </child>
227+ <child>
228 <object class="GtkAlignment" id="alignment15">
229 <property name="visible">True</property>
230 <property name="can_focus">False</property>
231@@ -713,8 +787,8 @@
232 <object class="GtkLabel" id="label29">
233 <property name="visible">True</property>
234 <property name="can_focus">False</property>
235+ <property name="label" translatable="yes">Notify me of a new Ubuntu version:</property>
236 <property name="xalign">1</property>
237- <property name="label" translatable="yes">Notify me of a new Ubuntu version:</property>
238 </object>
239 <packing>
240 <property name="expand">False</property>
241@@ -746,7 +820,7 @@
242 <packing>
243 <property name="expand">False</property>
244 <property name="fill">False</property>
245- <property name="position">2</property>
246+ <property name="position">3</property>
247 </packing>
248 </child>
249 </object>
250@@ -775,9 +849,9 @@
251 <object class="GtkLabel" id="label27">
252 <property name="visible">True</property>
253 <property name="can_focus">False</property>
254- <property name="xalign">0</property>
255 <property name="label" translatable="yes">&lt;b&gt;Trusted software providers&lt;/b&gt;</property>
256 <property name="use_markup">True</property>
257+ <property name="xalign">0</property>
258 </object>
259 <packing>
260 <property name="expand">False</property>
261@@ -851,7 +925,6 @@
262 <property name="has_tooltip">True</property>
263 <property name="tooltip_markup" translatable="yes">Import the public key from a trusted software provider</property>
264 <property name="tooltip_text" translatable="yes">Import the public key from a trusted software provider</property>
265- <property name="use_action_appearance">False</property>
266 <property name="use_underline">True</property>
267 <signal name="clicked" handler="add_key_clicked" swapped="no"/>
268 </object>
269@@ -868,7 +941,6 @@
270 <property name="visible">True</property>
271 <property name="can_focus">True</property>
272 <property name="receives_default">True</property>
273- <property name="use_action_appearance">False</property>
274 <property name="use_stock">True</property>
275 <signal name="clicked" handler="remove_key_clicked" swapped="no"/>
276 </object>
277@@ -900,7 +972,6 @@
278 <property name="has_tooltip">True</property>
279 <property name="tooltip_markup" translatable="yes">Restore the default keys of your distribution</property>
280 <property name="tooltip_text" translatable="yes">Restore the default keys of your distribution</property>
281- <property name="use_action_appearance">False</property>
282 <property name="use_underline">True</property>
283 <signal name="clicked" handler="on_restore_clicked" swapped="no"/>
284 </object>
285@@ -1001,8 +1072,8 @@
286 <property name="visible">True</property>
287 <property name="can_focus">False</property>
288 <property name="halign">start</property>
289+ <property name="label" translatable="yes">No proprietary drivers are in use.</property>
290 <property name="xalign">0</property>
291- <property name="label" translatable="yes">No proprietary drivers are in use.</property>
292 </object>
293 <packing>
294 <property name="expand">True</property>
295@@ -1024,11 +1095,11 @@
296 <object class="GtkLabel" id="label_disc">
297 <property name="visible">True</property>
298 <property name="can_focus">False</property>
299- <property name="xalign">0</property>
300 <property name="label" translatable="yes">&lt;small&gt;A proprietary driver has private code that Ubuntu developers can't review or improve. Security and other updates are dependent on the driver vendor.&lt;/small&gt;</property>
301 <property name="use_markup">True</property>
302 <property name="wrap">True</property>
303- <property name="max-width-chars">50</property>
304+ <property name="max_width_chars">50</property>
305+ <property name="xalign">0</property>
306 </object>
307 <packing>
308 <property name="expand">False</property>
309@@ -1090,7 +1161,7 @@
310 <property name="label" translatable="yes">Use proposed updates if you’re willing to report bugs on any problems that occur.</property>
311 <property name="use_markup">True</property>
312 <property name="wrap">True</property>
313- <property name="max-width-chars">110</property>
314+ <property name="max_width_chars">110</property>
315 </object>
316 </child>
317 </object>
318@@ -1138,7 +1209,6 @@
319 <property name="can_focus">True</property>
320 <property name="can_default">True</property>
321 <property name="receives_default">True</property>
322- <property name="use_action_appearance">False</property>
323 <property name="use_underline">True</property>
324 <signal name="clicked" handler="on_button_revert_clicked" swapped="no"/>
325 </object>
326@@ -1157,7 +1227,6 @@
327 <property name="can_focus">True</property>
328 <property name="can_default">True</property>
329 <property name="receives_default">True</property>
330- <property name="use_action_appearance">False</property>
331 <property name="use_stock">True</property>
332 <signal name="clicked" handler="on_close_button" swapped="no"/>
333 </object>
334
335=== added file 'data/gtkbuilder/ubuntu-one.png'
336Binary files data/gtkbuilder/ubuntu-one.png 1970-01-01 00:00:00 +0000 and data/gtkbuilder/ubuntu-one.png 2017-12-14 14:42:25 +0000 differ
337=== added file 'data/gtkbuilder/ubuntuone-dialog.ui'
338--- data/gtkbuilder/ubuntuone-dialog.ui 1970-01-01 00:00:00 +0000
339+++ data/gtkbuilder/ubuntuone-dialog.ui 2017-12-14 14:42:25 +0000
340@@ -0,0 +1,372 @@
341+<?xml version="1.0" encoding="UTF-8"?>
342+<!-- Generated with glade 3.19.0 -->
343+<interface>
344+ <requires lib="gtk+" version="3.0"/>
345+ <object class="GtkDialog" id="dialog_ubuntuone">
346+ <property name="use-header-bar">1</property>
347+ <property name="resizable">False</property>
348+ <action-widgets>
349+ <action-widget response="cancel">cancel_button</action-widget>
350+ </action-widgets>
351+ <child internal-child="headerbar">
352+ <object class="GtkHeaderBar">
353+ <property name="show_close_button">False</property>
354+ <child>
355+ <object class="GtkButton" id="cancel_button">
356+ <property name="label" translatable="yes">_Cancel</property>
357+ <property name="visible">True</property>
358+ <property name="can_focus">True</property>
359+ <property name="receives_default">True</property>
360+ <property name="use_underline">True</property>
361+ </object>
362+ <packing>
363+ <property name="pack-type">start</property>
364+ </packing>
365+ </child>
366+ <child>
367+ <object class="GtkButton" id="next_button">
368+ <property name="label" translatable="yes">_Continue</property>
369+ <property name="visible">True</property>
370+ <property name="can_focus">True</property>
371+ <property name="can_default">True</property>
372+ <property name="receives_default">True</property>
373+ <property name="use_underline">True</property>
374+ <style>
375+ <class name="suggested-action"/>
376+ </style>
377+ </object>
378+ <packing>
379+ <property name="pack-type">end</property>
380+ </packing>
381+ </child>
382+ </object>
383+ </child>
384+ <child internal-child="vbox">
385+ <object class="GtkBox" id="content_box">
386+ <property name="visible">True</property>
387+ <property name="can_focus">False</property>
388+ <property name="margin_left">20</property>
389+ <property name="margin_right">20</property>
390+ <property name="margin_top">20</property>
391+ <property name="margin_bottom">20</property>
392+ <property name="orientation">vertical</property>
393+ <property name="spacing">40</property>
394+ <child>
395+ <object class="GtkBox">
396+ <property name="visible">True</property>
397+ <property name="can_focus">False</property>
398+ <property name="spacing">20</property>
399+ <child>
400+ <object class="GtkImage">
401+ <property name="visible">True</property>
402+ <property name="can_focus">False</property>
403+ <property name="yalign">0</property>
404+ <property name="file">/usr/share/software-properties/gtkbuilder/ubuntu-one.png</property>
405+ </object>
406+ <packing>
407+ <property name="expand">False</property>
408+ <property name="fill">True</property>
409+ <property name="position">0</property>
410+ </packing>
411+ </child>
412+ <child>
413+ <object class="GtkStack" id="page_stack">
414+ <property name="visible">True</property>
415+ <property name="can_focus">False</property>
416+ <child>
417+ <object class="GtkGrid" id="page-login">
418+ <property name="visible">True</property>
419+ <property name="can_focus">False</property>
420+ <child>
421+ <object class="GtkLabel" id="prompt_label">
422+ <property name="visible">True</property>
423+ <property name="can_focus">False</property>
424+ <property name="margin_bottom">20</property>
425+ <property name="label" translatable="yes">To enable livepatch service, you need an Ubuntu Single Sign-On account.</property>
426+ <property name="wrap">True</property>
427+ <property name="xalign">0</property>
428+ </object>
429+ <packing>
430+ <property name="left_attach">0</property>
431+ <property name="top_attach">0</property>
432+ <property name="width">2</property>
433+ </packing>
434+ </child>
435+ <child>
436+ <object class="GtkAccelLabel">
437+ <property name="visible">True</property>
438+ <property name="can_focus">False</property>
439+ <property name="halign">end</property>
440+ <property name="margin_right">10</property>
441+ <property name="margin_bottom">20</property>
442+ <property name="label" translatable="yes">_Email address:</property>
443+ <property name="use_underline">True</property>
444+ <property name="xalign">1</property>
445+ </object>
446+ <packing>
447+ <property name="left_attach">0</property>
448+ <property name="top_attach">1</property>
449+ </packing>
450+ </child>
451+ <child>
452+ <object class="GtkEntry" id="email_entry">
453+ <property name="visible">True</property>
454+ <property name="can_focus">True</property>
455+ <property name="margin_bottom">20</property>
456+ <property name="hexpand">True</property>
457+ <property name="input_purpose">email</property>
458+ </object>
459+ <packing>
460+ <property name="left_attach">1</property>
461+ <property name="top_attach">1</property>
462+ </packing>
463+ </child>
464+ <child>
465+ <object class="GtkRadioButton" id="login_radio">
466+ <property name="label" translatable="yes">I have an Ubuntu Single Sign-On account</property>
467+ <property name="visible">True</property>
468+ <property name="can_focus">True</property>
469+ <property name="receives_default">False</property>
470+ <property name="margin_bottom">5</property>
471+ <property name="xalign">0</property>
472+ <property name="active">True</property>
473+ <property name="draw_indicator">True</property>
474+ </object>
475+ <packing>
476+ <property name="left_attach">0</property>
477+ <property name="top_attach">2</property>
478+ <property name="width">2</property>
479+ </packing>
480+ </child>
481+ <child>
482+ <object class="GtkAccelLabel">
483+ <property name="visible">True</property>
484+ <property name="can_focus">False</property>
485+ <property name="halign">end</property>
486+ <property name="margin_left">25</property>
487+ <property name="margin_right">10</property>
488+ <property name="margin_bottom">5</property>
489+ <property name="label" translatable="yes">_Password:</property>
490+ <property name="use_underline">True</property>
491+ <property name="xalign">1</property>
492+ </object>
493+ <packing>
494+ <property name="left_attach">0</property>
495+ <property name="top_attach">3</property>
496+ </packing>
497+ </child>
498+ <child>
499+ <object class="GtkEntry" id="password_entry">
500+ <property name="visible">True</property>
501+ <property name="can_focus">True</property>
502+ <property name="margin_bottom">5</property>
503+ <property name="hexpand">True</property>
504+ <property name="visibility">False</property>
505+ <property name="invisible_char">•</property>
506+ <property name="input_purpose">password</property>
507+ <property name="activates_default">True</property>
508+ </object>
509+ <packing>
510+ <property name="left_attach">1</property>
511+ <property name="top_attach">3</property>
512+ </packing>
513+ </child>
514+ <child>
515+ <object class="GtkRadioButton" id="register_radio">
516+ <property name="label" translatable="yes">I want to register for an account now</property>
517+ <property name="visible">True</property>
518+ <property name="can_focus">True</property>
519+ <property name="receives_default">False</property>
520+ <property name="margin_bottom">20</property>
521+ <property name="xalign">0</property>
522+ <property name="active">True</property>
523+ <property name="draw_indicator">True</property>
524+ <property name="group">login_radio</property>
525+ </object>
526+ <packing>
527+ <property name="left_attach">0</property>
528+ <property name="top_attach">5</property>
529+ <property name="width">2</property>
530+ </packing>
531+ </child>
532+ <child>
533+ <object class="GtkRadioButton" id="reset_radio">
534+ <property name="label" translatable="yes">I've forgotten my password</property>
535+ <property name="visible">True</property>
536+ <property name="can_focus">True</property>
537+ <property name="receives_default">False</property>
538+ <property name="xalign">0</property>
539+ <property name="active">True</property>
540+ <property name="draw_indicator">True</property>
541+ <property name="group">login_radio</property>
542+ </object>
543+ <packing>
544+ <property name="left_attach">0</property>
545+ <property name="top_attach">6</property>
546+ <property name="width">2</property>
547+ </packing>
548+ </child>
549+ <child>
550+ <placeholder/>
551+ </child>
552+ </object>
553+ <packing>
554+ <property name="name">page-login</property>
555+ </packing>
556+ </child>
557+ <child>
558+ <object class="GtkGrid" id="page-otp">
559+ <property name="visible">True</property>
560+ <property name="can_focus">False</property>
561+ <child>
562+ <object class="GtkLabel">
563+ <property name="visible">True</property>
564+ <property name="can_focus">False</property>
565+ <property name="margin_bottom">20</property>
566+ <property name="label" translatable="yes">Enter your one-time password for two-factor authentication.</property>
567+ <property name="wrap">True</property>
568+ <property name="xalign">0</property>
569+ </object>
570+ <packing>
571+ <property name="left_attach">0</property>
572+ <property name="top_attach">0</property>
573+ <property name="width">2</property>
574+ </packing>
575+ </child>
576+ <child>
577+ <object class="GtkAccelLabel">
578+ <property name="visible">True</property>
579+ <property name="can_focus">False</property>
580+ <property name="margin_right">10</property>
581+ <property name="label" translatable="yes">One-time password:</property>
582+ </object>
583+ <packing>
584+ <property name="left_attach">0</property>
585+ <property name="top_attach">1</property>
586+ </packing>
587+ </child>
588+ <child>
589+ <object class="GtkEntry" id="passcode_entry">
590+ <property name="visible">True</property>
591+ <property name="can_focus">True</property>
592+ <property name="hexpand">True</property>
593+ <property name="input_purpose">pin</property>
594+ <property name="activates_default">True</property>
595+ </object>
596+ <packing>
597+ <property name="left_attach">1</property>
598+ <property name="top_attach">1</property>
599+ </packing>
600+ </child>
601+ </object>
602+ <packing>
603+ <property name="name">page-otp</property>
604+ <property name="position">1</property>
605+ </packing>
606+ </child>
607+ <child>
608+ <object class="GtkGrid" id="page-success">
609+ <property name="visible">True</property>
610+ <property name="can_focus">False</property>
611+ <child>
612+ <object class="GtkLabel">
613+ <property name="visible">True</property>
614+ <property name="can_focus">False</property>
615+ <property name="label" translatable="yes">You are now signed into Ubuntu One.</property>
616+ </object>
617+ <packing>
618+ <property name="left_attach">0</property>
619+ <property name="top_attach">0</property>
620+ </packing>
621+ </child>
622+ </object>
623+ <packing>
624+ <property name="name">page-success</property>
625+ <property name="position">2</property>
626+ </packing>
627+ </child>
628+ </object>
629+ <packing>
630+ <property name="expand">True</property>
631+ <property name="fill">True</property>
632+ <property name="position">1</property>
633+ </packing>
634+ </child>
635+ </object>
636+ <packing>
637+ <property name="expand">True</property>
638+ <property name="fill">True</property>
639+ <property name="position">0</property>
640+ </packing>
641+ </child>
642+ <child>
643+ <object class="GtkBox">
644+ <property name="visible">True</property>
645+ <property name="can_focus">False</property>
646+ <property name="spacing">10</property>
647+ <child>
648+ <object class="GtkBox">
649+ <property name="visible">True</property>
650+ <property name="can_focus">False</property>
651+ <child>
652+ <object class="GtkStack" id="status_stack">
653+ <property name="visible">True</property>
654+ <property name="can_focus">False</property>
655+ <property name="margin_right">5</property>
656+ <child>
657+ <object class="GtkImage" id="status_image">
658+ <property name="visible">True</property>
659+ <property name="can_focus">False</property>
660+ </object>
661+ <packing>
662+ <property name="name">status-image</property>
663+ </packing>
664+ </child>
665+ <child>
666+ <object class="GtkSpinner" id="status_spinner">
667+ <property name="visible">True</property>
668+ <property name="can_focus">False</property>
669+ <property name="active">True</property>
670+ </object>
671+ <packing>
672+ <property name="name">status-spinner</property>
673+ <property name="position">1</property>
674+ </packing>
675+ </child>
676+ </object>
677+ <packing>
678+ <property name="expand">False</property>
679+ <property name="fill">True</property>
680+ <property name="position">0</property>
681+ </packing>
682+ </child>
683+ <child>
684+ <object class="GtkLabel" id="status_label">
685+ <property name="visible">True</property>
686+ <property name="can_focus">False</property>
687+ <property name="xalign">0</property>
688+ </object>
689+ <packing>
690+ <property name="expand">True</property>
691+ <property name="fill">True</property>
692+ <property name="position">1</property>
693+ </packing>
694+ </child>
695+ </object>
696+ <packing>
697+ <property name="expand">True</property>
698+ <property name="fill">True</property>
699+ <property name="position">0</property>
700+ </packing>
701+ </child>
702+ </object>
703+ <packing>
704+ <property name="expand">False</property>
705+ <property name="fill">True</property>
706+ <property name="position">1</property>
707+ </packing>
708+ </child>
709+ </object>
710+ </child>
711+ </object>
712+</interface>
713
714=== modified file 'debian/control'
715--- debian/control 2017-04-10 21:33:09 +0000
716+++ debian/control 2017-12-14 14:42:25 +0000
717@@ -41,7 +41,7 @@
718 Package: software-properties-common
719 Architecture: all
720 Depends: ${python3:Depends}, ${misc:Depends}, python3,
721- python3-gi, gir1.2-glib-2.0, python-apt-common (>= 0.9), python3-dbus,
722+ python3-gi, gir1.2-glib-2.0, gir1.2-snapd-1, python-apt-common (>= 0.9), python3-dbus,
723 python3-software-properties (= ${binary:Version}), ca-certificates
724 Breaks: python-software-properties (<< 0.85), python3-software-properties (<< 0.85)
725 Replaces: python-software-properties (<< 0.85), python3-software-properties (<< 0.85)
726@@ -59,7 +59,11 @@
727 python3-software-properties (= ${binary:Version}),
728 python3-gi,
729 gir1.2-gtk-3.0,
730+ gir1.2-secret-1,
731 python3-aptdaemon.gtk3widgets,
732+ python3-macaroonbakery,
733+ python3-pymacaroons,
734+ python3-requests,
735 software-properties-common,
736 ubuntu-drivers-common (>= 1:0.2.75),
737 python3-gi,
738
739=== modified file 'softwareproperties/SoftwareProperties.py'
740--- softwareproperties/SoftwareProperties.py 2017-03-01 17:35:37 +0000
741+++ softwareproperties/SoftwareProperties.py 2017-12-14 14:42:25 +0000
742@@ -31,6 +31,7 @@
743 import os
744 import glob
745 import shutil
746+import subprocess
747 import threading
748 import atexit
749 import tempfile
750@@ -63,6 +64,10 @@
751 from . import ppa
752 from . import cloudarchive
753
754+import gi
755+gi.require_version('Snapd', '1')
756+from gi.repository import Gio, Snapd
757+
758 _SHORTCUT_FACTORIES = [
759 ppa.shortcut_handler,
760 cloudarchive.shortcut_handler,
761@@ -126,6 +131,8 @@
762 # apt-key stuff
763 self.apt_key = AptAuth(rootdir=rootdir)
764
765+ self.cancellable = Gio.Cancellable()
766+
767 atexit.register(self.wait_for_threads)
768
769 def wait_for_threads(self):
770@@ -858,6 +865,130 @@
771 except:
772 return False
773
774+ #
775+ # Livepatch
776+ #
777+ def init_snapd(self):
778+ self.snapd_client = Snapd.Client()
779+ if not self.snapd_client.connect_sync():
780+ self.snapd_client = None
781+ return self.snapd_client is not None
782+
783+ def get_livepatch_snap_async(self, callback):
784+ assert self.snapd_client
785+ self.snapd_client.list_one_async('canonical-livepatch',
786+ self.cancellable,
787+ self.on_list_one_ready_cb,
788+ callback)
789+
790+ def on_list_one_ready_cb(self, source_object, result, user_data):
791+ callback = user_data
792+ try:
793+ snap = source_object.list_one_finish(result)
794+ except:
795+ snap = None
796+ if snap:
797+ if callback: callback(snap)
798+ return
799+ else:
800+ assert self.snapd_client
801+ self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME,
802+ 'canonical-livepatch',
803+ self.cancellable,
804+ self.on_find_ready_cb,
805+ callback)
806+
807+ def on_find_ready_cb(self, source_object, result, user_data):
808+ callback = user_data
809+ try:
810+ snaps = source_object.find_finish(result)
811+ except:
812+ snaps = list()
813+ snap = snaps[0] if len(snaps) else None
814+ if callback: callback(snap)
815+
816+ def get_livepatch_snap_status(self, snap):
817+ if snap is None:
818+ return Snapd.SnapStatus.UNKNOWN
819+ return snap.get_status()
820+
821+ # glib-snapd does not keep track of the status of the snap. Use this decorator
822+ # to make it easy to write async functions that will always have an updated
823+ # snap object.
824+ def require_livepatch_snap(func):
825+ def get_livepatch_snap_and_call(*args, **kwargs):
826+ return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs))
827+ return get_livepatch_snap_and_call
828+
829+ def is_livepatch_enabled(self):
830+ file = Gio.File.new_for_path(path='/var/snap/canonical-livepatch/common/machine-token')
831+ return file.query_exists(None)
832+
833+ @require_livepatch_snap
834+ def set_livepatch_enabled_async(self, enabled, token, callback, snap=None):
835+ status = self.get_livepatch_snap_status(snap)
836+ if status == Snapd.SnapStatus.UNKNOWN:
837+ if callback: callback(True, _("canonical-livepatch cannot be installed"))
838+ elif status == Snapd.SnapStatus.ACTIVE:
839+ if enabled:
840+ error = self.enable_livepatch_service(token)
841+ else:
842+ error = self.disable_livepatch_service()
843+ if callback: callback(len(error) > 0, error)
844+ elif status == Snapd.SnapStatus.INSTALLED:
845+ if enabled:
846+ self.snapd_client.enable_async(name='canonical-livepatch',
847+ cancellable=self.cancellable,
848+ callback=self.livepatch_enable_snap_cb,
849+ user_data=(callback, token))
850+ else:
851+ if callback: callback(False, "")
852+ elif status == Snapd.SnapStatus.AVAILABLE:
853+ if enabled:
854+ self.snapd_client.install_async(name='canonical-livepatch',
855+ cancellable=self.cancellable,
856+ callback=self.livepatch_install_snap_cb,
857+ user_data=(callback, token))
858+ else:
859+ if callback: callback(False, "")
860+
861+ def livepatch_enable_snap_cb(self, source_object, result, user_data):
862+ (callback, token) = user_data
863+ try:
864+ if source_object.enable_finish(result):
865+ error = self.enable_livepatch_service(token)
866+ if callback: callback(len(error) > 0, error)
867+ except Exception:
868+ if callback: callback(True, _("canonical-livepatch cannot be installed"))
869+
870+ def livepatch_install_snap_cb(self, source_object, result, user_data):
871+ (callback, token) = user_data
872+ try:
873+ if source_object.install_finish(result):
874+ error = self.enable_livepatch_service(token)
875+ if callback: callback(len(error) > 0, error)
876+ except Exception:
877+ if callback: callback(True, _("canonical-livepatch cannot be installed"))
878+
879+ def enable_livepatch_service(self, token):
880+ if self.is_livepatch_enabled():
881+ return ""
882+
883+ try:
884+ subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT)
885+ return ""
886+ except subprocess.CalledProcessError as e:
887+ return e.output if e.output else ""
888+
889+ def disable_livepatch_service(self):
890+ if not self.is_livepatch_enabled():
891+ return ""
892+
893+ try:
894+ subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT)
895+ return ""
896+ except subprocess.CalledProcessError as e:
897+ return e.output if e.output else ""
898
899 def shortcut_handler(shortcut):
900 for factory in _SHORTCUT_FACTORIES:
901
902=== modified file 'softwareproperties/dbus/SoftwarePropertiesDBus.py'
903--- softwareproperties/dbus/SoftwarePropertiesDBus.py 2015-11-01 12:39:02 +0000
904+++ softwareproperties/dbus/SoftwarePropertiesDBus.py 2017-12-14 14:42:25 +0000
905@@ -61,6 +61,8 @@
906 self.enforce_polkit = True
907 logging.debug("waiting for connections")
908
909+ self.init_snapd()
910+
911 # override set_modified_sourceslist to emit a signal
912 def save_sourceslist(self):
913 super(SoftwarePropertiesDBus, self).save_sourceslist()
914@@ -317,6 +319,15 @@
915 sender, conn, "com.ubuntu.softwareproperties.applychanges")
916 return self.update_keys()
917
918+ # LivePatch
919+ @dbus.service.method(DBUS_INTERFACE_NAME,
920+ sender_keyword="sender", connection_keyword="conn",
921+ in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler'))
922+ def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None):
923+ self._check_policykit_privilege(
924+ sender, conn, "com.ubuntu.softwareproperties.applychanges")
925+ self.set_livepatch_enabled_async(enabled, token, reply_handler)
926+
927 # helper from jockey
928 def _check_policykit_privilege(self, sender, conn, privilege):
929 '''Verify that sender has a given PolicyKit privilege.
930
931=== added file 'softwareproperties/gtk/DialogLivepatchError.py'
932--- softwareproperties/gtk/DialogLivepatchError.py 1970-01-01 00:00:00 +0000
933+++ softwareproperties/gtk/DialogLivepatchError.py 2017-12-14 14:42:25 +0000
934@@ -0,0 +1,56 @@
935+#
936+# Copyright (c) 2017 Canonical
937+#
938+# Authors:
939+# Andrea Azzarone <andrea.azzarone@canonical.com>
940+#
941+# This program is free software; you can redistribute it and/or
942+# modify it under the terms of the GNU General Public License as
943+# published by the Free Software Foundation; either version 2 of the
944+# License, or (at your option) any later version.
945+#
946+# This program is distributed in the hope that it will be useful,
947+# but WITHOUT ANY WARRANTY; without even the implied warranty of
948+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
949+# GNU General Public License for more details.
950+#
951+# You should have received a copy of the GNU General Public License
952+# along with this program; if not, write to the Free Software
953+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
954+# USA
955+
956+import os
957+
958+from softwareproperties.gtk.utils import (
959+ setup_ui,
960+)
961+
962+
963+class DialogLivepatchError:
964+
965+ RESPONSE_SETTINGS = 100
966+ RESPONSE_IGNORE = 101
967+
968+ def __init__(self, parent, datadir):
969+ """setup up the gtk dialog"""
970+ self.parent = parent
971+
972+ setup_ui(self, os.path.join(datadir, "gtkbuilder",
973+ "dialog-livepatch-error.ui"), domain="software-properties")
974+
975+ self.dialog = self.messagedialog_livepatch
976+ self.dialog.use_header_bar = True
977+ self.dialog.set_transient_for(parent)
978+
979+ def run(self, error):
980+ self.dialog.format_secondary_markup(
981+ "The error was: \"%s\"" % error.strip())
982+ res = self.dialog.run()
983+ self.dialog.hide()
984+ return res
985+
986+ def on_button_settings_clicked(self, b, d=None):
987+ self.dialog.response(self.RESPONSE_SETTINGS)
988+
989+ def on_button_ignore_clicked(self, b, d=None):
990+ self.dialog.response(self.RESPONSE_IGNORE)
991
992=== added file 'softwareproperties/gtk/DialogUbuntuOne.py'
993--- softwareproperties/gtk/DialogUbuntuOne.py 1970-01-01 00:00:00 +0000
994+++ softwareproperties/gtk/DialogUbuntuOne.py 2017-12-14 14:42:25 +0000
995@@ -0,0 +1,162 @@
996+# dialog_cache_outdated.py - inform the user to update the apt cache
997+#
998+# Copyright (c) 2017 Canonical
999+#
1000+# Authors: Andrea Azzarone <andrea.azzarone@canonical.com>
1001+#
1002+# This program is free software; you can redistribute it and/or
1003+# modify it under the terms of the GNU General Public License as
1004+# published by the Free Software Foundation; either version 2 of the
1005+# License, or (at your option) any later version.
1006+#
1007+# This program is distributed in the hope that it will be useful,
1008+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1009+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1010+# GNU General Public License for more details.
1011+#
1012+# You should have received a copy of the GNU General Public License
1013+# along with this program; if not, write to the Free Software
1014+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1015+# USA
1016+
1017+import os
1018+import gi
1019+gi.require_version("Gtk", "3.0")
1020+from gi.repository import Gio, Gtk
1021+from gettext import gettext as _
1022+import re
1023+
1024+from softwareproperties.gtk.utils import (
1025+ setup_ui,
1026+)
1027+
1028+class DialogUbuntuOne:
1029+
1030+ def __init__(self, parent, datadir):
1031+ """setup up the gtk dialog"""
1032+ self.parent = parent
1033+
1034+ setup_ui(self, os.path.join(datadir, "gtkbuilder", "ubuntuone-dialog.ui"), domain="software-properties")
1035+
1036+ self.dialog = self.dialog_ubuntuone
1037+ self.dialog.use_header_bar = True
1038+ self.dialog.set_transient_for(parent)
1039+ self.dialog.set_default(self.next_button)
1040+
1041+ self.next_button.connect('clicked', self.__next_button_clicked_cb)
1042+ self.email_entry.connect('notify::text', self.__entry_edited_cb)
1043+ self.password_entry.connect('notify::text', self.__entry_edited_cb)
1044+ self.passcode_entry.connect('notify::text', self.__entry_edited_cb)
1045+ self.login_radio.connect('toggled', self.__radio_button_toggled_cb)
1046+ self.register_radio.connect('toggled', self.__radio_button_toggled_cb)
1047+ self.reset_radio.connect('toggled', self.__radio_button_toggled_cb)
1048+
1049+ self.__update_widgets()
1050+
1051+ def run(self, user_interaction_cb):
1052+ self.user_interaction_cb = user_interaction_cb
1053+ self.focus_default_entry()
1054+ res = self.dialog.run()
1055+ self.dialog.hide()
1056+ return res
1057+
1058+ def get_lp_username(self):
1059+ return self.email_entry.get_text()
1060+
1061+ def get_lp_password(self):
1062+ return self.password_entry.get_text()
1063+
1064+ def get_lp_passcode(self):
1065+ passcode = self.passcode_entry.get_text()
1066+ return passcode if passcode is not None and len(passcode) > 0 else None
1067+
1068+ def show_status(self, text, is_error):
1069+ self.status_stack.set_visible(True)
1070+ if is_error:
1071+ self.status_stack.set_visible_child_name('status-image')
1072+ self.status_image.set_from_icon_name('gtk-dialog-error', Gtk.IconSize.BUTTON)
1073+ else:
1074+ self.status_stack.set_visible_child_name('status-spinner')
1075+ self.status_label.set_text(text)
1076+ self.__update_widgets()
1077+
1078+ def focus_default_entry(self):
1079+ if self.page_stack.get_visible_child_name() == 'page-login':
1080+ self.email_entry.grab_focus()
1081+ elif self.page_stack.get_visible_child_name() == 'page-otp':
1082+ self.passcode_entry.grab_focus()
1083+
1084+ def reset_status(self):
1085+ self.status_stack.set_visible(False)
1086+ self.status_label.set_text("")
1087+
1088+ def ask_otp(self):
1089+ self.page_stack.set_visible_child_name('page-otp')
1090+ self.reset_status()
1091+ self.__update_widgets()
1092+
1093+ def show_success(self):
1094+ self.page_stack.set_visible_child_name('page-success')
1095+ self.reset_status()
1096+ self.__update_widgets()
1097+
1098+ # Widget logic
1099+ def __is_email_address(self, text):
1100+ return re.match(r'[^@]+@[^@]+\.[^@]+', text) is not None
1101+
1102+ def __update_widgets(self):
1103+ if self.page_stack.get_visible_child_name() == 'page-login':
1104+ self.email_entry.set_sensitive(True)
1105+ self.cancel_button.set_sensitive(True)
1106+ self.next_button.set_sensitive(
1107+ not self.login_radio.get_active() or
1108+ (self.__is_email_address(self.email_entry.get_text()) and
1109+ self.password_entry.get_text_length() > 0))
1110+ self.password_entry.set_sensitive(self.login_radio.get_active())
1111+ elif self.page_stack.get_visible_child_name() == 'page-otp':
1112+ self.passcode_entry.set_sensitive(True)
1113+ self.cancel_button.set_sensitive(True)
1114+ self.next_button.set_sensitive(self.passcode_entry.get_text_length())
1115+ elif self.page_stack.get_visible_child_name() == 'page-success':
1116+ self.cancel_button.set_visible(False)
1117+ self.next_button.set_label(_('Continue'))
1118+ self.next_button.set_sensitive(True)
1119+
1120+ # Widgets callbacks
1121+ def __send_login_request(self):
1122+ self.cancel_button.set_sensitive(False)
1123+ self.next_button.set_sensitive(False)
1124+ self.login_radio.set_sensitive(False)
1125+ self.register_radio.set_sensitive(False)
1126+ self.reset_radio.set_sensitive(False)
1127+ self.email_entry.set_sensitive(False)
1128+ self.passcode_entry.set_sensitive(False)
1129+ self.passcode_entry.set_sensitive(False)
1130+
1131+ self.show_status(_("Signing in…"), False)
1132+
1133+ data = {
1134+ 'email': self.get_lp_username(),
1135+ 'password': self.get_lp_password(),
1136+ 'otp': self.get_lp_passcode()
1137+ }
1138+ self.user_interaction_cb(self, data)
1139+
1140+ def __radio_button_toggled_cb(self, radio_button):
1141+ self.__update_widgets()
1142+
1143+ def __entry_edited_cb(self, entry, text):
1144+ self.__update_widgets()
1145+
1146+ def __next_button_clicked_cb(self, button):
1147+ if self.page_stack.get_visible_child_name() == 'page-login':
1148+ if self.login_radio.get_active():
1149+ self.__send_login_request()
1150+ elif self.register_radio.get_active():
1151+ Gio.AppInfo.launch_default_for_uri('https://login.ubuntu.com/+new_account')
1152+ elif self.reset_radio.get_active():
1153+ Gio.AppInfo.launch_default_for_uri('https://login.ubuntu.com/+forgot_password')
1154+ elif self.page_stack.get_visible_child_name() == 'page-otp':
1155+ self.__send_login_request()
1156+ elif self.page_stack.get_visible_child_name() == 'page-success':
1157+ self.dialog.response(Gtk.ResponseType.OK)
1158
1159=== added file 'softwareproperties/gtk/LivePatchTokenGetter.py'
1160--- softwareproperties/gtk/LivePatchTokenGetter.py 1970-01-01 00:00:00 +0000
1161+++ softwareproperties/gtk/LivePatchTokenGetter.py 2017-12-14 14:42:25 +0000
1162@@ -0,0 +1,192 @@
1163+# Copyright (c) 2017 Canonical Ltd.
1164+#
1165+# Author: Andrea Azzarone <andrea.azzarone@canonical.com>
1166+#
1167+# This program is free software; you can redistribute it and/or
1168+# modify it under the terms of the GNU General Public License as
1169+# published by the Free Software Foundation; either version 2 of the
1170+# License, or (at your option) any later version.
1171+#
1172+# This program is distributed in the hope that it will be useful,
1173+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1174+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1175+# GNU General Public License for more details.
1176+#
1177+# You should have received a copy of the GNU General Public License
1178+# along with this program; if not, write to the Free Software
1179+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1180+# USA
1181+
1182+from functools import partial
1183+import json
1184+import logging
1185+from urllib.parse import urlencode
1186+import threading
1187+
1188+import requests
1189+import macaroonbakery
1190+import pymacaroons
1191+from macaroonbakery import httpbakery
1192+
1193+from .DialogUbuntuOne import DialogUbuntuOne
1194+
1195+import gi
1196+gi.require_version("Gtk", "3.0")
1197+from gi.repository import Gtk, GLib
1198+from gettext import gettext as _
1199+
1200+
1201+LIVEPATCH_AUTH_ROOT_URL = 'https://auth.livepatch.canonical.com'
1202+UBUNTU_SSO_ROOT_URL = 'https://login.ubuntu.com'
1203+
1204+
1205+class AuthenticationCancelled(Exception):
1206+ def __init__(self):
1207+ super(AuthenticationCancelled, self).__init__('authentication cancelled by the user')
1208+
1209+
1210+class AuthenticationFailed(Exception):
1211+ def __init__(self, msg):
1212+ super(AuthenticationFailed, self).__init__('authentication failed: {}'.format(msg))
1213+
1214+
1215+
1216+class LivePatchTokenGetter(object):
1217+
1218+ def __init__(self, window_main, datadir):
1219+ cookies = requests.cookies.RequestsCookieJar()
1220+ self.session = requests.Session()
1221+
1222+ self.usso_macaroon_interactor = USSOMacaroonInteractor(self.session, window_main, datadir)
1223+ client = httpbakery.Client(interaction_methods=[self.usso_macaroon_interactor], cookies=cookies)
1224+
1225+ self.session.auth = client.auth()
1226+ self.session.cookies = cookies
1227+
1228+ def get_token(self):
1229+ url = '{}/api/v1/tokens?{}'.format(LIVEPATCH_AUTH_ROOT_URL, urlencode({'token_type': 'user'}))
1230+ response = self.session.get(url, headers={'User-Agent': 'software-properties'})
1231+ if response.ok:
1232+ data = response.json()
1233+ return self.usso_macaroon_interactor.lp_username, data['token']
1234+ elif response.status_code == 403:
1235+ return self.usso_macaroon_interactor.lp_username, None
1236+ else:
1237+ raise AuthenticationFailed('received {} response from server'.format(response.status_code))
1238+
1239+
1240+class USSOMacaroonInteractor(httpbakery.LegacyInteractor):
1241+
1242+ def __init__(self, session, window_main, datadir):
1243+ self.session = requests.Session()
1244+ self.window_main = window_main
1245+ self.datadir = datadir
1246+
1247+ def kind(self):
1248+ return "interactive"
1249+
1250+ def legacy_interact(self, client, location, visit_url):
1251+ usso_url = self.get_usso_macaroon_url(visit_url)
1252+ usso_macaroon = self.get_usso_macaroon(usso_url)
1253+ self.discharge_usso_macaroon(usso_macaroon, partial(self.complete_usso_macaroon_discharge, usso_url))
1254+
1255+ def get_usso_macaroon_url(self, url):
1256+ # find interaction methods for discharge
1257+ response = self.session.get(url, headers={'Accept': 'application/json'})
1258+ if not response.ok:
1259+ raise AuthenticationFailed('can not find interaction methods')
1260+
1261+ data = response.json()
1262+ # expect usso_macaroon interaction method
1263+ if 'usso_macaroon' not in data:
1264+ raise AuthenticationFailed('missing usso_macaroon interaction method')
1265+ return data['usso_macaroon']
1266+
1267+ def get_usso_macaroon(self, url):
1268+ response = self.session.get(url)
1269+ if not response.ok:
1270+ raise AuthenticationFailed('can not get usso macaroon')
1271+ return response.json()['macaroon']
1272+
1273+ def discharge_usso_macaroon(self, macaroon, macaroon_discharged_cb):
1274+ usso_caveats = [
1275+ cav['cid'] for cav in macaroon.get('caveats', [])
1276+ if cav.get('cl') == UBUNTU_SSO_ROOT_URL]
1277+ if not usso_caveats:
1278+ raise AuthenticationFailed('no valid usso caveat found')
1279+
1280+ data = {'caveat_id': usso_caveats[0]}
1281+
1282+ def discharge_macaroon_response_cb(dialog, response):
1283+ show_generic_error = False
1284+ if not response.ok:
1285+ if response.status_code == 401 or response.status_code == 403:
1286+ code = response.json().get('code')
1287+ if code == 'TWOFACTOR_REQUIRED':
1288+ dialog.ask_otp()
1289+ elif code == 'INVALID_CREDENTIALS':
1290+ dialog.show_status(_('Incorrect email or password'), True)
1291+ elif code == 'ACCOUNT_SUSPENDED':
1292+ dialog.show_status(_('Account suspended'), True)
1293+ elif code == 'ACCOUNT_DEACTIVATED':
1294+ dialog.show_status(_('Account deactivated'), True)
1295+ elif code == 'EMAIL_INVALIDATED':
1296+ dialog.show_status(_('Email invalidated'), True)
1297+ elif code == 'TWOFACTOR_FAILURE':
1298+ dialog.show_status(_('Two-factor authentication failed'), True)
1299+ elif code == 'PASSWORD_POLICY_ERROR':
1300+ dialog.show_status(_('Password reset required'), True)
1301+ elif code == 'TOO-MANY_REQUESTS':
1302+ dialog.show_status(_('Too many requests'), True)
1303+ else:
1304+ show_generic_error = True
1305+ else:
1306+ show_generic_error = True
1307+
1308+ if show_generic_error:
1309+ dialog.show_status(_("Authentication issue"), True)
1310+
1311+ dialog.focus_default_entry()
1312+ else:
1313+ self.lp_username = dialog.get_lp_username()
1314+ dialog.show_success()
1315+
1316+ root_m = macaroonbakery.Macaroon.deserialize_json(json.dumps(macaroon)).macaroon
1317+ discharge_m = pymacaroons.Macaroon.deserialize(response.json()['discharge_macaroon'])
1318+ bound_discharge = root_m.prepare_for_request(discharge_m)
1319+ macaroon_discharged_cb([root_m.serialize(), bound_discharge.serialize()])
1320+
1321+ def user_interaction_cb(dialog, new_data):
1322+ data.update(new_data)
1323+ self.discharge_macaroon_async(data, dialog, discharge_macaroon_response_cb)
1324+
1325+ dialog = DialogUbuntuOne(self.window_main, self.datadir)
1326+ dialog_response = dialog.run(user_interaction_cb)
1327+
1328+ if dialog_response != Gtk.ResponseType.OK:
1329+ raise AuthenticationCancelled()
1330+
1331+ def discharge_macaroon_async(self, data, dialog, callback):
1332+ def target_function():
1333+ try:
1334+ response = self.session.post(
1335+ '{}/api/v2/tokens/discharge'.format(UBUNTU_SSO_ROOT_URL),
1336+ data=json.dumps(data),
1337+ headers={'Content-Type': 'application/json'})
1338+ except Exception as e:
1339+ logging.error(e)
1340+ response = requests.models.Response()
1341+ response.status_code = 500
1342+ # Call callback in main thread
1343+ GLib.timeout_add(0, lambda data: callback(dialog, response), None)
1344+
1345+ t = threading.Thread(target=target_function)
1346+ t.start()
1347+
1348+ def complete_usso_macaroon_discharge(self, url, discharges):
1349+ data = {'macaroons': discharges}
1350+ response = self.session.post(
1351+ url, data=json.dumps(data),
1352+ headers={'Content-Type': 'application/json'})
1353+ if not response.ok:
1354+ raise AuthenticationFailed('can not complete usso macaroon discharge')
1355
1356=== modified file 'softwareproperties/gtk/SoftwarePropertiesGtk.py'
1357--- softwareproperties/gtk/SoftwarePropertiesGtk.py 2017-04-10 21:33:20 +0000
1358+++ softwareproperties/gtk/SoftwarePropertiesGtk.py 2017-12-14 14:42:25 +0000
1359@@ -40,7 +40,8 @@
1360 import gi
1361 gi.require_version("Gdk", "3.0")
1362 gi.require_version("Gtk", "3.0")
1363-from gi.repository import GObject, Gdk, Gtk, Gio, GLib
1364+gi.require_version('Secret', '1')
1365+from gi.repository import GObject, Gdk, Gtk, Gio, GLib, Secret
1366
1367 from .SimpleGtkbuilderApp import SimpleGtkbuilderApp
1368 from .DialogAdd import DialogAdd
1369@@ -48,6 +49,8 @@
1370 from .DialogEdit import DialogEdit
1371 from .DialogCacheOutdated import DialogCacheOutdated
1372 from .DialogAddSourcesList import DialogAddSourcesList
1373+from .DialogLivepatchError import DialogLivepatchError
1374+from .LivePatchTokenGetter import LivePatchTokenGetter, AuthenticationCancelled
1375
1376 import softwareproperties
1377 import softwareproperties.distro
1378@@ -78,6 +81,11 @@
1379 STORE_VISIBLE
1380 ) = list(range(5))
1381
1382+SECRETS_SCHEMA = Secret.Schema.new('com.ubuntu.SotwareProperties',
1383+ Secret.SchemaFlags.NONE,
1384+ {'key': Secret.SchemaAttributeType.STRING})
1385+
1386+INFINIT_DBUS_TIMEOUT = 0x7fffffff/1000
1387
1388 def error(parent_window, summary, msg):
1389 """ show a error dialog """
1390@@ -182,6 +190,8 @@
1391 self.show_distro()
1392 # Setup and show the Additonal Drivers tab
1393 self.init_drivers()
1394+ # Setup and show the LivePatch tab
1395+ self.init_livepatch()
1396
1397 # Connect to switch-page before setting initial tab. Otherwise the
1398 # first switch goes unnoticed.
1399@@ -283,7 +293,7 @@
1400
1401 def set_security_update_level(self):
1402 """Fetch the security level, Enable/Disable and set the value appropriately"""
1403-
1404+
1405 # Security Updates
1406 level_sec = self.get_update_automation_level()
1407 if level_sec == None:
1408@@ -298,7 +308,7 @@
1409 self.combobox_security_updates.set_active(1) # Download automatically
1410 elif level_sec == softwareproperties.UPDATE_INST_SEC:
1411 self.combobox_security_updates.set_active(2) # Download and install automatically
1412-
1413+
1414
1415 def init_distro(self):
1416 """Setup the user interface elements to represent the distro"""
1417@@ -493,7 +503,7 @@
1418 except dbus.DBusException as e:
1419 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1420 logging.error("Authentication canceled, changes have not been saved")
1421-
1422+
1423 combo_handler = self.handlers[self.combobox_security_updates]
1424 self.combobox_security_updates.handler_block(combo_handler)
1425 self.set_security_update_level()
1426@@ -520,13 +530,13 @@
1427 except dbus.DBusException as e:
1428 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1429 logging.error("Authentication canceled, changes have not been saved")
1430-
1431+
1432 combo_handler = self.handlers[self.combobox_release_upgrades]
1433 self.combobox_release_upgrades.handler_block(combo_handler)
1434 i = self.get_release_upgrades_policy()
1435 self.combobox_release_upgrades.set_active(i)
1436 self.combobox_release_upgrades.handler_unblock(combo_handler)
1437-
1438+
1439
1440 def on_combobox_server_changed(self, combobox):
1441 """
1442@@ -880,7 +890,7 @@
1443 except dbus.DBusException as e:
1444 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1445 logging.error("Authentication canceled, changes have not been saved")
1446-
1447+
1448 update_days = self.get_update_interval()
1449 combo_handler = self.handlers[self.combobox_update_interval]
1450 for key in self.combobox_interval_mapping:
1451@@ -1012,6 +1022,7 @@
1452 def on_delete_event(self, widget, args):
1453 """Close the window if requested"""
1454 self.on_close_button(widget)
1455+ return self.quit_when_livepatch_responds
1456
1457 def on_close_button(self, widget):
1458 """Show a dialog that a reload of the channel information is required
1459@@ -1021,7 +1032,11 @@
1460 d = DialogCacheOutdated(self.window_main,
1461 self.datadir)
1462 d.run()
1463- self.quit()
1464+ if self.waiting_livepatch_response:
1465+ self.quit_when_livepatch_responds = True
1466+ self.hide()
1467+ else:
1468+ self.quit()
1469
1470 def on_button_add_cdrom_clicked(self, widget):
1471 """ when a cdrom is requested for adding """
1472@@ -1429,3 +1444,158 @@
1473 % {'count': self.nonfree_drivers})
1474 else:
1475 self.label_driver_action.set_label(_("No proprietary drivers are in use."))
1476+
1477+ #
1478+ # Livepatch
1479+ #
1480+ def init_livepatch(self):
1481+ self.waiting_livepatch_response = False
1482+ self.quit_when_livepatch_responds = False
1483+
1484+ if not self.is_livepatch_supported():
1485+ self.grid_livepatch.set_visible(False)
1486+ return
1487+
1488+ lp_username, lp_token = self.get_livepatch_credentials()
1489+ self.set_livepatch_credentials(lp_username, lp_token, store=False)
1490+ self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1491+
1492+ file = Gio.File.new_for_path(path='/var/snap/canonical-livepatch/common/machine-token')
1493+ self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE)
1494+
1495+ self.handlers[self.checkbutton_livepatch] = \
1496+ self.checkbutton_livepatch.connect("toggled", self.on_checkbutton_livepatch_toggled)
1497+ self.handlers[self.button_ubuntuone] = \
1498+ self.button_ubuntuone.connect('clicked', self.on_button_ubuntuone_clicked)
1499+ self.handlers[self.lp_monitor] = \
1500+ self.lp_monitor.connect('changed', self.on_livepatch_status_changed)
1501+
1502+ def is_livepatch_supported(self):
1503+ supported_releases = ['trusty', 'xenial']
1504+ return any([self.distro.is_codename(d) for d in supported_releases])
1505+
1506+ def get_livepatch_credentials(self):
1507+ lp_username = Secret.password_lookup_sync(SECRETS_SCHEMA, {'key': 'livepatch-username'}, None)
1508+ lp_token = Secret.password_lookup_sync(SECRETS_SCHEMA, {'key': 'livepatch-token'}, None)
1509+ return lp_username, lp_token
1510+
1511+ def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type):
1512+ if not self.waiting_livepatch_response:
1513+ self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1514+
1515+ def set_livepatch_credentials(self, lp_username, lp_token, store):
1516+ self.lp_username = lp_username
1517+ self.lp_token = lp_token
1518+
1519+ if self.lp_username:
1520+ self.button_ubuntuone.set_label(_('Sign Out'))
1521+
1522+ if self.lp_token:
1523+ self.checkbutton_livepatch.set_sensitive(True)
1524+ self.label_livepatch_login.set_label(_('Signed in as %s' % self.lp_username))
1525+ else:
1526+ self.checkbutton_livepatch.set_sensitive(False)
1527+ text = _('%s isn\'t authorized to use Livepatch.' % self.lp_username)
1528+ text = "<span color='red'>" + text + "</span>"
1529+ self.label_livepatch_login.set_markup(text)
1530+ else:
1531+ self.lp_token = None
1532+ self.checkbutton_livepatch.set_sensitive(False)
1533+ self.button_ubuntuone.set_label(_('Sign In…'))
1534+ self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.'))
1535+
1536+
1537+ if store:
1538+ if self.lp_username:
1539+ Secret.password_store(SECRETS_SCHEMA, {'key': 'livepatch-username'}, None, 'com.ubuntu.SoftwareProperties', self.lp_username)
1540+ else:
1541+ Secret.password_clear(SECRETS_SCHEMA, {'key': 'livepatch-username'}, None, None, None)
1542+
1543+ if self.lp_token:
1544+ Secret.password_store(SECRETS_SCHEMA, {'key': 'livepatch-token'}, None, 'com.ubuntu.SoftwareProperties', self.lp_token)
1545+ else:
1546+ Secret.password_clear(SECRETS_SCHEMA, {'key': 'livepatch-token'}, None, None, None)
1547+
1548+ def on_button_ubuntuone_clicked(self, button):
1549+ if self.is_user_logged():
1550+ self.do_logout()
1551+ else:
1552+ self.do_login()
1553+
1554+ def is_user_logged(self):
1555+ return self.lp_username is not None
1556+
1557+ def do_login(self):
1558+ try:
1559+ lp_token_getter = LivePatchTokenGetter(self.window_main, self.datadir)
1560+ lp_username, lp_token = lp_token_getter.get_token()
1561+ except AuthenticationCancelled:
1562+ pass
1563+ except Exception as e:
1564+ logging.error(e)
1565+ error(self.window_main,
1566+ _("Error enabling Canonical Livepatch"),
1567+ _("Please check your Internet connection."))
1568+ else:
1569+ self.set_livepatch_credentials(lp_username, lp_token, store=True)
1570+ if lp_username and lp_token:
1571+ self.checkbutton_livepatch.set_active(True)
1572+
1573+ def do_logout(self):
1574+ self.set_livepatch_credentials(None, None, store=True)
1575+ self.checkbutton_livepatch.set_active(False)
1576+
1577+ def on_checkbutton_livepatch_toggled(self, checkbutton):
1578+ if self.waiting_livepatch_response:
1579+ return
1580+
1581+ self.waiting_livepatch_response = True
1582+
1583+ if self.checkbutton_livepatch.get_active():
1584+ self.backend.SetLivepatchEnabled(True, self.lp_token if self.lp_token else '',
1585+ reply_handler=self.livepatch_enabled_reply_handler,
1586+ error_handler=self.livepatch_enabled_error_handler,
1587+ timeout=INFINIT_DBUS_TIMEOUT)
1588+ else:
1589+ self.backend.SetLivepatchEnabled(False, '',
1590+ reply_handler=self.livepatch_enabled_reply_handler,
1591+ error_handler=self.livepatch_enabled_error_handler,
1592+ timeout=INFINIT_DBUS_TIMEOUT)
1593+
1594+ def livepatch_enabled_reply_handler(self, is_error, prompt):
1595+ self.sync_checkbutton_livepatch(is_error, prompt)
1596+
1597+ def livepatch_enabled_error_handler(self, e):
1598+ if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1599+ logging.error("Authentication canceled, changes have not been saved")
1600+ self.sync_checkbutton_livepatch(is_error=True, prompt=None)
1601+ else:
1602+ self.sync_checkbutton_livepatch(is_error=True, prompt=str(e))
1603+
1604+ def sync_checkbutton_livepatch(self, is_error, prompt):
1605+ if is_error:
1606+ self.waiting_livepatch_response = False
1607+ self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch])
1608+ self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1609+ self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch])
1610+
1611+ if prompt:
1612+ dialog = DialogLivepatchError(self.window_main, self.datadir)
1613+ response = dialog.run(prompt)
1614+ if response == DialogLivepatchError.RESPONSE_SETTINGS:
1615+ self.window_main.show()
1616+ self.quit_when_livepatch_responds = False
1617+ else:
1618+ if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active():
1619+ self.backend.SetLivepatchEnabled(False, '',
1620+ reply_handler=self.livepatch_enabled_reply_handler,
1621+ error_handler=self.livepatch_enabled_error_handler)
1622+ elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active():
1623+ self.backend.SetLivepatchEnabled(True, self.lp_token if self.lp_token else '',
1624+ reply_handler=self.livepatch_enabled_reply_handler,
1625+ error_handler=self.livepatch_enabled_error_handler)
1626+ else:
1627+ self.waiting_livepatch_response = False
1628+
1629+ if self.quit_when_livepatch_responds:
1630+ self.on_close_button(self.button_close)

Subscribers

People subscribed via source and target branches

to status/vote changes: