diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/helpers.py b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/helpers.py index dfd5d485..9b732ac7 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/helpers.py +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/helpers.py @@ -6,9 +6,6 @@ from ckan.logic import NotFound from flask import has_request_context from logging import getLogger -from datetime import datetime, timedelta -import iso8601 -import requests from html import escape as html_escape from urllib.parse import quote @@ -83,64 +80,6 @@ def call_toolkit_function(fn, args, kwargs): return getattr(toolkit, fn)(*args, **kwargs) -NEWS_CACHE = None - - -def get_homepage_news(count=4, cache_duration=timedelta(days=1), language=None): - global NEWS_CACHE - log.debug('Fetching homepage news') - if NEWS_CACHE is None or datetime.now() - NEWS_CACHE[0] > cache_duration: - log.debug('Updating news cache') - news_endpoint_url = toolkit.config.get('ckanext.restricteddata.news.endpoint_url') - news_ssl_verify = toolkit.asbool(toolkit.config.get('ckanext.restricteddata.news.ssl_verify', True)) - news_tags = toolkit.config.get('ckanext.restricteddata.news.tags') - news_url_template = toolkit.config.get('ckanext.restricteddata.news.url_template') - - if not news_endpoint_url: - log.warning('ckanext.restricteddata.news.endpoint_url not set') - news = [] - else: - log.debug('Fetching from %s', news_endpoint_url) - try: - news_items = requests.get(news_endpoint_url, verify=news_ssl_verify).json() - log.debug('Received %i news items', len(news_items)) - - tags = set(t.strip() for t in news_tags.split(',')) if news_tags else None - if tags: - log.debug('Filtering with tags: %s', repr(tags)) - news_items = [n for n in news_items if any(t.get('slug') in tags for t in n.get('tags', []))] - - news = [{'title': {tl: t for tl, t in list(item.get('title', {}).items()) if t != 'undefined'}, - 'content': {tl: t for tl, t in list(item.get('content', {}).items()) if t != 'undefined'}, - 'published': iso8601.parse_date(item.get('publishedAt')), - 'brief': item.get('brief', {}), - 'image': '', - 'image_alt': '', - 'tags': [tag for tag in item.get('tags', []) if tag.get('slug') in news_tags], - 'url': {lang: news_url_template.format(**{'id': item.get('id'), 'language': lang}) - for lang in list(item.get('title').keys())}} - for item in news_items] - news.sort(key=lambda x: x['published'], reverse=True) - - log.debug('Updating news cache with %i news', len(news)) - news_cache_timestamp = datetime.now() - NEWS_CACHE = (news_cache_timestamp, news) - - except Exception as e: - # Fetch failed for some reason, keep old value until cache invalidates - log.error(e) - news = [] if NEWS_CACHE is None else NEWS_CACHE[1] - - else: - log.debug('Returning cached news') - news_cache_timestamp, news = NEWS_CACHE - - if language: - news = [n for n in news if language in n.get('title', {}) and language in n.get('content', {})] - - return news[:count] - - def get_homepage_groups(): return toolkit.get_action('group_list')({}, { 'all_fields': True, diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/action.py b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/action.py index 3244f06b..af2aa717 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/action.py +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/action.py @@ -70,3 +70,9 @@ def user_autocomplete(original_action, context, data_dict): return [] return original_action(context, data_dict) + + +@toolkit.chained_action +def member_list(original_action, context, data_dict): + data_dict['object_type'] = data_dict.get('object_type', 'package') + return original_action(context, data_dict) diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/auth.py b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/auth.py index 578b9159..35046b9e 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/auth.py +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/logic/auth.py @@ -29,6 +29,15 @@ def member_delete(next_auth: AuthFunction, context: Context, data_dict: DataDict return next_auth(context, data_dict) +@toolkit.chained_auth_function +def member_list(next_auth: AuthFunction, context: Context, data_dict: DataDict) -> AuthResult: + match data_dict['object_type']: + case 'user': + return sysadmin_only(context, data_dict) + + return next_auth(context, data_dict) + + def sysadmin_only(contaxt, data_dict): return {'success': False, 'message': 'Only sysadmins are allowed to call this'} diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/plugin.py b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/plugin.py index 0e02aa45..50a7e474 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/plugin.py +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/plugin.py @@ -69,7 +69,6 @@ def get_helpers(self): helpers.scheming_language_text_or_empty, 'get_lang_prefix': helpers.get_lang_prefix, 'call_toolkit_function': helpers.call_toolkit_function, - 'get_homepage_news': helpers.get_homepage_news, 'get_homepage_groups': helpers.get_homepage_groups, 'scheming_category_list': helpers.scheming_category_list, 'build_nav_main': helpers.build_nav_main, @@ -208,7 +207,8 @@ def get_actions(self): return { 'user_create': action.user_create, 'member_roles_list': action.member_roles_list, - 'user_autocomplete': action.user_autocomplete + 'user_autocomplete': action.user_autocomplete, + 'member_list': action.member_list } # IAuthFunctions @@ -217,6 +217,10 @@ def get_auth_functions(self): return { 'member_create': auth.member_create, 'member_delete': auth.member_delete, + 'member_list': auth.member_list, + 'organization_member_create': auth.sysadmin_only, + 'organization_member_delete': auth.sysadmin_only, + 'organization_member_list': auth.sysadmin_only, 'api_token_create': auth.sysadmin_only, 'user_list': auth.sysadmin_only, 'user_update': auth.sysadmin_only, diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/templates/home/layout1.html b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/templates/home/layout1.html index 5486c7e8..29c6907f 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/templates/home/layout1.html +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/templates/home/layout1.html @@ -85,23 +85,4 @@

{{ _('Suomi.fi open data') }}

- -
-

Ajankohtaista

- - {{ _('Browse all news and disruption notices') }} -
diff --git a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/tests/test_plugin.py b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/tests/test_plugin.py index 66961582..d197fdc9 100644 --- a/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/tests/test_plugin.py +++ b/ckan/ckanext/ckanext-restricteddata/ckanext/restricteddata/tests/test_plugin.py @@ -744,6 +744,45 @@ def test_only_sysadmin_can_manage_organization_members(): assert all(member_id != another_user["id"] for member_id, _, _ in members) +@pytest.mark.usefixtures("with_plugins", "clean_db") +def test_normal_user_has_no_access_to_organization_member_edit_pages(app): + user = User() + organization = Organization(user=user, **minimal_organization()) + client = app.test_client(use_cookies=True) + headers = {"Authorization": APIToken(user=user['name'])["token"]} + + # Make sure the user has admin privileges to the organization + result = client.get(toolkit.url_for("organization.edit", id=organization['name']), headers=headers) + assert result.status_code == 200 + + # Verify the user cannot view the member edit pages + result = client.get(toolkit.url_for("organization.member_new", id=organization['name']), headers=headers) + assert result.status_code == 403 + + result = client.get(toolkit.url_for("organization.member_delete", id=organization['name'], user=user["id"]), + headers=headers) + assert result.status_code == 403 + + result = client.get(toolkit.url_for("organization.members", id=organization['name']), headers=headers) + assert result.status_code == 403 + + +@pytest.mark.usefixtures("with_plugins", "clean_db") +def test_member_add_and_delete_for_dataset_in_group(app): + group = Group(**minimal_group()) + user = User() + dataset_fields = minimal_dataset_with_one_resource_fields(user) + dataset_fields['groups'] = [{'name': group['name']}] + dataset = Dataset(**dataset_fields) + context = {"user": user["name"], "ignore_auth": False} + members = call_action('member_list', context=context, id=group["name"]) + assert len(members) == 1 + call_action('member_delete', context=context, + id=group["name"], object_type="package", object=dataset["name"]) + members = call_action('member_list', context=context, id=group["name"]) + assert len(members) == 0 + + @pytest.mark.usefixtures("with_plugins", "clean_db") def test_normal_user_cannot_edit_user_profile(app): user = User() diff --git a/docker/.env.template b/docker/.env.template index 2fd4ee7f..6dd9e465 100644 --- a/docker/.env.template +++ b/docker/.env.template @@ -3,7 +3,7 @@ REGISTRY="" REPOSITORY="" # opendata images -CKAN_IMAGE_TAG="c551dc2ab798d6c9548bfe0c675381e81adf747b" +CKAN_IMAGE_TAG="0077682c1e65f71f78200a2ec8ed331e7bca4b14" SOLR_IMAGE_TAG="1b7cce3b73e5415180fe2862bfba7d27b57c5ee8" NGINX_IMAGE_TAG="1b7cce3b73e5415180fe2862bfba7d27b57c5ee8"