You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2026/06/29 20:05:10 UTC
(allura) branch master updated (0ec1fd989 -> 79f1f6485)
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
from 0ec1fd989 bump joserfc 1.6.4 -> 1.7.2
new 4a81f0240 [#8610] Add encrypted field for Users email addresses
new 79f1f6485 [#8610] Add support for list fields to field conversion script
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
Allura/allura/model/auth.py | 18 +++++++++++++++++
Allura/allura/tests/model/test_auth.py | 35 ++++++++++++++++++++++++++++++++++
scripts/convert_encrypted_field.py | 24 +++++++++++++++++++----
3 files changed, 73 insertions(+), 4 deletions(-)
(allura) 02/02: [#8610] Add support for list fields to field conversion script
Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 79f1f6485cabfc8ede607cf858836812d5ac51c8
Author: Carlos Cruz <ca...@slashdotmedia.com>
AuthorDate: Wed Jun 17 11:24:53 2026 -0600
[#8610] Add support for list fields to field conversion script
---
scripts/convert_encrypted_field.py | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/scripts/convert_encrypted_field.py b/scripts/convert_encrypted_field.py
index 78ce8155c..e99c25a77 100644
--- a/scripts/convert_encrypted_field.py
+++ b/scripts/convert_encrypted_field.py
@@ -80,6 +80,19 @@ def _get_nested_value(rec: dict, field_name: str):
return value
+def _is_encrypted_list_schema(field_schema) -> bool:
+ return (
+ isinstance(field_schema, schema.Array)
+ and isinstance(field_schema.field_type, schema.Binary)
+ )
+
+
+def _encrypt_field_value(Model: type[MappedClass], value, field_schema):
+ if _is_encrypted_list_schema(field_schema):
+ return [Model.encr(v) if v is not None else None for v in value or []]
+ return Model.encr(value)
+
+
def main(class_name: str, plain_field_name: str,
*, remove_unencrypted: bool = False, redo_all: bool = False, limit: int | None = None):
"""
@@ -103,7 +116,7 @@ def main(class_name: str, plain_field_name: str,
# model schema, but we can still create it with raw MongoDB updates
encr_schema = None
if encr_schema is not None:
- assert isinstance(encr_schema, schema.Binary)
+ assert isinstance(encr_schema, schema.Binary) or _is_encrypted_list_schema(encr_schema)
elif remove_unencrypted:
raise AssertionError(
f'Cannot use --remove-unencrypted because {encrypted_field_name!r} '
@@ -111,7 +124,10 @@ def main(class_name: str, plain_field_name: str,
if remove_unencrypted:
if '.' not in plain_field_name:
plain_prop = Model.__dict__[plain_field_name] # getattr() better but needs Ming fix released
- assert isinstance(plain_prop, DecryptedProperty)
+ if _is_encrypted_list_schema(encr_schema):
+ assert hasattr(plain_prop, 'encrypted_field')
+ else:
+ assert isinstance(plain_prop, DecryptedProperty)
assert plain_prop.encrypted_field == encrypted_field_name
# TODO: figure out how it works with inheritance
@@ -132,7 +148,7 @@ def main(class_name: str, plain_field_name: str,
bulk_update_result = Model.query.update(
query,
{
- '$set': {encrypted_field_name: Model.encr(bulk_val)},
+ '$set': {encrypted_field_name: _encrypt_field_value(Model, bulk_val, encr_schema)},
},
multi=True,
)
@@ -159,7 +175,7 @@ def main(class_name: str, plain_field_name: str,
for rec_doc in docs:
val = _get_nested_value(rec_doc, plain_field_name)
- encr_val = Model.encr(val)
+ encr_val = _encrypt_field_value(Model, val, encr_schema)
raw_collection.update_one({'_id': rec_doc['_id']}, {
'$set': {encrypted_field_name: encr_val},
})
(allura) 01/02: [#8610] Add encrypted field for Users email addresses
Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 4a81f02409aee1e2e7f87d1f2db13731b0ff3086
Author: Carlos Cruz <ca...@slashdotmedia.com>
AuthorDate: Tue Jun 16 12:56:24 2026 -0600
[#8610] Add encrypted field for Users email addresses
---
Allura/allura/model/auth.py | 18 +++++++++++++++++
Allura/allura/tests/model/test_auth.py | 35 ++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index a4abc45d7..d9d26baf1 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -266,6 +266,15 @@ def get_decrypted(self, instance):
return super().__get__(instance, type(instance))
+class UserEmailAddressesMapperExtension(MapperExtension):
+
+ def before_insert(self, obj, state, sess):
+ obj.sync_email_addresses_encrypted()
+
+ def before_update(self, obj, state, sess):
+ obj.sync_email_addresses_encrypted()
+
+
class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
SALT_LEN = 8
@@ -274,6 +283,7 @@ class __mongometa__:
session = main_orm_session
indexes = ['tool_data.AuthPasswordReset.hash']
unique_indexes = ['username']
+ extensions = [UserEmailAddressesMapperExtension]
custom_indexes = [
dict(fields=('tool_data.phone_verification.number_hash',), sparse=True),
]
@@ -286,6 +296,7 @@ class __mongometa__:
sfx_userid = FieldProperty(S.Deprecated)
username = FieldProperty(str)
email_addresses = FieldProperty([str])
+ email_addresses_encrypted = FieldProperty([S.Binary], if_missing=[])
password = FieldProperty(str) # hashed
last_password_updated = FieldProperty(datetime) # to access, use AuthProvider's get_last_password_updated
password_algorithm = FieldProperty(str)
@@ -787,6 +798,13 @@ def set_tool_data(self, tool, **kw):
d.update(kw)
state(self).soil()
+ @classmethod
+ def encrypt_email_addresses(cls, email_addresses):
+ return [cls.encr(addr) if addr is not None else None for addr in email_addresses or []]
+
+ def sync_email_addresses_encrypted(self):
+ self.email_addresses_encrypted = type(self).encrypt_email_addresses(self.email_addresses)
+
def address_object(self, addr):
return EmailAddress.get(email=addr, claimed_by_user_id=self._id)
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index d1dd70771..014cf0021 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -432,6 +432,41 @@ def test_display_name_is_stored_encrypted(self):
display_name_encrypted=M.User.encr('Encrypted Display Name'))
assert user.username == 'encrypted-display-name-test'
+ def test_email_addresses_encrypted_is_populated_on_creation(self):
+ user = M.User(
+ username='email-addresses-encrypted-create-test',
+ email_addresses=['primary@example.com', None, 'secondary@example.com'])
+ ThreadLocalODMSession.flush_all()
+
+ expected_encrypted = [
+ M.User.encr('primary@example.com'),
+ None,
+ M.User.encr('secondary@example.com'),
+ ]
+ assert user.email_addresses == ['primary@example.com', None, 'secondary@example.com']
+ assert user.email_addresses_encrypted == expected_encrypted
+ assert state(user).document['email_addresses'] == ['primary@example.com', None, 'secondary@example.com']
+ assert state(user).document['email_addresses_encrypted'] == expected_encrypted
+
+ def test_email_addresses_encrypted_is_synced_on_update(self):
+ user = M.User(
+ username='email-addresses-encrypted-update-test',
+ email_addresses=['keep@example.com', 'remove@example.com'])
+ ThreadLocalODMSession.flush_all()
+
+ user.email_addresses.append('add@example.com')
+ user.email_addresses.remove('remove@example.com')
+ ThreadLocalODMSession.flush_all()
+
+ expected_encrypted = [
+ M.User.encr('keep@example.com'),
+ M.User.encr('add@example.com'),
+ ]
+ assert user.email_addresses == ['keep@example.com', 'add@example.com']
+ assert user.email_addresses_encrypted == expected_encrypted
+ assert state(user).document['email_addresses'] == ['keep@example.com', 'add@example.com']
+ assert state(user).document['email_addresses_encrypted'] == expected_encrypted
+
def test_set_display_name_pref_updates_encrypted_field_and_cache(self):
user = M.User(username='display-name-cache-test',
display_name='Original Display Name')