From 778db7d510ccb7d772d4ed6677325f784ed96f0b Mon Sep 17 00:00:00 2001 From: Hunternif Date: Sat, 15 Feb 2025 23:55:21 +0000 Subject: [PATCH 1/3] server: add privilege posts:view:unsafe --- server/config.yaml.dist | 1 + server/szurubooru/api/post_api.py | 2 ++ .../tests/api/test_post_retrieving.py | 26 ++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/server/config.yaml.dist b/server/config.yaml.dist index 193aac3ac..fa1b86e68 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -100,6 +100,7 @@ privileges: 'posts:reverse_search': regular 'posts:view': anonymous 'posts:view:featured': anonymous + 'posts:view:unsafe': regular 'posts:edit:content': power 'posts:edit:flags': regular 'posts:edit:notes': regular diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py index daba7f7ea..7883f5e96 100644 --- a/server/szurubooru/api/post_api.py +++ b/server/szurubooru/api/post_api.py @@ -114,6 +114,8 @@ def create_snapshots_for_post( def get_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: auth.verify_privilege(ctx.user, "posts:view") post = _get_post(params) + if post.safety == model.Post.SAFETY_UNSAFE: + auth.verify_privilege(ctx.user, "posts:view:unsafe") return _serialize_post(ctx, post) diff --git a/server/szurubooru/tests/api/test_post_retrieving.py b/server/szurubooru/tests/api/test_post_retrieving.py index ac984c244..a40ab0e9c 100644 --- a/server/szurubooru/tests/api/test_post_retrieving.py +++ b/server/szurubooru/tests/api/test_post_retrieving.py @@ -14,6 +14,7 @@ def inject_config(config_injector): "privileges": { "posts:list": model.User.RANK_REGULAR, "posts:view": model.User.RANK_REGULAR, + "posts:view:unsafe": model.User.RANK_REGULAR, }, } ) @@ -73,7 +74,10 @@ def test_trying_to_use_special_tokens_without_logging_in( ): config_injector( { - "privileges": {"posts:list": "anonymous"}, + "privileges": { + "posts:list": "anonymous", + "posts:list:unsafe": "regular", + }, } ) with pytest.raises(errors.SearchError): @@ -125,3 +129,23 @@ def test_trying_to_retrieve_single_without_privileges( context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)), {"post_id": 999}, ) + + +def test_trying_to_retrieve_unsafe_without_privileges( + user_factory, context_factory, post_factory, config_injector +): + config_injector( + { + "privileges": { + "posts:view": "anonymous", + "posts:view:unsafe": "regular", + }, + } + ) + db.session.add(post_factory(id=1, safety=model.Post.SAFETY_UNSAFE)) + db.session.flush() + with pytest.raises(errors.AuthError): + api.post_api.get_post( + context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)), + {"post_id": 1}, + ) From fb763ada0fb5027f29f003f7dc3261c6b87f30a7 Mon Sep 17 00:00:00 2001 From: Hunternif Date: Sun, 16 Feb 2025 01:22:59 +0000 Subject: [PATCH 2/3] server: add privilege posts:list:unsafe, filter unsafe posts for users without privilege --- server/config.yaml.dist | 1 + .../search/configs/post_search_config.py | 27 +++++++++---- .../search/configs/test_post_search_config.py | 39 +++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/server/config.yaml.dist b/server/config.yaml.dist index fa1b86e68..b55afad49 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -97,6 +97,7 @@ privileges: 'posts:create:anonymous': regular 'posts:create:identified': regular 'posts:list': anonymous + 'posts:list:unsafe': regular 'posts:reverse_search': regular 'posts:view': anonymous 'posts:view:featured': anonymous diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py index 8d4672d46..8c2b5b9fc 100644 --- a/server/szurubooru/search/configs/post_search_config.py +++ b/server/szurubooru/search/configs/post_search_config.py @@ -3,7 +3,7 @@ import sqlalchemy as sa from szurubooru import db, errors, model -from szurubooru.func import util +from szurubooru.func import auth, util from szurubooru.search import criteria, tokens from szurubooru.search.configs import util as search_util from szurubooru.search.configs.base_search_config import ( @@ -150,6 +150,15 @@ def _category_filter( return query.filter(expr) +def _safety_filter( + query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool +) -> SaQuery: + assert criterion + return search_util.create_str_filter( + model.Post.safety, _safety_transformer + )(query, criterion, negated) + + class PostSearchConfig(BaseSearchConfig): def __init__(self) -> None: self.user = None # type: Optional[model.User] @@ -208,6 +217,15 @@ def create_count_query(self, _disable_eager_loads: bool) -> SaQuery: return db.session.query(model.Post) def finalize_query(self, query: SaQuery) -> SaQuery: + if self.user and not auth.has_privilege(self.user, "posts:list:unsafe"): + # exclude unsafe posts: + query = _safety_filter( + query, + criteria.PlainCriterion( + model.Post.SAFETY_UNSAFE, model.Post.SAFETY_UNSAFE + ), + negated=True, + ) return query.order_by(model.Post.post_id.desc()) @property @@ -363,12 +381,7 @@ def named_filters(self) -> Dict[str, Filter]: model.Post.last_feature_time ), ), - ( - ["safety", "rating"], - search_util.create_str_filter( - model.Post.safety, _safety_transformer - ), - ), + (["safety", "rating"], _safety_filter), (["note-text"], _note_filter), ( ["flag"], diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py index b86fa273e..e726f3199 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -3,6 +3,12 @@ import pytest from szurubooru import db, errors, model, search +from szurubooru.func import cache + + +@pytest.fixture(autouse=True) +def purge_cache(): + cache.purge() @pytest.fixture @@ -915,3 +921,36 @@ def test_search_by_tag_category( ) db.session.flush() verify_unpaged(input, expected_post_ids) + + +def test_filter_unsafe_without_privilege( + auth_executor, + verify_unpaged, + post_factory, + config_injector, +): + config_injector( + { + "privileges": { + "posts:list:unsafe": model.User.RANK_REGULAR, + } + } + ) + post1 = post_factory(id=1) + post2 = post_factory(id=2, safety=model.Post.SAFETY_SKETCHY) + post3 = post_factory(id=3, safety=model.Post.SAFETY_UNSAFE) + db.session.add_all([post1, post2, post3]) + db.session.flush() + user = auth_executor() + user.rank = model.User.RANK_ANONYMOUS + verify_unpaged("", [1, 2]) + verify_unpaged("safety:safe", [1]) + verify_unpaged("safety:safe,sketchy", [1, 2]) + verify_unpaged("safety:safe,sketchy,unsafe", [1, 2]) + # adjust user's rank and retry + user.rank = model.User.RANK_REGULAR + cache.purge() + verify_unpaged("", [1, 2, 3]) + verify_unpaged("safety:safe", [1]) + verify_unpaged("safety:safe,sketchy", [1, 2]) + verify_unpaged("safety:safe,sketchy,unsafe", [1, 2, 3]) From 7e09f39cb9c6ad5ffb20411906c13aca74343617 Mon Sep 17 00:00:00 2001 From: Hunternif Date: Sun, 16 Feb 2025 01:59:39 +0000 Subject: [PATCH 3/3] server: fix tests for unsafe posts --- .../tests/api/test_post_retrieving.py | 1 + .../search/configs/test_post_search_config.py | 18 +++++++++--------- .../szurubooru/tests/search/test_executor.py | 11 ++++++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/server/szurubooru/tests/api/test_post_retrieving.py b/server/szurubooru/tests/api/test_post_retrieving.py index a40ab0e9c..4daf11f07 100644 --- a/server/szurubooru/tests/api/test_post_retrieving.py +++ b/server/szurubooru/tests/api/test_post_retrieving.py @@ -15,6 +15,7 @@ def inject_config(config_injector): "posts:list": model.User.RANK_REGULAR, "posts:view": model.User.RANK_REGULAR, "posts:view:unsafe": model.User.RANK_REGULAR, + "posts:list:unsafe": model.User.RANK_REGULAR, }, } ) diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py index e726f3199..299b2ed23 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -60,7 +60,15 @@ def executor(): @pytest.fixture -def auth_executor(executor, user_factory): +def auth_executor(executor, user_factory, config_injector): + config_injector( + { + "privileges": { + "posts:list:unsafe": model.User.RANK_REGULAR, + } + } + ) + def wrapper(): auth_user = user_factory() db.session.add(auth_user) @@ -927,15 +935,7 @@ def test_filter_unsafe_without_privilege( auth_executor, verify_unpaged, post_factory, - config_injector, ): - config_injector( - { - "privileges": { - "posts:list:unsafe": model.User.RANK_REGULAR, - } - } - ) post1 = post_factory(id=1) post2 = post_factory(id=2, safety=model.Post.SAFETY_SKETCHY) post3 = post_factory(id=3, safety=model.Post.SAFETY_UNSAFE) diff --git a/server/szurubooru/tests/search/test_executor.py b/server/szurubooru/tests/search/test_executor.py index 4530beec7..5c52f724b 100644 --- a/server/szurubooru/tests/search/test_executor.py +++ b/server/szurubooru/tests/search/test_executor.py @@ -2,10 +2,19 @@ import pytest -from szurubooru import search +from szurubooru import search, model from szurubooru.func import cache +@pytest.fixture(autouse=True) +def inject_config(config_injector): + config_injector( + { + "privileges": {"posts:list:unsafe": model.User.RANK_REGULAR}, + } + ) + + def test_retrieving_from_cache(): config = unittest.mock.MagicMock() with unittest.mock.patch("szurubooru.func.cache.has"), unittest.mock.patch(