Merge lp:~mikemc/unity-scope-click/remove-invalid-account-and-sync into lp:unity-scope-click

Proposed by Mike McCracken
Status: Merged
Approved by: Mike McCracken
Approved revision: 86
Merged at revision: 93
Proposed branch: lp:~mikemc/unity-scope-click/remove-invalid-account-and-sync
Merge into: lp:unity-scope-click
Diff against target: 362 lines (+210/-28)
5 files modified
src/click-scope.vala (+11/-7)
src/click-webservice.vala (+1/-1)
src/download-manager.vala (+3/-3)
src/test-click-webservice.vala (+177/-16)
src/ubuntuone-credentials.vala (+18/-1)
To merge this branch: bzr merge lp:~mikemc/unity-scope-click/remove-invalid-account-and-sync
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
dobey (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+199352@code.launchpad.net

Commit message

- Handle invalidated tokens by removing UOA account. (LP: #1248326)

Description of the change

- Handle invalidated tokens by removing UOA account. (LP: #1248326)

Responds to an invalid-token error received while attempting to download an app by deleting the UOA account.
This is currently the best way to remove the token, as the U1 custom UI is required to correctly re-auth and it is not included in a signon plugin, which would be the officially supported way to use the UOA architecture.

Includes improved test coverage for ClickScope.install_app(), which required some visibility and inheritance tweaks in other parts of the code so that we could use Vala subclassing to provide fake objects for tests.

Removed outdated TODO comment about caching webservice - caching is
already done in click-webservice.vala

NOTE: the change in this branch to using a single ClickWebservice
member of the ClickScope object does not affect or effect caching.

Removed test_fetch_credentials, because as far as I can tell, it only
tested the fake class.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/click-scope.vala'
2--- src/click-scope.vala 2013-12-10 22:21:24 +0000
3+++ src/click-scope.vala 2013-12-17 20:34:09 +0000
4@@ -79,11 +79,18 @@
5 const string LABEL_REVIEWS = "Reviews";
6 const string LABEL_COMMENTS = "Comments";
7
8- ClickInterface click_if = new ClickInterface ();
9+ public ClickInterface click_if = new ClickInterface ();
10+ public UbuntuoneCredentials u1creds = new UbuntuoneCredentials ();
11+ public ClickWebservice webservice = new ClickWebservice ();
12
13 public ClickScope ()
14 {
15 }
16+
17+ // Wrapper to be overridden for tests:
18+ protected virtual SignedDownload get_signed_download (HashTable<string, string> credentials) {
19+ return new SignedDownload (credentials);
20+ }
21
22 Unity.Preview build_error_preview (string message) {
23 var preview = new Unity.GenericPreview("Error", message, null);
24@@ -107,9 +114,7 @@
25
26 internal async Unity.Preview build_app_preview(Unity.ScopeResult result) {
27 var app_id = result.metadata.get(METADATA_APP_ID).get_string();
28- var webservice = new ClickWebservice();
29 try {
30- // TODO: add caching of this webservice call
31 var details = yield webservice.get_details(app_id);
32 var icon = new FileIcon(File.new_for_uri(details.icon_url));
33 var screenshot = new FileIcon(File.new_for_uri(details.main_screenshot_url));
34@@ -286,14 +291,12 @@
35 public async GLib.ObjectPath install_app (string app_id) throws ClickScopeError {
36 try {
37 debug ("getting details for %s", app_id);
38- var click_ws = new ClickWebservice ();
39- var app_details = yield click_ws.get_details (app_id);
40+ var app_details = yield webservice.get_details (app_id);
41 debug ("got details: %s", app_details.title);
42- var u1creds = new UbuntuoneCredentials ();
43 debug ("getting creds");
44 var credentials = yield u1creds.get_credentials ();
45 debug ("got creds");
46- var signed_download = new SignedDownload (credentials);
47+ var signed_download = get_signed_download(credentials);
48
49 var download_url = app_details.download_url;
50
51@@ -304,6 +307,7 @@
52 } catch (DownloadError download_error) {
53 debug ("Got DownloadError: %s", download_error.message);
54 if (download_error is DownloadError.INVALID_CREDENTIALS) {
55+ yield u1creds.invalidate_credentials ();
56 throw new ClickScopeError.LOGIN_ERROR (download_error.message);
57 } else {
58 throw new ClickScopeError.INSTALL_ERROR (download_error.message);
59
60=== modified file 'src/click-webservice.vala'
61--- src/click-webservice.vala 2013-12-10 22:21:24 +0000
62+++ src/click-webservice.vala 2013-12-17 20:34:09 +0000
63@@ -302,7 +302,7 @@
64 }
65 }
66
67- public async AppDetails get_details(string app_name) throws WebserviceError {
68+ public virtual async AppDetails get_details(string app_name) throws WebserviceError {
69 WebserviceError failure = null;
70 string url = get_details_url().printf(app_name);
71 string response = "{}";
72
73=== modified file 'src/download-manager.vala'
74--- src/download-manager.vala 2013-10-03 22:35:56 +0000
75+++ src/download-manager.vala 2013-12-17 20:34:09 +0000
76@@ -80,7 +80,7 @@
77 }
78 }
79
80-errordomain DownloadError {
81+public errordomain DownloadError {
82 DOWNLOAD_ERROR,
83 INVALID_CREDENTIALS
84 }
85@@ -99,7 +99,7 @@
86 }
87 }
88
89-class SignedDownload : GLib.Object {
90+public class SignedDownload : GLib.Object {
91 const string CLICK_TOKEN_HEADER = "X-Click-Token";
92
93 const string CONSUMER_KEY = "consumer_key";
94@@ -163,7 +163,7 @@
95 return click_token;
96 }
97
98- public async GLib.ObjectPath start_download (string uri, string app_id) throws DownloadError {
99+ public virtual async GLib.ObjectPath start_download (string uri, string app_id) throws DownloadError {
100 debug ("Starting download");
101
102 var click_token = yield fetch_click_token (uri);
103
104=== modified file 'src/test-click-webservice.vala'
105--- src/test-click-webservice.vala 2013-12-06 18:27:14 +0000
106+++ src/test-click-webservice.vala 2013-12-17 20:34:09 +0000
107@@ -16,7 +16,11 @@
108
109 using Assertions;
110
111-private const string FAKE_APP_ID = "FAKE_APP_ID";
112+const string FAKE_APP_ID = "FAKE_APP_ID";
113+const string FAKE_OBJECT_PATH = "/com/fake/object";
114+const string FAKE_DOWNLOAD_ERROR_MESSAGE = "fake generic download error message";
115+const string FAKE_CREDS_ERROR_MESSAGE = "fake creds error message";
116+const string FAKE_INVALID_CREDS_ERROR_MESSAGE = "fake invalid creds message";
117
118 public class ClickTestCase
119 {
120@@ -168,27 +172,181 @@
121 }
122
123 class FakeCredentials : UbuntuoneCredentials {
124- public async HashTable<string, string> get_credentials () throws CredentialsError {
125+
126+ public bool invalidate_called = false;
127+
128+ public override async HashTable<string, string> get_credentials () throws CredentialsError {
129 string credentials = "consumer_key=consumer&consumer_secret=consumer_secret&token=token&token_secret=token_secret";
130 return Soup.Form.decode (credentials);
131 }
132- }
133-
134- public static void test_fetch_credentials ()
135- {
136+
137+ public override async void invalidate_credentials () throws CredentialsError {
138+ this.invalidate_called = true;
139+ }
140+ }
141+
142+ class FakeClickWebservice : ClickWebservice {
143+ public override async AppDetails get_details (string app_name) throws WebserviceError {
144+ return new AppDetails.from_json (FAKE_JSON_PACKAGE_DETAILS);
145+ }
146+ }
147+
148+ class FakeSignedDownloadOK : SignedDownload {
149+ public async override new GLib.ObjectPath start_download (string uri, string app_id) throws DownloadError {
150+ return new GLib.ObjectPath (FAKE_OBJECT_PATH);
151+ }
152+
153+ public FakeSignedDownloadOK (HashTable<string, string> credentials) {
154+ base (credentials);
155+ }
156+ }
157+
158+ class ClickScopeWithFakeSignedDownloadOK : ClickScope {
159+ protected override SignedDownload get_signed_download (HashTable<string, string> credentials) {
160+ return new FakeSignedDownloadOK (credentials);
161+ }
162+ }
163+
164+ public static void test_install_app_ok ()
165+ {
166 MainLoop mainloop = new MainLoop ();
167- var u1creds = new FakeCredentials ();
168-
169- u1creds.get_credentials.begin((obj, res) => {
170+
171+ var scope = new ClickScopeWithFakeSignedDownloadOK ();
172+ scope.click_if = new FakeClickInterface ();
173+ scope.u1creds = new FakeCredentials ();
174+ scope.webservice = new FakeClickWebservice ();
175+
176+ scope.install_app.begin (FAKE_APP_ID, (obj, res) => {
177 mainloop.quit ();
178 try {
179- var creds = u1creds.get_credentials.end (res);
180- assert (creds["consumer_key"] == "consumer");
181+
182+ var objpath = scope.install_app.end (res);
183+ assert (objpath == FAKE_OBJECT_PATH);
184+
185 } catch (GLib.Error e) {
186- error ("Can't fetch credentials: %s", e.message);
187- }
188- });
189- assert (run_with_timeout (mainloop, 10000));
190+ error ("Caught GLib.Error in test_install_app_ok: %s", e.message);
191+ }
192+ });
193+ assert (run_with_timeout (mainloop, 10000));
194+
195+ }
196+
197+ class FakeBadCredentials : UbuntuoneCredentials {
198+ public override async new HashTable<string, string> get_credentials () throws CredentialsError {
199+ throw new CredentialsError.CREDENTIALS_ERROR (FAKE_CREDS_ERROR_MESSAGE);
200+ }
201+ }
202+
203+ public static void test_install_app_bad_creds ()
204+ {
205+ MainLoop mainloop = new MainLoop ();
206+
207+ var scope = new ClickScopeWithFakeSignedDownloadOK ();
208+ scope.click_if = new FakeClickInterface ();
209+ scope.u1creds = new FakeBadCredentials ();
210+ scope.webservice = new FakeClickWebservice ();
211+
212+ scope.install_app.begin (FAKE_APP_ID, (obj, res) => {
213+ mainloop.quit ();
214+ try {
215+
216+ var objpath = scope.install_app.end (res);
217+ assert_not_reached ();
218+
219+ } catch (ClickScopeError.LOGIN_ERROR e) {
220+ assert (e.message == FAKE_CREDS_ERROR_MESSAGE);
221+
222+ } catch {
223+ assert_not_reached ();
224+ }
225+ });
226+ assert (run_with_timeout (mainloop, 10000));
227+ }
228+
229+ class FakeSignedDownloadInvalid : SignedDownload {
230+ public async override new GLib.ObjectPath start_download (string uri, string app_id) throws DownloadError {
231+ throw new DownloadError.INVALID_CREDENTIALS (FAKE_INVALID_CREDS_ERROR_MESSAGE);
232+ }
233+ public FakeSignedDownloadInvalid (HashTable<string, string> credentials) {
234+ base (credentials);
235+ }
236+ }
237+
238+ class ClickScopeWithFakeSignedDownloadInvalid : ClickScope {
239+ protected override SignedDownload get_signed_download (HashTable<string, string> credentials) {
240+ return new FakeSignedDownloadInvalid (credentials);
241+ }
242+ }
243+
244+ public static void test_install_app_invalid_creds ()
245+ {
246+ MainLoop mainloop = new MainLoop ();
247+
248+ var scope = new ClickScopeWithFakeSignedDownloadInvalid ();
249+ scope.click_if = new FakeClickInterface ();
250+ scope.u1creds = new FakeCredentials ();
251+ scope.webservice = new FakeClickWebservice ();
252+
253+ scope.install_app.begin (FAKE_APP_ID, (obj, res) => {
254+ mainloop.quit ();
255+ try {
256+
257+ var objpath = scope.install_app.end (res);
258+ assert_not_reached ();
259+
260+ } catch (ClickScopeError.LOGIN_ERROR e) {
261+ assert (e.message == FAKE_INVALID_CREDS_ERROR_MESSAGE);
262+ assert (((FakeCredentials) scope.u1creds).invalidate_called == true);
263+
264+ } catch {
265+ assert_not_reached ();
266+ }
267+ });
268+ assert (run_with_timeout (mainloop, 10000));
269+
270+ }
271+
272+ class FakeSignedDownloadError : SignedDownload {
273+ public async override new GLib.ObjectPath start_download (string uri, string app_id) throws DownloadError {
274+ throw new DownloadError.DOWNLOAD_ERROR (FAKE_DOWNLOAD_ERROR_MESSAGE);
275+ }
276+ public FakeSignedDownloadError (HashTable<string, string> credentials) {
277+ base (credentials);
278+ }
279+ }
280+
281+ class ClickScopeWithFakeSignedDownloadError : ClickScope {
282+ protected override SignedDownload get_signed_download (HashTable<string, string> credentials) {
283+ return new FakeSignedDownloadError (credentials);
284+ }
285+ }
286+
287+ public static void test_install_app_download_error ()
288+ {
289+ MainLoop mainloop = new MainLoop ();
290+
291+ var scope = new ClickScopeWithFakeSignedDownloadError ();
292+ scope.click_if = new FakeClickInterface ();
293+ scope.u1creds = new FakeCredentials ();
294+ scope.webservice = new FakeClickWebservice ();
295+
296+ scope.install_app.begin (FAKE_APP_ID, (obj, res) => {
297+ mainloop.quit ();
298+ try {
299+
300+ var objpath = scope.install_app.end (res);
301+ assert_not_reached ();
302+
303+ } catch (ClickScopeError.INSTALL_ERROR e) {
304+ assert (e.message == FAKE_DOWNLOAD_ERROR_MESSAGE);
305+ assert (((FakeCredentials) scope.u1creds).invalidate_called == false);
306+
307+ } catch {
308+ assert_not_reached ();
309+ }
310+ });
311+ assert (run_with_timeout (mainloop, 10000));
312+
313 }
314
315 public static void test_click_get_dotdesktop ()
316@@ -343,7 +501,10 @@
317 Test.add_data_func ("/Unit/ClickChecker/Test_Parse_App_Details", test_parse_app_details);
318 Test.add_data_func ("/Unit/ClickChecker/Test_Parse_Skinny_Details", test_parse_skinny_details);
319 Test.add_data_func ("/Unit/ClickChecker/Test_Available_Apps", test_available_apps);
320- Test.add_data_func ("/Unit/ClickChecker/Test_Fetch_Credentials", test_fetch_credentials);
321+ Test.add_data_func ("/Unit/ClickChecker/Test_Install_App_OK", test_install_app_ok);
322+ Test.add_data_func ("/Unit/ClickChecker/Test_Install_App_Bad_Creds", test_install_app_bad_creds);
323+ Test.add_data_func ("/Unit/ClickChecker/Test_Install_App_Invalid_Creds", test_install_app_invalid_creds);
324+ Test.add_data_func ("/Unit/ClickChecker/Test_Install_App_Download_Error", test_install_app_download_error);
325 Test.add_data_func ("/Unit/ClickChecker/Test_Click_GetDotDesktop", test_click_get_dotdesktop);
326 Test.add_data_func ("/Unit/ClickChecker/Test_Scope_Build_Purchasing_Preview",
327 test_scope_build_purchasing_preview);
328
329=== modified file 'src/ubuntuone-credentials.vala'
330--- src/ubuntuone-credentials.vala 2013-12-10 22:21:24 +0000
331+++ src/ubuntuone-credentials.vala 2013-12-17 20:34:09 +0000
332@@ -20,7 +20,7 @@
333
334 public class UbuntuoneCredentials : GLib.Object {
335
336- public async HashTable<string, string> get_credentials () throws CredentialsError {
337+ public virtual async HashTable<string, string> get_credentials () throws CredentialsError {
338 string encoded_creds = null;
339 string error_message = "";
340
341@@ -59,4 +59,21 @@
342 return Soup.Form.decode (encoded_creds);
343
344 }
345+
346+ public virtual async void invalidate_credentials () throws CredentialsError {
347+ Ag.Manager _manager = new Ag.Manager.for_service_type ("ubuntuone");
348+ GLib.List<uint> _accts = _manager.list_by_service_type ("ubuntuone");
349+
350+ foreach (var account_id in _accts) {
351+ debug ("Removing account id: %u", account_id);
352+ var account = _manager.get_account (account_id);
353+ account.delete();
354+ try {
355+ yield account.store_async();
356+ } catch (Ag.AccountsError e) {
357+ string error_message = "Please delete your existing Ubuntu One account and create a new one in System Settings.";
358+ throw new CredentialsError.CREDENTIALS_ERROR(error_message);
359+ }
360+ }
361+ }
362 }

Subscribers

People subscribed via source and target branches

to all changes: