From 37c2c4e307b0b8713ed77337ced975c4be84c97a Mon Sep 17 00:00:00 2001 From: Michael Douchin Date: Thu, 22 Aug 2024 16:45:48 +0200 Subject: [PATCH] Attribute filter - Allow multiple values for PostgreSQL layers and active option --- lizmap_server/lizmap_accesscontrol.py | 41 ++++++++++++++++++--------- test/test_lizmap_accesscontrol.py | 36 +++++++++++++++++++++-- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/lizmap_server/lizmap_accesscontrol.py b/lizmap_server/lizmap_accesscontrol.py index 776c95ce..89a6162f 100755 --- a/lizmap_server/lizmap_accesscontrol.py +++ b/lizmap_server/lizmap_accesscontrol.py @@ -391,19 +391,25 @@ def get_lizmap_layer_filter(self, layer: QgsVectorLayer, filter_type: FilterType return login_filter - login_filter = self._filter_by_login(cfg_layer_login_filter, groups, user_login) + login_filter = self._filter_by_login( + cfg_layer_login_filter, + groups, + user_login, + layer.dataProvider().name() + ) if polygon_filter: return f'{polygon_filter} AND {login_filter}' return login_filter @staticmethod - def _filter_by_login(cfg_layer_login_filter: dict, groups: tuple, login: str) -> str: + def _filter_by_login(cfg_layer_login_filter: dict, groups: tuple, login: str, provider: str) -> str: """ Build the string according to the filter by login configuration. :param cfg_layer_login_filter: The Lizmap Filter by login configuration. :param groups: List of groups for the current user :param login: The current user + :param provider: The layer data provider ('postgres' for example) """ # List of values for expression values = [] @@ -420,7 +426,9 @@ def _filter_by_login(cfg_layer_login_filter: dict, groups: tuple, login: str) -> # Since LWC 3.8, we allow to have a list of groups (or logins) # separated by comma, with NO SPACES - # e.g. field "filter_field" can contain 'group_a,group_b,group_c' + # only for PostgreSQL layers and if the option allow_multiple_acl_values + # is set to True + # For example the field can contain 'group_a,group_b,group_c' # To use only pure SQL allowed by QGIS, we can use LIKE items # For big dataset, a GIN index with pg_trgm must be used for the # filter field to improve performance @@ -442,17 +450,22 @@ def _filter_by_login(cfg_layer_login_filter: dict, groups: tuple, login: str) -> # equality filters.append(f'{quoted_field} = {quoted_value}') - # begins with value & comma - quoted_like_value = QgsExpression.quotedString(f'{value},%') - filters.append(f'{quoted_field} LIKE {quoted_like_value}') - - # ends with comma & value - quoted_like_value = QgsExpression.quotedString(f'%,{value}') - filters.append(f'{quoted_field} LIKE {quoted_like_value}') - - # value between two commas - quoted_like_value = QgsExpression.quotedString(f'%,{value},%') - filters.append(f'{quoted_field} LIKE {quoted_like_value}') + if ( + provider == 'postgres' and + 'allow_multiple_acl_values' in cfg_layer_login_filter and + cfg_layer_login_filter['allow_multiple_acl_values'] + ): + # begins with value & comma + quoted_like_value = QgsExpression.quotedString(f'{value},%') + filters.append(f'{quoted_field} LIKE {quoted_like_value}') + + # ends with comma & value + quoted_like_value = QgsExpression.quotedString(f'%,{value}') + filters.append(f'{quoted_field} LIKE {quoted_like_value}') + + # value between two commas + quoted_like_value = QgsExpression.quotedString(f'%,{value},%') + filters.append(f'{quoted_field} LIKE {quoted_like_value}') # Build the filter for this value value_filters.append(' OR '.join(filters)) diff --git a/test/test_lizmap_accesscontrol.py b/test/test_lizmap_accesscontrol.py index bed13fff..af6ba8c6 100644 --- a/test/test_lizmap_accesscontrol.py +++ b/test/test_lizmap_accesscontrol.py @@ -595,14 +595,44 @@ def test_tos_strict_layers_true(client): assert "bing-satellite" not in content -def test_filter_by_login(): - """ Test about comma separated list of values with the current user.""" +def test_filter_by_login_simple_values(): + """ Test filter with simple values in field with the current user.""" config = { 'filterPrivate': ['a', 'b'], 'filterAttribute': 'f', + 'allow_multiple_acl_values': False, } - output = LizmapAccessControlFilter._filter_by_login(config, ('grp_1', 'grp_2'), 'a') + output = LizmapAccessControlFilter._filter_by_login(config, ('grp_1', 'grp_2'), 'a', 'other_provider') + assert ( + "\"f\" = 'a' OR \"f\" = 'all'" + ) == output, output + + +def test_filter_by_login_postgres_multiple_values(): + """ Test filter with comma separated list of values with the current user.""" + config = { + 'filterPrivate': ['a', 'b'], + 'filterAttribute': 'f', + 'allow_multiple_acl_values': True, + } + output = LizmapAccessControlFilter._filter_by_login(config, ('grp_1', 'grp_2'), 'a', 'postgres') assert ( "\"f\" = 'a' OR \"f\" LIKE 'a,%' OR \"f\" LIKE '%,a' OR \"f\" LIKE '%,a,%' OR \"f\" = 'all' " "OR \"f\" LIKE 'all,%' OR \"f\" LIKE '%,all' OR \"f\" LIKE '%,all,%'" ) == output, output + + +def test_filter_by_login_postgres_multiple_values_deactivated(): + """ + Test filter with comma separated list of values with the current user + but with option disabled + """ + config = { + 'filterPrivate': ['a', 'b'], + 'filterAttribute': 'f', + 'allow_multiple_acl_values': False, + } + output = LizmapAccessControlFilter._filter_by_login(config, ('grp_1', 'grp_2'), 'a', 'postgres') + assert ( + "\"f\" = 'a' OR \"f\" = 'all'" + ) == output, output