Merge lp:~stub/launchpad/oauth-db into lp:launchpad

Proposed by Stuart Bishop
Status: Rejected
Rejected by: Stuart Bishop
Proposed branch: lp:~stub/launchpad/oauth-db
Merge into: lp:launchpad
Diff against target: 436 lines (+55/-169)
7 files modified
lib/canonical/launchpad/database/oauth.py (+44/-36)
lib/canonical/launchpad/database/tests/test_oauth.py (+2/-8)
lib/canonical/launchpad/doc/oauth.txt (+8/-26)
lib/canonical/launchpad/interfaces/oauth.py (+1/-16)
lib/canonical/launchpad/scripts/garbo.py (+0/-38)
lib/canonical/launchpad/scripts/tests/test_garbo.py (+0/-41)
lib/canonical/launchpad/zcml/oauth.zcml (+0/-4)
To merge this branch: bzr merge lp:~stub/launchpad/oauth-db
Reviewer Review Type Date Requested Status
Canonical Launchpad Engineering Pending
Review via email: mp+27025@code.launchpad.net

Description of the change

The OAuthNonce table is our most heavily updated table by an order of magnitude and responsible for the majority of our replication load. It generally sits at 40 updates per second and spikes at over two hundred for short periods.

We maintain the nonce seen history per the OAuth spec to help prevent replay attacks. There is no need for this information to be stored in a relational database at all - it is overkill. Memcache seems a better storage mechanism.

If memory pressure causes memcached to prematurely evict nonce history, users will not notice - what we lose is the replay protection. Monitoring is being put in place so we will see when evictions start happening and we can add RAM to our memcached instances or investigate alternative storages.

To post a comment you must log in.
lp:~stub/launchpad/oauth-db updated
5590. By Stuart Bishop

Better expiry to avoid memcached pressure

5591. By Stuart Bishop

Betterer expiry

5592. By Stuart Bishop

Shorten keys

Unmerged revisions

5592. By Stuart Bishop

Shorten keys

5591. By Stuart Bishop

Betterer expiry

5590. By Stuart Bishop

Better expiry to avoid memcached pressure

5589. By Stuart Bishop

Drop OAuthNonce DB class, replacing its use with memcached

5588. By Stuart Bishop

Log OAuthNonces in memcached rather than PostgreSQL

5587. By Stuart Bishop

Merge from lp:~launchpad-pqm/launchpad/devel

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/database/oauth.py'
--- lib/canonical/launchpad/database/oauth.py 2010-04-19 09:39:29 +0000
+++ lib/canonical/launchpad/database/oauth.py 2010-06-08 12:11:25 +0000
@@ -6,16 +6,16 @@
6 'OAuthAccessToken',6 'OAuthAccessToken',
7 'OAuthConsumer',7 'OAuthConsumer',
8 'OAuthConsumerSet',8 'OAuthConsumerSet',
9 'OAuthNonce',
10 'OAuthRequestToken',9 'OAuthRequestToken',
11 'OAuthRequestTokenSet']10 'OAuthRequestTokenSet']
1211
13import pytz12import time
14from datetime import datetime, timedelta13from datetime import datetime, timedelta
1514
16from zope.component import getUtility15from zope.component import getUtility
17from zope.interface import implements16from zope.interface import implements
1817
18from pytz import UTC
19from sqlobject import BoolCol, ForeignKey, StringCol19from sqlobject import BoolCol, ForeignKey, StringCol
20from storm.expr import And20from storm.expr import And
2121
@@ -32,8 +32,9 @@
32from lp.registry.interfaces.projectgroup import IProjectGroup32from lp.registry.interfaces.projectgroup import IProjectGroup
33from lp.registry.interfaces.distributionsourcepackage import (33from lp.registry.interfaces.distributionsourcepackage import (
34 IDistributionSourcePackage)34 IDistributionSourcePackage)
35from lp.services.memcache.interfaces import IMemcacheClient
35from canonical.launchpad.interfaces import (36from canonical.launchpad.interfaces import (
36 IOAuthAccessToken, IOAuthConsumer, IOAuthConsumerSet, IOAuthNonce,37 IOAuthAccessToken, IOAuthConsumer, IOAuthConsumerSet,
37 IOAuthRequestToken, IOAuthRequestTokenSet, NonceAlreadyUsed,38 IOAuthRequestToken, IOAuthRequestTokenSet, NonceAlreadyUsed,
38 TimestampOrderingError, ClockSkew)39 TimestampOrderingError, ClockSkew)
39from canonical.launchpad.webapp.interfaces import (40from canonical.launchpad.webapp.interfaces import (
@@ -46,7 +47,7 @@
46# timestamp "MUST be equal or greater than the timestamp used in previous47# timestamp "MUST be equal or greater than the timestamp used in previous
47# requests," but this is likely to cause problems if the client does request48# requests," but this is likely to cause problems if the client does request
48# pipelining, so we use a time window (relative to the timestamp of the49# pipelining, so we use a time window (relative to the timestamp of the
49# existing OAuthNonce) to check if the timestamp can is acceptable. As50# existing nonce) to check if the timestamp can is acceptable. As
50# suggested by Robert, we use a window which is at least twice the size of our51# suggested by Robert, we use a window which is at least twice the size of our
51# hard time out. This is a safe bet since no requests should take more than52# hard time out. This is a safe bet since no requests should take more than
52# one hard time out.53# one hard time out.
@@ -87,7 +88,7 @@
87 def newRequestToken(self):88 def newRequestToken(self):
88 """See `IOAuthConsumer`."""89 """See `IOAuthConsumer`."""
89 key, secret = create_token_key_and_secret(table=OAuthRequestToken)90 key, secret = create_token_key_and_secret(table=OAuthRequestToken)
90 date_expires = (datetime.now(pytz.timezone('UTC'))91 date_expires = (datetime.now(UTC)
91 + timedelta(hours=REQUEST_TOKEN_VALIDITY))92 + timedelta(hours=REQUEST_TOKEN_VALIDITY))
92 return OAuthRequestToken(93 return OAuthRequestToken(
93 consumer=self, key=key, secret=secret, date_expires=date_expires)94 consumer=self, key=key, secret=secret, date_expires=date_expires)
@@ -161,35 +162,52 @@
161162
162 def checkNonceAndTimestamp(self, nonce, timestamp):163 def checkNonceAndTimestamp(self, nonce, timestamp):
163 """See `IOAuthAccessToken`."""164 """See `IOAuthAccessToken`."""
164 timestamp = float(timestamp)165 date = float(timestamp)
165 date = datetime.fromtimestamp(timestamp, pytz.UTC)
166 # Determine if the timestamp is too far off from now.166 # Determine if the timestamp is too far off from now.
167 skew = timedelta(seconds=TIMESTAMP_SKEW_WINDOW)167 skew = TIMESTAMP_SKEW_WINDOW
168 now = datetime.now(pytz.UTC)168 now = time.time()
169 if date < (now-skew) or date > (now+skew):169 if date < (now-skew) or date > (now+skew):
170 raise ClockSkew('Timestamp appears to come from bad system clock')170 raise ClockSkew('Timestamp appears to come from bad system clock')
171
172 # Have we seen this nonce before with this token?
173 # Pull the nonce details, if they exists, from memcached.
174 cache = getUtility(IMemcacheClient)
175 nonce_seen_key = 'oauth:t_%s:n_%s' % (
176 self.key.encode('US-ASCII'), nonce.encode('US-ASCII'))
177 nonce_seen = cache.get(nonce_seen_key)
178
171 # Determine if the nonce was already used for this timestamp.179 # Determine if the nonce was already used for this timestamp.
172 store = OAuthNonce.getStore()180 if nonce_seen == date:
173 oauth_nonce = store.find(OAuthNonce,
174 And(OAuthNonce.access_token==self,
175 OAuthNonce.nonce==nonce,
176 OAuthNonce.request_timestamp==date)
177 ).one()
178 if oauth_nonce is not None:
179 raise NonceAlreadyUsed('This nonce has been used already.')181 raise NonceAlreadyUsed('This nonce has been used already.')
182
180 # Determine if the timestamp is too old compared to most recent183 # Determine if the timestamp is too old compared to most recent
181 # request.184 # request.
182 limit = date + timedelta(seconds=TIMESTAMP_ACCEPTANCE_WINDOW)185 token_seen_key = 'oauth:t_%s:seen' % self.key.encode('US-ASCII')
183 match = store.find(OAuthNonce,186 token_seen = cache.get(token_seen_key)
184 And(OAuthNonce.access_token==self,187
185 OAuthNonce.request_timestamp>limit)188 limit = date + TIMESTAMP_ACCEPTANCE_WINDOW
186 ).any()189 if token_seen > limit:
187 if match is not None:
188 raise TimestampOrderingError(190 raise TimestampOrderingError(
189 'Timestamp too old compared to most recent request')191 'Timestamp too old compared to most recent request')
190 # Looks OK. Give a Nonce object back.192
191 return OAuthNonce(193 # Looks OK.
192 access_token=self, nonce=nonce, request_timestamp=date)194
195 # No need to remember things beyond
196 # TIMESTAMP_ACCEPTANCE_WINDOW * 2 seconds
197 # Where n==TIMESTAMP_ACCEPTANCE_WINDOW, n * 2 is the worst case.
198 # We will accept a timestamp claiming to be from n seconds in
199 # the future. If, in n*2 seconds time we receive the same
200 # timestamp, it would still be withing the acceptable skew
201 # and we need to generate an 'already seen' exception.
202 expires = TIMESTAMP_SKEW_WINDOW * 2 + TIMESTAMP_ACCEPTANCE_WINDOW
203
204 # Remember when we saw this nonce to avoid replay attacks.
205 cache.set(nonce_seen_key, date, expires)
206
207 # Remember when we last checked a nonce for this access
208 # token so we don't accept nonces in the past but still within
209 # acceptable skew.
210 cache.set(token_seen_key, max(date, token_seen), expires)
193211
194212
195class OAuthRequestToken(OAuthBase):213class OAuthRequestToken(OAuthBase):
@@ -240,7 +258,7 @@
240 """See `IOAuthRequestToken`."""258 """See `IOAuthRequestToken`."""
241 assert not self.is_reviewed, (259 assert not self.is_reviewed, (
242 "Request tokens can be reviewed only once.")260 "Request tokens can be reviewed only once.")
243 self.date_reviewed = datetime.now(pytz.timezone('UTC'))261 self.date_reviewed = datetime.now(UTC)
244 self.person = user262 self.person = user
245 self.permission = permission263 self.permission = permission
246 if IProduct.providedBy(context):264 if IProduct.providedBy(context):
@@ -286,16 +304,6 @@
286 return OAuthRequestToken.selectOneBy(key=key)304 return OAuthRequestToken.selectOneBy(key=key)
287305
288306
289class OAuthNonce(OAuthBase):
290 """See `IOAuthNonce`."""
291 implements(IOAuthNonce)
292
293 access_token = ForeignKey(
294 dbName='access_token', foreignKey='OAuthAccessToken', notNull=True)
295 request_timestamp = UtcDateTimeCol(default=UTC_NOW, notNull=True)
296 nonce = StringCol(notNull=True)
297
298
299def create_token_key_and_secret(table):307def create_token_key_and_secret(table):
300 """Create a key and secret for an OAuth token.308 """Create a key and secret for an OAuth token.
301309
302310
=== modified file 'lib/canonical/launchpad/database/tests/test_oauth.py'
--- lib/canonical/launchpad/database/tests/test_oauth.py 2010-03-24 17:37:26 +0000
+++ lib/canonical/launchpad/database/tests/test_oauth.py 2010-06-08 12:11:25 +0000
@@ -13,7 +13,7 @@
13from zope.component import getUtility13from zope.component import getUtility
1414
15from canonical.launchpad.database.oauth import (15from canonical.launchpad.database.oauth import (
16 OAuthAccessToken, OAuthConsumer, OAuthNonce, OAuthRequestToken)16 OAuthAccessToken, OAuthConsumer, OAuthRequestToken)
17from canonical.testing.layers import DatabaseFunctionalLayer17from canonical.testing.layers import DatabaseFunctionalLayer
18from canonical.launchpad.webapp.interfaces import MAIN_STORE, MASTER_FLAVOR18from canonical.launchpad.webapp.interfaces import MAIN_STORE, MASTER_FLAVOR
1919
@@ -45,14 +45,8 @@
45 class_ = OAuthConsumer45 class_ = OAuthConsumer
4646
4747
48class OAuthNonceTestCase(BaseOAuthTestCase):
49 class_ = OAuthNonce
50
51
52def test_suite():48def test_suite():
53 return unittest.TestSuite((49 return unittest.TestSuite((
54 unittest.makeSuite(OAuthAccessTokenTestCase),50 unittest.makeSuite(OAuthAccessTokenTestCase),
55 unittest.makeSuite(OAuthRequestTokenTestCase),51 unittest.makeSuite(OAuthRequestTokenTestCase),
56 unittest.makeSuite(OAuthNonceTestCase),52 unittest.makeSuite(OAuthConsumerTestCase)))
57 unittest.makeSuite(OAuthConsumerTestCase),
58 ))
5953
=== modified file 'lib/canonical/launchpad/doc/oauth.txt'
--- lib/canonical/launchpad/doc/oauth.txt 2010-04-16 15:06:55 +0000
+++ lib/canonical/launchpad/doc/oauth.txt 2010-06-08 12:11:25 +0000
@@ -15,7 +15,7 @@
15 ... AccessLevel, OAuthPermission)15 ... AccessLevel, OAuthPermission)
16 >>> from canonical.launchpad.interfaces import (16 >>> from canonical.launchpad.interfaces import (
17 ... IOAuthAccessToken, IOAuthConsumer, IOAuthConsumerSet,17 ... IOAuthAccessToken, IOAuthConsumer, IOAuthConsumerSet,
18 ... IOAuthNonce, IOAuthRequestToken, IPersonSet)18 ... IOAuthRequestToken, IPersonSet)
19 >>> consumer_set = getUtility(IOAuthConsumerSet)19 >>> consumer_set = getUtility(IOAuthConsumerSet)
20 >>> verifyObject(IOAuthConsumerSet, consumer_set)20 >>> verifyObject(IOAuthConsumerSet, consumer_set)
21 True21 True
@@ -307,26 +307,16 @@
307307
308 >>> import time308 >>> import time
309 >>> now = time.time() - 1309 >>> now = time.time() - 1
310 >>> nonce1 = access_token.checkNonceAndTimestamp('boo', now)310 >>> access_token.checkNonceAndTimestamp('boo', now)
311 >>> verifyObject(IOAuthNonce, nonce1)
312 True
313311
314- We can use an existing nonce with a new time.312- We can use an existing nonce with a new time.
315313
316 >>> now += 1314 >>> now += 1
317 >>> nonce2 = access_token.checkNonceAndTimestamp('boo', now)315 >>> access_token.checkNonceAndTimestamp('boo', now)
318 >>> IOAuthNonce.providedBy(nonce2)
319 True
320 >>> nonce1 is nonce2
321 False
322316
323- We can use a new nonce with the same time.317- We can use a new nonce with the same time.
324318
325 >>> nonce3 = access_token.checkNonceAndTimestamp('surprise!', now)319 >>> access_token.checkNonceAndTimestamp('surprise!', now)
326 >>> IOAuthNonce.providedBy(nonce3)
327 True
328 >>> nonce1 is nonce3 or nonce2 is nonce3
329 False
330320
331- But we cannot use an existing nonce used for the same time.321- But we cannot use an existing nonce used for the same time.
332322
@@ -368,12 +358,8 @@
368 ... TIMESTAMP_ACCEPTANCE_WINDOW)358 ... TIMESTAMP_ACCEPTANCE_WINDOW)
369 >>> TIMESTAMP_ACCEPTANCE_WINDOW359 >>> TIMESTAMP_ACCEPTANCE_WINDOW
370 60360 60
371 >>> nonce4 = access_token.checkNonceAndTimestamp('boo', now-30)361 >>> access_token.checkNonceAndTimestamp('boo', now-30)
372 >>> IOAuthNonce.providedBy(nonce4)362 >>> access_token.checkNonceAndTimestamp('boo', now-60)
373 True
374 >>> nonce5 = access_token.checkNonceAndTimestamp('boo', now-60)
375 >>> IOAuthNonce.providedBy(nonce5)
376 True
377363
378- Once outside of the window (defined by the *latest* timestamp, even if it364- Once outside of the window (defined by the *latest* timestamp, even if it
379 is not the most recent), we get a TimestampOrderingError.365 is not the most recent), we get a TimestampOrderingError.
@@ -405,9 +391,7 @@
405 ... TIMESTAMP_SKEW_WINDOW)391 ... TIMESTAMP_SKEW_WINDOW)
406 >>> TIMESTAMP_SKEW_WINDOW392 >>> TIMESTAMP_SKEW_WINDOW
407 3600393 3600
408 >>> nonce6 = access_token.checkNonceAndTimestamp('boo', now + 55*60)394 >>> access_token.checkNonceAndTimestamp('boo', now + 55*60)
409 >>> IOAuthNonce.providedBy(nonce6)
410 True
411395
412- We cannot access it 65 minutes in the future.396- We cannot access it 65 minutes in the future.
413397
@@ -419,9 +403,7 @@
419- It's also worth noting that now the TIMESTAMP_ACCEPTANCE_WINDOW is based403- It's also worth noting that now the TIMESTAMP_ACCEPTANCE_WINDOW is based
420 off of the time 55 minutes in the future.404 off of the time 55 minutes in the future.
421405
422 >>> nonce7 = access_token.checkNonceAndTimestamp('boo', now + 54*60 + 30)406 >>> access_token.checkNonceAndTimestamp('boo', now + 54*60 + 30)
423 >>> IOAuthNonce.providedBy(nonce7)
424 True
425 >>> access_token.checkNonceAndTimestamp('boo', now + 60)407 >>> access_token.checkNonceAndTimestamp('boo', now + 60)
426 Traceback (most recent call last):408 Traceback (most recent call last):
427 ...409 ...
428410
=== modified file 'lib/canonical/launchpad/interfaces/oauth.py'
--- lib/canonical/launchpad/interfaces/oauth.py 2010-04-19 14:47:49 +0000
+++ lib/canonical/launchpad/interfaces/oauth.py 2010-06-08 12:11:25 +0000
@@ -13,7 +13,6 @@
13 'IOAuthAccessToken',13 'IOAuthAccessToken',
14 'IOAuthConsumer',14 'IOAuthConsumer',
15 'IOAuthConsumerSet',15 'IOAuthConsumerSet',
16 'IOAuthNonce',
17 'IOAuthRequestToken',16 'IOAuthRequestToken',
18 'IOAuthRequestTokenSet',17 'IOAuthRequestTokenSet',
19 'IOAuthSignedRequest',18 'IOAuthSignedRequest',
@@ -174,7 +173,7 @@
174 +/- `TIMESTAMP_SKEW_WINDOW` of now.173 +/- `TIMESTAMP_SKEW_WINDOW` of now.
175174
176 If the nonce has never been used together with this token and175 If the nonce has never been used together with this token and
177 timestamp before, we store it in the database with the given timestamp176 timestamp before, we store it with the given timestamp
178 and associated with this token.177 and associated with this token.
179 """178 """
180179
@@ -236,20 +235,6 @@
236 """235 """
237236
238237
239class IOAuthNonce(Interface):
240 """The unique (nonce,timestamp) for requests using a given access token.
241
242 The nonce value (which is unique for all requests with that timestamp)
243 is generated by the consumer and included, together with the timestamp,
244 in each request made. It's used to prevent replay attacks.
245 """
246
247 request_timestamp = Datetime(
248 title=_('Date issued'), required=True, readonly=True)
249 access_token = Object(schema=IOAuthAccessToken, title=_('The token'))
250 nonce = TextLine(title=_('Nonce'), required=True, readonly=True)
251
252
253class IOAuthSignedRequest(Interface):238class IOAuthSignedRequest(Interface):
254 """Marker interface for a request signed with OAuth credentials."""239 """Marker interface for a request signed with OAuth credentials."""
255240
256241
=== modified file 'lib/canonical/launchpad/scripts/garbo.py'
--- lib/canonical/launchpad/scripts/garbo.py 2010-05-27 16:49:09 +0000
+++ lib/canonical/launchpad/scripts/garbo.py 2010-06-08 12:11:25 +0000
@@ -23,7 +23,6 @@
23from canonical.launchpad.database.emailaddress import EmailAddress23from canonical.launchpad.database.emailaddress import EmailAddress
24from lp.hardwaredb.model.hwdb import HWSubmission24from lp.hardwaredb.model.hwdb import HWSubmission
25from canonical.launchpad.database.librarian import LibraryFileAlias25from canonical.launchpad.database.librarian import LibraryFileAlias
26from canonical.launchpad.database.oauth import OAuthNonce
27from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce26from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
28from canonical.launchpad.interfaces import IMasterStore27from canonical.launchpad.interfaces import IMasterStore
29from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus28from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
@@ -51,42 +50,6 @@
51ONE_DAY_IN_SECONDS = 24*60*6050ONE_DAY_IN_SECONDS = 24*60*60
5251
5352
54class OAuthNoncePruner(TunableLoop):
55 """An ITunableLoop to prune old OAuthNonce records.
56
57 We remove all OAuthNonce records older than 1 day.
58 """
59 maximum_chunk_size = 6*60*60 # 6 hours in seconds.
60
61 def __init__(self, log, abort_time=None):
62 super(OAuthNoncePruner, self).__init__(log, abort_time)
63 self.store = IMasterStore(OAuthNonce)
64 self.oldest_age = self.store.execute("""
65 SELECT COALESCE(EXTRACT(EPOCH FROM
66 CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
67 - MIN(request_timestamp)), 0)
68 FROM OAuthNonce
69 """).get_one()[0]
70
71 def isDone(self):
72 return self.oldest_age <= ONE_DAY_IN_SECONDS
73
74 def __call__(self, chunk_size):
75 self.oldest_age = max(
76 ONE_DAY_IN_SECONDS, self.oldest_age - chunk_size)
77
78 self.log.debug(
79 "Removed OAuthNonce rows older than %d seconds"
80 % self.oldest_age)
81
82 self.store.find(
83 OAuthNonce,
84 OAuthNonce.request_timestamp < SQL(
85 "CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - interval '%d seconds'"
86 % self.oldest_age)).remove()
87 transaction.commit()
88
89
90class OpenIDConsumerNoncePruner(TunableLoop):53class OpenIDConsumerNoncePruner(TunableLoop):
91 """An ITunableLoop to prune old OpenIDConsumerNonce records.54 """An ITunableLoop to prune old OpenIDConsumerNonce records.
9255
@@ -739,7 +702,6 @@
739class HourlyDatabaseGarbageCollector(BaseDatabaseGarbageCollector):702class HourlyDatabaseGarbageCollector(BaseDatabaseGarbageCollector):
740 script_name = 'garbo-hourly'703 script_name = 'garbo-hourly'
741 tunable_loops = [704 tunable_loops = [
742 OAuthNoncePruner,
743 OpenIDConsumerNoncePruner,705 OpenIDConsumerNoncePruner,
744 OpenIDConsumerAssociationPruner,706 OpenIDConsumerAssociationPruner,
745 RevisionCachePruner,707 RevisionCachePruner,
746708
=== modified file 'lib/canonical/launchpad/scripts/tests/test_garbo.py'
--- lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-05-26 01:48:12 +0000
+++ lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-06-08 12:11:25 +0000
@@ -20,7 +20,6 @@
20from canonical.config import config20from canonical.config import config
21from canonical.database.constants import THIRTY_DAYS_AGO, UTC_NOW21from canonical.database.constants import THIRTY_DAYS_AGO, UTC_NOW
22from canonical.launchpad.database.message import Message22from canonical.launchpad.database.message import Message
23from canonical.launchpad.database.oauth import OAuthNonce
24from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce23from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
25from canonical.launchpad.interfaces import IMasterStore24from canonical.launchpad.interfaces import IMasterStore
26from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus25from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
@@ -90,46 +89,6 @@
90 collector.main()89 collector.main()
91 return collector90 return collector
9291
93 def test_OAuthNoncePruner(self):
94 now = datetime.utcnow().replace(tzinfo=UTC)
95 timestamps = [
96 now - timedelta(days=2), # Garbage
97 now - timedelta(days=1) - timedelta(seconds=60), # Garbage
98 now - timedelta(days=1) + timedelta(seconds=60), # Not garbage
99 now, # Not garbage
100 ]
101 LaunchpadZopelessLayer.switchDbUser('testadmin')
102 store = IMasterStore(OAuthNonce)
103
104 # Make sure we start with 0 nonces.
105 self.failUnlessEqual(store.find(OAuthNonce).count(), 0)
106
107 for timestamp in timestamps:
108 OAuthNonce(
109 access_tokenID=1,
110 request_timestamp = timestamp,
111 nonce = str(timestamp))
112 transaction.commit()
113
114 # Make sure we have 4 nonces now.
115 self.failUnlessEqual(store.find(OAuthNonce).count(), 4)
116
117 self.runHourly(maximum_chunk_size=60) # 1 minute maximum chunk size
118
119 store = IMasterStore(OAuthNonce)
120
121 # Now back to two, having removed the two garbage entries.
122 self.failUnlessEqual(store.find(OAuthNonce).count(), 2)
123
124 # And none of them are older than a day.
125 # Hmm... why is it I'm putting tz aware datetimes in and getting
126 # naive datetimes back? Bug in the SQLObject compatibility layer?
127 # Test is still fine as we know the timezone.
128 self.failUnless(
129 store.find(
130 Min(OAuthNonce.request_timestamp)).one().replace(tzinfo=UTC)
131 >= now - timedelta(days=1))
132
133 def test_OpenIDConsumerNoncePruner(self):92 def test_OpenIDConsumerNoncePruner(self):
134 now = int(time.mktime(time.gmtime()))93 now = int(time.mktime(time.gmtime()))
135 MINUTES = 6094 MINUTES = 60
13695
=== modified file 'lib/canonical/launchpad/zcml/oauth.zcml'
--- lib/canonical/launchpad/zcml/oauth.zcml 2009-07-13 18:15:02 +0000
+++ lib/canonical/launchpad/zcml/oauth.zcml 2010-06-08 12:11:25 +0000
@@ -44,8 +44,4 @@
44 permission="launchpad.Edit"44 permission="launchpad.Edit"
45 set_schema="canonical.launchpad.interfaces.IOAuthAccessToken"/>45 set_schema="canonical.launchpad.interfaces.IOAuthAccessToken"/>
46 </class>46 </class>
47
48 <class class="canonical.launchpad.database.OAuthNonce">
49 <allow interface="canonical.launchpad.interfaces.IOAuthNonce"/>
50 </class>
51</configure>47</configure>