mirror of
https://github.com/zulip/zulip.git
synced 2025-10-28 02:23:57 +00:00
Compare commits
15 Commits
chat.zulip
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6a60fd533 | ||
|
|
5c2b0d91d5 | ||
|
|
421ba8afcf | ||
|
|
3ca5a49557 | ||
|
|
c31a23d589 | ||
|
|
2ea21b00f7 | ||
|
|
ad19c16dca | ||
|
|
cce328a38f | ||
|
|
1714bfa173 | ||
|
|
69f2e95e49 | ||
|
|
7e29b35fa0 | ||
|
|
c17564ac27 | ||
|
|
5319b767a1 | ||
|
|
3f2aca5481 | ||
|
|
efa28c3a65 |
@@ -70,7 +70,8 @@ HTTP headers in all API responses:
|
||||
and can vary by server and over time. The default configuration
|
||||
currently limits:
|
||||
|
||||
* Every user is limited to 200 total API requests per minute.
|
||||
* Every user is limited to 200 total API requests per minute, and 2000
|
||||
total API requests per hour.
|
||||
* Separate, much lower limits for authentication/login attempts.
|
||||
|
||||
When the Zulip server has configured multiple rate limits that apply
|
||||
|
||||
13
puppet/zulip/lib/facter/zulip_version.rb
Normal file
13
puppet/zulip/lib/facter/zulip_version.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
Facter.add(:zulip_version) do
|
||||
setcode do
|
||||
Dir.chdir("/home/zulip/deployments/current") do
|
||||
output = `python3 -c 'import version; print(version.ZULIP_VERSION_WITHOUT_COMMIT' 2>&1`
|
||||
if not $?.success?
|
||||
Facter.debug("zulip_version error: #{output}")
|
||||
nil
|
||||
else
|
||||
output.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
puppet/zulip/lib/puppet/functions/get_django_setting_slow.rb
Normal file
22
puppet/zulip/lib/puppet/functions/get_django_setting_slow.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require "shellwords"
|
||||
|
||||
# Note that this is very slow (~350ms) and may get values which will
|
||||
# rapidly go out of date, since settings are changed much more
|
||||
# frequently than deploys -- in addition to potentially just not
|
||||
# working if we're not on the application server. We should generally
|
||||
# avoid using this if at all possible.
|
||||
|
||||
Puppet::Functions.create_function(:get_django_setting_slow) do
|
||||
def get_django_setting_slow(name)
|
||||
if File.exist?("/etc/zulip/settings.py")
|
||||
output = `/home/zulip/deployments/current/scripts/get-django-setting #{name.shellescape} 2>&1`
|
||||
if $?.success?
|
||||
output.strip
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -35,6 +35,8 @@ class zulip::camo (String $listen_address = '0.0.0.0') {
|
||||
$proxy = ''
|
||||
}
|
||||
|
||||
$zulip_version = $facts['zulip_version']
|
||||
$external_uri = pick(get_django_setting_slow('ROOT_DOMAIN_URI'), 'https://zulip.com')
|
||||
file { "${zulip::common::supervisor_conf_dir}/go-camo.conf":
|
||||
ensure => file,
|
||||
require => [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[program:go-camo]
|
||||
command=/usr/local/bin/secret-env-wrapper GOCAMO_HMAC=camo_key <%= @bin %> --listen=<%= @listen_address %>:9292 -H "Strict-Transport-Security: max-age=15768000" -H "X-Frame-Options: DENY" --metrics --verbose --allow-content-video
|
||||
command=/usr/local/bin/secret-env-wrapper GOCAMO_HMAC=camo_key <%= @bin %> --listen=<%= @listen_address %>:9292 -H "Strict-Transport-Security: max-age=15768000" -H "X-Frame-Options: DENY" --metrics --verbose --allow-content-video --user-agent "Zulip-Server/<%= @zulip_version %> (<%= @external_uri %>/) go-camo/<%= @version %>" --server-name "go-camo/<%= @version %>"
|
||||
environment=HTTP_PROXY="<%= @proxy %>",HTTPS_PROXY="<%= @proxy %>"
|
||||
priority=15
|
||||
autostart=true
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
version=0.8.22
|
||||
version=0.9.5
|
||||
arch="$(uname -m)"
|
||||
tarball="uv-$arch-unknown-linux-gnu.tar.gz"
|
||||
declare -A sha256=(
|
||||
[aarch64]=726b72a137fda33565143325f7d31c42cd30ff9ccdf067e00d124d37b4081cb2
|
||||
[x86_64]=741ff1f5742c5a4a25d2f829e8395355e43f7a5ae2ebc6368e9ae2df0efb69cf
|
||||
[aarch64]=9db0c2f6683099f86bfeea47f4134e915f382512278de95b2a0e625957594ff3
|
||||
[x86_64]=2cf10babba653310606f8b49876cfb679928669e7ddaa1fb41fb00ce73e64f66
|
||||
)
|
||||
|
||||
check_version() {
|
||||
|
||||
@@ -26,6 +26,11 @@ if git rev-parse --verify --quiet "origin/$local_branch" >/dev/null; then
|
||||
fi
|
||||
git checkout -b "$local_branch" "upstream/$branch"
|
||||
|
||||
# Clear out local `.mo` files which cause locale/*/LC_MESSAGES/
|
||||
# directories to not be empty when their .po files vanish, so git
|
||||
# doesn't remove the directory.
|
||||
rm locale/*/LC_MESSAGES/*.mo
|
||||
|
||||
wlc lock "zulip/frontend$suffix"
|
||||
wlc lock "zulip/django$suffix"
|
||||
trap 'wlc unlock "zulip/frontend$suffix" && wlc unlock "zulip/django$suffix"' EXIT
|
||||
@@ -59,6 +64,10 @@ else
|
||||
|
||||
# Update locale/*/legacy_stream_translations.json
|
||||
./tools/i18n/update-for-legacy-translations
|
||||
|
||||
# Trim out any now-empty locale directories
|
||||
find locale/ -type d -empty -delete
|
||||
|
||||
git add locale/
|
||||
|
||||
# Double-check that they all compile
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
from subprocess import check_output
|
||||
@@ -239,11 +240,19 @@ def update_for_legacy_stream_translations(
|
||||
print(f"Updated {number_of_updates} strings in: {path}")
|
||||
|
||||
|
||||
expected_legacy_filenames = set()
|
||||
for locale in get_locales():
|
||||
current = get_json_filename(locale)
|
||||
legacy = get_legacy_filename(locale)
|
||||
expected_legacy_filenames.add(legacy)
|
||||
if os.path.exists(current) and os.path.exists(legacy):
|
||||
print(f"Checking legacy translations for: {current}")
|
||||
current_translations = get_translations(current)
|
||||
legacy_translations = get_translations(legacy)
|
||||
update_for_legacy_stream_translations(current_translations, legacy_translations, current)
|
||||
|
||||
for extra_file in (
|
||||
set(glob.glob("locale/*/legacy_stream_translations.json")) - expected_legacy_filenames
|
||||
):
|
||||
print(f"Removing dangling legacy translation file {extra_file}")
|
||||
os.unlink(extra_file)
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
ZULIP_VERSION = "12.0-dev+git"
|
||||
|
||||
# Add information on number of commits and commit hash to version, if available
|
||||
ZULIP_VERSION_WITHOUT_COMMIT = ZULIP_VERSION
|
||||
zulip_git_version_file = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "zulip-git-version"
|
||||
)
|
||||
@@ -49,4 +50,4 @@ API_FEATURE_LEVEL = 427
|
||||
# historical commits sharing the same major version, in which case a
|
||||
# minor version bump suffices.
|
||||
|
||||
PROVISION_VERSION = (353, 0) # bumped 2025-10-07 to rebuild emoji_names
|
||||
PROVISION_VERSION = (354, 0) # bumped 2025-10-23 to upgrade Python requirements
|
||||
|
||||
@@ -232,7 +232,12 @@ export function paste_handler_converter(
|
||||
// that such a text node exists in `has_single_textful_child_node`.
|
||||
const text_content = get_the_only_textful_child_content([...copied_html_fragment.children]);
|
||||
if (text_content) {
|
||||
return text_content;
|
||||
// Firefox preserves the wrapped newline characters in the textContent of <p>
|
||||
// tags in `paste_html`, even when the original content does not contain newlines.
|
||||
// This results in unexpected bad wrapping of paragraphs when copy-pasting
|
||||
// text back into the compose box.
|
||||
// This replace logic is used to handle that edge case.
|
||||
return text_content.replaceAll(/\s*\n\s*/g, " ");
|
||||
}
|
||||
// Ideally, this should never happen.
|
||||
// Just for fallback in case it does.
|
||||
|
||||
@@ -277,12 +277,17 @@ export function initialize_custom_date_type_fields(
|
||||
}
|
||||
}
|
||||
|
||||
let common_class_name = "modal_text_input";
|
||||
if (for_profile_settings_panel) {
|
||||
common_class_name = "settings_text_input";
|
||||
}
|
||||
|
||||
flatpickr($date_picker_elements, {
|
||||
altInput: true,
|
||||
// We would need to handle the altInput separately
|
||||
// than ".custom_user_field_value" elements to handle
|
||||
// invalid values typed in the input.
|
||||
altInputClass: "date-field-alt-input settings_text_input",
|
||||
altInputClass: "date-field-alt-input " + common_class_name,
|
||||
altFormat: "F j, Y",
|
||||
allowInput: true,
|
||||
static: true,
|
||||
|
||||
@@ -429,6 +429,7 @@
|
||||
border: 1px solid hsl(0deg 0% 80%);
|
||||
cursor: pointer;
|
||||
background-color: hsl(0deg 0% 100%);
|
||||
max-width: 100%;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
@@ -456,6 +457,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal__body,
|
||||
.modal__content {
|
||||
.dropdown-widget-button,
|
||||
.dropdown_widget_with_label_wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal_password_input,
|
||||
.modal_url_input,
|
||||
.modal_text_input {
|
||||
@@ -470,6 +479,7 @@
|
||||
margin-bottom: 10px;
|
||||
/* subtract padding (6px each side) and border (1px each side) */
|
||||
width: calc(var(--modal-input-width) - 14px);
|
||||
max-width: calc(100% - 14px);
|
||||
|
||||
&:focus {
|
||||
border-color: hsl(206deg 80% 62% / 80%);
|
||||
|
||||
@@ -607,6 +607,8 @@ ul.popover-group-menu-member-list {
|
||||
/* Override default modal input width, since that overflows.
|
||||
This is 185px (the default "width: unset" width) at 14px/em */
|
||||
width: 13.2142em;
|
||||
/* subtract padding (6px on left and 28px on right) and border (1px each side) */
|
||||
max-width: calc(100% - 36px);
|
||||
}
|
||||
|
||||
.group-search:placeholder-shown + #clear_groups_search,
|
||||
@@ -905,6 +907,9 @@ ul.popover-group-menu-member-list {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
/* Unset max-width set using modal_text_input selector as box-sizing
|
||||
is set to border-box here and we already set width to 100%. */
|
||||
max-width: unset;
|
||||
|
||||
&.empty-topic-display::placeholder {
|
||||
color: inherit;
|
||||
|
||||
@@ -813,6 +813,11 @@ input[type="checkbox"] {
|
||||
grid-template-columns: minmax(0, 1fr) 1.4285em; /* 20px at 14px em */
|
||||
align-items: center;
|
||||
width: var(--modal-input-width);
|
||||
|
||||
.flatpickr-wrapper {
|
||||
grid-column: datepicker-start / close-button-end;
|
||||
grid-row: close-button;
|
||||
}
|
||||
}
|
||||
|
||||
.control-label-disabled {
|
||||
@@ -1713,12 +1718,14 @@ label.preferences-radio-choice-label {
|
||||
#edit-user-form {
|
||||
.person_picker {
|
||||
/* Subtract (1px border and 2px of padding) on each side */
|
||||
min-width: calc(var(--modal-input-width) - 6px);
|
||||
width: calc(var(--modal-input-width) - 6px);
|
||||
max-width: calc(100% - 6px);
|
||||
}
|
||||
|
||||
& textarea {
|
||||
/* Subtract (1px border and 6px padding) on each side */
|
||||
width: calc(var(--modal-input-width) - 14px);
|
||||
max-width: calc(100% - 14px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2109,12 +2116,6 @@ label.preferences-radio-choice-label {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#edit-user-form {
|
||||
.custom_user_field textarea {
|
||||
width: calc(100% - 25px);
|
||||
}
|
||||
}
|
||||
|
||||
.topic_date_updated {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -279,6 +279,12 @@ run_test("paste_handler_converter", () => {
|
||||
'<meta http-equiv="content-type" content="text/html; charset=utf-8"><style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style><span style="font-size:10pt;font-family:Arial;font-style:normal;text-align:right;" data-sheets-value="{"1":3,"3":123}" data-sheets-userformat="{"2":769,"3":{"1":0},"11":3,"12":0}">123</span>';
|
||||
assert.equal(compose_paste.paste_handler_converter(input), "123");
|
||||
|
||||
// Pasting a long, visually line-wrapped single-line message from Firefox should not insert extraneous newlines.
|
||||
input = `<html><body>\n<!--StartFragment--><div class="message_content rendered_markdown">\n<p>At some point recently, Zulip changed such that copying a \nlong message includes hard newlines, rather than putting things all on \none line when they were on one line in the original message.</p>\n</div><!--EndFragment-->\n</body>\n</html>`;
|
||||
assert.equal(
|
||||
compose_paste.paste_handler_converter(input),
|
||||
"At some point recently, Zulip changed such that copying a long message includes hard newlines, rather than putting things all on one line when they were on one line in the original message.",
|
||||
);
|
||||
// Pasting from Excel
|
||||
input = `<html xmlns:v="urn:schemas-microsoft-com:vml"\nxmlns:o="urn:schemas-microsoft-com:office:office"\nxmlns:x="urn:schemas-microsoft-com:office:excel"\nxmlns="http://www.w3.org/TR/REC-html40">\n<head>\n<meta http-equiv=Content-Type content="text/html; charset=utf-8">\n<meta name=ProgId content=Excel.Sheet>\n<meta name=Generator content="Microsoft Excel 15">\n<link id=Main-File rel=Main-File\nhref="file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip.htm">\n<link rel=File-List\nhref="file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_filelist.xml">\n<style>\n<!--table\n {mso-displayed-decimal-separator:"\\.";\n mso-displayed-thousand-separator:"\\,";}\n@page\n {margin:.75in .7in .75in .7in;\n mso-header-margin:.3in;\n mso-footer-margin:.3in;}\ntr\n {mso-height-source:auto;}\ncol\n {mso-width-source:auto;}\nbr\n {mso-data-placement:same-cell;}\ntd\n {padding-top:1px;\n padding-right:1px;\n padding-left:1px;\n mso-ignore:padding;\n color:black;\n font-size:11.0pt;\n font-weight:400;\n font-style:normal;\n text-decoration:none;\n font-family:Calibri, sans-serif;\n mso-font-charset:0;\n mso-number-format:General;\n text-align:general;\n vertical-align:bottom;\n border:none;\n mso-background-source:auto;\n mso-pattern:auto;\n mso-protection:locked visible;\n white-space:nowrap;\n mso-rotate:0;}\n.xl65\n {mso-number-format:"_\\(\\0022$\\0022* \\#\\,\\#\\#0\\.00_\\)\\;_\\(\\0022$\\0022* \\\\\\(\\#\\,\\#\\#0\\.00\\\\\\)\\;_\\(\\0022$\\0022* \\0022-\\0022??_\\)\\;_\\(\\@_\\)";}\n-->\n</style>\n</head>\n<body link="#0563C1" vlink="#954F72">\n<table border=0 cellpadding=0 cellspacing=0 width=88 style='border-collapse:\n collapse;width:66pt'>\n<!--StartFragment-->\n <col width=88 style='mso-width-source:userset;mso-width-alt:3218;width:66pt'>\n <tr height=20 style='height:15.0pt'>\n <td height=20 class=xl65 width=88 style='height:15.0pt;width:66pt;font-size:\n 11.0pt;color:black;font-weight:400;text-decoration:none;text-underline-style:\n none;text-line-through:none;font-family:Calibri, sans-serif;border-top:.5pt solid #5B9BD5;\n border-right:none;border-bottom:none;border-left:none'><span\n style='mso-spacerun:yes'> </span>$<span style='mso-spacerun:yes'>\n </span>20.00 </td>\n </tr>\n <tr height=20 style='height:15.0pt'>\n <td height=20 class=xl65 style='height:15.0pt;font-size:11.0pt;color:black;\n font-weight:400;text-decoration:none;text-underline-style:none;text-line-through:\n none;font-family:Calibri, sans-serif;border-top:.5pt solid #5B9BD5;\n border-right:none;border-bottom:none;border-left:none'><span\n style='mso-spacerun:yes'> </span>$<span\n style='mso-spacerun:yes'> </span>7.00 </td>\n </tr>\n<!--EndFragment-->\n</table>\n</body>\n</html>`;
|
||||
|
||||
|
||||
@@ -268,7 +268,9 @@ class RateLimitedError(JsonableError):
|
||||
@staticmethod
|
||||
@override
|
||||
def msg_format() -> str:
|
||||
return _("API usage exceeded rate limit")
|
||||
return _(
|
||||
"API usage exceeded rate limit; see https://zulip.com/api/http-headers#rate-limiting-response-headers"
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
||||
@@ -2861,17 +2861,17 @@ class ZulipSAMLIdentityProvider(SAMLIdentityProvider):
|
||||
result = super().get_user_details(attributes)
|
||||
|
||||
extra_attr_names = self.conf.get("extra_attrs", [])
|
||||
result["extra_attrs"] = {}
|
||||
extra_attrs = {}
|
||||
|
||||
if (groups_list := attributes.get("zulip_groups")) is not None:
|
||||
result["extra_attrs"]["zulip_groups"] = groups_list
|
||||
extra_attrs["zulip_groups"] = groups_list
|
||||
|
||||
for extra_attr_name in extra_attr_names:
|
||||
result["extra_attrs"][extra_attr_name] = self.get_attr(
|
||||
attributes=attributes, conf_key=None, default_attribute=extra_attr_name
|
||||
extra_attrs[extra_attr_name] = self.get_attr(
|
||||
attributes=attributes, conf_key="<extra>", default_attributes=(extra_attr_name,)
|
||||
)
|
||||
|
||||
return result
|
||||
return {**result, "extra_attrs": extra_attrs}
|
||||
|
||||
|
||||
class SAMLDocument:
|
||||
@@ -3062,11 +3062,18 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@override
|
||||
def get_idp(self, idp_name: str) -> ZulipSAMLIdentityProvider:
|
||||
"""Given the name of an IdP, get a SAMLIdentityProvider instance.
|
||||
def get_idp(self, idp_name: str | None) -> ZulipSAMLIdentityProvider:
|
||||
"""Given the name of an IdP, get a SAMLIdentityProvider instance
|
||||
Forked to use our subclass of SAMLIdentityProvider for more flexibility."""
|
||||
idp_config = self.setting("ENABLED_IDPS")[idp_name]
|
||||
return ZulipSAMLIdentityProvider(idp_name, **idp_config)
|
||||
enabled_idps: dict[str, dict[str, str]] = self.setting("ENABLED_IDPS")
|
||||
if idp_name is None: # nocoverage
|
||||
# RelayState was missing, perhaps an IdP initiated flow
|
||||
if len(enabled_idps) != 1:
|
||||
raise AuthMissingParameter(self, "RelayState.idp")
|
||||
# Use the only configured IDP
|
||||
idp_name = next(iter(enabled_idps))
|
||||
idp_config = enabled_idps[idp_name]
|
||||
return ZulipSAMLIdentityProvider(self, idp_name, **idp_config)
|
||||
|
||||
@override
|
||||
def auth_url(self) -> str:
|
||||
@@ -3374,7 +3381,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
|
||||
# super().auth_complete expects to have RelayState set to the idp_name,
|
||||
# so we need to replace this param.
|
||||
post_params = self.strategy.request.POST.copy()
|
||||
post_params["RelayState"] = idp_name
|
||||
post_params["RelayState"] = orjson.dumps({"idp": idp_name}).decode()
|
||||
self.strategy.request.POST = post_params
|
||||
|
||||
# Call the auth_complete method of SocialAuthMixIn
|
||||
|
||||
@@ -266,6 +266,8 @@ DEFAULT_RATE_LIMITING_RULES = {
|
||||
"api_by_user": [
|
||||
# 200 requests per minute
|
||||
(60, 200),
|
||||
# 2000 requests per hour
|
||||
(3600, 2000),
|
||||
],
|
||||
# Limits total number of unauthenticated API requests (primarily
|
||||
# used by the public access option). Since these are
|
||||
|
||||
Reference in New Issue
Block a user