Status: | Merged |
---|---|
Merged at revision: | 5933 |
Proposed branch: | lp:~mvo/ubiquity/ssologin |
Merge into: | lp:ubiquity |
Diff against target: |
1200 lines (+1122/-6) 8 files modified
debian/ubiquity.templates (+16/-0) gui/gtk/stepUbuntuOne.ui (+451/-0) plugin-viewer-gtk.py (+87/-0) scripts/plugininstall.py (+10/-5) scripts/ubuntuone-keyring-helper (+31/-0) tests/test_ubi_ubuntuone.py (+144/-0) ubiquity/gtkwidgets.py (+5/-1) ubiquity/plugins/ubi-ubuntuone.py (+378/-0) |
To merge this branch: | bzr merge lp:~mvo/ubiquity/ssologin |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Installer Team | Pending | ||
Review via email: mp+137264@code.launchpad.net |
Commit message
Description of the change
This branch adds a "Ubuntu ONE" login page to the install and moves the oauth token into the users login keyring.
At this point its WIP and what is still missing is:
- a API for registering new users without a capture (or with a delayed capture)
- approval for the UI design
- the lp:~mvo/+junk/cli-sso-login helper binary must go into a package (like ubuntu-sso-login)
- review from a ubiquity developer to get all the ubiquity details right
- kill(?) plugin-viewer, I mostly used it to explore the system
But its work for existing logins and the token get added to the users login keyring.
Michael Vogt (mvo) wrote : | # |
Stéphane Graber (stgraber) wrote : | # |
Some comments:
- You appear to have a duplicate ubiquity/
- KEYRING_FILE shouldn't hardcode the live username as the livefs username changes depending on the flavour or casper configuration.
- I'm surprised not to see any added dependencies to debian/control? Does ubiquity already depend on gnome-keyring and the other libraries you're using?
A few more thoughts:
- Did you test your plugin in pre-seeded "automatic" mode? My guess is that it should be skipped in such case.
- What happens when the user reinstalls the system (keeping /home intact) instead of doing a clean install?
I must admit having only very quickly read through the code, so there very well may be more
- 5806. By Michael Vogt
-
debian/
ubiquity. templates: remove duplicated ubiquity/ text/ubuntuone_ heading_ label - thanks Stephane - 5807. By Michael Vogt
-
do not hardcode KEYRING_FILE but get from uid 999 instead, thanks to Stephane
- 5808. By Michael Vogt
-
skip copy of the keyring if the target path is already there, thanks to Stephane
- 5809. By Michael Vogt
-
ubiquity/
plugins/ ubi-ubuntuone. py: do not create the page if in UBIQUITY_AUTOMATIC mode (or if the user has UBIQUITY_NO_SSO set). also skip if GnomeKeyring can not be imported
Michael Vogt (mvo) wrote : | # |
Thanks a lot Stephane!
that is very helpful feedback and I addressed the points you raised now.
The only thing I'm unsure about is the dependencies. Right now the regular ubuntu desktop CD has
everything needed and I'm not sure what the policy should be for the other desktop environments, i.e.
I don't know if xubuntu has a gnome-keyring-
So I changed the code to simply skip the page if "from gi.repository import GnomeKeyring" fails.
Michael Vogt (mvo) wrote : | # |
Setting back to WIP based on the recent discussions around if this actually should be part of the installer of if it should go in as a first-run wizard.
Preview Diff
1 | === modified file 'debian/ubiquity.templates' |
2 | --- debian/ubiquity.templates 2012-10-19 14:27:05 +0000 |
3 | +++ debian/ubiquity.templates 2012-12-04 15:21:26 +0000 |
4 | @@ -149,6 +149,22 @@ |
5 | Type: text |
6 | _Description: Take Photo |
7 | |
8 | +Template: ubiquity/text/ubuntuone_heading_label |
9 | +Type: text |
10 | +_Description: Setup Ubuntu ONE |
11 | + |
12 | +Template: ubiquity/text/error_register |
13 | +Type: text |
14 | +_Description: Error registering account |
15 | + |
16 | +Template: ubiquity/text/error_login |
17 | +Type: text |
18 | +_Description: Error loging into the account |
19 | + |
20 | +Template: ubiquity/text/email_inactive_label |
21 | +Type: text |
22 | +_Description: Enter your E-Mail address |
23 | + |
24 | Template: ubiquity/text/fullname_label |
25 | Type: text |
26 | _Description: Your name: |
27 | |
28 | === added file 'gui/gtk/stepUbuntuOne.ui' |
29 | --- gui/gtk/stepUbuntuOne.ui 1970-01-01 00:00:00 +0000 |
30 | +++ gui/gtk/stepUbuntuOne.ui 2012-12-04 15:21:26 +0000 |
31 | @@ -0,0 +1,451 @@ |
32 | +<?xml version="1.0" encoding="UTF-8"?> |
33 | +<interface> |
34 | + <!-- interface-requires gtk+ 3.0 --> |
35 | + <object class="GtkBox" id="stepUbuntuOne"> |
36 | + <property name="visible">True</property> |
37 | + <property name="can_focus">False</property> |
38 | + <property name="orientation">vertical</property> |
39 | + <child> |
40 | + <object class="GtkNotebook" id="notebook_main"> |
41 | + <property name="visible">True</property> |
42 | + <property name="can_focus">True</property> |
43 | + <property name="show_border">False</property> |
44 | + <child> |
45 | + <object class="GtkAlignment" id="alignment3"> |
46 | + <property name="visible">True</property> |
47 | + <property name="can_focus">False</property> |
48 | + <property name="top_padding">6</property> |
49 | + <property name="bottom_padding">6</property> |
50 | + <property name="left_padding">6</property> |
51 | + <property name="right_padding">6</property> |
52 | + <child> |
53 | + <object class="GtkBox" id="box2"> |
54 | + <property name="visible">True</property> |
55 | + <property name="can_focus">False</property> |
56 | + <property name="orientation">vertical</property> |
57 | + <property name="spacing">18</property> |
58 | + <child> |
59 | + <object class="GtkLabel" id="label4"> |
60 | + <property name="visible">True</property> |
61 | + <property name="can_focus">False</property> |
62 | + <property name="xalign">0</property> |
63 | + <property name="label" translatable="yes">Ubuntu ONE</property> |
64 | + <attributes> |
65 | + <attribute name="weight" value="bold"/> |
66 | + </attributes> |
67 | + </object> |
68 | + <packing> |
69 | + <property name="expand">False</property> |
70 | + <property name="fill">True</property> |
71 | + <property name="position">0</property> |
72 | + </packing> |
73 | + </child> |
74 | + <child> |
75 | + <object class="GtkLabel" id="label10"> |
76 | + <property name="visible">True</property> |
77 | + <property name="can_focus">False</property> |
78 | + <property name="xalign">0</property> |
79 | + <property name="label" translatable="yes">Ubuntu ONE allows you to store, share and sync your files accross multiple plattforms...</property> |
80 | + </object> |
81 | + <packing> |
82 | + <property name="expand">False</property> |
83 | + <property name="fill">True</property> |
84 | + <property name="position">1</property> |
85 | + </packing> |
86 | + </child> |
87 | + <child> |
88 | + <object class="GtkGrid" id="grid1"> |
89 | + <property name="visible">True</property> |
90 | + <property name="can_focus">False</property> |
91 | + <property name="row_spacing">6</property> |
92 | + <property name="column_spacing">6</property> |
93 | + <child> |
94 | + <object class="GtkEntry" id="entry_email"> |
95 | + <property name="visible">True</property> |
96 | + <property name="can_focus">True</property> |
97 | + <property name="invisible_char">•</property> |
98 | + <property name="invisible_char_set">True</property> |
99 | + <signal name="changed" handler="info_loop" swapped="no"/> |
100 | + </object> |
101 | + <packing> |
102 | + <property name="left_attach">1</property> |
103 | + <property name="top_attach">0</property> |
104 | + <property name="width">1</property> |
105 | + <property name="height">1</property> |
106 | + </packing> |
107 | + </child> |
108 | + <child> |
109 | + <object class="GtkLabel" id="label6"> |
110 | + <property name="visible">True</property> |
111 | + <property name="can_focus">False</property> |
112 | + <property name="label" translatable="yes">Your E-Mail:</property> |
113 | + </object> |
114 | + <packing> |
115 | + <property name="left_attach">0</property> |
116 | + <property name="top_attach">0</property> |
117 | + <property name="width">1</property> |
118 | + <property name="height">1</property> |
119 | + </packing> |
120 | + </child> |
121 | + <child> |
122 | + <object class="GtkLabel" id="label11"> |
123 | + <property name="visible">True</property> |
124 | + <property name="can_focus">False</property> |
125 | + <property name="label" translatable="yes">Password:</property> |
126 | + </object> |
127 | + <packing> |
128 | + <property name="left_attach">0</property> |
129 | + <property name="top_attach">1</property> |
130 | + <property name="width">1</property> |
131 | + <property name="height">1</property> |
132 | + </packing> |
133 | + </child> |
134 | + <child> |
135 | + <object class="GtkLabel" id="label12"> |
136 | + <property name="visible">True</property> |
137 | + <property name="can_focus">False</property> |
138 | + <property name="label" translatable="yes">Password:</property> |
139 | + </object> |
140 | + <packing> |
141 | + <property name="left_attach">0</property> |
142 | + <property name="top_attach">2</property> |
143 | + <property name="width">1</property> |
144 | + <property name="height">1</property> |
145 | + </packing> |
146 | + </child> |
147 | + <child> |
148 | + <object class="GtkEntry" id="entry_new_password"> |
149 | + <property name="visible">True</property> |
150 | + <property name="can_focus">True</property> |
151 | + <property name="visibility">False</property> |
152 | + <property name="invisible_char">•</property> |
153 | + <signal name="changed" handler="info_loop" swapped="no"/> |
154 | + </object> |
155 | + <packing> |
156 | + <property name="left_attach">1</property> |
157 | + <property name="top_attach">1</property> |
158 | + <property name="width">1</property> |
159 | + <property name="height">1</property> |
160 | + </packing> |
161 | + </child> |
162 | + <child> |
163 | + <object class="GtkEntry" id="entry_new_password2"> |
164 | + <property name="visible">True</property> |
165 | + <property name="can_focus">True</property> |
166 | + <property name="visibility">False</property> |
167 | + <property name="invisible_char">•</property> |
168 | + <signal name="changed" handler="info_loop" swapped="no"/> |
169 | + </object> |
170 | + <packing> |
171 | + <property name="left_attach">1</property> |
172 | + <property name="top_attach">2</property> |
173 | + <property name="width">1</property> |
174 | + <property name="height">1</property> |
175 | + </packing> |
176 | + </child> |
177 | + </object> |
178 | + <packing> |
179 | + <property name="expand">False</property> |
180 | + <property name="fill">True</property> |
181 | + <property name="position">2</property> |
182 | + </packing> |
183 | + </child> |
184 | + <child> |
185 | + <object class="GtkBox" id="box4"> |
186 | + <property name="visible">True</property> |
187 | + <property name="can_focus">False</property> |
188 | + <property name="orientation">vertical</property> |
189 | + <property name="spacing">12</property> |
190 | + <child> |
191 | + <object class="GtkBox" id="box1"> |
192 | + <property name="visible">True</property> |
193 | + <property name="can_focus">False</property> |
194 | + <child> |
195 | + <object class="GtkLinkButton" id="linkbutton_have_account"> |
196 | + <property name="label" translatable="yes">I'm already a registered user</property> |
197 | + <property name="visible">True</property> |
198 | + <property name="can_focus">True</property> |
199 | + <property name="receives_default">True</property> |
200 | + <property name="has_tooltip">True</property> |
201 | + <property name="relief">none</property> |
202 | + <signal name="clicked" handler="on_button_have_account_clicked" swapped="no"/> |
203 | + </object> |
204 | + <packing> |
205 | + <property name="expand">False</property> |
206 | + <property name="fill">True</property> |
207 | + <property name="position">0</property> |
208 | + </packing> |
209 | + </child> |
210 | + <child> |
211 | + <placeholder/> |
212 | + </child> |
213 | + </object> |
214 | + <packing> |
215 | + <property name="expand">False</property> |
216 | + <property name="fill">True</property> |
217 | + <property name="position">0</property> |
218 | + </packing> |
219 | + </child> |
220 | + <child> |
221 | + <object class="GtkBox" id="box5"> |
222 | + <property name="visible">True</property> |
223 | + <property name="can_focus">False</property> |
224 | + <child> |
225 | + <object class="GtkLinkButton" id="linkbutton_skip_account"> |
226 | + <property name="label" translatable="yes">Skip this step</property> |
227 | + <property name="visible">True</property> |
228 | + <property name="can_focus">True</property> |
229 | + <property name="receives_default">True</property> |
230 | + <property name="has_tooltip">True</property> |
231 | + <property name="relief">none</property> |
232 | + <signal name="clicked" handler="on_button_skip_account_clicked" swapped="no"/> |
233 | + </object> |
234 | + <packing> |
235 | + <property name="expand">False</property> |
236 | + <property name="fill">True</property> |
237 | + <property name="position">0</property> |
238 | + </packing> |
239 | + </child> |
240 | + <child> |
241 | + <placeholder/> |
242 | + </child> |
243 | + </object> |
244 | + <packing> |
245 | + <property name="expand">False</property> |
246 | + <property name="fill">True</property> |
247 | + <property name="position">1</property> |
248 | + </packing> |
249 | + </child> |
250 | + </object> |
251 | + <packing> |
252 | + <property name="expand">False</property> |
253 | + <property name="fill">False</property> |
254 | + <property name="position">3</property> |
255 | + </packing> |
256 | + </child> |
257 | + </object> |
258 | + </child> |
259 | + </object> |
260 | + </child> |
261 | + <child type="tab"> |
262 | + <object class="GtkLabel" id="label1"> |
263 | + <property name="visible">True</property> |
264 | + <property name="can_focus">False</property> |
265 | + <property name="label" translatable="yes">page 1</property> |
266 | + </object> |
267 | + <packing> |
268 | + <property name="tab_fill">False</property> |
269 | + </packing> |
270 | + </child> |
271 | + <child> |
272 | + <object class="GtkAlignment" id="alignment4"> |
273 | + <property name="visible">True</property> |
274 | + <property name="can_focus">False</property> |
275 | + <property name="top_padding">12</property> |
276 | + <property name="bottom_padding">12</property> |
277 | + <property name="left_padding">12</property> |
278 | + <property name="right_padding">12</property> |
279 | + <child> |
280 | + <object class="GtkBox" id="box6"> |
281 | + <property name="visible">True</property> |
282 | + <property name="can_focus">False</property> |
283 | + <property name="orientation">vertical</property> |
284 | + <property name="spacing">12</property> |
285 | + <child> |
286 | + <object class="GtkLabel" id="label5"> |
287 | + <property name="visible">True</property> |
288 | + <property name="can_focus">False</property> |
289 | + <property name="xalign">0</property> |
290 | + <property name="label" translatable="yes">Connect to Ubuntu ONE</property> |
291 | + <attributes> |
292 | + <attribute name="weight" value="bold"/> |
293 | + </attributes> |
294 | + </object> |
295 | + <packing> |
296 | + <property name="expand">False</property> |
297 | + <property name="fill">True</property> |
298 | + <property name="position">0</property> |
299 | + </packing> |
300 | + </child> |
301 | + <child> |
302 | + <object class="GtkAlignment" id="alignment1"> |
303 | + <property name="visible">True</property> |
304 | + <property name="can_focus">False</property> |
305 | + <property name="yalign">1</property> |
306 | + <property name="xscale">0.5</property> |
307 | + <property name="yscale">0.5</property> |
308 | + <child> |
309 | + <object class="GtkGrid" id="grid2"> |
310 | + <property name="visible">True</property> |
311 | + <property name="can_focus">False</property> |
312 | + <property name="column_spacing">6</property> |
313 | + <child> |
314 | + <object class="GtkEntry" id="entry_existing_email"> |
315 | + <property name="visible">True</property> |
316 | + <property name="can_focus">True</property> |
317 | + <property name="invisible_char">•</property> |
318 | + <property name="invisible_char_set">True</property> |
319 | + <signal name="changed" handler="info_loop" swapped="no"/> |
320 | + </object> |
321 | + <packing> |
322 | + <property name="left_attach">1</property> |
323 | + <property name="top_attach">0</property> |
324 | + <property name="width">1</property> |
325 | + <property name="height">1</property> |
326 | + </packing> |
327 | + </child> |
328 | + <child> |
329 | + <object class="GtkEntry" id="entry_existing_password"> |
330 | + <property name="visible">True</property> |
331 | + <property name="can_focus">True</property> |
332 | + <property name="visibility">False</property> |
333 | + <property name="invisible_char">•</property> |
334 | + <property name="invisible_char_set">True</property> |
335 | + <signal name="changed" handler="info_loop" swapped="no"/> |
336 | + </object> |
337 | + <packing> |
338 | + <property name="left_attach">1</property> |
339 | + <property name="top_attach">1</property> |
340 | + <property name="width">1</property> |
341 | + <property name="height">1</property> |
342 | + </packing> |
343 | + </child> |
344 | + <child> |
345 | + <object class="GtkLabel" id="label8"> |
346 | + <property name="visible">True</property> |
347 | + <property name="can_focus">False</property> |
348 | + <property name="label" translatable="yes">E-Mail:</property> |
349 | + </object> |
350 | + <packing> |
351 | + <property name="left_attach">0</property> |
352 | + <property name="top_attach">0</property> |
353 | + <property name="width">1</property> |
354 | + <property name="height">1</property> |
355 | + </packing> |
356 | + </child> |
357 | + <child> |
358 | + <object class="GtkLabel" id="label9"> |
359 | + <property name="visible">True</property> |
360 | + <property name="can_focus">False</property> |
361 | + <property name="label" translatable="yes">Password:</property> |
362 | + </object> |
363 | + <packing> |
364 | + <property name="left_attach">0</property> |
365 | + <property name="top_attach">1</property> |
366 | + <property name="width">1</property> |
367 | + <property name="height">1</property> |
368 | + </packing> |
369 | + </child> |
370 | + </object> |
371 | + </child> |
372 | + </object> |
373 | + <packing> |
374 | + <property name="expand">True</property> |
375 | + <property name="fill">True</property> |
376 | + <property name="position">1</property> |
377 | + </packing> |
378 | + </child> |
379 | + <child> |
380 | + <object class="GtkBox" id="box3"> |
381 | + <property name="visible">True</property> |
382 | + <property name="can_focus">False</property> |
383 | + <child> |
384 | + <object class="GtkLinkButton" id="linkbutton_need_account"> |
385 | + <property name="label" translatable="yes">I need to create a new account</property> |
386 | + <property name="visible">True</property> |
387 | + <property name="can_focus">True</property> |
388 | + <property name="receives_default">True</property> |
389 | + <property name="has_tooltip">True</property> |
390 | + <property name="relief">none</property> |
391 | + <signal name="clicked" handler="on_button_need_account_clicked" swapped="no"/> |
392 | + </object> |
393 | + <packing> |
394 | + <property name="expand">False</property> |
395 | + <property name="fill">True</property> |
396 | + <property name="position">0</property> |
397 | + </packing> |
398 | + </child> |
399 | + <child> |
400 | + <placeholder/> |
401 | + </child> |
402 | + </object> |
403 | + <packing> |
404 | + <property name="expand">False</property> |
405 | + <property name="fill">True</property> |
406 | + <property name="position">2</property> |
407 | + </packing> |
408 | + </child> |
409 | + </object> |
410 | + </child> |
411 | + </object> |
412 | + <packing> |
413 | + <property name="position">1</property> |
414 | + </packing> |
415 | + </child> |
416 | + <child type="tab"> |
417 | + <object class="GtkLabel" id="label2"> |
418 | + <property name="visible">True</property> |
419 | + <property name="can_focus">False</property> |
420 | + <property name="label" translatable="yes">page 2</property> |
421 | + </object> |
422 | + <packing> |
423 | + <property name="position">1</property> |
424 | + <property name="tab_fill">False</property> |
425 | + </packing> |
426 | + </child> |
427 | + <child> |
428 | + <object class="GtkAspectFrame" id="aspectframe1"> |
429 | + <property name="visible">True</property> |
430 | + <property name="can_focus">False</property> |
431 | + <property name="label_xalign">0</property> |
432 | + <property name="shadow_type">none</property> |
433 | + <child> |
434 | + <object class="GtkSpinner" id="spinner_connect"> |
435 | + <property name="height_request">32</property> |
436 | + <property name="visible">True</property> |
437 | + <property name="can_focus">False</property> |
438 | + </object> |
439 | + </child> |
440 | + </object> |
441 | + <packing> |
442 | + <property name="position">2</property> |
443 | + </packing> |
444 | + </child> |
445 | + <child type="tab"> |
446 | + <object class="GtkLabel" id="label3"> |
447 | + <property name="visible">True</property> |
448 | + <property name="can_focus">False</property> |
449 | + <property name="label" translatable="yes">page 3</property> |
450 | + </object> |
451 | + <packing> |
452 | + <property name="position">2</property> |
453 | + <property name="tab_fill">False</property> |
454 | + </packing> |
455 | + </child> |
456 | + </object> |
457 | + <packing> |
458 | + <property name="expand">False</property> |
459 | + <property name="fill">True</property> |
460 | + <property name="position">0</property> |
461 | + </packing> |
462 | + </child> |
463 | + <child> |
464 | + <object class="GtkLabel" id="label_global_error"> |
465 | + <property name="visible">True</property> |
466 | + <property name="can_focus">False</property> |
467 | + <property name="xalign">0</property> |
468 | + <property name="xpad">12</property> |
469 | + <property name="ypad">12</property> |
470 | + <property name="label" translatable="yes">error label</property> |
471 | + </object> |
472 | + <packing> |
473 | + <property name="expand">False</property> |
474 | + <property name="fill">True</property> |
475 | + <property name="position">1</property> |
476 | + </packing> |
477 | + </child> |
478 | + <child> |
479 | + <placeholder/> |
480 | + </child> |
481 | + </object> |
482 | +</interface> |
483 | |
484 | === added file 'plugin-viewer-gtk.py' |
485 | --- plugin-viewer-gtk.py 1970-01-01 00:00:00 +0000 |
486 | +++ plugin-viewer-gtk.py 2012-12-04 15:21:26 +0000 |
487 | @@ -0,0 +1,87 @@ |
488 | +#!/usr/bin/python3 |
489 | + |
490 | +import os |
491 | +import sys |
492 | + |
493 | +from gi.repository import Gtk |
494 | + |
495 | +# we could use this as the base for the MockController as well |
496 | +# from ubiquity.frontend.base import Controller |
497 | + |
498 | + |
499 | +class MockController(object): |
500 | + |
501 | + def __init__(self, parent): |
502 | + self.parent = parent |
503 | + self.oem_user_config = None |
504 | + self.oem_config = None |
505 | + self.dbfilter = None |
506 | + self._allow_go_foward = True |
507 | + self._allow_go_backward = True |
508 | + |
509 | + def go_forward(self): |
510 | + self.parent.button_next.clicked() |
511 | + |
512 | + def get_string(self, s, lang): |
513 | + return "get_string: %s (lang=%s)" % (s, lang) |
514 | + |
515 | + def add_builder(self, builder): |
516 | + pass |
517 | + |
518 | + def allow_go_forward(self, v): |
519 | + self._allow_go_forward = v |
520 | + self.parent.button_next.set_sensitive(v) |
521 | + |
522 | + def allow_go_backward(self, v): |
523 | + self._allow_go_backward = v |
524 | + self.parent.button_back.set_sensitive(v) |
525 | + |
526 | + |
527 | +if __name__ == "__main__": |
528 | + """ Run with: |
529 | + ./plugin-viewer-gtk.py ubi-ubuntuone |
530 | + """ |
531 | + def _on_button_next_clicked(button): |
532 | + stop = page_gtk.plugin_on_next_clicked() |
533 | + if not stop: |
534 | + Gtk.main_quit() |
535 | + |
536 | + # setup env |
537 | + for envvar, path in ( |
538 | + ("UBIQUITY_PLUGIN_PATH", "./ubiquity/plugins"), |
539 | + ("UBIQUITY_GLADE", "./gui/gtk")): |
540 | + if os.path.exists(path): |
541 | + os.environ[envvar] = path |
542 | + # ... and then import the plugin_manager |
543 | + from ubiquity.plugin_manager import load_plugin |
544 | + |
545 | + plugin_name = sys.argv[1] |
546 | + plugin_module = load_plugin(plugin_name) |
547 | + |
548 | + win = Gtk.Window() |
549 | + win.button_next = Gtk.Button("next") |
550 | + win.button_back = Gtk.Button("back") |
551 | + |
552 | + mock_controller = MockController(win) |
553 | + page_gtk = plugin_module.PageGtk(mock_controller) |
554 | + page_gtk.plugin_translate("en") |
555 | + |
556 | + win.button_next.connect( |
557 | + "clicked", _on_button_next_clicked) |
558 | + win.button_back.connect( |
559 | + "clicked", lambda b: page_gtk.plugin_on_back_clicked()) |
560 | + |
561 | + button_box = Gtk.ButtonBox(spacing=12) |
562 | + button_box.set_layout(Gtk.ButtonBoxStyle.END) |
563 | + button_box.pack_start(win.button_back, True, True, 6) |
564 | + button_box.pack_start(win.button_next, True, True, 6) |
565 | + |
566 | + box = Gtk.VBox() |
567 | + box.pack_start(page_gtk.page, True, True, 6) |
568 | + box.pack_start(button_box, True, True, 6) |
569 | + |
570 | + win.add(box) |
571 | + win.connect("destroy", Gtk.main_quit) |
572 | + win.show_all() |
573 | + |
574 | + Gtk.main() |
575 | |
576 | === modified file 'scripts/plugininstall.py' |
577 | --- scripts/plugininstall.py 2012-11-22 16:27:21 +0000 |
578 | +++ scripts/plugininstall.py 2012-12-04 15:21:26 +0000 |
579 | @@ -312,9 +312,8 @@ |
580 | |
581 | self.db.progress('SET', self.end) |
582 | |
583 | - def configure_face(self): |
584 | - PHOTO_PATH = '/var/lib/ubiquity/webcam_photo.png' |
585 | - target_user = self.db.get('passwd/username') |
586 | + def _get_uid_gid_on_target(self, target_user): |
587 | + """Helper that gets the uid/gid of the username in the target chroot""" |
588 | uid = subprocess.Popen( |
589 | ['chroot', self.target, 'sudo', '-u', target_user, '--', |
590 | 'id', '-u'], stdout=subprocess.PIPE, universal_newlines=True) |
591 | @@ -327,8 +326,14 @@ |
592 | uid = int(uid) |
593 | gid = int(gid) |
594 | except ValueError: |
595 | - return |
596 | - if os.path.exists(PHOTO_PATH): |
597 | + return (None, None) |
598 | + return uid, gid |
599 | + |
600 | + def configure_face(self): |
601 | + PHOTO_PATH = '/var/lib/ubiquity/webcam_photo.png' |
602 | + target_user = self.db.get('passwd/username') |
603 | + uid, gid = self._get_uid_gid_on_target(target_user) |
604 | + if os.path.exists(PHOTO_PATH) and uid and gid: |
605 | targetpath = self.target_file('home', target_user, '.face') |
606 | shutil.copy2(PHOTO_PATH, targetpath) |
607 | os.lchown(targetpath, uid, gid) |
608 | |
609 | === added file 'scripts/ubuntuone-keyring-helper' |
610 | --- scripts/ubuntuone-keyring-helper 1970-01-01 00:00:00 +0000 |
611 | +++ scripts/ubuntuone-keyring-helper 2012-12-04 15:21:26 +0000 |
612 | @@ -0,0 +1,31 @@ |
613 | +#!/usr/bin/python |
614 | + |
615 | +import os |
616 | +import syslog |
617 | +import sys |
618 | + |
619 | +from gi.repository import GnomeKeyring, GLib |
620 | + |
621 | +if __name__ == "__main__": |
622 | + |
623 | + if not "DBUS_SESSION_BUS_ADDRESS" in os.environ: |
624 | + os.environ["DBUS_SESSION_BUS_ADDRESS"] = "autolaunch:" |
625 | + |
626 | + password = sys.stdin.readline().strip("\n") |
627 | + token = sys.stdin.readline().strip("\n") |
628 | + |
629 | + # we create the keyring using the users login password |
630 | + KEYRING_NAME = "login" |
631 | + TOKEN_NAME = "Ubuntu one" |
632 | + res = GnomeKeyring.create_sync(KEYRING_NAME, password) |
633 | + if res == GnomeKeyring.Result.OK: |
634 | + res = GnomeKeyring.item_create_sync( |
635 | + KEYRING_NAME, |
636 | + GnomeKeyring.ItemType.GENERIC_SECRET, |
637 | + TOKEN_NAME, |
638 | + GLib.Array(), |
639 | + token, |
640 | + True) |
641 | + syslog.syslog("failed to create item '%s': %s" % (TOKEN_NAME, res)) |
642 | + else: |
643 | + syslog.syslog("failed to create keyring '%s': %s" % (KEYRING_NAME, res)) |
644 | |
645 | === added file 'tests/test_ubi_ubuntuone.py' |
646 | --- tests/test_ubi_ubuntuone.py 1970-01-01 00:00:00 +0000 |
647 | +++ tests/test_ubi_ubuntuone.py 2012-12-04 15:21:26 +0000 |
648 | @@ -0,0 +1,144 @@ |
649 | +#!/usr/bin/python3 |
650 | + |
651 | +import tempfile |
652 | +import unittest |
653 | + |
654 | +import mock |
655 | +from gi.repository import Gtk, GObject, GLib |
656 | + |
657 | +from ubiquity import plugin_manager |
658 | + |
659 | + |
660 | +ubi_ubuntuone = plugin_manager.load_plugin('ubi-ubuntuone') |
661 | + |
662 | + |
663 | +class BaseTestPageGtk(unittest.TestCase): |
664 | + |
665 | + def setUp(self): |
666 | + mock_controller = mock.Mock() |
667 | + self.page = ubi_ubuntuone.PageGtk(mock_controller, ui=mock.Mock()) |
668 | + |
669 | + |
670 | +class TestPageGtk(BaseTestPageGtk): |
671 | + |
672 | + def test_ui_visible(self): |
673 | + self.page.plugin_get_current_page() |
674 | + self.assertTrue(self.page.entry_email.get_property("visible")) |
675 | + |
676 | + def test_init_ui(self): |
677 | + self.page.plugin_get_current_page() |
678 | + self.assertEqual( |
679 | + self.page.notebook_main.get_current_page(), |
680 | + ubi_ubuntuone.PAGE_REGISTER) |
681 | + |
682 | + def test_switch_pages(self): |
683 | + self.page.plugin_get_current_page() |
684 | + self.page.linkbutton_have_account.clicked() |
685 | + self.assertEqual( |
686 | + self.page.notebook_main.get_current_page(), |
687 | + ubi_ubuntuone.PAGE_LOGIN) |
688 | + self.page.linkbutton_need_account.clicked() |
689 | + self.assertEqual( |
690 | + self.page.notebook_main.get_current_page(), |
691 | + ubi_ubuntuone.PAGE_REGISTER) |
692 | + |
693 | + def test_verify_email_entry(self): |
694 | + self.assertFalse(self.page._verify_email_entry("meep")) |
695 | + self.assertTrue(self.page._verify_email_entry("mup@example.com")) |
696 | + |
697 | + def test_verify_password_entry(self): |
698 | + self.assertFalse(self.page._verify_password_entry("")) |
699 | + self.assertTrue(self.page._verify_password_entry("xxx")) |
700 | + |
701 | + |
702 | +class MockSSOTestCase(BaseTestPageGtk): |
703 | + |
704 | + class MockUbuntuSSO(): |
705 | + |
706 | + TOKEN = "{'token': 'nonex'}" |
707 | + |
708 | + def mock_done(self, callback, errback, data): |
709 | + callback(self.TOKEN, data) |
710 | + Gtk.main_quit() |
711 | + |
712 | + def register(self, email, passw, callback, errback, data): |
713 | + GObject.idle_add(self.mock_done, callback, errback, data) |
714 | + |
715 | + def login(self, email, passw, callback, errback, data): |
716 | + GObject.idle_add(self.mock_done, callback, errback, data) |
717 | + |
718 | + def test_click_next(self): |
719 | + self.page.ubuntu_sso = self.MockUbuntuSSO() |
720 | + with mock.patch.object( |
721 | + self.page, "_create_keyring_and_store_u1_token") as m_create: |
722 | + self.page.plugin_on_next_clicked() |
723 | + self.assertEqual(self.page.notebook_main.get_current_page(), |
724 | + ubi_ubuntuone.PAGE_SPINNER) |
725 | + m_create.assert_called_with(self.page.ubuntu_sso.TOKEN) |
726 | + |
727 | + |
728 | +class RegisterTestCase(BaseTestPageGtk): |
729 | + |
730 | + def test_register_allow_go_forward_not_yet(self): |
731 | + self.page.entry_email.set_text("foo") |
732 | + self.page.controller.allow_go_forward.assert_called_with(False) |
733 | + |
734 | + def test_register_allow_go_foward(self): |
735 | + self.page.entry_email.set_text("foo@bar.com") |
736 | + self.page.entry_new_password.set_text("pw") |
737 | + self.page.entry_new_password2.set_text("pw") |
738 | + self.page.controller.allow_go_forward.assert_called_with(True) |
739 | + |
740 | + |
741 | +class LoginTestCase(BaseTestPageGtk): |
742 | + |
743 | + def test_login_allow_go_forward_not_yet(self): |
744 | + self.page.entry_existing_email.set_text("foo") |
745 | + self.page.entry_existing_password.set_text("pass") |
746 | + self.page.controller.allow_go_forward.assert_called_with(False) |
747 | + |
748 | + def test_login_allow_go_foward(self): |
749 | + self.page.linkbutton_have_account.clicked() |
750 | + self.page.entry_existing_email.set_text("foo@bar.com") |
751 | + self.page.entry_existing_password.set_text("pass") |
752 | + self.page.controller.allow_go_forward.assert_called_with(True) |
753 | + |
754 | + |
755 | +class UbuntuSSOHelperTestCase(unittest.TestCase): |
756 | + |
757 | + def setUp(self): |
758 | + self.callback = mock.Mock() |
759 | + self.callback.side_effect = lambda *args: self.loop.quit() |
760 | + self.errback = mock.Mock() |
761 | + self.errback.side_effect = lambda *args: self.loop.quit() |
762 | + self.loop = GLib.MainLoop(GLib.main_context_default()) |
763 | + self.sso_helper = ubi_ubuntuone.UbuntuSSO() |
764 | + |
765 | + def test_spawning_error(self): |
766 | + self.sso_helper.login("foo@example.com", "nopass", |
767 | + self.callback, self.errback) |
768 | + self.loop.run() |
769 | + self.assertTrue(self.errback.called) |
770 | + self.assertFalse(self.callback.called) |
771 | + |
772 | + def test_spawning_success(self): |
773 | + self.sso_helper.BINARY = "/bin/echo" |
774 | + self.sso_helper.login("foo@example.com", "nopass", |
775 | + self.callback, self.errback, data="data") |
776 | + self.loop.run() |
777 | + self.assertFalse(self.errback.called) |
778 | + self.assertTrue(self.callback.called) |
779 | + # ensure stdout is captured and data is also send |
780 | + self.callback.assert_called_with("--login foo@example.com\n", "data") |
781 | + |
782 | + |
783 | +if __name__ == '__main__': |
784 | + # run tests in a sourcetree with: |
785 | + """ |
786 | + UBIQUITY_GLADE=./gui/gtk \ |
787 | + UBIQUITY_PLUGIN_PATH=./ubiquity/plugins/ \ |
788 | + PYTHONPATH=. python3 tests/test_ubi_ubuntuone.py |
789 | + """ |
790 | + #from test.support import run_unittest |
791 | + # run_unittest() |
792 | + unittest.main() |
793 | |
794 | === modified file 'ubiquity/gtkwidgets.py' |
795 | --- ubiquity/gtkwidgets.py 2012-11-27 05:48:42 +0000 |
796 | +++ ubiquity/gtkwidgets.py 2012-12-04 15:21:26 +0000 |
797 | @@ -2,7 +2,7 @@ |
798 | |
799 | import cairo |
800 | from gi.repository import Gtk, Gdk, GObject, Pango |
801 | -from gi.repository import UbiquityWebcam, GdkPixbuf |
802 | +from gi.repository import GdkPixbuf |
803 | |
804 | from ubiquity import misc |
805 | |
806 | @@ -383,7 +383,11 @@ |
807 | self.photo_label = Gtk.Label('Take a photo:') |
808 | vb_left.pack_start(self.photo_label, False, False, 0) |
809 | f = Gtk.Frame() |
810 | + |
811 | + # import here because mvo is too lazy to build the tree |
812 | + from gi.repository import UbiquityWebcam |
813 | self.webcam = UbiquityWebcam.Webcam() |
814 | + |
815 | self.webcam.connect('image-captured', self.image_captured) |
816 | f.add(self.webcam) |
817 | vb_left.pack_start(f, True, True, 0) |
818 | |
819 | === added file 'ubiquity/plugins/ubi-ubuntuone.py' |
820 | --- ubiquity/plugins/ubi-ubuntuone.py 1970-01-01 00:00:00 +0000 |
821 | +++ ubiquity/plugins/ubi-ubuntuone.py 2012-12-04 15:21:26 +0000 |
822 | @@ -0,0 +1,378 @@ |
823 | +# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*- |
824 | + |
825 | +# Copyright (C) 2012 Canonical Ltd. |
826 | +# Written by Michael Vogt <mvo@ubuntu.com> |
827 | +# |
828 | +# This program is free software; you can redistribute it and/or modify |
829 | +# it under the terms of the GNU General Public License as published by |
830 | +# the Free Software Foundation; either version 2 of the License, or |
831 | +# (at your option) any later version. |
832 | +# |
833 | +# This program is distributed in the hope that it will be useful, |
834 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
835 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
836 | +# GNU General Public License for more details. |
837 | +# |
838 | +# You should have received a copy of the GNU General Public License |
839 | +# along with this program; if not, write to the Free Software |
840 | +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
841 | + |
842 | +import pwd |
843 | +import re |
844 | +import os |
845 | +import os.path |
846 | +import subprocess |
847 | +import shutil |
848 | +import syslog |
849 | + |
850 | +from ubiquity import plugin, misc |
851 | + |
852 | + |
853 | +NAME = 'ubuntuone' |
854 | +AFTER = 'usersetup' |
855 | +WEIGHT = 10 |
856 | + |
857 | +(PAGE_REGISTER, |
858 | + PAGE_LOGIN, |
859 | + PAGE_SPINNER, |
860 | + ) = range(3) |
861 | + |
862 | +# TODO: |
863 | +# - network awareness (steal from timezone map page) |
864 | +# - rename this all to ubuntu sso instead of ubuntuone to avoid confusion |
865 | +# that we force people to sign up for payed services on install (?) where |
866 | +# what we want is to make it super simple to use our services |
867 | +# - take the username from the usersetup step when creating the token |
868 | +# - get a design for the UI |
869 | +# * to create a new account |
870 | +# * to login into a existing account |
871 | +# * deal with forgoten passwords |
872 | +# * skip account creation |
873 | + |
874 | + |
875 | +# TESTING end-to-end for real |
876 | +# |
877 | +# * get a raring cdimage |
878 | +# * run: |
879 | +# kvm -m 1500 -hda /path/to/random-image -cdrom /path/to/raring-arch.iso \ |
880 | +# -boot d |
881 | +# * in the VM: |
882 | +# - add universe |
883 | +# - sudo apt-get install bzr build-essential python3-setuptools debhelper python3-piston-mini-client |
884 | +# - bzr co lp:~mvo/+junk/cli-sso-login |
885 | +# - (cd cli-sso-login; dpkg-buildpackage; sudo dpkg -i ../python3*.deb) |
886 | +# |
887 | +# - install cli-sso-login from |
888 | +# - bzr co --lightweight lp:~mvo/ubiquity/ssologin |
889 | +# - cd ssologin |
890 | +# - sudo cp ubiquity/plugins/* /usr/lib/ubiquity/plugins |
891 | +# - sudo cp ubiquity/* /usr/lib/ubiquity/ubiquity |
892 | +# - sudo cp gui/gtk//*.ui /usr/share/ubiquity/gtk |
893 | +# - sudo cp scripts/* /usr/share/ubiquity/ |
894 | +# - sudo cp bin/ubiquity /usr/bin |
895 | +# - sudo ubiquity |
896 | + |
897 | + |
898 | +class UbuntuSSO(object): |
899 | + |
900 | + # this will need the helper |
901 | + # lp:~mvo/+junk/cli-sso-login installed |
902 | + |
903 | + BINARY = "/usr/bin/ubuntu-sso-cli" |
904 | + |
905 | + def _child_exited(self, pid, status, data): |
906 | + stdin_fd, stdout_fd, stderr_fd, callback, errback, user_data = data |
907 | + exit_code = os.WEXITSTATUS(status) |
908 | + # the delayed reading will only work if the amount of data is |
909 | + # small enough to not cause the pipe to block which on most |
910 | + # system is ok as "ulimit -p" shows 8 pages by default (4k) |
911 | + stdout = os.read(stdout_fd, 2048).decode("utf-8") |
912 | + stderr = os.read(stderr_fd, 2048).decode("utf-8") |
913 | + if exit_code == 0: |
914 | + callback(stdout, user_data) |
915 | + else: |
916 | + errback(stderr, user_data) |
917 | + |
918 | + def _spawn_sso_helper(self, cmd, password, callback, errback, data): |
919 | + from gi.repository import GLib |
920 | + res, pid, stdin_fd, stdout_fd, stderr_fd = GLib.spawn_async_with_pipes( |
921 | + "/", cmd, None, |
922 | + (GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN | |
923 | + GLib.SpawnFlags.DO_NOT_REAP_CHILD), None, None) |
924 | + if res: |
925 | + os.write(stdin_fd, password.encode("utf-8")) |
926 | + os.write(stdin_fd, "\n".encode("utf-8")) |
927 | + GLib.child_watch_add( |
928 | + GLib.PRIORITY_DEFAULT, pid, self._child_exited, |
929 | + (stdin_fd, stdout_fd, stderr_fd, callback, errback, data)) |
930 | + else: |
931 | + errback("Failed to spawn %s" % cmd, data) |
932 | + |
933 | + def login(self, email, password, callback, errback, data=None): |
934 | + cmd = [self.BINARY, "--login", email] |
935 | + self._spawn_sso_helper(cmd, password, callback, errback, data) |
936 | + |
937 | + def register(self, email, password, callback, errback, data=None): |
938 | + cmd = [self.BINARY, "--register", email] |
939 | + self._spawn_sso_helper(cmd, password, callback, errback, data) |
940 | + |
941 | + |
942 | +class Page(plugin.Plugin): |
943 | + |
944 | + def prepare(self, unfiltered=False): |
945 | + self.ui._user_password = self.db.get('passwd/user-password') |
946 | + return plugin.Plugin.prepare(unfiltered) |
947 | + |
948 | + |
949 | +class PageGtk(plugin.PluginUI): |
950 | + plugin_title = 'ubiquity/text/ubuntuone_heading_label' |
951 | + |
952 | + def __init__(self, controller, *args, **kwargs): |
953 | + from gi.repository import Gtk |
954 | + self.controller = controller |
955 | + # check if we are needed at all |
956 | + if ('UBIQUITY_AUTOMATIC' in os.environ or |
957 | + 'UBIQUITY_NO_SSO' in os.environ): |
958 | + self.page = None |
959 | + return |
960 | + # check dependencies |
961 | + try: |
962 | + from gi.repository import GnomeKeyring |
963 | + assert(GnomeKeyring) |
964 | + except ImportError as e: |
965 | + syslog.syslog("skipping SSO page, no GnomeKeyring (%s)" % e) |
966 | + self.page = None |
967 | + return |
968 | + # add builder/signals |
969 | + builder = Gtk.Builder() |
970 | + self.controller.add_builder(builder) |
971 | + builder.add_from_file( |
972 | + os.path.join(os.environ['UBIQUITY_GLADE'], 'stepUbuntuOne.ui')) |
973 | + builder.connect_signals(self) |
974 | + # make the widgets available under their gtkbuilder name |
975 | + for obj in builder.get_objects(): |
976 | + if issubclass(type(obj), Gtk.Buildable): |
977 | + setattr(self, Gtk.Buildable.get_name(obj), obj) |
978 | + self.page = builder.get_object('stepUbuntuOne') |
979 | + self.notebook_main.set_show_tabs(False) |
980 | + self.plugin_widgets = self.page |
981 | + self.oauth_token = None |
982 | + self.skip_step = False |
983 | + self.online = False |
984 | + self.label_global_error.set_text("") |
985 | + # the worker |
986 | + self.ubuntu_sso = UbuntuSSO() |
987 | + self.info_loop(None) |
988 | + |
989 | + def plugin_set_online_state(self, state): |
990 | + self.online = state |
991 | + |
992 | + def plugin_get_current_page(self): |
993 | + self.page.show_all() |
994 | + self.notebook_main.set_current_page(PAGE_REGISTER) |
995 | + return self.page |
996 | + |
997 | + def plugin_on_back_clicked(self): |
998 | + # stop whatever needs stopping |
999 | + return False |
1000 | + |
1001 | + def plugin_on_next_clicked(self): |
1002 | + from gi.repository import Gtk |
1003 | + if self.skip_step: |
1004 | + return False |
1005 | + if self.notebook_main.get_current_page() == PAGE_REGISTER: |
1006 | + self.ubuntu_sso.register(self.entry_email.get_text(), |
1007 | + self.entry_new_password.get_text(), |
1008 | + callback=self._ubuntu_sso_callback, |
1009 | + errback=self._ubuntu_sso_errback, |
1010 | + data=PAGE_REGISTER) |
1011 | + elif self.notebook_main.get_current_page() == PAGE_LOGIN: |
1012 | + self.ubuntu_sso.login(self.entry_existing_email.get_text(), |
1013 | + self.entry_existing_password.get_text(), |
1014 | + callback=self._ubuntu_sso_callback, |
1015 | + errback=self._ubuntu_sso_errback, |
1016 | + data=PAGE_LOGIN) |
1017 | + else: |
1018 | + raise AssertionError("Should never be reached happen") |
1019 | + |
1020 | + self.notebook_main.set_current_page(PAGE_SPINNER) |
1021 | + self.spinner_connect.start() |
1022 | + # the ubuntu_sso.{login,register} will stop this loop when its done |
1023 | + Gtk.main() |
1024 | + self.spinner_connect.stop() |
1025 | + |
1026 | + # if there is no token at this point, there is a error, |
1027 | + # so stop moving forward |
1028 | + if self.oauth_token is None: |
1029 | + return True |
1030 | + |
1031 | + # all good, create a (encrypted) keyring and store the token for later |
1032 | + self._create_keyring_and_store_u1_token(self.oauth_token) |
1033 | + return False |
1034 | + |
1035 | + def _create_keyring_and_store_u1_token(self, token): |
1036 | + """Helper that spawns a external helper to create the keyring""" |
1037 | + # this needs to be a external helper as ubiquity is running as |
1038 | + # root and it seems that anything other than "drop_all_privileges" |
1039 | + # will not trigger the correct dbus activation for the |
1040 | + # gnome-keyring daemon |
1041 | + # |
1042 | + # mvo: We could do this in the "install" phase too, but more fragile |
1043 | + # I think, here is what would be required: |
1044 | + # - copy over XAUTHORITY to /target/home/$targetuser/.Xauthority |
1045 | + # - chown $targetuser.$targetuser \ |
1046 | + # /target/home/$targetuser/.Xauthority |
1047 | + # - (bind)mount /proc in /target |
1048 | + # - run "dbus-uuidgen --ensure" in /target to get a dbus |
1049 | + # machine-id |
1050 | + # - run the helper with: |
1051 | + # chroot /target sudo -u $targetuser HOME=/home/$targetuser \ |
1052 | + # XAUTHORITY=/home/$targetuser/.Xauthority \ |
1053 | + # DBUS_SESSION_BUS_ADDRESS="autolaunch:" \ |
1054 | + # ubuntuone-keyring-helper |
1055 | + # - ensure that the dbus-daemon and gnome-keyring-daemon that |
1056 | + # get spawned in /target get killed so that /target can |
1057 | + # get unmounted again |
1058 | + # - umount /proc |
1059 | + p = subprocess.Popen( |
1060 | + ["/usr/share/ubiquity/ubuntuone-keyring-helper"], |
1061 | + stdin=subprocess.PIPE, |
1062 | + preexec_fn=misc.drop_all_privileges, |
1063 | + universal_newlines=True) |
1064 | + p.stdin.write(self._user_password) |
1065 | + p.stdin.write("\n") |
1066 | + p.stdin.write(token) |
1067 | + p.stdin.write("\n") |
1068 | + res = p.wait() |
1069 | + syslog.syslog("keyring helper returned %s" % res) |
1070 | + |
1071 | + def plugin_translate(self, lang): |
1072 | + pasw = self.controller.get_string('password_inactive_label', lang) |
1073 | + self.entry_new_password.set_placeholder_text(pasw) |
1074 | + self.entry_existing_password.set_placeholder_text(pasw) |
1075 | + pasw_again = self.controller.get_string( |
1076 | + 'password_again_inactive_label', lang) |
1077 | + self.entry_new_password2.set_placeholder_text(pasw_again) |
1078 | + email_p = self.controller.get_string('email_inactive_label', lang) |
1079 | + self.entry_email.set_placeholder_text(email_p) |
1080 | + self.entry_existing_email.set_placeholder_text(email_p) |
1081 | + # error messages |
1082 | + self._error_register = self.controller.get_string( |
1083 | + 'error_register', lang) |
1084 | + self._error_login = self.controller.get_string( |
1085 | + 'error_login', lang) |
1086 | + |
1087 | + # callbacks |
1088 | + def _ubuntu_sso_callback(self, oauth_token, data): |
1089 | + """Called when a oauth token was returned successfully""" |
1090 | + from gi.repository import Gtk |
1091 | + self.oauth_token = oauth_token |
1092 | + Gtk.main_quit() |
1093 | + |
1094 | + def _ubuntu_sso_errback(self, error, data): |
1095 | + """Called when a error acquiring the oauth token from the helper""" |
1096 | + from gi.repository import Gtk |
1097 | + syslog.syslog("ubuntu sso failed: '%s'" % error) |
1098 | + self.notebook_main.set_current_page(data) |
1099 | + if data == PAGE_REGISTER: |
1100 | + err = self._error_register |
1101 | + else: |
1102 | + err = self._error_login |
1103 | + self.label_global_error.set_markup("<b><big>%s</big></b>" % err) |
1104 | + Gtk.main_quit() |
1105 | + |
1106 | + # signals |
1107 | + def on_button_have_account_clicked(self, button): |
1108 | + self.notebook_main.set_current_page(PAGE_LOGIN) |
1109 | + |
1110 | + def on_button_need_account_clicked(self, button): |
1111 | + self.notebook_main.set_current_page(PAGE_REGISTER) |
1112 | + |
1113 | + def on_button_skip_account_clicked(self, button): |
1114 | + self.oauth_token = None |
1115 | + self.skip_step = True |
1116 | + self.controller.go_forward() |
1117 | + |
1118 | + def _verify_email_entry(self, email): |
1119 | + """Return True if the email address looks valid""" |
1120 | + EMAIL_REGEXP = "[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+" |
1121 | + match = re.match(EMAIL_REGEXP, email) |
1122 | + return (match is not None) |
1123 | + |
1124 | + def _verify_password_entry(self, password): |
1125 | + """Return True if there is a valid password""" |
1126 | + return len(password) > 0 |
1127 | + |
1128 | + def info_loop(self, widget): |
1129 | + """Run each time the user inputs something to make controlls |
1130 | + sensitive or insensitive |
1131 | + """ |
1132 | + complete = False |
1133 | + if self.notebook_main.get_current_page() == PAGE_REGISTER: |
1134 | + email = self.entry_email.get_text() |
1135 | + password = self.entry_new_password.get_text() |
1136 | + password2 = self.entry_new_password2.get_text() |
1137 | + complete = (self._verify_email_entry(email) and |
1138 | + len(password) > 0 and |
1139 | + (password == password2)) |
1140 | + elif self.notebook_main.get_current_page() == PAGE_LOGIN: |
1141 | + email = self.entry_existing_email.get_text() |
1142 | + password = self.entry_existing_password.get_text() |
1143 | + complete = (self._verify_email_entry(email) and |
1144 | + self._verify_password_entry(password)) |
1145 | + self.controller.allow_go_forward(complete) |
1146 | + |
1147 | + |
1148 | +class Install(plugin.InstallPlugin): |
1149 | + |
1150 | + def install(self, target, progress, *args, **kwargs): |
1151 | + self.configure_oauth_token(target) |
1152 | + |
1153 | + def _get_target_uid(self, target_path, target_user): |
1154 | + # stolen from: plugininstall.py, is there a better way? |
1155 | + p = subprocess.Popen( |
1156 | + ['chroot', target_path, 'sudo', '-u', target_user, '--', |
1157 | + 'id', '-u'], stdout=subprocess.PIPE, universal_newlines=True) |
1158 | + uid = int(p.communicate()[0].strip('\n')) |
1159 | + return uid |
1160 | + |
1161 | + def _get_casper_user_keyring_file_path(self): |
1162 | + # stolen (again) from pluginstall.py |
1163 | + try: |
1164 | + casper_user = pwd.getpwuid(999).pw_name |
1165 | + except KeyError: |
1166 | + # We're on a weird system where the casper user isn't uid 999 |
1167 | + # just stop there |
1168 | + return "" |
1169 | + casper_user_home = os.path.expanduser('~%s' % casper_user) |
1170 | + keyring_file = os.path.join(casper_user_home, ".local", "share", |
1171 | + "keyrings", "login.keyring") |
1172 | + return keyring_file |
1173 | + |
1174 | + # XXX: I am untested |
1175 | + def configure_oauth_token(self, target): |
1176 | + target_user = self.db.get('passwd/username') |
1177 | + uid = self._get_target_uid(target, target_user) |
1178 | + keyring_file = self._get_casper_user_keyring_file_path() |
1179 | + if os.path.exists(keyring_file) and uid: |
1180 | + targetpath = os.path.join( |
1181 | + target, 'home', target_user, '.local', 'share', 'keyrings', |
1182 | + 'login.keyring') |
1183 | + # skip copy if the target already exists, this can happen |
1184 | + # if e.g. the user selected reinstall-with-keep-home |
1185 | + if os.path.exists(targetpath): |
1186 | + syslog.syslog("keyring path: '%s' already exists, skip copy" % |
1187 | + targetpath) |
1188 | + return |
1189 | + basedir = os.path.dirname(targetpath) |
1190 | + # ensure we have the basedir with the righ permissions |
1191 | + if not os.path.exists(basedir): |
1192 | + basedir_in_chroot = os.path.join( |
1193 | + "home", target_user, ".local", "share", "keyrings") |
1194 | + subprocess.call( |
1195 | + ["chroot", target, "sudo", "-u", target_user, "--", |
1196 | + "mkdir", "-p", basedir_in_chroot]) |
1197 | + shutil.copy2(self.KEYRING_FILE, targetpath) |
1198 | + os.lchown(targetpath, uid, uid) |
1199 | + os.chmod(targetpath, 0o600) |
1200 | + os.chmod(basedir, 0o700) |
I think the branch is at a point where a first tentative review from a ubiquity developer would be nice to get a idea if it fits well enough into ubiquity or if it needs refactor there. I expect some UI tweaks when the final design is there, but that should all be relatively contained.