realm_export: Add a new endpoint to fetch private data export consents.

This commit adds a new endpoint `export/realm/consents` to
fetch the consents of users for their private data exports.

Fixes part of #31201.
This commit is contained in:
Prakhar Pratyush 2024-09-17 23:28:09 +05:30 committed by Tim Abbott
parent ffbf2aaaff
commit 86194efe7b
8 changed files with 117 additions and 3 deletions

View File

@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 10.0
**Feature level 295**
* [`GET /export/realm/consents`](/api/get-realm-export-consents): Added
a new endpoint to fetch the consents of users for their [private data
exports](/help/export-your-organization#full-export-with-member-consent).
**Feature level 294**
* [`POST /register`](/api/register-queue): Clients that do not

View File

@ -118,6 +118,7 @@
* [Update realm-level defaults of user settings](/api/update-realm-user-settings-defaults)
* [Get all public data exports](/api/get-realm-exports)
* [Create a public data export](/api/export-realm)
* [Get data export consent state](/api/get-realm-export-consents)
#### Real-time events

View File

@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 294 # Last bumped for `include_daectivated_groups` client capability.
API_FEATURE_LEVEL = 295 # Last bumped for `/export/realm/consents` endpoint.
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump

View File

@ -694,6 +694,16 @@ def export_realm(client: Client) -> None:
validate_against_openapi_schema(result, "/export/realm", "post", "200")
@openapi_test_function("/export/realm/consents:get")
def get_realm_export_consents(client: Client) -> None:
# {code_example|start}
# Get the consents of users for their private data exports.
result = client.call_endpoint(url="/export/realm/consents", method="GET")
# {code_example|end}
assert_success_response(result)
validate_against_openapi_schema(result, "/export/realm/consents", "get", "200")
@openapi_test_function("/users/me:get")
def get_profile(client: Client) -> None:
# {code_example|start}
@ -1822,6 +1832,7 @@ def test_server_organizations(client: Client) -> None:
create_realm_profile_field(client)
export_realm(client)
get_realm_exports(client)
get_realm_export_consents(client)
def test_errors(client: Client) -> None:

View File

@ -12763,6 +12763,57 @@ paths:
description: |
An example JSON error response for when the public data export
exceeds the maximum allowed data export size.
/export/realm/consents:
get:
operationId: get-realm-export-consents
summary: Get data export consent state
tags: ["server_and_organizations"]
x-requires-administrator: true
description: |
Fetches which users have [consented](/help/export-your-organization#full-export-with-member-consent)
for their private data to be exported by organization administrators.
**Changes**: New in Zulip 10.0 (feature level 295).
responses:
"200":
description: Success.
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/JsonSuccessBase"
- additionalProperties: false
properties:
result: {}
msg: {}
ignored_parameters_unsupported: {}
export_consents:
type: array
description: |
An array of objects where each object contains a user ID and
whether the user has consented for their private data to be exported.
items:
type: object
additionalProperties: false
properties:
user_id:
description: |
The user ID.
type: integer
consented:
description: |
Whether the user has consented for their private data export.
type: boolean
example:
{
"export_consents":
[
{"user_id": 11, "consented": true},
{"user_id": 6, "consented": false},
],
"msg": "",
"result": "success",
}
/invites:
get:
operationId: get-invites

View File

@ -6,6 +6,7 @@ from django.conf import settings
from django.utils.timezone import now as timezone_now
from analytics.models import RealmCount
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib.exceptions import JsonableError
from zerver.lib.queue import queue_json_publish
from zerver.lib.test_classes import ZulipTestCase
@ -16,7 +17,7 @@ from zerver.lib.test_helpers import (
stdout_suppressed,
use_s3_backend,
)
from zerver.models import Realm, RealmAuditLog
from zerver.models import Realm, RealmAuditLog, UserProfile
from zerver.models.realm_audit_logs import AuditLogEventType
from zerver.views.realm_export import export_realm
@ -323,3 +324,30 @@ class RealmExportTest(ZulipTestCase):
result,
f"Please request a manual export from {settings.ZULIP_ADMINISTRATOR}.",
)
def test_get_users_export_consents(self) -> None:
admin = self.example_user("iago")
self.login_user(admin)
# By default, export consent is set to False.
self.assertFalse(
UserProfile.objects.filter(
realm=admin.realm, is_active=True, is_bot=False, allow_private_data_export=True
).exists()
)
# Hamlet and Aaron consented to export their private data.
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
for user in [hamlet, aaron]:
do_change_user_setting(user, "allow_private_data_export", True, acting_user=None)
# Verify export consents of users.
result = self.client_get("/json/export/realm/consents")
response_dict = self.assert_json_success(result)
export_consents = response_dict["export_consents"]
for export_consent in export_consents:
if export_consent["user_id"] in [hamlet.id, aaron.id]:
self.assertTrue(export_consent["consented"])
continue
self.assertFalse(export_consent["consented"])

View File

@ -112,3 +112,14 @@ def delete_realm_export(request: HttpRequest, user: UserProfile, export_id: int)
raise JsonableError(_("Export still in progress"))
do_delete_realm_export(user, audit_log_entry)
return json_success(request)
@require_realm_admin
def get_users_export_consents(request: HttpRequest, user: UserProfile) -> HttpResponse:
rows = UserProfile.objects.filter(realm=user.realm, is_active=True, is_bot=False).values(
"id", "allow_private_data_export"
)
export_consents = [
{"user_id": row["id"], "consented": row["allow_private_data_export"]} for row in rows
]
return json_success(request, data={"export_consents": export_consents})

View File

@ -119,7 +119,12 @@ from zerver.views.realm_domains import (
patch_realm_domain,
)
from zerver.views.realm_emoji import delete_emoji, list_emoji, upload_emoji
from zerver.views.realm_export import delete_realm_export, export_realm, get_realm_exports
from zerver.views.realm_export import (
delete_realm_export,
export_realm,
get_realm_exports,
get_users_export_consents,
)
from zerver.views.realm_icon import delete_icon_backend, get_icon_backend, upload_icon
from zerver.views.realm_linkifiers import (
create_linkifier,
@ -504,6 +509,7 @@ v1_api_and_json_patterns = [
# export/realm -> zerver.views.realm_export
rest_path("export/realm", POST=export_realm, GET=get_realm_exports),
rest_path("export/realm/<int:export_id>", DELETE=delete_realm_export),
rest_path("export/realm/consents", GET=get_users_export_consents),
]
integrations_view = IntegrationView.as_view()