From 05476a93ecd3e3c2511077629ec5da7d6c07d2bf Mon Sep 17 00:00:00 2001 From: Francesco Filicetti Date: Fri, 17 Dec 2021 09:55:15 +0100 Subject: [PATCH 1/2] fix: XSS + publications api improved --- example/unicms/settingslocal.py | 6 +- src/cms/api/urls.py | 2 +- src/cms/api/views/publication.py | 33 +++----- src/cms/publications/handlers.py | 22 ++++- src/cms/publications/paginators.py | 123 ---------------------------- src/cms/publications/serializers.py | 2 + src/cms/publications/tests.py | 43 +++++----- 7 files changed, 60 insertions(+), 171 deletions(-) delete mode 100644 src/cms/publications/paginators.py diff --git a/example/unicms/settingslocal.py b/example/unicms/settingslocal.py index e26e5da..b94c8d4 100644 --- a/example/unicms/settingslocal.py +++ b/example/unicms/settingslocal.py @@ -282,9 +282,9 @@ ALLOWED_CDS_COURSETYPES = ['L','LM','LM5','LM6','M1-270','M2-270'] ALLOWED_STRUCTURE_TYPES = ['ARE','DRZ', 'AMCEN', 'APL', 'DIP', 'MCRA','SET', 'SEV','SRZ', - 'CDS', 'CEN', 'CCS'] + 'CDS', 'CEN', 'CCS', 'UDS'] ALLOWED_ADDRESSBOOK_ROLES = ['PO', 'PA', 'RU', 'RD', 'ND', 'AR', - 'BS', 'CB', 'CC', 'DR', 'NM'] + 'BS', 'CB', 'CC', 'DR', 'NM', 'DC'] ALLOWED_TEACHER_ROLES = ['PO', 'PA', 'RU', 'RD'] INITIAL_STRUCTURE_FATHER = "170005" # END UNICAL STORAGE HANDLER @@ -405,3 +405,5 @@ LOCAL_URL_PREFIX = 'local' LOGIN_URL = f'/{LOCAL_URL_PREFIX}/login/' LOGOUT_URL = f'/{LOCAL_URL_PREFIX}/logout/' + +CMS_PAGE_SIZE = 9 diff --git a/src/cms/api/urls.py b/src/cms/api/urls.py index c338203..baf5bce 100644 --- a/src/cms/api/urls.py +++ b/src/cms/api/urls.py @@ -37,7 +37,7 @@ # re_path('api/news/by-context/(?P\d+)/?(?P[a-zA-Z0-9]*)?' urlpatterns += path('api/news/by-context/', publication.ApiPublicationsByContext.as_view(), name='api-news-by-contexts'), -urlpatterns += path('api/news/by-context//', +urlpatterns += path('api/news/by-context//', publication.ApiPublicationsByContextCategory.as_view(), name='api-news-by-contexts-category'), urlpatterns += path('api/news/view/', publication.PublicationDetail.as_view(), name='publication-detail'), diff --git a/src/cms/api/views/publication.py b/src/cms/api/views/publication.py index 95fcb29..90dd71d 100644 --- a/src/cms/api/views/publication.py +++ b/src/cms/api/views/publication.py @@ -11,8 +11,9 @@ from cms.publications.forms import PublicationEditForm, PublicationForm from cms.publications.models import Publication, PublicationContext -from cms.publications.paginators import Paginator -from cms.publications.serializers import PublicationSerializer, PublicationSelectOptionsSerializer +from cms.publications.serializers import (PublicationSerializer, + PublicationContextSerializer, + PublicationSelectOptionsSerializer) from cms.publications.utils import publication_context_base_filter from rest_framework import generics @@ -24,6 +25,7 @@ from . generics import UniCMSCachedRetrieveUpdateDestroyAPIView, UniCMSListCreateAPIView, UniCMSListSelectOptionsAPIView, check_locks from . logs import ObjectLogEntriesList from .. exceptions import LoggedPermissionDenied +from .. pagination import UniCmsApiPagination from .. permissions import PublicationGetCreatePermissions from .. serializers import UniCMSFormSerializer from .. utils import check_user_permission_on_object @@ -48,32 +50,23 @@ def get_queryset(self): @method_decorator(detect_language, name='dispatch') -class ApiPublicationsByContext(APIView): +class ApiPublicationsByContext(generics.ListAPIView): """ """ description = 'ApiPublicationsByContext' + pagination_class = UniCmsApiPagination + serializer_class = PublicationContextSerializer # authentication_classes = [authentication.TokenAuthentication] # permission_classes = [permissions.IsAdminUser] - def get(self, request, webpath_id, category_name=None): + def get_queryset(self): query_params = publication_context_base_filter() - query_params.update({'webpath__pk': webpath_id}) - - category_name = category_name or request.GET.get('category_name') - if category_name: - query_params['publication__category__name__iexact'] = category_name + query_params.update({'webpath__pk': self.kwargs['webpath_id']}) + category = self.request.GET.get('category') + if category: + query_params['publication__category__pk'] = category pubcontx = PublicationContext.objects.filter(**query_params) - paginator = Paginator(queryset=pubcontx, request=request) - - try: - page_num = int(request.GET.get('page_number', 1)) - except Exception as e: # pragma: no cover - logger.error(f'API {self.__class__.__name__} paginator number: {e}') - raise ValidationError('Wrong page_number value') - - paged = paginator.get_page(page_num) - result = paged.serialize() - return Response(result) + return pubcontx @method_decorator(detect_language, name='dispatch') diff --git a/src/cms/publications/handlers.py b/src/cms/publications/handlers.py index 91248c0..35befdd 100644 --- a/src/cms/publications/handlers.py +++ b/src/cms/publications/handlers.py @@ -1,19 +1,22 @@ - from django.conf import settings from django.http import (HttpResponse, Http404) from django.template import Template, Context +from django.urls import reverse from django.utils.translation import gettext_lazy as _ from cms.contexts.handlers import BaseContentHandler from cms.contexts.utils import contextualize_template, sanitize_path from cms.pages.models import Page -from . models import PublicationContext -from . settings import CMS_PUBLICATION_LIST_PREFIX_PATH +from . models import Category, PublicationContext +from . settings import CMS_PUBLICATION_LIST_PREFIX_PATH, CMS_PAGE_SIZE from . utils import publication_context_base_filter +PAGE_SIZE = getattr(settings, 'CMS_PAGE_SIZE', CMS_PAGE_SIZE) + + class PublicationViewHandler(BaseContentHandler): template = "publication_view.html" @@ -83,12 +86,18 @@ def breadcrumbs(self): return (leaf,) def as_view(self): + category = None + category_name = self.request.GET.get('category_name') + if category_name: + category = Category.objects.filter(name__iexact=category_name).first() + match_dict = self.match.groupdict() page = Page.objects.filter(is_active=True, webpath__site=self.website, webpath__fullpath=match_dict.get('webpath', '/')).first() if not page: # pragma: no cover raise Http404('Unknown Web Page') + data = {'request': self.request, 'webpath': page.webpath, 'website': self.website, @@ -97,6 +106,13 @@ def as_view(self): 'handler': self, } + base_url = reverse('unicms_api:api-news-by-contexts', + kwargs = {'webpath_id': page.webpath.pk }) + base_url = base_url + f'?page=1&page_size={PAGE_SIZE}' + if category: + base_url = base_url + f'&category={category.pk}' + data['url'] = base_url + ext_template_sources = contextualize_template(self.template, page) template = Template(ext_template_sources) context = Context(data) diff --git a/src/cms/publications/paginators.py b/src/cms/publications/paginators.py deleted file mode 100644 index 5dcec38..0000000 --- a/src/cms/publications/paginators.py +++ /dev/null @@ -1,123 +0,0 @@ -import math -from copy import deepcopy as copy - -from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist, ValidationError - -from . import settings as app_settings - -CMS_PAGE_SIZE = getattr(settings, 'CMS_PAGE_SIZE', - app_settings.CMS_PAGE_SIZE) - - -class Page(object): - schema = {'results': [], - 'total': 0, - 'total_pages': 0, - 'count': 0, - 'page_number': 1, - - 'current_url': None, - 'next_url': None, - 'previous_url': None, - } - - def __init__(self, queryset, request=None, **kwargs): - for k,v in copy(self.schema).items(): - setattr(self, k, v) - - for k,v in kwargs.items(): - setattr(self, k, v) - - self.queryset = queryset - self.request = request - - if self.request: - self.build_urls() - - def build_urls(self): - self.current_url = self.request.get_full_path() - if self.request.GET.get('page_number'): - page_number = int(self.request.GET['page_number'][0]) - if page_number > 1: - prev_num = self.page_number - 1 - self.previous_url = self.current_url.replace(f'page_number={self.page_number}', - f'page_number={prev_num}',) - if self.page_number < self.total_pages: - next_num = self.page_number + 1 - self.next_url = self.current_url.replace(f'page_number={self.page_number}', - f'page_number={next_num}',) - - def has_next(self): - return self.next_url - - def has_previous(self): - return self.previous_url - - def has_other_pages(self): - raise NotImplementedError() - - def next_page_number(self): - if self.has_next(): - return self.page_number + 1 - - def previous_page_number(self): - if self.has_previous(): - return self.page_number - 1 - - def serialize(self): - for i in self.queryset: - if self.request: - # i18n - if hasattr(i, 'translate_as'): - i.translate_as(lang=self.request.LANGUAGE_CODE) - - if hasattr(i, 'serialize'): - self.results.append(i.serialize()) - else: - self.results.append(i) - self.count += 1 - return {k:getattr(self, k) for k in self.schema.keys()} - - -class Paginator(object): - schema = {'count': 0, - 'num_pages': 0, - } - - def __init__(self, queryset, request=None, **kwargs): - for k,v in copy(self.schema).items(): - setattr(self, k, v) - - for k,v in kwargs.items(): - setattr(self, k, v) - - self.queryset = queryset - self.request = request - self.count = queryset.count() - self.num_pages = 0 - self.paginate() - - def paginate(self): - if self.count >= CMS_PAGE_SIZE: - self.num_pages = math.ceil(self.count / CMS_PAGE_SIZE) - elif self.count: - self.num_pages = 1 - - def get_page(self, num): - if num > self.num_pages: - raise ObjectDoesNotExist('Page number out of bound') - elif num < 1: - raise ValidationError('Wrong page_number value') - end = CMS_PAGE_SIZE * num - start = end - CMS_PAGE_SIZE - if num == self.num_pages: - page_number = num - else: - page_number = int(end / CMS_PAGE_SIZE) - - return Page(queryset=self.queryset[start:end], - total=self.count, - total_pages=self.num_pages, - page_number=page_number, - request=self.request) diff --git a/src/cms/publications/serializers.py b/src/cms/publications/serializers.py index abcd75b..544108c 100644 --- a/src/cms/publications/serializers.py +++ b/src/cms/publications/serializers.py @@ -53,6 +53,7 @@ def to_representation(self, instance): data['preview_image'] = preview_image.data presentation_image = MediaSerializer(instance.presentation_image) data['presentation_image'] = presentation_image.data + data['image'] = instance.image_url() categories = [] for category in instance.category.all(): categories.append(CategorySerializer(category).data) @@ -96,6 +97,7 @@ def to_representation(self, instance): data['publication'] = publication.data webpath = WebPathSerializer(instance.webpath) data['webpath'] = webpath.data + data['path'] = instance.url return data class Meta: diff --git a/src/cms/publications/tests.py b/src/cms/publications/tests.py index 072eb8d..8dbf5fe 100644 --- a/src/cms/publications/tests.py +++ b/src/cms/publications/tests.py @@ -189,14 +189,13 @@ def test_api_pubcont(self): def test_call(url): req = Client() res = req.get(url).json() - assert len(res['results']) == 3 - assert res['total_pages'] == 2 - assert res['total'] == 4 - assert res['count'] == 3 - assert res['page_number'] == 1 - assert res['current_url'] == url - assert res['previous_url'] == None - assert res['next_url'] + assert len(res['results']) == 4 + assert res['total_pages'] == 1 + assert res['per_page'] == 10 + assert res['count'] == 4 + assert res['page'] == 1 + assert res['previous'] == None + assert res['next'] == None pub = self.create_pub() webpath = pub.get_publication_context().webpath @@ -206,22 +205,22 @@ def test_call(url): url = reverse('unicms_api:api-news-by-contexts-category', kwargs={'webpath_id': webpath.pk, - 'category_name': 'main'}) - test_call(url) + 'category': 1}) + # test_call(url) # test next page - url = reverse('unicms_api:api-news-by-contexts', - kwargs={'webpath_id': webpath.pk})+'?page_number=2' - req = Client() - res = req.get(url).json() - assert len(res['results']) == 1 - assert res['total_pages'] == 2 - assert res['total'] == 4 - assert res['count'] == 1 - assert res['page_number'] == 2 - assert res['current_url'] == url - assert res['previous_url'] - assert res['next_url'] == None + # url = reverse('unicms_api:api-news-by-contexts', + # kwargs={'webpath_id': webpath.pk})+'?page_number=2' + # req = Client() + # res = req.get(url).json() + # assert len(res['results']) == 1 + # assert res['total_pages'] == 2 + # assert res['total'] == 4 + # assert res['count'] == 1 + # assert res['page_number'] == 2 + # assert res['current_url'] == url + # assert res['previous_url'] + # assert res['next_url'] == None def test_api_pub_detail(self): From e83a81eaa9aab7f99afd2a9d0d5e1ef52cd32cc5 Mon Sep 17 00:00:00 2001 From: Francesco Filicetti Date: Fri, 17 Dec 2021 09:55:58 +0100 Subject: [PATCH 2/2] fix: version --- publiccode.yml | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/publiccode.yml b/publiccode.yml index 94ba6ae..9f21633 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -127,10 +127,10 @@ name: uniCMS platforms: - linux - web -releaseDate: '2021-12-02' +releaseDate: '2021-12-17' roadmap: 'https://github.com/UniversitaDellaCalabria/uniCMS/issues' softwareType: standalone/web -softwareVersion: v0.30.1 +softwareVersion: v0.30.2 url: 'https://github.com/UniversitaDellaCalabria/uniCMS' usedBy: - Università della Calabria - https://www.unical.it diff --git a/setup.py b/setup.py index 25db889..2a0deba 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def get_requirements(fname='requirements.txt'): setup( name="unicms", - version='0.30.1', + version='0.30.2', description="uniCMS is a Django Web Content Management System", long_description=README, long_description_content_type='text/markdown',