track the date an email address was confirmed
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index ac83b28..76b02db 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -360,6 +360,7 @@ def _verify_addr(self, addr, do_auth_check=True):
send_system_mail_to_user(user, 'New Email Address Added', email_body)
addr.confirmed = True
+ addr.confirmed_date = datetime.utcnow()
flash('Email address confirmed')
h.auditlog_user('Email address verified: %s', addr.email, user=user)
if user.get_pref('email_address') is None:
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 630e6c5..ed9bbac 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -88,6 +88,7 @@ class __mongometa__:
email = FieldProperty(str)
claimed_by_user_id = FieldProperty(S.ObjectId, if_missing=None)
confirmed = FieldProperty(bool, if_missing=False)
+ confirmed_date = FieldProperty(datetime)
nonce = FieldProperty(str)
valid_address = FieldProperty(bool, if_missing=None)
valid_details = FieldProperty(S.Anything, if_missing=None)
@@ -189,6 +190,15 @@ def send_verification_link(self):
message_id=h.gen_message_id(),
text=text)
+ @property
+ def confirmed_date_with_fallback(self) -> datetime | None:
+ # since confirmed_date was only added in 2025, use _id as approximate fallback
+ if self.confirmed_date:
+ return self.confirmed_date
+ if self.confirmed and self._id:
+ return self._id.generation_time.replace(tzinfo=None)
+ return None
+
class AuthGlobals(MappedClass):
class __mongometa__:
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 7afb845..36a7727 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -22,6 +22,7 @@
import textwrap
from datetime import datetime, timedelta
+from bson import ObjectId
from tg import tmpl_context as c, app_globals as g, request as r
from webob import Request
from mock import patch, Mock
@@ -113,6 +114,23 @@ def test_email_address_send_verification_link(self):
return_path, rcpts, body = _client.sendmail.call_args[0]
assert rcpts == ['test_admin@domain.net']
+ def test_email_address_confirmed_date_with_fallback(self):
+ assert M.EmailAddress().confirmed_date_with_fallback is None
+
+ test_date = datetime(2025, 1, 1)
+
+ assert M.EmailAddress(confirmed_date=test_date).confirmed_date_with_fallback == test_date
+
+ assert M.EmailAddress(
+ _id=ObjectId.from_datetime(test_date),
+ confirmed=False,
+ ).confirmed_date_with_fallback is None
+
+ assert M.EmailAddress(
+ _id=ObjectId.from_datetime(test_date),
+ confirmed=True,
+ ).confirmed_date_with_fallback == test_date
+
@td.with_user_project('test-admin')
def test_user(self):
assert c.user.url() .endswith('/u/test-admin/')
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 18989a0..c7a903f 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -209,6 +209,7 @@ def set_nbhd_wiki_content(nbhd_proj, content):
admin_email = M.EmailAddress.get(email='test-admin@users.localhost')
admin_email.confirmed = True
+ admin_email.confirmed_date = datetime.utcnow()
else:
u_admin = make_user('Admin 1', username='admin1')
# Admin1 is almost root, with admin access for Users and Projects
@@ -330,6 +331,7 @@ def create_user(display_name, username=None, password='foo', make_project=False)
kw = {"email": email}
em = EmailAddress.get(**kw)
em.confirmed = True
+ em.confirmed_date = datetime.utcnow()
em.set_nonce_hash()
user.set_pref('email_address', email)
fast_set_pwd(user, password)