mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
Compare commits
5 Commits
b8f8056f56
...
14902688d2
Author | SHA1 | Date | |
---|---|---|---|
|
14902688d2 | ||
|
b42d3e77e7 | ||
|
fdcfafd13d | ||
|
79e718ed3a | ||
|
a5d25826bd |
2
.github/workflows/api-docs-update-check.yml
vendored
2
.github/workflows/api-docs-update-check.yml
vendored
@@ -66,6 +66,8 @@ jobs:
|
||||
- name: Run tools/notify-if-api-docs-changed
|
||||
id: run_check
|
||||
run: ./tools/notify-if-api-docs-changed >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Report status to CZO
|
||||
if: ${{github.repository == 'zulip/zulip'}}
|
||||
|
@@ -36,7 +36,7 @@ Come find us on the [development community chat](https://zulip.com/development-c
|
||||
contributors](https://zulip.readthedocs.io/en/latest/contributing/contributing.html)
|
||||
to get started. We have invested in making Zulip’s code highly
|
||||
readable, thoughtfully tested, and easy to modify. Beyond that, we
|
||||
have written an extraordinary 150K words of documentation for Zulip
|
||||
have written an extraordinary 185K words of documentation for Zulip
|
||||
contributors.
|
||||
|
||||
- **Contributing non-code**. [Report an
|
||||
|
@@ -30,7 +30,7 @@ your internship experience with the Zulip project will be highly interactive.
|
||||
> and welcoming. You learn a lot just by watching others work and talk.”_ – Sai
|
||||
> Rohitth Chiluka, Zulip GSoC 2021 participant
|
||||
|
||||
As part of our commitment to mentorship, Zulip has over 160,000 words of
|
||||
As part of our commitment to mentorship, Zulip has over 185,000 words of
|
||||
[documentation for
|
||||
developers](../index.md#zulip-documentation-overview), much of it
|
||||
designed to explain not just how Zulip works, but why Zulip works the way that
|
||||
|
@@ -252,7 +252,7 @@
|
||||
<p>
|
||||
Zulip makes it easy to
|
||||
<a href="https://zulip.readthedocs.io/en/stable/production/modify.html">
|
||||
maintain a fork</a> with customized features, with 175,000 words of documentation for
|
||||
maintain a fork</a> with customized features, with 225,000 words of documentation for
|
||||
system administrators and developers.
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -32,7 +32,7 @@ fully committed to helping bring up the next generation of open-source
|
||||
contributors from a wide range of backgrounds.
|
||||
|
||||
We have invested into making Zulip’s code uniquely readable, well tested, and
|
||||
easy to modify. Beyond that, we have written an extraordinary 150K words of
|
||||
easy to modify. Beyond that, we have written an extraordinary 185K words of
|
||||
documentation on [how to contribute to
|
||||
Zulip](https://zulip.readthedocs.io/en/latest/overview/contributing.html), with
|
||||
topics ranging from [practical Git
|
||||
|
@@ -13,9 +13,13 @@
|
||||
|
||||
{% block manage_preferences %}
|
||||
{% if remote_server_email %}
|
||||
<p>You are receiving this email to update you about important changes to Zulip's Terms of Service.</p>
|
||||
<a href="{{ unsubscribe_link }}">Unsubscribe</a>
|
||||
{% if released_version %}
|
||||
<p>You are receiving this email because you opted into release notifications.</p>
|
||||
{% else %}
|
||||
<p>You are receiving this email to update you about important changes to Zulip's Terms of Service.</p>
|
||||
<a href="{{ unsubscribe_link }}">Unsubscribe</a>
|
||||
{% endif %}
|
||||
{% elif unsubscribe_link %}
|
||||
<p><a href="{{ realm_url }}/#settings/notifications">{{ _("Manage email preferences") }}</a> | <a href="{{ unsubscribe_link }}">{{ _("Unsubscribe from marketing emails") }}</a></p>
|
||||
<p><a href="{{ realm_url }}/#settings/notifications">{{ _("Manage email preferences") }}</a> | <a href="{{ unsubscribe_link }}">{{ _("Unsubscribe from marketing emails") }}</a></p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@@ -1,9 +1,13 @@
|
||||
|
||||
---
|
||||
{% if remote_server_email %}
|
||||
{% if released_version %}
|
||||
You are receiving this email because you opted into release notifications.
|
||||
{% else %}
|
||||
You are receiving this email to update you about important changes to Zulip's Terms of Service.
|
||||
|
||||
Unsubscribe: {{ unsubscribe_link }}
|
||||
{% endif %}
|
||||
{% elif unsubscribe_link %}
|
||||
{{ _("Manage email preferences") }}:
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
os.chdir(os.path.dirname(TOOLS_DIR))
|
||||
@@ -11,11 +13,26 @@ sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
||||
from zerver.openapi.merge_api_changelogs import get_feature_level
|
||||
|
||||
|
||||
def get_build_url_from_environment() -> str:
|
||||
server = os.environ["GITHUB_SERVER_URL"]
|
||||
def get_pull_request_number_or_commit_hash() -> str:
|
||||
github_token = os.environ["GITHUB_TOKEN"]
|
||||
repo = os.environ["GITHUB_REPOSITORY"]
|
||||
run_id = os.environ["GITHUB_RUN_ID"]
|
||||
return f"{server}/{repo}/actions/runs/{run_id}"
|
||||
commit_hash = os.environ["GITHUB_SHA"]
|
||||
|
||||
url = f"https://api.github.com/repos/{repo}/commits/{commit_hash}/pulls"
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.groot-preview+json",
|
||||
"Authorization": f"token {github_token}",
|
||||
}
|
||||
|
||||
try:
|
||||
req = Request(url, headers=headers)
|
||||
with urlopen(req) as response:
|
||||
pull_requests = json.load(response)
|
||||
if len(pull_requests) > 0:
|
||||
return f"#{pull_requests[0]['number']}"
|
||||
return commit_hash
|
||||
except Exception:
|
||||
return commit_hash
|
||||
|
||||
|
||||
def get_changed_api_endpoints() -> list[str]:
|
||||
@@ -41,15 +58,12 @@ def get_changed_api_endpoints() -> list[str]:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
branch = os.environ.get("GITHUB_REF", "unknown branch").split("/")[-1]
|
||||
topic = f"{branch} failing"
|
||||
build_url = get_build_url_from_environment()
|
||||
github_actor = os.environ.get("GITHUB_ACTOR", "unknown user")
|
||||
pull_request = get_pull_request_number_or_commit_hash()
|
||||
|
||||
feature_level = get_feature_level(update_feature_level=False)
|
||||
endpoints = get_changed_api_endpoints()
|
||||
topic = f"new feature level: {feature_level}"
|
||||
endpoints_string = ", ".join(endpoints)
|
||||
|
||||
content = f"[Build]({build_url}) triggered by {github_actor} on branch `{branch}` has updated the API documentation for the following endpoints: {endpoints_string}."
|
||||
content = f"{pull_request} has updated the API documentation for the following endpoints: {endpoints_string}."
|
||||
print(f"topic={topic}\ncontent={content}")
|
||||
|
@@ -68,6 +68,11 @@ DEACTIVATED_ACCOUNT_ERROR = gettext_lazy(
|
||||
)
|
||||
PASSWORD_TOO_WEAK_ERROR = gettext_lazy("The password is too weak.")
|
||||
|
||||
# Set Form.EmailField to match the default max_length on Model.EmailField,
|
||||
# can be removed when https://code.djangoproject.com/ticket/35119 is
|
||||
# completed.
|
||||
EMAIL_MAX_LENGTH = 254
|
||||
|
||||
|
||||
class OverridableValidationError(ValidationError):
|
||||
pass
|
||||
@@ -242,7 +247,7 @@ class ToSForm(forms.Form):
|
||||
|
||||
|
||||
class HomepageForm(forms.Form):
|
||||
email = forms.EmailField()
|
||||
email = forms.EmailField(max_length=EMAIL_MAX_LENGTH)
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.realm = kwargs.pop("realm", None)
|
||||
@@ -321,7 +326,9 @@ class ImportRealmOwnerSelectionForm(forms.Form):
|
||||
|
||||
class RealmCreationForm(RealmDetailsForm):
|
||||
# This form determines whether users can create a new realm.
|
||||
email = forms.EmailField(validators=[email_not_system_bot, email_is_not_disposable])
|
||||
email = forms.EmailField(
|
||||
validators=[email_not_system_bot, email_is_not_disposable], max_length=EMAIL_MAX_LENGTH
|
||||
)
|
||||
import_from = forms.ChoiceField(
|
||||
choices=PreregistrationRealm.IMPORT_FROM_CHOICES,
|
||||
required=False,
|
||||
@@ -539,7 +546,7 @@ def rate_limit_password_reset_form_by_email(email: str) -> None:
|
||||
|
||||
class CreateUserForm(forms.Form):
|
||||
full_name = forms.CharField(max_length=100)
|
||||
email = forms.EmailField()
|
||||
email = forms.EmailField(max_length=EMAIL_MAX_LENGTH)
|
||||
|
||||
|
||||
class OurAuthenticationForm(AuthenticationForm):
|
||||
|
@@ -654,10 +654,18 @@ def custom_email_sender(
|
||||
with open(subject_path, "w") as f:
|
||||
f.write(get_header(subject, parsed_email_template.get("subject"), "subject"))
|
||||
|
||||
already_printed_once = False
|
||||
|
||||
def send_one_email(
|
||||
context: dict[str, Any], to_user_id: int | None = None, to_email: str | None = None
|
||||
) -> None:
|
||||
assert to_user_id is not None or to_email is not None
|
||||
if dry_run:
|
||||
nonlocal already_printed_once
|
||||
if already_printed_once:
|
||||
return
|
||||
else:
|
||||
already_printed_once = True
|
||||
with suppress(EmailNotDeliveredError):
|
||||
send_immediate_email(
|
||||
email_id,
|
||||
@@ -715,9 +723,6 @@ def send_custom_email(
|
||||
to_user_id=user_profile.id,
|
||||
context=context,
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
break
|
||||
return users
|
||||
|
||||
|
||||
@@ -753,9 +758,6 @@ def send_custom_server_email(
|
||||
context=context,
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
break
|
||||
|
||||
|
||||
def log_email_config_errors() -> None:
|
||||
"""
|
||||
|
@@ -9,11 +9,12 @@ from typing_extensions import override
|
||||
|
||||
from confirmation.models import one_click_unsubscribe_link
|
||||
from zerver.lib.management import ZulipBaseCommand
|
||||
from zerver.lib.send_email import send_custom_email, send_custom_server_email
|
||||
from zerver.lib.send_email import custom_email_sender, send_custom_email, send_custom_server_email
|
||||
from zerver.models import Realm, UserProfile
|
||||
|
||||
if settings.ZILENCER_ENABLED:
|
||||
from zilencer.models import RemoteZulipServer
|
||||
from corporate.lib.stripe import BILLING_SUPPORT_EMAIL
|
||||
from zilencer.models import RemoteRealmBillingUser, RemoteServerBillingUser, RemoteZulipServer
|
||||
|
||||
|
||||
class Command(ZulipBaseCommand):
|
||||
@@ -41,6 +42,11 @@ class Command(ZulipBaseCommand):
|
||||
action="store_true",
|
||||
help="Send to registered contact email addresses for remote Zulip servers.",
|
||||
)
|
||||
targets.add_argument(
|
||||
"--announce-release",
|
||||
metavar="VERSION",
|
||||
help="Announce a major or minor release to remote servers.",
|
||||
)
|
||||
targets.add_argument(
|
||||
"--all-sponsored-org-admins",
|
||||
action="store_true",
|
||||
@@ -133,6 +139,45 @@ class Command(ZulipBaseCommand):
|
||||
for server in servers:
|
||||
print(f" {server.contact_email} ({server.hostname})")
|
||||
return
|
||||
elif options["announce_release"]:
|
||||
server_users = RemoteServerBillingUser.objects.filter(
|
||||
is_active=True,
|
||||
remote_server__deactivated=False,
|
||||
)
|
||||
realm_users = RemoteRealmBillingUser.objects.filter(
|
||||
is_active=True,
|
||||
remote_realm__server__deactivated=False,
|
||||
remote_realm__is_system_bot_realm=False,
|
||||
remote_realm__registration_deactivated=False,
|
||||
remote_realm__realm_deactivated=False,
|
||||
remote_realm__realm_locally_deleted=False,
|
||||
)
|
||||
if options["announce_release"].endswith(".0"):
|
||||
server_users = server_users.filter(enable_major_release_emails=True)
|
||||
realm_users = realm_users.filter(enable_major_release_emails=True)
|
||||
else:
|
||||
server_users = server_users.filter(enable_maintenance_release_emails=True)
|
||||
realm_users = realm_users.filter(enable_maintenance_release_emails=True)
|
||||
# This does an implicit "distinct"
|
||||
all_emails = server_users.union(realm_users).values_list("email", flat=True)
|
||||
del options["from_address"]
|
||||
email_sender = custom_email_sender(
|
||||
dry_run=dry_run, from_address=BILLING_SUPPORT_EMAIL, **options
|
||||
)
|
||||
|
||||
for email in all_emails:
|
||||
email_sender(
|
||||
to_email=email,
|
||||
context={
|
||||
"remote_server_email": True,
|
||||
"released_version": options["announce_release"],
|
||||
},
|
||||
)
|
||||
if dry_run:
|
||||
print("Would send the above email to:")
|
||||
for email in all_emails:
|
||||
print(f" {email}")
|
||||
return
|
||||
|
||||
if options["entire_server"]:
|
||||
users = UserProfile.objects.filter(
|
||||
|
@@ -1108,6 +1108,11 @@ class LoginTest(ZulipTestCase):
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertContains(result, "Enter a valid email address")
|
||||
|
||||
invalid_email = "a" * 260 + "@example.com"
|
||||
result = self.client_post("/accounts/home/", {"email": invalid_email}, subdomain="zulip")
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertContains(result, "Ensure this value has at most 254 characters (it has 272).")
|
||||
|
||||
def test_register_deactivated_partway_through(self) -> None:
|
||||
"""
|
||||
If you try to register for a deactivated realm, you get a clear error
|
||||
@@ -4743,6 +4748,33 @@ class TestFindMyTeam(ZulipTestCase):
|
||||
result = self.client_get("/accounts/find/", {"emails": "invalid"})
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_find_team_long_email_address(self) -> None:
|
||||
# Emails over 320 characters are considered invalid.
|
||||
data = {"emails": "a" * 320 + "@example.com"}
|
||||
result = self.client_post("/accounts/find/", data)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertIn(b"Enter a valid email", result.content)
|
||||
from django.core.mail import outbox
|
||||
|
||||
self.assert_length(outbox, 0)
|
||||
|
||||
# Emails in the database are never over 254 characters,
|
||||
# but searching for them does not cause an error.
|
||||
# When https://code.djangoproject.com/ticket/35119 is
|
||||
# resolved, Django's email validator will return this
|
||||
# case as invalid, so this test will need to be updated.
|
||||
data = {"emails": "a" * 260 + "@example.com"}
|
||||
result = self.client_post("/accounts/find/", data)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
content = result.content.decode()
|
||||
self.assertIn("Emails sent! The addresses entered on", content)
|
||||
self.assertIn("a@example.com", content)
|
||||
from django.core.mail import outbox
|
||||
|
||||
self.assert_length(outbox, 1)
|
||||
message = outbox[0]
|
||||
self.assertIn("Unfortunately, no Zulip Cloud accounts", message.body)
|
||||
|
||||
def test_find_team_zero_emails(self) -> None:
|
||||
data = {"emails": ""}
|
||||
result = self.client_post("/accounts/find/", data)
|
||||
|
Reference in New Issue
Block a user