mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
streams: Add API endpoint to get stream email.
This commit adds new API endpoint to get stream email which is used by the web-app as well to get the email when a user tries to open the stream email modal. The stream email is returned only to the users who have access to it. Specifically for private streams only subscribed users have access to its email. And for public streams, all non-guest users and only subscribed guests have access to its email. All users can access email of web-public streams.
This commit is contained in:
committed by
Alex Vandiver
parent
0a3800332f
commit
6e119842bd
@@ -31,6 +31,10 @@ format used by the Zulip server that they are interacting with.
|
||||
`email_address` field from subscription objects. This change was backported
|
||||
from Zulip 8.0, where it was introduced in feature level 226.
|
||||
|
||||
* [`GET /streams/{stream_id}/email_address`](/api/get-stream-email-address):
|
||||
Added new endpoint to get email address of a stream. This change was
|
||||
backported from Zulip 8.0, where it was introduced in feature level 226.
|
||||
|
||||
## Changes in Zulip 7.0
|
||||
|
||||
**Feature level 185**
|
||||
|
@@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 185
|
||||
API_FEATURE_LEVEL = 186
|
||||
|
||||
# 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
|
||||
|
@@ -496,6 +496,13 @@ export function can_toggle_subscription(sub) {
|
||||
);
|
||||
}
|
||||
|
||||
export function can_access_stream_email(sub) {
|
||||
return (
|
||||
(sub.subscribed || sub.is_web_public || (!page_params.is_guest && !sub.invite_only)) &&
|
||||
!page_params.is_spectator
|
||||
);
|
||||
}
|
||||
|
||||
export function can_access_topic_history(sub) {
|
||||
// Anyone can access topic history for web-public streams and
|
||||
// subscriptions; additionally, members can access history for
|
||||
|
@@ -240,6 +240,7 @@ export function show_settings_for(node) {
|
||||
page_params.realm_org_type === settings_config.all_org_type_values.business.code,
|
||||
is_admin: page_params.is_admin,
|
||||
org_level_message_retention_setting: get_display_text_for_realm_message_retention_setting(),
|
||||
can_access_stream_email: stream_data.can_access_stream_email(sub),
|
||||
});
|
||||
scroll_util.get_content_element($("#stream_settings")).html(html);
|
||||
|
||||
@@ -400,6 +401,66 @@ export function get_stream_email_address(flags, address) {
|
||||
return clean_address.replace("@", flag_string + "@");
|
||||
}
|
||||
|
||||
function show_stream_email_address_modal(address) {
|
||||
const copy_email_address_modal_html = render_copy_email_address_modal({
|
||||
email_address: address,
|
||||
tags: [
|
||||
{
|
||||
name: "show-sender",
|
||||
description: $t({
|
||||
defaultMessage: "The sender's email address",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "include-footer",
|
||||
description: $t({defaultMessage: "Email footers (e.g., signature)"}),
|
||||
},
|
||||
{
|
||||
name: "include-quotes",
|
||||
description: $t({defaultMessage: "Quoted original email (in replies)"}),
|
||||
},
|
||||
{
|
||||
name: "prefer-html",
|
||||
description: $t({
|
||||
defaultMessage: "Use html encoding (not recommended)",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dialog_widget.launch({
|
||||
html_heading: $t_html({defaultMessage: "Generate stream email address"}),
|
||||
html_body: copy_email_address_modal_html,
|
||||
id: "copy_email_address_modal",
|
||||
html_submit_button: $t_html({defaultMessage: "Copy address"}),
|
||||
html_exit_button: $t_html({defaultMessage: "Close"}),
|
||||
help_link: "/help/message-a-stream-by-email#configuration-options",
|
||||
on_click() {},
|
||||
close_on_submit: false,
|
||||
});
|
||||
$("#show-sender").prop("checked", true);
|
||||
|
||||
new ClipboardJS("#copy_email_address_modal .dialog_submit_button", {
|
||||
text() {
|
||||
return address;
|
||||
},
|
||||
});
|
||||
|
||||
$("#copy_email_address_modal .tag-checkbox").on("change", () => {
|
||||
const $checked_checkboxes = $(".copy-email-modal").find("input:checked");
|
||||
|
||||
const flags = [];
|
||||
|
||||
$($checked_checkboxes).each(function () {
|
||||
flags.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
address = get_stream_email_address(flags, address);
|
||||
|
||||
$(".email-address").text(address);
|
||||
});
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
$("#main_div").on("click", ".stream_sub_unsub_button", (e) => {
|
||||
e.preventDefault();
|
||||
@@ -480,64 +541,20 @@ export function initialize() {
|
||||
e.stopPropagation();
|
||||
|
||||
const stream_id = get_stream_id(e.target);
|
||||
const stream = sub_store.get(stream_id);
|
||||
let address = stream.email_address;
|
||||
|
||||
const copy_email_address = render_copy_email_address_modal({
|
||||
email_address: address,
|
||||
tags: [
|
||||
{
|
||||
name: "show-sender",
|
||||
description: $t({
|
||||
defaultMessage: "The sender's email address",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "include-footer",
|
||||
description: $t({defaultMessage: "Email footers (e.g., signature)"}),
|
||||
},
|
||||
{
|
||||
name: "include-quotes",
|
||||
description: $t({defaultMessage: "Quoted original email (in replies)"}),
|
||||
},
|
||||
{
|
||||
name: "prefer-html",
|
||||
description: $t({
|
||||
defaultMessage: "Use html encoding (not recommended)",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dialog_widget.launch({
|
||||
html_heading: $t_html({defaultMessage: "Generate stream email address"}),
|
||||
html_body: copy_email_address,
|
||||
id: "copy_email_address_modal",
|
||||
html_submit_button: $t_html({defaultMessage: "Copy address"}),
|
||||
help_link: "/help/message-a-stream-by-email#configuration-options",
|
||||
on_click() {},
|
||||
close_on_submit: true,
|
||||
});
|
||||
$("#show-sender").prop("checked", true);
|
||||
|
||||
new ClipboardJS("#copy_email_address_modal .dialog_submit_button", {
|
||||
text() {
|
||||
return address;
|
||||
channel.get({
|
||||
url: "/json/streams/" + stream_id + "/email_address",
|
||||
success(data) {
|
||||
const address = data.email;
|
||||
show_stream_email_address_modal(address);
|
||||
},
|
||||
error(xhr) {
|
||||
ui_report.error(
|
||||
$t_html({defaultMessage: "Failed"}),
|
||||
xhr,
|
||||
$(".stream_email_address_error"),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
$("#copy_email_address_modal .tag-checkbox").on("change", () => {
|
||||
const $checked_checkboxes = $(".copy-email-modal").find("input:checked");
|
||||
|
||||
const flags = [];
|
||||
|
||||
$($checked_checkboxes).each(function () {
|
||||
flags.push($(this).attr("id"));
|
||||
});
|
||||
|
||||
address = get_stream_email_address(flags, address);
|
||||
|
||||
$(".email-address").text(address);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -943,8 +943,15 @@ h4.user_group_setting_subsection_title {
|
||||
}
|
||||
}
|
||||
|
||||
.copy_email_button {
|
||||
padding: 10px 15px;
|
||||
.stream-email-box {
|
||||
.stream_email_address_error {
|
||||
vertical-align: top;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.copy_email_button {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading_indicator_text {
|
||||
|
@@ -64,11 +64,14 @@
|
||||
can_remove_subscribers_setting_widget_name="can_remove_subscribers_group_id" }}
|
||||
</div>
|
||||
{{/with}}
|
||||
<div class="stream-email-box" {{#unless sub.email_address}}style="display: none;"{{/unless}}>
|
||||
<h3 class="stream_setting_subsection_title">
|
||||
{{t "Email address" }}
|
||||
{{> ../help_link_widget link="/help/message-a-stream-by-email" }}
|
||||
</h3>
|
||||
<div class="stream-email-box" {{#unless can_access_stream_email}}style="display: none;"{{/unless}}>
|
||||
<div class="stream-email-box-header">
|
||||
<h3 class="stream_setting_subsection_title">
|
||||
{{t "Email address" }}
|
||||
{{> ../help_link_widget link="/help/message-a-stream-by-email" }}
|
||||
</h3>
|
||||
<div class="stream_email_address_error alert-notification"></div>
|
||||
</div>
|
||||
<p>
|
||||
{{t "You can use email to send messages to Zulip streams."}}
|
||||
</p>
|
||||
|
@@ -981,3 +981,44 @@ test("can_unsubscribe_others", () => {
|
||||
page_params.is_admin = false;
|
||||
assert.equal(stream_data.can_unsubscribe_others(sub), false);
|
||||
});
|
||||
|
||||
test("can_access_stream_email", () => {
|
||||
const social = {
|
||||
subscribed: true,
|
||||
color: "red",
|
||||
name: "social",
|
||||
stream_id: 2,
|
||||
is_muted: false,
|
||||
invite_only: true,
|
||||
history_public_to_subscribers: false,
|
||||
};
|
||||
page_params.is_admin = false;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
page_params.is_admin = true;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
social.subscribed = false;
|
||||
assert.equal(stream_data.can_access_stream_email(social), false);
|
||||
|
||||
social.invite_only = false;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
page_params.is_admin = false;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
page_params.is_guest = true;
|
||||
assert.equal(stream_data.can_access_stream_email(social), false);
|
||||
|
||||
social.subscribed = true;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
social.is_web_public = true;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
social.subscribed = false;
|
||||
assert.equal(stream_data.can_access_stream_email(social), true);
|
||||
|
||||
page_params.is_spectator = true;
|
||||
assert.equal(stream_data.can_access_stream_email(social), false);
|
||||
});
|
||||
|
@@ -15687,6 +15687,55 @@ paths:
|
||||
description: |
|
||||
An example JSON response for when invalid combination of stream permission
|
||||
parameters are passed.
|
||||
/streams/{stream_id}/email_address:
|
||||
get:
|
||||
operationId: get-stream-email-address
|
||||
summary: Get the email address of a stream
|
||||
tags: ["streams"]
|
||||
description: |
|
||||
Get email address of a stream.
|
||||
|
||||
**Changes**: New in Zulip 7.5 (feature level 186).
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/StreamIdInPath"
|
||||
responses:
|
||||
"200":
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/JsonSuccessBase"
|
||||
- additionalProperties: false
|
||||
properties:
|
||||
result: {}
|
||||
msg: {}
|
||||
ignored_parameters_unsupported: {}
|
||||
email:
|
||||
type: string
|
||||
description: |
|
||||
Email address of the stream.
|
||||
example:
|
||||
{
|
||||
"result": "success",
|
||||
"msg": "",
|
||||
"email": "test_stream.af64447e9e39374841063747ade8e6b0.show-sender@testserver",
|
||||
}
|
||||
"400":
|
||||
description: Bad request.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/JsonError"
|
||||
- example:
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"msg": "Invalid stream ID",
|
||||
"result": "error",
|
||||
}
|
||||
description: |
|
||||
An example JSON response for when the supplied stream does not exist:
|
||||
/streams/{stream_id}/delete_topic:
|
||||
post:
|
||||
operationId: delete-topic
|
||||
|
@@ -31,12 +31,14 @@ from zerver.actions.streams import (
|
||||
bulk_remove_subscriptions,
|
||||
deactivated_streams_by_old_name,
|
||||
do_change_stream_group_based_setting,
|
||||
do_change_stream_permission,
|
||||
do_change_stream_post_policy,
|
||||
do_deactivate_stream,
|
||||
do_unarchive_stream,
|
||||
)
|
||||
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
|
||||
from zerver.actions.users import do_change_user_role, do_deactivate_user
|
||||
from zerver.lib.email_mirror_helpers import encode_email_address_helper
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.message import UnreadStreamInfo, aggregate_unread_data, get_raw_unread_data
|
||||
from zerver.lib.response import json_success
|
||||
@@ -5611,6 +5613,54 @@ class GetStreamsTest(ZulipTestCase):
|
||||
self.assertEqual(json["stream"]["name"], "private_stream")
|
||||
self.assertEqual(json["stream"]["stream_id"], private_stream.id)
|
||||
|
||||
def test_get_stream_email_address(self) -> None:
|
||||
self.login("hamlet")
|
||||
hamlet = self.example_user("hamlet")
|
||||
iago = self.example_user("iago")
|
||||
polonius = self.example_user("polonius")
|
||||
realm = get_realm("zulip")
|
||||
denmark_stream = get_stream("Denmark", realm)
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
json = self.assert_json_success(result)
|
||||
denmark_email = encode_email_address_helper(
|
||||
denmark_stream.name, denmark_stream.email_token, show_sender=True
|
||||
)
|
||||
self.assertEqual(json["email"], denmark_email)
|
||||
|
||||
self.login("polonius")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
self.assert_json_error(result, "Invalid stream ID")
|
||||
|
||||
self.subscribe(polonius, "Denmark")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
json = self.assert_json_success(result)
|
||||
self.assertEqual(json["email"], denmark_email)
|
||||
|
||||
do_change_stream_permission(
|
||||
denmark_stream,
|
||||
invite_only=True,
|
||||
history_public_to_subscribers=True,
|
||||
is_web_public=False,
|
||||
acting_user=iago,
|
||||
)
|
||||
self.login("hamlet")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
json = self.assert_json_success(result)
|
||||
self.assertEqual(json["email"], denmark_email)
|
||||
|
||||
self.unsubscribe(hamlet, "Denmark")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
self.assert_json_error(result, "Invalid stream ID")
|
||||
|
||||
self.login("iago")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
json = self.assert_json_success(result)
|
||||
self.assertEqual(json["email"], denmark_email)
|
||||
|
||||
self.unsubscribe(iago, "Denmark")
|
||||
result = self.client_get(f"/json/streams/{denmark_stream.id}/email_address")
|
||||
self.assert_json_error(result, "Invalid stream ID")
|
||||
|
||||
|
||||
class StreamIdTest(ZulipTestCase):
|
||||
def test_get_stream_id(self) -> None:
|
||||
|
@@ -47,6 +47,7 @@ from zerver.decorator import (
|
||||
require_post,
|
||||
require_realm_admin,
|
||||
)
|
||||
from zerver.lib.email_mirror_helpers import encode_email_address
|
||||
from zerver.lib.exceptions import (
|
||||
ErrorCode,
|
||||
JsonableError,
|
||||
@@ -1073,3 +1074,18 @@ def update_subscription_properties_backend(
|
||||
)
|
||||
|
||||
return json_success(request)
|
||||
|
||||
|
||||
@has_request_variables
|
||||
def get_stream_email_address(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
stream_id: int = REQ("stream", converter=to_non_negative_int, path_only=True),
|
||||
) -> HttpResponse:
|
||||
(stream, sub) = access_stream_by_id(
|
||||
user_profile,
|
||||
stream_id,
|
||||
)
|
||||
stream_email = encode_email_address(stream, show_sender=True)
|
||||
|
||||
return json_success(request, data={"email": stream_email})
|
||||
|
@@ -146,6 +146,7 @@ from zerver.views.streams import (
|
||||
deactivate_stream_backend,
|
||||
delete_in_topic,
|
||||
get_stream_backend,
|
||||
get_stream_email_address,
|
||||
get_streams_backend,
|
||||
get_subscribers_backend,
|
||||
get_topics_backend,
|
||||
@@ -464,6 +465,7 @@ v1_api_and_json_patterns = [
|
||||
PATCH=update_stream_backend,
|
||||
DELETE=deactivate_stream_backend,
|
||||
),
|
||||
rest_path("streams/<int:stream_id>/email_address", GET=get_stream_email_address),
|
||||
# Delete topic in stream
|
||||
rest_path("streams/<int:stream_id>/delete_topic", POST=delete_in_topic),
|
||||
rest_path("default_streams", POST=add_default_stream, DELETE=remove_default_stream),
|
||||
|
Reference in New Issue
Block a user