Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion cms/dynamic_content/blocks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.core.exceptions import ValidationError
from django.db import models
from common.auth.permissions import check_page_permissions
from wagtail import blocks
from wagtail.blocks import (
CharBlock,
Expand Down Expand Up @@ -72,7 +73,6 @@ class PageLinkChooserBlock(PageChooserBlock):
def get_api_representation(cls, value, context=None) -> str | None:
if value:
return value.full_url

return None


Expand Down Expand Up @@ -206,6 +206,42 @@ class PageLink(StructBlock):
)
page = PageLinkChooserBlock(target_model=["topic.TopicPage"])

def get_api_representation(self, value, context=None):
data = super().get_api_representation(value, context)
page = value.get("page")

if not page:
data["is_authorised"] = False
data["title"] = ""
data["sub_title"] = ""
data["page"] = ""
return data

page = page.specific
request = context.get("request") if context else None
user = getattr(request, "user", None)

if not page.is_public:
user_permissions = getattr(user, "permission_sets", None)

full_user_permissions = (
user_permissions.get("permission_sets") if user_permissions else None
)
if not check_page_permissions(
permission_sets=full_user_permissions,
theme_id=getattr(page, "theme", None),
sub_theme_id=getattr(page, "sub_theme", None),
topic_id=getattr(page, "topic", None),
):
data["is_authorised"] = False
data["title"] = ""
data["sub_title"] = ""
data["page"] = ""
return data

data["is_authorised"] = True
return data


class InternalPageLinks(StreamBlock):
page_link = PageLink()
Expand Down
168 changes: 167 additions & 1 deletion tests/unit/cms/dynamic_content/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest
from wagtail.blocks import StructBlock, StructValue

from cms.dynamic_content.blocks import SourceLinkBlock
from cms.dynamic_content.blocks import PageLink, SourceLinkBlock


class TestSourceLinkBlockClean:
Expand Down Expand Up @@ -133,3 +133,169 @@ def test_does_not_raise_when_only_external_url_set(self):
)

SourceLinkBlock._validate_only_one_of_page_or_external_url(value=value)


class TestPageLinkBlock:
"""Tests for PageLink.get_api_representation()."""

def test_no_page_returns_unauthorised(self):
"""
Given a value with no page set
When get_api_representation() is called
Then the response is unauthorised and fields are blanked.
"""
block = PageLink()
value = {
"title": "Test title",
"sub_title": "Test subtitle",
"page": None,
}

result = block.get_api_representation(value=value, context={})

assert result["is_authorised"] is False
assert result["title"] == ""
assert result["sub_title"] == ""
assert result["page"] == ""

def test_public_page_is_always_authorised(self):
"""
Given a public page
When get_api_representation() is called
Then the response is authorised and fields are preserved.
"""
block = PageLink()

mock_page = mock.MagicMock()
mock_page.specific = mock_page
mock_page.is_public = True

value = {
"title": "Test title",
"sub_title": "Test subtitle",
"page": mock_page,
}

result = block.get_api_representation(value=value, context={})

assert result["is_authorised"] is True
assert result["title"] == "Test title"
assert result["sub_title"] == "Test subtitle"

@mock.patch("cms.dynamic_content.blocks.check_page_permissions")
def test_non_public_page_permission_denied(self, mock_check_page_permissions):
"""
Given a non-public page and permissions are denied
When get_api_representation() is called
Then the response is unauthorised and fields are blanked.
"""
mock_check_page_permissions.return_value = False

block = PageLink()

mock_page = mock.MagicMock()
mock_page.specific = mock_page
mock_page.is_public = False
mock_page.theme = 1
mock_page.sub_theme = 2
mock_page.topic = 3

mock_user = mock.MagicMock()
mock_user.permission_sets = mock.MagicMock()
mock_user.permission_sets = {"permission_sets": []}

mock_request = mock.MagicMock()
mock_request.user = mock_user

value = {
"title": "Test title",
"sub_title": "Test subtitle",
"page": mock_page,
}

context = {"request": mock_request}

result = block.get_api_representation(value=value, context=context)

assert result["is_authorised"] is False
assert result["title"] == ""
assert result["sub_title"] == ""
assert result["page"] == ""

mock_check_page_permissions.assert_called_once()

@mock.patch("cms.dynamic_content.blocks.check_page_permissions")
def test_non_public_page_permission_granted(self, mock_check_page_permissions):
"""
Given a non-public page and permissions are granted
When get_api_representation() is called
Then the response is authorised and fields are preserved.
"""
mock_check_page_permissions.return_value = True

block = PageLink()

mock_page = mock.MagicMock()
mock_page.specific = mock_page
mock_page.is_public = False
mock_page.theme = 1
mock_page.sub_theme = 2
mock_page.topic = 3
mock_page.full_url = "https://test-page-url"

mock_user = mock.MagicMock()
mock_user.permission_sets = mock.MagicMock()
mock_user.permission_sets = {"permission_sets": []}

mock_request = mock.MagicMock()
mock_request.user = mock_user

value = {
"title": "Test title",
"sub_title": "Test subtitle",
"page": mock_page,
}

context = {"request": mock_request}

result = block.get_api_representation(value=value, context=context)

assert result["is_authorised"] is True
assert result["title"] == "Test title"
assert result["sub_title"] == "Test subtitle"
assert result["page"] == "https://test-page-url"

mock_check_page_permissions.assert_called_once()

@mock.patch("cms.dynamic_content.blocks.check_page_permissions")
def test_non_public_page_missing_request(self, mock_check_page_permissions):
"""
Given a non-public page and no request in context
When get_api_representation() is called
Then the response is unauthorised and fields are blanked.
"""
mock_check_page_permissions.return_value = False

block = PageLink()

mock_page = mock.MagicMock()
mock_page.specific = mock_page
mock_page.is_public = False
mock_page.theme = 1
mock_page.sub_theme = 2
mock_page.topic = 3

value = {
"title": "Test title",
"sub_title": "Test subtitle",
"page": mock_page,
}

result = block.get_api_representation(value=value, context={})

assert result["is_authorised"] is False
assert result["title"] == ""
assert result["sub_title"] == ""
assert result["page"] == ""

mock_check_page_permissions.assert_called_once()
36 changes: 36 additions & 0 deletions tests/unit/common/auth/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,7 @@ def test_check_page_permissions_invalid_access(
"user_permissions, theme_id, sub_theme_id, topic_id",
[
([{}], "10", "20", "30"),
("invalid", "10", "20", "30"),
(None, "10", "20", "30"),
([{"sub_theme": {"id": "-1"}, "topic": {"id": "-1"}}], "10", "20", "30"),
(
Expand Down Expand Up @@ -1125,3 +1126,38 @@ def test_check_page_permissions_with_missing_values(
sub_theme_id=sub_theme_id,
topic_id=topic_id,
)

@pytest.mark.parametrize(
"user_permissions",
[
["invalid"],
[123],
[None],
[{"theme": {"id": "10"}}, "invalid"],
],
)
def test_check_page_permissions_non_dict_permission_entry(self, user_permissions):
assert not check_page_permissions(
permission_sets=user_permissions,
theme_id="10",
sub_theme_id="20",
topic_id="30",
)

@pytest.mark.parametrize(
"theme_id, sub_theme_id, topic_id",
[
(None, "20", "30"),
("10", None, "30"),
("10", "20", None),
],
)
def test_check_page_permissions_invalid_resource_ids(
self, theme_id, sub_theme_id, topic_id
):
assert not check_page_permissions(
permission_sets=[{"theme": {"id": "-1"}}],
theme_id=theme_id,
sub_theme_id=sub_theme_id,
topic_id=topic_id,
)
Loading