Merge lp:~dobey/ubuntuone-credentials/server-timestamp into lp:ubuntuone-credentials

Proposed by dobey
Status: Merged
Approved by: dobey
Approved revision: 228
Merged at revision: 222
Proposed branch: lp:~dobey/ubuntuone-credentials/server-timestamp
Merge into: lp:ubuntuone-credentials
Diff against target: 503 lines (+292/-11)
9 files modified
.bzrignore (+1/-0)
debian/libubuntuoneauth-2.0-0.symbols (+2/-0)
libubuntuoneauth/tests/CMakeLists.txt (+5/-0)
libubuntuoneauth/tests/main.cpp (+4/-2)
libubuntuoneauth/tests/mock_sso_server.py.in (+57/-0)
libubuntuoneauth/tests/test_token.cpp (+103/-2)
libubuntuoneauth/tests/test_token.h (+14/-1)
libubuntuoneauth/token.cpp (+102/-5)
libubuntuoneauth/token.h (+4/-1)
To merge this branch: bzr merge lp:~dobey/ubuntuone-credentials/server-timestamp
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Abstain
Charles Kerr (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+279173@code.launchpad.net

Commit message

Retrieve current timestamp from server to use in OAuth signatures.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
220. By dobey

Update the symbols file too.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
221. By dobey

It's 0replaceme, not @replaceme.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
222. By dobey

Add fake server for testing timestamp retrieval.
Add tests for timestamp retrieval.
Use QEventLoop instead of promise/future for synchronous API.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
223. By dobey

Add caching of the timestamp, to avoid hitting server every time.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
224. By dobey

Use uint for the time to check, as QDateTime.toTime_t returns a uint.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
225. By dobey

Fix copyright years.

226. By dobey

Update another copyright header.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Please put lines 387-395 in a separate function, and add unit tests for it with the following cases:
- server time is much later than client
- server time is much earlier than client

Even better: make a new class so _ts_check and _ts_skew are member objects and can be accessed easily by tests, and make signUrl have a static instance of that new class.

Please ping me if you need further reviews during my time off.

review: Needs Fixing
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Correction: I meant lines 381-395.

227. By dobey

Default to UTC for current times, not local time.
Move code to separate function as requested.
Add requested tests, and remove superfluous test.

228. By dobey

Add the new symbol to the symbols file too.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Looks good.

The one nitpick I have is approving while alecu is on leave, since the branch doesn't expose -ts_check and _ts_skew as requested by him. But he expressed it as a stretch goal, and after talking with dobey about it in irc I think this version is fine.

review: Approve
Revision history for this message
Alejandro J. Cura (alecu) wrote :

I'm not finding the time to do a proper review, but I see that the changes I requested have been made, so I'm changing my review to Abstain.

review: Abstain

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2014-01-15 19:35:23 +0000
3+++ .bzrignore 2015-12-07 21:37:17 +0000
4@@ -8,6 +8,7 @@
5 signing-example
6 test-ubuntuoneauth
7 moc_*.cpp
8+mock_sso_server.py
9 qmldir
10 ui_*.h
11 *_automoc.cpp
12
13=== modified file 'debian/libubuntuoneauth-2.0-0.symbols'
14--- debian/libubuntuoneauth-2.0-0.symbols 2015-06-17 16:58:41 +0000
15+++ debian/libubuntuoneauth-2.0-0.symbols 2015-12-07 21:37:17 +0000
16@@ -114,6 +114,8 @@
17 (c++)"UbuntuOne::Token::consumerKey() const@Base" 15.10+15.10.20150617
18 (c++)"UbuntuOne::Token::created() const@Base" 14.04+14.10.20140818
19 (c++)"UbuntuOne::Token::updated() const@Base" 14.04+14.10.20140818
20+ (c++)"UbuntuOne::Token::getServerTimestamp() const@Base" 0replaceme
21+ (c++)"UbuntuOne::Token::addOAuthTimestamp(QString) const@Base" 0replaceme
22 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token)@Base" 13.08
23 (c++)"UbuntuOne::Keyring::storeToken(UbuntuOne::Token, QString const&)@Base" 14.04+14.10.20140802
24 (c++)"UbuntuOne::Keyring::tokenFound(UbuntuOne::Token const&)@Base" 13.08
25
26=== modified file 'libubuntuoneauth/tests/CMakeLists.txt'
27--- libubuntuoneauth/tests/CMakeLists.txt 2013-06-14 22:30:19 +0000
28+++ libubuntuoneauth/tests/CMakeLists.txt 2015-12-07 21:37:17 +0000
29@@ -16,6 +16,11 @@
30 -L${CMAKE_BINARY_DIR}/libubuntuoneauth
31 ${AUTH_LIB_NAME})
32
33+configure_file(
34+ mock_sso_server.py.in
35+ mock_sso_server.py
36+)
37+
38 add_custom_target(ubuntuoneauth-tests
39 COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${TESTS_TARGET}
40 DEPENDS ${TESTS_TARGET}
41
42=== modified file 'libubuntuoneauth/tests/main.cpp'
43--- libubuntuoneauth/tests/main.cpp 2013-06-14 22:30:19 +0000
44+++ libubuntuoneauth/tests/main.cpp 2015-12-07 21:37:17 +0000
45@@ -1,5 +1,5 @@
46 /*
47- * Copyright 2013 Canonical Ltd.
48+ * Copyright 2013-2015 Canonical Ltd.
49 *
50 * This library is free software; you can redistribute it and/or
51 * modify it under the terms of version 3 of the GNU Lesser General Public
52@@ -24,7 +24,7 @@
53 #include "test_requests.h"
54 #include "test_responses.h"
55
56-int main()
57+int main(int argc, char** argv)
58 {
59 QByteArray fake_home = QDir::currentPath().toUtf8() + "/_test_temp";
60 /* Need to alter some environment variables to avoid contamination. */
61@@ -40,6 +40,8 @@
62
63 /* Token tests */
64 TestToken test_token;
65+ test_token.argc = argc;
66+ test_token.argv = argv;
67
68 /* Request tests */
69 TestOAuthTokenRequests test_oauth_token_requests;
70
71=== added file 'libubuntuoneauth/tests/mock_sso_server.py.in'
72--- libubuntuoneauth/tests/mock_sso_server.py.in 1970-01-01 00:00:00 +0000
73+++ libubuntuoneauth/tests/mock_sso_server.py.in 2015-12-07 21:37:17 +0000
74@@ -0,0 +1,57 @@
75+#!/usr/bin/python3
76+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
77+#
78+# Copyright (C) 2015 Canonical Ltd.
79+#
80+# This program is free software; you can redistribute it and/or modify
81+# it under the terms of the GNU General Public License version 3, as published
82+# by the Free Software Foundation.
83+#
84+# This program is distributed in the hope that it will be useful,
85+# but WITHOUT ANY WARRANTY; without even the implied warranty of
86+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
87+# GNU General Public License for more details.
88+#
89+# You should have received a copy of the GNU General Public License
90+# along with this program. If not, see <http://www.gnu.org/licenses/>.
91+
92+import datetime
93+import json
94+import threading
95+
96+from email.utils import parsedate_to_datetime
97+from http.server import BaseHTTPRequestHandler, HTTPServer
98+
99+
100+class MyHandler(BaseHTTPRequestHandler):
101+
102+ def do_HEAD(self):
103+ self.send_response(200)
104+ self.end_headers();
105+
106+ def date_time_string(self):
107+ if self.path == '/muchearlier':
108+ ctime = parsedate_to_datetime(self.headers['Date'])
109+ value = ctime + datetime.timedelta(days=10000)
110+ return value.strftime('%a, %d %b %Y %H:%M:%S GMT')
111+ elif self.path == '/muchlater':
112+ ctime = parsedate_to_datetime(self.headers['Date'])
113+ value = ctime - datetime.timedelta(days=10000)
114+ return value.strftime('%a, %d %b %Y %H:%M:%S GMT')
115+ else:
116+ return 'Thu, 01 Jan 1970 00:00:10 GMT'
117+
118+
119+def run_server():
120+ server_address = ('', 8000)
121+ httpd = HTTPServer(server_address, MyHandler)
122+ while True:
123+ httpd.handle_request()
124+
125+
126+def run_mocked_settings():
127+ t = threading.Thread(target=run_server)
128+ t.start()
129+
130+
131+run_mocked_settings()
132
133=== modified file 'libubuntuoneauth/tests/test_token.cpp'
134--- libubuntuoneauth/tests/test_token.cpp 2015-05-11 14:25:00 +0000
135+++ libubuntuoneauth/tests/test_token.cpp 2015-12-07 21:37:17 +0000
136@@ -1,5 +1,5 @@
137 /*
138- * Copyright 2013 Canonical Ltd.
139+ * Copyright 2013-2015 Canonical Ltd.
140 *
141 * This library is free software; you can redistribute it and/or
142 * modify it under the terms of version 3 of the GNU Lesser General Public
143@@ -15,6 +15,7 @@
144 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
145 * Boston, MA 02110-1301, USA.
146 */
147+#include <QDir>
148 #include <QHostInfo>
149 #include <QUuid>
150
151@@ -25,8 +26,16 @@
152 using namespace UbuntuOne;
153
154
155+void TestToken::cleanupTestCase()
156+{
157+ qputenv("SSO_AUTH_BASE_URL", old_base_url.toUtf8().data());
158+ process->close();
159+ process->deleteLater();
160+}
161+
162 TestToken::TestToken()
163- : test_hostname(QHostInfo::localHostName())
164+ : test_hostname(QHostInfo::localHostName()),
165+ old_base_url("")
166 {
167 }
168
169@@ -96,6 +105,98 @@
170 delete token;
171 }
172
173+void TestToken::testSignUrl()
174+{
175+ Token *token = new Token("a", "b", "c", "d");
176+ auto result = token->signUrl(QStringLiteral("https://login.ubuntu.com"),
177+ QStringLiteral("GET"));
178+ QVERIFY(result.startsWith("OAuth oauth_consumer_key"));
179+ delete token;
180+}
181+
182+void TestToken::testGetServerTimestamp()
183+{
184+ Token *token = new Token("a", "b", "c", "d");
185+ auto result = token->getServerTimestamp();
186+ delete token;
187+
188+ QCOMPARE(result.toTime_t(), (uint)time(NULL));
189+}
190+
191+void TestToken::testGetServerTimestampMuchEarlier()
192+{
193+ old_base_url = qgetenv("SSO_AUTH_BASE_URL");
194+ qputenv("SSO_AUTH_BASE_URL", "http://localhost:8000/muchearlier");
195+ qputenv("U1_TEST_TIMESTAMP", "Thu, 01 Jan 1970 04:32:16 GMT");
196+
197+ auto _app = new QCoreApplication(argc, argv);
198+
199+ QDateTime result;
200+ QTimer::singleShot(0, [this, &result, &_app](){
201+ process = new QProcess(this);
202+ QSignalSpy spy(process, SIGNAL(started()));
203+ QString program = "python3";
204+ QString script = QDir::currentPath() + "/mock_sso_server.py";
205+ process->start(program, QStringList() << script);
206+ QTRY_COMPARE(spy.count(), 1);
207+
208+ // Wait for server to start
209+ QTimer timer;
210+ QSignalSpy spy2(&timer, SIGNAL(timeout()));
211+ timer.setInterval(2000);
212+ timer.setSingleShot(true);
213+ timer.start();
214+ QTRY_COMPARE(spy2.count(), 1);
215+
216+ Token *token = new Token("a", "b", "c", "d");
217+ result = token->getServerTimestamp();
218+ delete token;
219+
220+ _app->quit();
221+ });
222+
223+ _app->exec();
224+
225+ QCOMPARE(result.toTime_t(), (uint)864016336);
226+}
227+
228+void TestToken::testGetServerTimestampMuchLater()
229+{
230+ old_base_url = qgetenv("SSO_AUTH_BASE_URL");
231+ qputenv("SSO_AUTH_BASE_URL", "http://localhost:8000/muchlater");
232+ qputenv("U1_TEST_TIMESTAMP", "Mon, 18 Jan 2038 22:14:07 GMT");
233+
234+ auto _app = new QCoreApplication(argc, argv);
235+
236+ QDateTime result;
237+ QTimer::singleShot(0, [this, &result, &_app](){
238+ process = new QProcess(this);
239+ QSignalSpy spy(process, SIGNAL(started()));
240+ QString program = "python3";
241+ QString script = QDir::currentPath() + "/mock_sso_server.py";
242+ process->start(program, QStringList() << script);
243+ QTRY_COMPARE(spy.count(), 1);
244+
245+ // Wait for server to start
246+ QTimer timer;
247+ QSignalSpy spy2(&timer, SIGNAL(timeout()));
248+ timer.setInterval(2000);
249+ timer.setSingleShot(true);
250+ timer.start();
251+ QTRY_COMPARE(spy2.count(), 1);
252+
253+ Token *token = new Token("a", "b", "c", "d");
254+ result = token->getServerTimestamp();
255+ delete token;
256+
257+ _app->quit();
258+ });
259+
260+ _app->exec();
261+
262+ QCOMPARE(result.toTime_t(), (uint)1283465647);
263+}
264+
265 void TestToken::testTimesCached()
266 {
267 Token *token = Token::fromQuery("consumer_key=c&consumer_secret=c_s&name=Name+%40+hostname&token=t&token_secret=t_s&updated=2014-08-11+18%3A40%3A20.777777&created=2014-08-11+18%3A40%3A20.777777");
268
269=== modified file 'libubuntuoneauth/tests/test_token.h'
270--- libubuntuoneauth/tests/test_token.h 2014-09-09 13:53:07 +0000
271+++ libubuntuoneauth/tests/test_token.h 2015-12-07 21:37:17 +0000
272@@ -1,5 +1,5 @@
273 /*
274- * Copyright 2013 Canonical Ltd.
275+ * Copyright 2013-2015 Canonical Ltd.
276 *
277 * This library is free software; you can redistribute it and/or
278 * modify it under the terms of version 3 of the GNU Lesser General Public
279@@ -18,6 +18,7 @@
280 #ifndef _TEST_TOKEN_H_
281 #define _TEST_TOKEN_H_
282
283+#include <QProcess>
284 #include <QtTest/QtTest>
285
286 class TestToken: public QObject
287@@ -26,10 +27,17 @@
288 public:
289 TestToken();
290
291+ int argc;
292+ char** argv;
293+
294 private:
295 QString test_hostname;
296+ QProcess *process;
297+ QString old_base_url;;
298
299 private slots:
300+ void cleanupTestCase();
301+
302 void testEmptyToken();
303 void testTokenArgs();
304 void testTokenCopy();
305@@ -40,6 +48,11 @@
306 void testTokenName();
307
308 void testSignUrlEmpty();
309+ void testSignUrl();
310+
311+ void testGetServerTimestamp();
312+ void testGetServerTimestampMuchEarlier();
313+ void testGetServerTimestampMuchLater();
314
315 void testTimesCached();
316 void testCreatedParsed();
317
318=== modified file 'libubuntuoneauth/token.cpp'
319--- libubuntuoneauth/token.cpp 2015-05-06 09:14:54 +0000
320+++ libubuntuoneauth/token.cpp 2015-12-07 21:37:17 +0000
321@@ -1,5 +1,5 @@
322 /*
323- * Copyright 2013 Canonical Ltd.
324+ * Copyright 2013-2015 Canonical Ltd.
325 *
326 * This library is free software; you can redistribute it and/or
327 * modify it under the terms of version 3 of the GNU Lesser General Public
328@@ -15,16 +15,20 @@
329 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
330 * Boston, MA 02110-1301, USA.
331 */
332+#include "token.h"
333+#include "ssoservice.h"
334+
335 #include <stdlib.h>
336 #include <oauth.h>
337
338+#include <QCoreApplication>
339 #include <QHash>
340 #include <QHostInfo>
341+#include <QNetworkReply>
342+#include <QNetworkRequest>
343 #include <QStringList>
344 #include <QUrl>
345
346-#include "token.h"
347-
348 #define TOKEN_NAME_KEY "name"
349 #define TOKEN_TOKEN_KEY "token"
350 #define TOKEN_TOKEN_SEC_KEY "token_secret"
351@@ -33,6 +37,8 @@
352 #define TOKEN_UPDATED_KEY "updated"
353 #define TOKEN_CREATED_KEY "created"
354
355+#define TIMESTAMP_CHECK_INTERVAL (60 * 60) // Seconds/minute * minutes
356+
357
358 namespace UbuntuOne {
359
360@@ -128,7 +134,7 @@
361 /**
362 * \fn QDateTime Token::updated()
363 *
364- * Retruns a QDateTime representing the time the token was last updated on
365+ * Returns a QDateTime representing the time the token was last updated on
366 * the server, or empty if unknown.
367 */
368 QDateTime Token::updated() const
369@@ -141,6 +147,95 @@
370 }
371
372 /**
373+ * \fn QDateTime Token::getServerTimestamp()
374+ *
375+ * Returns a QDateTime representing the current time known on the
376+ * Ubuntu One authentication server, or current local time on errors,
377+ * or when a QCoreApplication instance is not running.
378+ */
379+ QDateTime Token::getServerTimestamp() const
380+ {
381+ // The DateTime object to return, defaulting to current time
382+ QDateTime _dt = QDateTime::currentDateTimeUtc();
383+
384+ // The timestamp of the client to send to the server.
385+ QDateTime _clientDate(_dt);
386+ auto _testDate = qgetenv("U1_TEST_TIMESTAMP");
387+ if (_testDate != "") {
388+ _clientDate = QDateTime::fromString(_testDate, Qt::RFC2822Date);
389+ }
390+
391+ auto _app = QCoreApplication::instance();
392+ if (_app != nullptr) {
393+ QSharedPointer<QNetworkAccessManager> _nam(new QNetworkAccessManager());
394+ QUrl _url{SSOService::getAuthBaseUrl()};
395+ QSharedPointer<QNetworkRequest> _req(new QNetworkRequest(_url));
396+ _req->setRawHeader("Cache-Control", "no-cache");
397+ _req->setRawHeader("Date",
398+ _clientDate.toString(Qt::RFC2822Date).toUtf8().data());
399+
400+ qDebug() << "Getting timestamp from server:" << _url.toString();
401+
402+ auto _reply = _nam->head(*_req);
403+ QEventLoop _loop;
404+ QObject::connect(_reply, &QNetworkReply::finished,
405+ [&_loop, &_reply, &_dt]() {
406+ auto _date = _reply->rawHeader("Date");
407+ _dt = QDateTime::fromString(_date,
408+ Qt::RFC2822Date);
409+ qDebug() << "Got server timestamp:"
410+ << _dt.toTime_t();
411+ _loop.quit();
412+ });
413+ typedef void(QNetworkReply::*QNetworkReplyErrorSignal)(QNetworkReply::NetworkError);
414+ QObject::connect(_reply, static_cast<QNetworkReplyErrorSignal>(&QNetworkReply::error),
415+ [&_loop, &_reply](QNetworkReply::NetworkError) {
416+ qCritical() << "Error fetching server timestamp:"
417+ << _reply->readAll();
418+ _loop.quit();
419+ });
420+
421+ _loop.exec();
422+ } else {
423+ qWarning() << "No main loop, defaulting to local timestamp.";
424+ }
425+ return _dt;
426+ }
427+
428+ /**
429+ * \fn QUrl Token::addOAuthTimestamp(const QString url)
430+ *
431+ * Returns a QUrl with the oauth_timestamp value added to the query string
432+ */
433+ QUrl Token::addOAuthTimestamp(const QString url) const
434+ {
435+ // A copy of the URL in case we need to fix the timestamp
436+ QUrl newurl{url};
437+
438+ // Get and use the server timestamp if necessary
439+ QDateTime _now = QDateTime::currentDateTimeUtc();
440+ // Static variables for caching the time to check, and skew
441+ static uint _ts_check = 0;
442+ static int _ts_skew = 0;
443+
444+ QDateTime timestamp;
445+ if (_ts_check <= _now.toTime_t()) {
446+ timestamp = getServerTimestamp();
447+ _ts_skew = timestamp.toTime_t() - _now.toTime_t();
448+ } else {
449+ // QDateTime doesn't override + operator, create from time_t
450+ timestamp = QDateTime::fromTime_t(_now.toTime_t() + _ts_skew);
451+ }
452+ _ts_check = _now.toTime_t() + TIMESTAMP_CHECK_INTERVAL;
453+ char buf[11];
454+ snprintf(buf, 11, "%010u", timestamp.toTime_t());
455+ newurl.setQuery(newurl.query(QUrl::FullyEncoded) +
456+ "&oauth_timestamp=" + buf);
457+
458+ return newurl;
459+ }
460+
461+ /**
462 * \fn QString Token::signUrl(const QString url, const QString method,
463 * bool asQuery = false)
464 *
465@@ -159,7 +254,9 @@
466 return result;
467 }
468
469- argc = oauth_split_url_parameters(url.toUtf8().data(), &argv);
470+ QUrl copy(addOAuthTimestamp(url));
471+ argc = oauth_split_url_parameters(copy.toString().toUtf8().data(),
472+ &argv);
473 // Fixup the URL as liboauth is escaping '+' to ' ' in it, incorrectly.
474 for (int a = 0; argv[0][a] != 0; a++)
475 argv[0][a] = argv[0][a] == ' ' ? '+' : argv[0][a];
476
477=== modified file 'libubuntuoneauth/token.h'
478--- libubuntuoneauth/token.h 2015-05-06 08:59:44 +0000
479+++ libubuntuoneauth/token.h 2015-12-07 21:37:17 +0000
480@@ -1,5 +1,5 @@
481 /*
482- * Copyright 2013 Canonical Ltd.
483+ * Copyright 2013-2015 Canonical Ltd.
484 *
485 * This library is free software; you can redistribute it and/or
486 * modify it under the terms of version 3 of the GNU Lesser General Public
487@@ -21,6 +21,7 @@
488 #include <QDateTime>
489 #include <QHash>
490 #include <QString>
491+#include <QUrl>
492
493
494 namespace UbuntuOne {
495@@ -45,6 +46,8 @@
496 QDateTime created() const;
497 QDateTime updated() const;
498
499+ QDateTime getServerTimestamp() const;
500+ QUrl addOAuthTimestamp(const QString url) const;
501 QString signUrl(const QString url, const QString method, bool asQuery = false) const;
502
503 static Token *fromQuery(const QString query);

Subscribers

People subscribed via source and target branches

to all changes: