← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-logintoken into launchpad:master.

Commit message:
Convert LoginToken to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/446394
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-logintoken into launchpad:master.
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 53e2358..10f2342 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -49,6 +49,7 @@ from storm.expr import (
     Desc,
     Exists,
     In,
+    Is,
     Join,
     LeftJoin,
     Min,
@@ -3178,16 +3179,22 @@ class Person(
     @property
     def unvalidatedemails(self):
         """See `IPerson`."""
-        query = """
-            requester = %s
-            AND (tokentype=%s OR tokentype=%s)
-            AND date_consumed IS NULL
-            """ % sqlvalues(
-            self.id,
-            LoginTokenType.VALIDATEEMAIL,
-            LoginTokenType.VALIDATETEAMEMAIL,
+        return sorted(
+            {
+                token.email
+                for token in IStore(LoginToken).find(
+                    LoginToken,
+                    LoginToken.requester == self,
+                    LoginToken.tokentype.is_in(
+                        (
+                            LoginTokenType.VALIDATEEMAIL,
+                            LoginTokenType.VALIDATETEAMEMAIL,
+                        )
+                    ),
+                    Is(LoginToken.date_consumed, None),
+                )
+            }
         )
-        return sorted({token.email for token in LoginToken.select(query)})
 
     @property
     def guessedemails(self):
diff --git a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
index d5b1fc6..d6f3073 100644
--- a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
+++ b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.rst
@@ -227,13 +227,14 @@ token to a fixed value:
     >>> from datetime import datetime, timezone
     >>> import hashlib
     >>> from lp.services.verification.model.logintoken import LoginToken
-    >>> logintoken = LoginToken.selectOneBy(
-    ...     _token=hashlib.sha256(token_value).hexdigest()
+    >>> logintoken = (
+    ...     IStore(LoginToken)
+    ...     .find(LoginToken, _token=hashlib.sha256(token_value).hexdigest())
+    ...     .one()
     ... )
     >>> logintoken.date_created = datetime(
     ...     2005, 4, 1, 12, 0, 0, tzinfo=timezone.utc
     ... )
-    >>> logintoken.sync()
 
 Back to Sample User. They visit the token URL and is asked to sign some
 text to prove they own the key.
@@ -317,8 +318,10 @@ If they sign the text correctly, they are redirected to their home page.
 
 Now that the key has been validated, the login token is consumed:
 
-    >>> consumed_token = LoginToken.selectOneBy(
-    ...     _token=hashlib.sha256(token_value).hexdigest()
+    >>> consumed_token = (
+    ...     IStore(LoginToken)
+    ...     .find(LoginToken, _token=hashlib.sha256(token_value).hexdigest())
+    ...     .one()
     ... )
     >>> consumed_token.date_consumed is not None
     True
diff --git a/lib/lp/services/verification/model/logintoken.py b/lib/lp/services/verification/model/logintoken.py
index 5aafa18..572cce2 100644
--- a/lib/lp/services/verification/model/logintoken.py
+++ b/lib/lp/services/verification/model/logintoken.py
@@ -10,7 +10,9 @@ import hashlib
 from datetime import timezone
 
 import six
-from storm.expr import And
+from storm.expr import And, Is
+from storm.properties import DateTime, Int, Unicode
+from storm.references import Reference
 from zope.component import getUtility
 from zope.interface import implementer
 
@@ -20,15 +22,9 @@ from lp.registry.interfaces.gpg import IGPGKeySet
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.config import config
 from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IPrimaryStore, IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
-from lp.services.database.sqlobject import (
-    ForeignKey,
-    SQLObjectNotFound,
-    StringCol,
-)
+from lp.services.database.stormbase import StormBase
 from lp.services.gpg.interfaces import IGPGHandler
 from lp.services.mail.helpers import get_email_template
 from lp.services.mail.sendmail import format_address, simple_sendmail
@@ -44,35 +40,51 @@ MAIL_APP = "services/verification"
 
 
 @implementer(ILoginToken)
-class LoginToken(SQLBase):
-    _table = "LoginToken"
-
-    redirection_url = StringCol(default=None)
-    requester = ForeignKey(dbName="requester", foreignKey="Person")
-    requesteremail = StringCol(
-        dbName="requesteremail", notNull=False, default=None
+class LoginToken(StormBase):
+    __storm_table__ = "LoginToken"
+
+    id = Int(primary=True)
+    redirection_url = Unicode(default=None)
+    requester_id = Int(name="requester")
+    requester = Reference(requester_id, "Person.id")
+    requesteremail = Unicode(
+        name="requesteremail", allow_none=True, default=None
     )
-    email = StringCol(dbName="email", notNull=True)
+    email = Unicode(name="email", allow_none=False)
 
     # The hex SHA-256 hash of the token.
-    _token = StringCol(dbName="token", unique=True)
+    _token = Unicode(name="token")
 
     tokentype = DBEnum(name="tokentype", allow_none=False, enum=LoginTokenType)
-    date_created = UtcDateTimeCol(dbName="created", notNull=True)
-    fingerprint = StringCol(dbName="fingerprint", notNull=False, default=None)
-    date_consumed = UtcDateTimeCol(default=None)
+    date_created = DateTime(
+        name="created", allow_none=False, tzinfo=timezone.utc
+    )
+    fingerprint = Unicode(name="fingerprint", allow_none=True, default=None)
+    date_consumed = DateTime(default=None, tzinfo=timezone.utc)
     password = ""  # Quick fix for Bug #2481
 
     title = "Launchpad Email Verification"
 
-    def __init__(self, *args, **kwargs):
-        token = kwargs.pop("token", None)
+    def __init__(
+        self,
+        email,
+        tokentype,
+        redirection_url=None,
+        requester=None,
+        requesteremail=None,
+        token=None,
+        fingerprint=None,
+    ):
+        super().__init__()
+        self.email = email
+        self.tokentype = tokentype
+        self.redirection_url = redirection_url
+        self.requester = requester
+        self.requesteremail = requesteremail
         if token is not None:
             self._plaintext_token = token
-            kwargs["_token"] = hashlib.sha256(
-                token.encode("UTF-8")
-            ).hexdigest()
-        super().__init__(*args, **kwargs)
+            self._token = hashlib.sha256(token.encode("UTF-8")).hexdigest()
+        self.fingerprint = fingerprint
 
     _plaintext_token = None
 
@@ -267,6 +279,10 @@ class LoginToken(SQLBase):
         self.consume()
         return lpkey, new
 
+    def destroySelf(self):
+        """See `ILoginToken`."""
+        IStore(self).remove(self)
+
 
 @implementer(ILoginTokenSet)
 class LoginTokenSet:
@@ -275,10 +291,10 @@ class LoginTokenSet:
 
     def get(self, id, default=None):
         """See ILoginTokenSet."""
-        try:
-            return LoginToken.get(id)
-        except SQLObjectNotFound:
+        token = IStore(LoginToken).get(LoginToken, id)
+        if token is None:
             return default
+        return token
 
     def searchByEmailRequesterAndType(
         self, email, requester, type, consumed=None
@@ -337,18 +353,20 @@ class LoginTokenSet:
 
     def getPendingGPGKeys(self, requesterid=None):
         """See ILoginTokenSet."""
-        query = (
-            "date_consumed IS NULL AND "
-            "(tokentype = %s OR tokentype = %s) "
-            % sqlvalues(
-                LoginTokenType.VALIDATEGPG, LoginTokenType.VALIDATESIGNONLYGPG
-            )
-        )
+        clauses = [
+            Is(LoginToken.date_consumed, None),
+            LoginToken.tokentype.is_in(
+                (
+                    LoginTokenType.VALIDATEGPG,
+                    LoginTokenType.VALIDATESIGNONLYGPG,
+                )
+            ),
+        ]
 
         if requesterid:
-            query += "AND requester=%s" % requesterid
+            clauses.append(LoginToken.requester == requesterid)
 
-        return LoginToken.select(query)
+        return IStore(LoginToken).find(LoginToken, *clauses)
 
     def deleteByFingerprintRequesterAndType(
         self, fingerprint, requester, type
@@ -383,7 +401,6 @@ class LoginTokenSet:
             email=email,
             token=token,
             tokentype=tokentype,
-            created=UTC_NOW,
             fingerprint=fingerprint,
             redirection_url=redirection_url,
         )