This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch cc/11102-part2b
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 4d226572c23b2f74563422e9b38762ce3d8dbf4b
Author: Carlos Cruz <[email protected]>
AuthorDate: Fri Apr 24 11:23:07 2026 -0600

    [#8604] Implement field level encryption for User's display_name_field
---
 Allura/allura/lib/plugin.py            |  7 +++----
 Allura/allura/model/auth.py            | 16 ++++++++++------
 Allura/allura/tests/model/test_auth.py | 19 +++++++++++--------
 3 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index ac21d66df..2e4e59956 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -1851,10 +1851,9 @@ def get_pref(self, user, pref_name):
         if pref_name in user.preferences:
             pref_value = user.preferences[pref_name]
         elif pref_name == 'display_name':
-            # get the value directly from ming's internals, bypassing
-            # FieldPropertyDisplayName which always calls back to this get_pref
-            # method (infinite recursion)
-            pref_value = user.__dict__['__ming__'].state.document.display_name
+            # bypass FieldPropertyDisplayName.__get__, which always calls
+            # back to this get_pref method (infinite recursion)
+            pref_value = type(user).display_name.get_decrypted(user)
         else:
             pref_value = getattr(user, pref_name)
 
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 05322ca82..399cd3f5e 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -38,8 +38,8 @@
 from tg import tmpl_context as c, app_globals as g
 from tg import request
 from ming import schema as S
-from ming.odm import session, state
-from ming.odm import FieldProperty, RelationProperty, ForeignIdProperty, 
DecryptedProperty
+from ming.odm import session, state, MapperExtension
+from ming.odm import DecryptedProperty, FieldProperty, RelationProperty, 
ForeignIdProperty
 from ming.odm.declarative import MappedClass
 from ming.odm.odmsession import ThreadLocalODMSession
 from ming.utils import LazyProperty
@@ -243,8 +243,8 @@ def get_next_uid(cls):
         return g.next_uid
 
 
-class FieldPropertyDisplayName(FieldProperty):
-    # display_name is mongo field but only for preference storage
+class FieldPropertyDisplayName(DecryptedProperty):
+    # display_name is stored encrypted in mongo but only for preference storage
     # force all requests for this field to use the get_pref mechanism
     # Cache it per user, since it may be re-used several times in a request
     # and non-local preferences (ldap, database, etc) can be relatively 
expensive
@@ -261,7 +261,9 @@ def __get__(self, instance, cls=None):
     def __set__(self, instance, value):
         instance.__dict__.pop('_cache_display_name', None)
         super().__set__(instance, value)
-        instance.display_name_encrypted = 
type(instance).encr(state(instance).document[self.name])
+
+    def get_decrypted(self, instance):
+        return super().__get__(instance, type(instance))
 
 
 class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
@@ -307,7 +309,7 @@ class __mongometa__:
     ))
     # Additional top-level fields can/should be accessed with get/set_pref also
     # Not sure why we didn't put them within the 'preferences' dictionary :(
-    display_name: str = FieldPropertyDisplayName(str)
+    display_name: str = FieldPropertyDisplayName(str, 'display_name_encrypted')
     display_name_encrypted = FieldProperty(S.Binary, if_missing=None)
     # Personal data
     sex = FieldProperty(
@@ -614,6 +616,8 @@ def get_pref(self, pref_name):
 
     def set_pref(self, pref_name, pref_value):
         ret = plugin.UserPreferencesProvider.get().set_pref(self, pref_name, 
pref_value)
+        if pref_name == 'display_name':
+            self.__dict__.pop('_cache_display_name', None)
         state(self).soil()  # to make sure it gets reindexed in solr (even if 
pref is not part on the User ming model)
         return ret
 
diff --git a/Allura/allura/tests/model/test_auth.py 
b/Allura/allura/tests/model/test_auth.py
index a035f4466..d1dd70771 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -415,10 +415,9 @@ def test_user_index(self):
         # provided bby auth provider
         assert 'user_registration_date_dt' in idx
 
-    def test_display_name_encrypted_is_populated_on_creation(self):
-        user = M.User(
-            username='encrypted-display-name-test',
-            display_name='Encrypted Display Name')
+    def test_display_name_is_stored_encrypted(self):
+        user = M.User(username='encrypted-display-name-test',
+                      display_name='Encrypted Display Name')
         ThreadLocalODMSession.flush_all()
 
         assert user.display_name == 'Encrypted Display Name'
@@ -426,12 +425,16 @@ def 
test_display_name_encrypted_is_populated_on_creation(self):
         assert user.display_name_encrypted
         assert user.display_name_encrypted == M.User.encr('Encrypted Display 
Name')
         assert M.User.decr(user.display_name_encrypted) == 'Encrypted Display 
Name'
-        assert user.__dict__['__ming__'].state.document.display_name == 
'Encrypted Display Name'
+        assert user.display_name_encrypted != 'Encrypted Display Name'
+        assert 'display_name' not in user.__dict__['__ming__'].state.document
+
+        user = M.User.query.get(
+            display_name_encrypted=M.User.encr('Encrypted Display Name'))
+        assert user.username == 'encrypted-display-name-test'
 
     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')
+        user = M.User(username='display-name-cache-test',
+                      display_name='Original Display Name')
         ThreadLocalODMSession.flush_all()
         original_encrypted_display_name = user.display_name_encrypted
 

Reply via email to