Merge ~cjwatson/launchpad:stormify-logintoken into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 6a191536e2746cf2ab24f7d82e2203e25f1b3e0e
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:stormify-logintoken
Merge into: launchpad:master
Diff against target: 244 lines (+80/-53)
3 files modified
lib/lp/registry/model/person.py (+16/-9)
lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst (+8/-5)
lib/lp/services/verification/model/logintoken.py (+56/-39)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+446394@code.launchpad.net

Commit message

Convert LoginToken to Storm

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
2index 53e2358..10f2342 100644
3--- a/lib/lp/registry/model/person.py
4+++ b/lib/lp/registry/model/person.py
5@@ -49,6 +49,7 @@ from storm.expr import (
6 Desc,
7 Exists,
8 In,
9+ Is,
10 Join,
11 LeftJoin,
12 Min,
13@@ -3178,16 +3179,22 @@ class Person(
14 @property
15 def unvalidatedemails(self):
16 """See `IPerson`."""
17- query = """
18- requester = %s
19- AND (tokentype=%s OR tokentype=%s)
20- AND date_consumed IS NULL
21- """ % sqlvalues(
22- self.id,
23- LoginTokenType.VALIDATEEMAIL,
24- LoginTokenType.VALIDATETEAMEMAIL,
25+ return sorted(
26+ {
27+ token.email
28+ for token in IStore(LoginToken).find(
29+ LoginToken,
30+ LoginToken.requester == self,
31+ LoginToken.tokentype.is_in(
32+ (
33+ LoginTokenType.VALIDATEEMAIL,
34+ LoginTokenType.VALIDATETEAMEMAIL,
35+ )
36+ ),
37+ Is(LoginToken.date_consumed, None),
38+ )
39+ }
40 )
41- return sorted({token.email for token in LoginToken.select(query)})
42
43 @property
44 def guessedemails(self):
45diff --git a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
46index d5b1fc6..d6f3073 100644
47--- a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
48+++ b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
49@@ -227,13 +227,14 @@ token to a fixed value:
50 >>> from datetime import datetime, timezone
51 >>> import hashlib
52 >>> from lp.services.verification.model.logintoken import LoginToken
53- >>> logintoken = LoginToken.selectOneBy(
54- ... _token=hashlib.sha256(token_value).hexdigest()
55+ >>> logintoken = (
56+ ... IStore(LoginToken)
57+ ... .find(LoginToken, _token=hashlib.sha256(token_value).hexdigest())
58+ ... .one()
59 ... )
60 >>> logintoken.date_created = datetime(
61 ... 2005, 4, 1, 12, 0, 0, tzinfo=timezone.utc
62 ... )
63- >>> logintoken.sync()
64
65 Back to Sample User. They visit the token URL and is asked to sign some
66 text to prove they own the key.
67@@ -317,8 +318,10 @@ If they sign the text correctly, they are redirected to their home page.
68
69 Now that the key has been validated, the login token is consumed:
70
71- >>> consumed_token = LoginToken.selectOneBy(
72- ... _token=hashlib.sha256(token_value).hexdigest()
73+ >>> consumed_token = (
74+ ... IStore(LoginToken)
75+ ... .find(LoginToken, _token=hashlib.sha256(token_value).hexdigest())
76+ ... .one()
77 ... )
78 >>> consumed_token.date_consumed is not None
79 True
80diff --git a/lib/lp/services/verification/model/logintoken.py b/lib/lp/services/verification/model/logintoken.py
81index 5aafa18..572cce2 100644
82--- a/lib/lp/services/verification/model/logintoken.py
83+++ b/lib/lp/services/verification/model/logintoken.py
84@@ -10,7 +10,9 @@ import hashlib
85 from datetime import timezone
86
87 import six
88-from storm.expr import And
89+from storm.expr import And, Is
90+from storm.properties import DateTime, Int, Unicode
91+from storm.references import Reference
92 from zope.component import getUtility
93 from zope.interface import implementer
94
95@@ -20,15 +22,9 @@ from lp.registry.interfaces.gpg import IGPGKeySet
96 from lp.registry.interfaces.person import IPersonSet
97 from lp.services.config import config
98 from lp.services.database.constants import UTC_NOW
99-from lp.services.database.datetimecol import UtcDateTimeCol
100 from lp.services.database.enumcol import DBEnum
101 from lp.services.database.interfaces import IPrimaryStore, IStore
102-from lp.services.database.sqlbase import SQLBase, sqlvalues
103-from lp.services.database.sqlobject import (
104- ForeignKey,
105- SQLObjectNotFound,
106- StringCol,
107-)
108+from lp.services.database.stormbase import StormBase
109 from lp.services.gpg.interfaces import IGPGHandler
110 from lp.services.mail.helpers import get_email_template
111 from lp.services.mail.sendmail import format_address, simple_sendmail
112@@ -44,35 +40,51 @@ MAIL_APP = "services/verification"
113
114
115 @implementer(ILoginToken)
116-class LoginToken(SQLBase):
117- _table = "LoginToken"
118-
119- redirection_url = StringCol(default=None)
120- requester = ForeignKey(dbName="requester", foreignKey="Person")
121- requesteremail = StringCol(
122- dbName="requesteremail", notNull=False, default=None
123+class LoginToken(StormBase):
124+ __storm_table__ = "LoginToken"
125+
126+ id = Int(primary=True)
127+ redirection_url = Unicode(default=None)
128+ requester_id = Int(name="requester")
129+ requester = Reference(requester_id, "Person.id")
130+ requesteremail = Unicode(
131+ name="requesteremail", allow_none=True, default=None
132 )
133- email = StringCol(dbName="email", notNull=True)
134+ email = Unicode(name="email", allow_none=False)
135
136 # The hex SHA-256 hash of the token.
137- _token = StringCol(dbName="token", unique=True)
138+ _token = Unicode(name="token")
139
140 tokentype = DBEnum(name="tokentype", allow_none=False, enum=LoginTokenType)
141- date_created = UtcDateTimeCol(dbName="created", notNull=True)
142- fingerprint = StringCol(dbName="fingerprint", notNull=False, default=None)
143- date_consumed = UtcDateTimeCol(default=None)
144+ date_created = DateTime(
145+ name="created", allow_none=False, tzinfo=timezone.utc
146+ )
147+ fingerprint = Unicode(name="fingerprint", allow_none=True, default=None)
148+ date_consumed = DateTime(default=None, tzinfo=timezone.utc)
149 password = "" # Quick fix for Bug #2481
150
151 title = "Launchpad Email Verification"
152
153- def __init__(self, *args, **kwargs):
154- token = kwargs.pop("token", None)
155+ def __init__(
156+ self,
157+ email,
158+ tokentype,
159+ redirection_url=None,
160+ requester=None,
161+ requesteremail=None,
162+ token=None,
163+ fingerprint=None,
164+ ):
165+ super().__init__()
166+ self.email = email
167+ self.tokentype = tokentype
168+ self.redirection_url = redirection_url
169+ self.requester = requester
170+ self.requesteremail = requesteremail
171 if token is not None:
172 self._plaintext_token = token
173- kwargs["_token"] = hashlib.sha256(
174- token.encode("UTF-8")
175- ).hexdigest()
176- super().__init__(*args, **kwargs)
177+ self._token = hashlib.sha256(token.encode("UTF-8")).hexdigest()
178+ self.fingerprint = fingerprint
179
180 _plaintext_token = None
181
182@@ -267,6 +279,10 @@ class LoginToken(SQLBase):
183 self.consume()
184 return lpkey, new
185
186+ def destroySelf(self):
187+ """See `ILoginToken`."""
188+ IStore(self).remove(self)
189+
190
191 @implementer(ILoginTokenSet)
192 class LoginTokenSet:
193@@ -275,10 +291,10 @@ class LoginTokenSet:
194
195 def get(self, id, default=None):
196 """See ILoginTokenSet."""
197- try:
198- return LoginToken.get(id)
199- except SQLObjectNotFound:
200+ token = IStore(LoginToken).get(LoginToken, id)
201+ if token is None:
202 return default
203+ return token
204
205 def searchByEmailRequesterAndType(
206 self, email, requester, type, consumed=None
207@@ -337,18 +353,20 @@ class LoginTokenSet:
208
209 def getPendingGPGKeys(self, requesterid=None):
210 """See ILoginTokenSet."""
211- query = (
212- "date_consumed IS NULL AND "
213- "(tokentype = %s OR tokentype = %s) "
214- % sqlvalues(
215- LoginTokenType.VALIDATEGPG, LoginTokenType.VALIDATESIGNONLYGPG
216- )
217- )
218+ clauses = [
219+ Is(LoginToken.date_consumed, None),
220+ LoginToken.tokentype.is_in(
221+ (
222+ LoginTokenType.VALIDATEGPG,
223+ LoginTokenType.VALIDATESIGNONLYGPG,
224+ )
225+ ),
226+ ]
227
228 if requesterid:
229- query += "AND requester=%s" % requesterid
230+ clauses.append(LoginToken.requester == requesterid)
231
232- return LoginToken.select(query)
233+ return IStore(LoginToken).find(LoginToken, *clauses)
234
235 def deleteByFingerprintRequesterAndType(
236 self, fingerprint, requester, type
237@@ -383,7 +401,6 @@ class LoginTokenSet:
238 email=email,
239 token=token,
240 tokentype=tokentype,
241- created=UTC_NOW,
242 fingerprint=fingerprint,
243 redirection_url=redirection_url,
244 )

Subscribers

People subscribed via source and target branches

to status/vote changes: