Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

components: make content moderation configurable #1862

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
13 changes: 13 additions & 0 deletions invenio_rdm_records/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import idutils
from invenio_i18n import lazy_gettext as _

import invenio_rdm_records.services.communities.moderation as communities_moderation
from invenio_rdm_records.services.components.verified import UserModerationHandler

from . import tokens
from .resources.serializers import DataCite43JSONSerializer
from .services import facets
Expand Down Expand Up @@ -555,6 +558,16 @@ def make_doi(prefix, record):
def lock_edit_published_files(service, identity, record=None, draft=None):
"""

RDM_CONTENT_MODERATION_HANDLERS = [
UserModerationHandler(),
]
"""Content moderation handlers."""

RDM_COMMUNITY_CONTENT_MODERATION_HANDLERS = [
communities_moderation.UserModerationHandler(),
]
"""Community content moderation handlers."""

# Feature flag to enable/disable user moderation
RDM_USER_MODERATION_ENABLED = False
"""Flag to enable creation of user moderation requests on specific user actions."""
Expand Down
27 changes: 2 additions & 25 deletions invenio_rdm_records/services/communities/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

"""Record communities service components."""

from flask import current_app
from invenio_access.permissions import system_identity
from invenio_communities.communities.records.systemfields.access import VisibilityEnum
from invenio_communities.communities.services.components import ChildrenComponent
from invenio_communities.communities.services.components import (
Expand All @@ -24,18 +22,16 @@
OwnershipComponent,
PIDComponent,
)
from invenio_drafts_resources.services.records.components import ServiceComponent
from invenio_i18n import lazy_gettext as _
from invenio_records_resources.services.records.components import (
MetadataComponent,
RelationsComponent,
)
from invenio_records_resources.services.uow import TaskOp
from invenio_requests.tasks import request_moderation
from invenio_search.engine import dsl

from ...proxies import current_community_records_service
from ..errors import InvalidCommunityVisibility
from .moderation import ContentModerationComponent


class CommunityAccessComponent(BaseAccessComponent):
Expand Down Expand Up @@ -66,25 +62,6 @@ def update(self, identity, data=None, record=None, **kwargs):
self._check_visibility(identity, record)


class ContentModerationComponent(ServiceComponent):
"""Service component for content moderation."""

def create(self, identity, data=None, record=None, **kwargs):
"""Create a moderation request if the user is not verified."""
if current_app.config["RDM_USER_MODERATION_ENABLED"]:
# If the publisher is the system process, we don't want to create a moderation request.
# Even if the record being published is owned by a user that is not system
if identity == system_identity:
return

# resolve current user and check if they are verified
is_verified = identity.user.verified_at is not None

if not is_verified:
# Spawn a task to request moderation.
self.uow.register(TaskOp(request_moderation, user_id=identity.id))


CommunityServiceComponents = [
MetadataComponent,
CommunityThemeComponent,
Expand All @@ -95,8 +72,8 @@ def create(self, identity, data=None, record=None, **kwargs):
OwnershipComponent,
FeaturedCommunityComponent,
OAISetComponent,
ContentModerationComponent,
CommunityDeletionComponent,
ChildrenComponent,
CommunityParentComponent,
ContentModerationComponent,
]
85 changes: 85 additions & 0 deletions invenio_rdm_records/services/communities/moderation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.

"""Content moderation for communities."""

from flask import current_app
from invenio_access.permissions import system_identity
from invenio_records_resources.services.records.components import ServiceComponent
from invenio_records_resources.services.uow import TaskOp
from invenio_requests.tasks import request_moderation


class BaseHandler:
"""Base class for content moderation handlers."""

def create(self, identity, record=None, data=None, uow=None, **kwargs):
"""Create handler."""
pass

def update(self, identity, record=None, data=None, uow=None, **kwargs):
"""Update handler."""
pass

def delete(self, identity, data=None, record=None, uow=None, **kwargs):
"""Delete handler."""
pass


class UserModerationHandler(BaseHandler):
"""Creates user moderation request if the user publishing is not verified."""

@property
def enabled(self):
"""Check if user moderation is enabled."""
return current_app.config["RDM_USER_MODERATION_ENABLED"]

def run(self, identity, record=None, uow=None):
"""Calculate the moderation score for a given record or draft."""
if self.enabled:
# If the publisher is the system process, we don't want to create a moderation request.
# Even if the record being published is owned by a user that is not system
if identity == system_identity:
return

# resolve current user and check if they are verified
is_verified = identity.user.verified_at is not None
if not is_verified:
# Spawn a task to request moderation.
self.uow.register(TaskOp(request_moderation, user_id=identity.id))

def create(self, identity, record=None, data=None, uow=None, **kwargs):
"""Handle create."""
self.run(identity, record=record, uow=uow)

def update(self, identity, record=None, data=None, uow=None, **kwargs):
"""Handle update."""
self.run(identity, record=record, uow=uow)


class ContentModerationComponent(ServiceComponent):
"""Service component for content moderation."""

def handler_for(action):
"""Get the handlers for an action."""

def _handler_method(self, *args, **kwargs):
handlers = current_app.config.get(
"RDM_COMMUNITY_CONTENT_MODERATION_HANDLERS", []
)
for handler in handlers:
action_method = getattr(handler, action, None)
if action_method:
action_method(*args, **kwargs, uow=self.uow)

return _handler_method

create = handler_for("create")
update = handler_for("update")
delete = handler_for("delete")

del handler_for
84 changes: 76 additions & 8 deletions invenio_rdm_records/services/components/verified.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,88 @@
from invenio_requests.tasks import request_moderation


class ContentModerationComponent(ServiceComponent):
"""Service component for content moderation."""
class BaseHandler:
"""Base class for content moderation handlers."""

def update_draft(
self, identity, data=None, record=None, errors=None, uow=None, **kwargs
):
"""Update draft handler."""
pass

def delete_draft(
self, identity, draft=None, record=None, force=False, uow=None, **kwargs
):
"""Delete draft handler."""
pass

def edit(self, identity, draft=None, record=None, uow=None, **kwargs):
"""Edit a record handler."""
pass

def new_version(self, identity, draft=None, record=None, uow=None, **kwargs):
"""New version handler."""
pass

def publish(self, identity, draft=None, record=None, uow=None, **kwargs):
"""Publish handler."""
pass

def post_publish(
self, identity, record=None, is_published=False, uow=None, **kwargs
):
"""Post publish handler."""
pass


class UserModerationHandler(BaseHandler):
"""Creates user moderation request if the user publishing is not verified."""

@property
def enabled(self):
"""Check if user moderation is enabled."""
return current_app.config["RDM_USER_MODERATION_ENABLED"]

def publish(self, identity, draft=None, record=None):
"""Create a moderation request if the user is not verified."""
if current_app.config["RDM_USER_MODERATION_ENABLED"]:
def run(self, identity, record=None, uow=None):
"""Calculate the moderation score for a given record or draft."""
if self.enabled:
# If the publisher is the system process, we don't want to create a moderation request.
# Even if the record being published is owned by a user that is not system
if identity == system_identity:
return

is_verified = record.parent.is_verified

if not is_verified:
# Spawn a task to request moderation.
owner_id = record.parent.access.owner.owner_id
self.uow.register(TaskOp(request_moderation, user_id=owner_id))
uow.register(
TaskOp(request_moderation, record.parent.access.owner.owner_id)
)

def publish(self, identity, draft=None, record=None, uow=None, **kwargs):
"""Handle publish."""
self.run(identity, record=record, uow=uow)


class ContentModerationComponent(ServiceComponent):
"""Service component for content moderation."""

def handler_for(action):
"""Get the handlers for an action."""

def _handler_method(self, *args, **kwargs):
handlers = current_app.config.get("RDM_CONTENT_MODERATION_HANDLERS", [])
for handler in handlers:
action_method = getattr(handler, action, None)
if action_method:
action_method(*args, **kwargs, uow=self.uow)

return _handler_method

update_draft = handler_for("update_draft")
slint marked this conversation as resolved.
Show resolved Hide resolved
delete_draft = handler_for("delete_draft")
edit = handler_for("edit")
publish = handler_for("publish")
post_publish = handler_for("post_publish")
new_version = handler_for("new_version")

del handler_for
7 changes: 7 additions & 0 deletions tests/requests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import pytest
from invenio_records_permissions.generators import AuthenticatedUser, SystemProcess

import invenio_rdm_records.services.communities.moderation as communities_moderation
from invenio_rdm_records.services.components.verified import UserModerationHandler
from invenio_rdm_records.services.permissions import RDMRecordPermissionPolicy


Expand All @@ -35,4 +37,9 @@ def app_config(app_config):
app_config["RDM_PERMISSION_POLICY"] = CustomRDMRecordPermissionPolicy
# Enable user moderation
app_config["RDM_USER_MODERATION_ENABLED"] = True
# Enable content moderation handlers
app_config["RDM_CONTENT_MODERATION_HANDLERS"] = [UserModerationHandler()]
app_config["RDM_COMMUNITY_CONTENT_MODERATION_HANDLERS"] = [
communities_moderation.UserModerationHandler(),
]
return app_config
Loading