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
