Compare commits

...

15 Commits

Author SHA1 Message Date
Tim Abbott
bccec80062 Release Zulip Server 2.0.6. 2019-09-23 16:08:57 -07:00
Anders Kaseorg
6e287db98e setup-apt-repo: Install groonga-keyring.
This allows the system to get updates to the Groonga repository
signing key, so `apt update` doesn’t start failing when the key
changes (like it recently did).

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-09-23 16:03:19 -07:00
Anders Kaseorg
9489d2d850 setup: Update groonga APT repository signing key.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-09-23 16:03:10 -07:00
Tim Abbott
226a96bdb8 message_list: Replace buggy rerender_the_whole_thing.
As it turns out, our rerender_the_whole_thing function (used whenever
we were adding messages and discovered that the resulting message list
would be out-of-order) was just broken and scrolled the browser to a
random location.

This caused two user-facing bugs:

* On very fast networks, if two users sent messages at very close to
  the same time, we could end up with out-of-order message deliveries,
  triggering this code path, which was intended to silently correct
  the situation, but failed.

* In some narrows to streams with muted topics in the history but some
  recent traffic, the user's browser-cached history might have some
  gaps that mean the server fetch we do after narrowing discovers the
  history is out-of-order, again triggering the
  rerender_the_whole_thing code path.

The fix is to just remove that function, adding a new option to the
well-tested rerender_preserving_scrolltop (which has explicit logic to
preserve the scroll position) instead.

Fixes #12067.  Likely also fixes #12498.
2019-09-18 11:44:27 -07:00
Tim Abbott
a4bf15bbc7 realm_filters: Allows more use of & and friends in URLs.
We had some excessively tight rules about what characters were
allowed, which in particular prevented using `?foo=bar&baz=quux`
structures in the realm filters URLs.

Fixes #12239.
2019-09-17 13:10:49 -07:00
Tim Abbott
2bb3af1ade Release Zulip Server 2.0.5. 2019-09-11 15:48:06 -07:00
Anders Kaseorg
5797f013b3 CVE-2019-16215: Fix DoS vulnerability in Markdown LINK_RE.
Any regex including a match-everything subpattern (.*, .*?, .+, or
.+?) is almost automatically wrong because it fails to disambiguate
when one subpattern should end and another should begin.  Among other
bugs, these kind of regexes tend to be especially prone to denial of
service vulnerabilities through catastrophic backtracking on strings
that fail to match in a large (in this case, exponential) number of
ways.

Lacking a specification to say what characters should actually be
allowed in these subpatterns (this syntax is too different from
CommonMark to be able to precisely apply those rules), I’ve tried to
make reasonable guesses and avoid changing much else.

Because Zulip doesn't store messages until they have successfully been
processed by the Markdown processor, this is not a stored DoS issue.

In general, Zulip protects against the broad category of DoS issues in
Markdown rendering via a timeout managed by another thread.  However,
details of Python's regular expression implementation mean that this
particular issue could prevent the timeout thread from being
scheduled, resulting in this being a DoS issue.

This was fixed in master a few months ago as a side effect of
abe2dab88c (#12979).

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-09-11 15:47:02 -07:00
Anders Kaseorg
1195841dfb CVE-2019-16216: Fix MIME type validation.
* Whitelist a small number of image/ types to be served as
  non-attachments.
* Serve the file using the type that we validated rather than relying
  on an independent guess to match.

This issue can lead to a stored XSS security vulnerability for older
browsers that don't support Content-Security-Policy.

It primarily affects servers using Zulip's local file uploads backend
for servers running Ubuntu 16.04 Xenial or newer; the legacy local
file upload backend for (now EOL) Ubuntu 14.04 Trusty was not affected
and it has limited impact for the S3 upload backend (which uses an
unprivileged S3 bucket domain to serve files).

This was fixed in master via 780ecb672b.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-09-11 15:46:55 -07:00
Rishi Gupta
dca727f178 docs: Make the intro to Production Installation less scary. 2019-09-08 20:29:35 -07:00
Fabian Stanke
77d5a37539 postfix: Inserted compulsory setting for postfix ≥ 2.10.
One of smtpd_relay_restrictions or smtpd_recipient_restrictions is
required by postfix ≥ 2.10 (see
http://www.postfix.org/SMTPD_ACCESS_README.html).

This is important for using the email mirror on Ubuntu Bionic.
2019-09-08 15:44:36 -07:00
Tim Abbott
2c16b9905d auth: Fix missing AzureAD section on /register. 2019-07-09 13:27:40 -07:00
Tim Abbott
3c5e69aa0d management: Fix obsolete check for double-adding a permission.
The .has_perm logic in this management command dates from use of
django-guardian that ended years ago.
2019-07-08 13:45:22 -07:00
Tim Abbott
32af9a95d5 management: Clean up a variable name in knight. 2019-07-08 13:45:17 -07:00
Vishnu Ks
3eeaafed73 install: Include no-dist-upgrade in args list.
This was missed out in 2e51ac8c49
2019-06-14 14:24:38 -07:00
Vishnu Ks
e9e25fd319 import: Handle hidden_by_limit case for files in slack import.
Fixes #12011
2019-06-06 21:54:52 -07:00
19 changed files with 171 additions and 63 deletions

View File

@@ -52,7 +52,7 @@ author = 'The Zulip Team'
# The short X.Y version. # The short X.Y version.
version = '2.0' version = '2.0'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '2.0.4' release = '2.0.6'
# This allows us to insert a warning that appears only on an unreleased # This allows us to insert a warning that appears only on an unreleased
# version, e.g. to say that something is likely to have changed. # version, e.g. to say that something is likely to have changed.

View File

@@ -7,6 +7,21 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated This section lists notable unreleased changes; it is generally updated
in bursts. in bursts.
### 2.0.6 -- 2019-09-23
- Updated signing keys for the PGroonga repository for Debian Stretch.
- Fixed creation of linkifiers with URLs containing &.
- Fixed a subtle bug that could cause the message list to suddenly
scroll up in certain rare race conditions.
### 2.0.5 -- 2019-09-11
- CVE-2019-16215: Fix DoS vulnerability in Markdown LINK_RE.
- CVE-2019-16216: Fix MIME type validation.
- Fixed email gateway postfix configuration for Ubuntu Bionic.
- Fixed support for hidden_by_limit messages in Slack import.
- Fixed confusing output from the `knight` management command.
### 2.0.4 -- 2019-06-29 ### 2.0.4 -- 2019-06-29
- Fixed several configuration-dependent bugs that caused - Fixed several configuration-dependent bugs that caused

View File

@@ -1,14 +1,16 @@
# Production Installation # Production Installation
Make sure you want to install a Zulip production server. If you'd
instead like to test or develop a new feature, we recommend the
[Zulip development server](../development/overview.html#requirements) instead.
If you just want to play around with Zulip and see what it looks like, you
can create a test organization at <https://zulipchat.com>.
You'll need an Ubuntu or Debian system that satisfies You'll need an Ubuntu or Debian system that satisfies
[the installation requirements](../production/requirements.html), or [the installation requirements](../production/requirements.html). Alternatively,
you can use Zulip's [experimental Docker image](../production/deployment.html#zulip-in-docker). you can use a preconfigured
[Digital Ocean droplet](https://marketplace.digitalocean.com/apps/zulip), or
Zulip's
[experimental Docker image](../production/deployment.html#zulip-in-docker).
Note that if you're developing for Zulip, you should install Zulip's
[development environment](../development/overview.html) instead. If
you're just looking to play around with Zulip and see what it looks like,
you can create a test organization at <https://zulipchat.com/new>.
## Step 1: Download the latest release ## Step 1: Download the latest release

View File

@@ -182,7 +182,7 @@ run_test('message_range', () => {
run_test('updates', () => { run_test('updates', () => {
var list = new MessageList({}); var list = new MessageList({});
list.view.rerender_the_whole_thing = noop; list.view.rerender_preserving_scrolltop = noop;
var messages = [ var messages = [
{ {

View File

@@ -15,6 +15,7 @@ smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
myhostname = <%= @fqdn %> myhostname = <%= @fqdn %>
alias_maps = hash:/etc/aliases alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases alias_database = hash:/etc/aliases

View File

@@ -12,6 +12,7 @@ Other options:
--self-signed-cert --self-signed-cert
--no-init-db --no-init-db
--cacert --cacert
--no-dist-upgrade
The --hostname and --email options are required, The --hostname and --email options are required,
unless --no-init-db is set and --certbot is not. unless --no-init-db is set and --certbot is not.
@@ -21,7 +22,7 @@ EOF
# Shell option parsing. Over time, we'll want to move some of the # Shell option parsing. Over time, we'll want to move some of the
# environment variables below into this self-documenting system. # environment variables below into this self-documenting system.
args="$(getopt -o '' --long help,no-init-db,self-signed-cert,certbot,hostname:,email:,cacert: -n "$0" -- "$@")" args="$(getopt -o '' --long help,no-init-db,no-dist-upgrade,self-signed-cert,certbot,hostname:,email:,cacert: -n "$0" -- "$@")"
eval "set -- $args" eval "set -- $args"
while true; do while true; do
case "$1" in case "$1" in

View File

@@ -65,4 +65,8 @@ else
apt-get update && rm -f "$STAMP_FILE" apt-get update && rm -f "$STAMP_FILE"
fi fi
if [ "$release" = "stretch" ]; then
apt-get install -y groonga-keyring
fi
echo "$DEPENDENCIES_HASH" > "$DEPENDENCIES_HASH_FILE" echo "$DEPENDENCIES_HASH" > "$DEPENDENCIES_HASH_FILE"

View File

@@ -12,19 +12,80 @@ rIbTnCy/oJRrEDbhTDhjhbMZgskWEVl7LguxW5y2WL/snj8E7bRBZ3Jvb25nYSBL
ZXkgKGdyb29uZ2EgT2ZmaWNpYWwgU2lnbmluZyBLZXkpIDxwYWNrYWdlc0Bncm9v ZXkgKGdyb29uZ2EgT2ZmaWNpYWwgU2lnbmluZyBLZXkpIDxwYWNrYWdlc0Bncm9v
bmdhLm9yZz6IYgQTEQIAIgUCT5uUPwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC bmdhLm9yZz6IYgQTEQIAIgUCT5uUPwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC
F4AACgkQcqdJa0VJlCnCeQCeNNMnOiri+zdLBU3EmBBuZZFet44AmgPwGZHfgA1r F4AACgkQcqdJa0VJlCnCeQCeNNMnOiri+zdLBU3EmBBuZZFet44AmgPwGZHfgA1r
SrzymknxZI07SFIsiEYEEBECAAYFAk+qcWgACgkQF0I/ZByDfzFXJwCg2ZeJ4+7i SrzymknxZI07SFIssAIAA4hGBBARAgAGBQJPqnFoAAoJEBdCP2Qcg38xVycAoNmX
KLSjio53xauxgjXfdL8An2wATGnD/z0Xm2iIqqHhcRvYBoaeiQIcBBABAgAGBQJP iePu4ii0o4qOd8WrsYI133S/AJ9sAExpw/89F5toiKqh4XEb2AaGnrACAACJAhwE
qnGpAAoJEJHRj88Hn4AHKO0P+wZGCLiB7GVR16z0spaHrgFvQKn8bVNYmfonwYn0 EAECAAYFAk+qcakACgkQkdGPzwefgAco7Q/7BkYIuIHsZVHXrPSyloeuAW9Aqfxt
9uOD0UdB3AivV55STrmv48nCz8BvPUE0P9DLmU0+a7Rdz5aYdkDGKqQJkx/uc1jf U1iZ+ifBifT244PRR0HcCK9XnlJOua/jycLPwG89QTQ/0MuZTT5rtF3Plph2QMYq
3p9b+ikbx8qgUSZK5TUsilZcFpTgsEDZAJtdc5k2QQ6C6rYe8DD8pXPRgfmgqsaI pAmTH+5zWN/en1v6KRvHyqBRJkrlNSyKVlwWlOCwQNkAm11zmTZBDoLqth7wMPyl
frb8Xdg80c8K8XOZR3FQC/sEQdRiWpxNBgWXTgX6PAHo7Ci83p1hW4guYlFegSoY c9GB+aCqxoh+tvxd2DzRzwrxc5lHcVAL+wRB1GJanE0GBZdOBfo8AejsKLzenWFb
1+pwLGaC9ALhRjXwMMsjpAqTmgbkBtQww3iW/ysN5uOlyoPJG3utJTjOyRkH0SgT iC5iUV6BKhjX6nAsZoL0AuFGNfAwyyOkCpOaBuQG1DDDeJb/Kw3m46XKg8kbe60l
QR2amwTkMEApF2bOcZyz5cSlZPRMcpEAp/p2zR96LQs3CM6UiYOoACRk+BT7yvQY OM7JGQfRKBNBHZqbBOQwQCkXZs5xnLPlxKVk9ExykQCn+nbNH3otCzcIzpSJg6gA
Q3mMK302fcIeEeq3sa1x4m6u9SL5YzX134zgsDOdcXYD9D+/lsJV+fvDa/A1CFxm JGT4FPvK9BhDeYwrfTZ9wh4R6rexrXHibq71IvljNfXfjOCwM51xdgP0P7+WwlX5
zaMZB7/4e13CIfUL2u9toHZBcxG8LRkOckXO2BTuBr2Y1u0tIgDemoQeIXwoiTeB +8Nr8DUIXGbNoxkHv/h7XcIh9Qva722gdkFzEbwtGQ5yRc7YFO4GvZjW7S0iAN6a
guMSN8gT+8BppkGwCSoab1XUv4E31gRzE0EU2ShZyIj7XdQ1qKk+yrVBULhpexRF hB4hfCiJN4GC4xI3yBP7wGmmQbAJKhpvVdS/gTfWBHMTQRTZKFnIiPtd1DWoqT7K
FjgyO9xzO4omxrtB6huFLbDNRhqqKeEVC0PyXBFyXC4n2hgVunz/UkiWIPiKpYEM tUFQuGl7FEUWODI73HM7iibGu0HqG4UtsM1GGqop4RULQ/JcEXJcLifaGBW6fP9S
epcLCIBVS9H7Eg4nD8ejpTFpwrv3uJsKErQOhDNim30sueqhRAqEIm/7RQUK2o8H SJYg+IqlgQx6lwsIgFVL0fsSDicPx6OlMWnCu/e4mwoStA6EM2KbfSy56qFECoQi
0fbF b/tFBQrajwfR9sWwAgAAiQIcBBABAgAGBQJPvHGKAAoJENIsGog0VdRICFsP/j9Q
=mkRj enxWaiMAfLQOaC2wpLW4BrEmdkhbs4qSeAfFwof/jO7vehmYkda6RHHVtE5xN6UQ
tTFUuLqLwNaMdz6sgBi1jc/02oYcajxLJENwAk3o3GaSfadd6HeMLKrqyf8rA1eY
Bs3/6F2MEpPMCvfZTddMFPyGfhstjvgzxBUoxbW7sCqj2kEci14azVFhf8jijStF
EFQVr1eh3oAaJjlOi5/uGB+H3yz8kRONBFyvLaBRSLepI3/5rU2wC0ItvlCISvdf
PCsOF7A8ho9N+cSpqym7zGA3u6kValmrLz/w7BRgbPX52MCh0ULBmarge0U+X6c1
D0Z/o3wxt5a0EApW8FN3WJK1vhV+cyTjAqJIO+B3c8hDfr7C7/4fcSSHdzdiPsg9
TgVIq36Q7Fl/cqR4hx2QGNr7ErZMzXLXuMK7ZFqQ9hqDBmS8r/E0z+ze53BTG52q
W5jjxdtc6l+KB29FnE5K+8EmuiR9dVbdrhV5DlrYNjiQG/pAtq8NdHh+yd3Q8mME
yd45shAZQM9LdiAW0AmNCjUCQzTbHbSHbaoE7V5qZcHznRNJ00l2zCGuJeW9aTjp
7gN3E+jtp54s03EGaxahwxIatI9bGKCxHPDF3zwGCweOh7ywYWap+7bF9WlOOgOL
+IKjX9Jn7c8RC5PzWHQLR4941zCQWMiHeFQvxnLKsAIAAJkCDQRabqOPARAAv+FK
JmXGbdsIw2+FqBRsVcQyEmn+JP8ZYkAs91ddQhzedyH93RrKozkKyU0abuXrlxKH
nG9GIolFiNvHg9SGo067rpxg4yOu9v7t/okehmtcJO54mv+bZaOCzGJeb2vwUJMV
SMfeTnKBwYeOpaFQJ22qvjsn1fq/XSCyVH9bcQCeuUSBmUopIJKEgqFZ0cDYYS6O
LLLuuTOqOJUbCOnVD/MKZ8vHSvBKUZUsK24nK5ZfpQQ8RWSIdTipZdruame5rUCj
jGwsFYYpXch63VBtTyOMpyCA7/f/K/ln+MHqAqpZ7CnSq3h3/fdMlnXtKkjE2Z8o
yX8mPKSjT5M9nFhyqcYis+g2g0lyQP7KAAJp3kYd+9C9PqvWG1C/0ymt4gZjWTmo
icUvsNqbeJ+2dBj6HM+ejLRh2NMy6ZZq5v/s/GwR/lb9TA0BLdYSBPn3QdrRiAwo
I73oBFUXJAnVm+mR3kb3JhO/1SqGNBmWQttPPABMnOh3fegdDVhA6aKAxU35GmeS
FXNVVtyuxRepdF5vvXy85i+y/L8wJzGQ+cQdQJw8P1qIsex5e7k3VVwZRVnJIrlN
jfN5rU/yH5bCt6AX2e1FSSVs14mNYhohA5gKpxDZkojfgzShmbrCTO+awnFDOO1y
MQtbVfm2G1X/06D3Zl35+RJUfZzI5e+42PkxJqkAEQEAAbRBR3Jvb25nYSBLZXkg
KEdyb29uZ2EgT2ZmaWNpYWwgU2lnbmluZyBLZXkpIDxwYWNrYWdlc0Bncm9vbmdh
Lm9yZz6JAk4EEwEIADgWIQQnAfMXz8zLl1yt6cJiTPd0NIOSJQUCWm6jjwIbAwUL
CQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRBiTPd0NIOSJb+SD/9CvmBD9OdRB/Nk
f/Rdiq4XOPnCP5La66i5NXb7IZKVOZuY3PqfGJ43XPTq6qBKLSlCAwkFbclXZP2z
lne+bYAknHJbJbFy3aceoRmAOnkSgFeMj7V0J0nfBbrUForvaaGjDzIDgx/G3/D7
gxq5C4zYXQNGbi39XommyDveB2hdaNbWdI1YOlG2fDMC7S4VPaMfNVYxDzrLBokf
DAgYB2QsYX3toEkdZpsQxPXfNCm9g2G+JApHtjI6EpLtEodgbSJf/Rp9lRqeRNZJ
X/lUY6MSujI73n2O8huZklgbBPGJioaJc1Q+EFEl1mNHt3nfCz7d/FlzON3lCbdb
g+X3fl6FX00fmUmlNj/XBG2G/InnNt7dgH1x6MIaTnFKsi0p0xj3U3lZmUJqei0o
7SAGMM+QY3at5A5D2YpGChmMpGLqDuICLjogQqFhClIpfm0yBt3lNa3wi41rFr0y
X5rMawfYV/g4y0FDT+xh/wxgMcLeKErtIYZRq6QLNYq9PSniEK3gssPsj/LdshcE
OC6NuuENXrgJdvQ2rgfI3wM0uFVUelNPE6cbc680waSoCNMGPl4Nk4ExtSa3WRQG
H4J6aZHkX8L6aWedRCoLTLQ2LB49Ow7Ol3KE66XfU7Y+JBfznvGPMYOzZEZjxdwl
ytR0bHU5H6b0dpVBDCgojAVphRlpyLACAAOIXQQQEQgAHRYhBMl+RkmiBR0M6hpz
+XKnSWtFSZQpBQJabssTAAoJEHKnSWtFSZQprgsAoKAistI/y3CMRyarH1va32fj
HNiyAJ9i7XDqQTBbrT1yMhISjrE9lscr3LACAAO5Ag0EWm6jjwEQALUgeK8Dm49G
cenJZS6WOPBFDfxMZM24d4NbRcbJyGJ9RJoky4CQTY42QbAJ4V3bC/p9kD9hW/w6
aeDB6G8EuV4NQJL0A1dy5AD0N4fVmYDTfbNze0DzJSrs1eCwhExYDGgvcR36tlrm
K9ZMvzq85ej1mQ/g8iUPgPPkpFI5BtgPHKlHghyDmK/JYcFwLmjz0R1cbsKPWEXM
AdESq5UojBH51xnXMKwB3aUHyU2gO7iqQxf5p1lXPMOX4ssnMuiz3DGWUkaQhy1s
gJZtzYiX3To1NTijbdSKlRJ+CXlG/vFagexgDWfMYrjbcmLQllKNt+Sc0FPUcUqG
tXKFxSI0ny3tUv+eTF8d5kqSaJk67nfHB6abxLjmXLoUMwMtlnyKPxHpKXJEe7+U
lTDo8UGT8ZILscftAWZPW2WLn8FOCXQQcIDG/G/7Adi374u/WQpOhi5Y86MPnbLV
53Bx2pP+XTbYDKFc/2wfKOVpxLHDDWCIsM3DL7YY63B1pSt5B3lJUnvjxuHhXtWN
G9UIgkaFCzvY8jgAXIVBnWAJcdkruUCwOD0tXmK45YPyK7fNLte4kWcOLxoh7rND
qo/wUyK9pB5G/CdE8J1G+T+egF+6qUM9IEIR8OJWQt9uR00ogMXdCAVztm4ZImX6
boiW5SbWEzHnzv4mdH0WlbQtzjrKkwClABEBAAGJAjYEGAEIACAWIQQnAfMXz8zL
l1yt6cJiTPd0NIOSJQUCWm6jjwIbDAAKCRBiTPd0NIOSJXtkD/9IthyF6CnQBazM
QO/JIHp3Kfe/9ll+4hSSSc9tpijYznXpNQVv3rQQwVooL3oV5XoATK8H6kv2IOyh
tGq2szMt9YQ2JuGdjcOb5Mc2A+QWD3Tn7KCcwpIdOWiL74EWBKX6yM5JG103nI0X
y1W5FSyCJ6lB1xDoCKgUdqrgfEwAgkt8kDeoi57j9wYilt2d5+UK85pXqNgOMKxR
0tLCHcngN1XKq4irfjBVVlh205qjsTApVzLrYYe0nGae/yejmGwCLMu37yd/XiNf
jMi56gEYvIU/ZehqJQf00O4Cmneggu5A+KCG7cEULtuPLcwUho7swdsm+bTCNAAM
CvhSFeTUAs1atIOIsw1rStonPPOvjd0Ig3qWyaVs2PgK8xh21aLg5tIXmn2bTegc
mFJGGfv6YAkkPAKqtjJ/RPVZZH93PzouR590dAX/mZWZYRfo6ipxgv6ALhL51z7l
E/Zqcdg7TSkG2tY2NJnoXLROpXg9Bs7gFkb6ia7YeSJTz3Q0uBbQMqWkQyrj1RB8
i18m28J9/OkLiSryhsyLh4UULqm7PUNyNKMV31TaIBQVvutKtLZ/GLWmPc/tBSa3
Uy5CBG5oTrh1xo/3ZO0JRUW2CYU+gMvTRowmLP2uhU7JOAtz3QAerpHpNhtRdfPs
0HAz6RfxSr0qk9eQec/UPhOATDujkLACAAM=
=IBdD
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----

View File

@@ -52,7 +52,7 @@ exports.MessageList.prototype = {
var render_info; var render_info;
if (interior_messages.length > 0) { if (interior_messages.length > 0) {
self.view.rerender_the_whole_thing(); self.view.rerender_preserving_scrolltop(true);
return true; return true;
} }
if (top_messages.length > 0) { if (top_messages.length > 0) {

View File

@@ -1037,7 +1037,7 @@ MessageListView.prototype = {
return true; return true;
}, },
rerender_preserving_scrolltop: function () { rerender_preserving_scrolltop: function (discard_rendering_state) {
// old_offset is the number of pixels between the top of the // old_offset is the number of pixels between the top of the
// viewable window and the selected message // viewable window and the selected message
var old_offset; var old_offset;
@@ -1046,6 +1046,13 @@ MessageListView.prototype = {
if (selected_in_view) { if (selected_in_view) {
old_offset = selected_row.offset().top; old_offset = selected_row.offset().top;
} }
if (discard_rendering_state) {
// If we know that the existing render is invalid way
// (typically because messages appear out-of-order), then
// we discard the message_list rendering state entirely.
this.clear_rendering_state(true);
this.update_render_window(this.list.selected_idx(), false);
}
return this.rerender_with_target_scrolltop(selected_row, old_offset); return this.rerender_with_target_scrolltop(selected_row, old_offset);
}, },
@@ -1224,13 +1231,6 @@ MessageListView.prototype = {
this.maybe_rerender(); this.maybe_rerender();
}, },
rerender_the_whole_thing: function () {
// TODO: Figure out if we can unify this with this.list.rerender().
this.clear_rendering_state(true);
this.update_render_window(this.list.selected_idx(), false);
this.render(this.list.all_messages().slice(this._render_win_start, this._render_win_end), 'bottom');
},
clear_table: function () { clear_table: function () {
// We do not want to call .empty() because that also clears // We do not want to call .empty() because that also clears
// jQuery data. This does mean, however, that we need to be // jQuery data. This does mean, however, that we need to be

View File

@@ -97,6 +97,17 @@ $(function () {
</form> </form>
</div> </div>
{% endif %} {% endif %}
{% if azuread_auth_enabled %}
<div class="login-social">
<form class="form-inline azure-wrapper" action="{{ url('signup-social', args=('azuread-oauth2',)) }}" method="get">
<input type='hidden' name='multiuse_object_key' value='{{ multiuse_object_key }}' />
<button class="login-social-button full-width">
{{ _('Sign up with %(identity_provider)s', identity_provider="Azure AD") }}
</button>
</form>
</div>
{% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
ZULIP_VERSION = "2.0.4" ZULIP_VERSION = "2.0.6"
LATEST_MAJOR_VERSION = "2.0" LATEST_MAJOR_VERSION = "2.0"
LATEST_RELEASE_VERSION = "2.0.4" LATEST_RELEASE_VERSION = "2.0.6"
LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/03/01/zulip-2-0-released/" LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/03/01/zulip-2-0-released/"
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision

View File

@@ -732,10 +732,11 @@ def process_message_files(message: ZerverFieldsT,
markdown_links = [] markdown_links = []
for fileinfo in files: for fileinfo in files:
if fileinfo.get('mode', '') == 'tombstone': if fileinfo.get('mode', '') in ['tombstone', 'hidden_by_limit']:
# Slack sometimes includes tombstone mode files with no # Slack sometimes includes tombstone mode files with no
# real data on the actual file (presumably in cases where # real data on the actual file (presumably in cases where
# the file was deleted). # the file was deleted). hidden_by_limit mode is for files
# that are hidden because of 10k cap in free plan.
continue continue
url = fileinfo['url_private'] url = fileinfo['url_private']

View File

@@ -1484,7 +1484,7 @@ def get_link_re() -> str:
# [text](url) or [text](<url>) or [text](url "title") # [text](url) or [text](<url>) or [text](url "title")
LINK_RE = NOIMG + BRK + \ LINK_RE = NOIMG + BRK + \
r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' r'''\(\s*(<(?:[^<>\\]|\\.)*>|(\([^()]*\)|[^()])*?)\s*(('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")\s*)?\)'''
return normal_compile(LINK_RE) return normal_compile(LINK_RE)
def prepare_realm_pattern(source: str) -> str: def prepare_realm_pattern(source: str) -> str:

View File

@@ -1,4 +1,4 @@
from typing import Dict, Optional, Tuple from typing import Optional, Tuple
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
@@ -41,6 +41,17 @@ DEFAULT_EMOJI_SIZE = 64
MAX_EMOJI_GIF_SIZE = 128 MAX_EMOJI_GIF_SIZE = 128
MAX_EMOJI_GIF_FILE_SIZE_BYTES = 128 * 1024 * 1024 # 128 kb MAX_EMOJI_GIF_FILE_SIZE_BYTES = 128 * 1024 * 1024 # 128 kb
INLINE_MIME_TYPES = [
"application/pdf",
"image/gif",
"image/jpeg",
"image/png",
"image/webp",
# To avoid cross-site scripting attacks, DO NOT add types such
# as application/xhtml+xml, application/x-shockwave-flash,
# image/svg+xml, text/html, or text/xml.
]
# Performance Note: # Performance Note:
# #
# For writing files to S3, the file could either be stored in RAM # For writing files to S3, the file could either be stored in RAM
@@ -267,10 +278,11 @@ def upload_image_to_s3(
key.set_metadata("user_profile_id", str(user_profile.id)) key.set_metadata("user_profile_id", str(user_profile.id))
key.set_metadata("realm_id", str(user_profile.realm_id)) key.set_metadata("realm_id", str(user_profile.realm_id))
headers = {}
if content_type is not None: if content_type is not None:
headers = {'Content-Type': content_type} # type: Optional[Dict[str, str]] headers["Content-Type"] = content_type
else: if content_type not in INLINE_MIME_TYPES:
headers = None headers["Content-Disposition"] = "attachment"
key.set_contents_from_string(contents, headers=headers) # type: ignore # https://github.com/python/typeshed/issues/1552 key.set_contents_from_string(contents, headers=headers) # type: ignore # https://github.com/python/typeshed/issues/1552

View File

@@ -38,25 +38,27 @@ ONLY perform this on customer request from an authorized person.
email = options['email'] email = options['email']
realm = self.get_realm(options) realm = self.get_realm(options)
profile = self.get_user(email, realm) user = self.get_user(email, realm)
if options['grant']: if options['grant']:
if profile.has_perm(options['permission'], profile.realm): if (user.is_realm_admin and options['permission'] == "administer" or
user.is_api_super_user and options['permission'] == "api_super_user"):
raise CommandError("User already has permission for this realm.") raise CommandError("User already has permission for this realm.")
else: else:
if options['ack']: if options['ack']:
do_change_is_admin(profile, True, permission=options['permission']) do_change_is_admin(user, True, permission=options['permission'])
print("Done!") print("Done!")
else: else:
print("Would have granted %s %s rights for %s" % ( print("Would have granted %s %s rights for %s" % (
email, options['permission'], profile.realm.string_id)) email, options['permission'], user.realm.string_id))
else: else:
if profile.has_perm(options['permission'], profile.realm): if (user.is_realm_admin and options['permission'] == "administer" or
user.is_api_super_user and options['permission'] == "api_super_user"):
if options['ack']: if options['ack']:
do_change_is_admin(profile, False, permission=options['permission']) do_change_is_admin(user, False, permission=options['permission'])
print("Done!") print("Done!")
else: else:
print("Would have removed %s's %s rights on %s" % (email, options['permission'], print("Would have removed %s's %s rights on %s" % (email, options['permission'],
profile.realm.string_id)) user.realm.string_id))
else: else:
raise CommandError("User did not have permission for this realm!") raise CommandError("User did not have permission for this realm!")

View File

@@ -594,7 +594,7 @@ def filter_pattern_validator(value: str) -> None:
raise ValidationError(error_msg) raise ValidationError(error_msg)
def filter_format_validator(value: str) -> None: def filter_format_validator(value: str) -> None:
regex = re.compile(r'^([\.\/:a-zA-Z0-9#_?=-]+%\(([a-zA-Z0-9_-]+)\)s)+[a-zA-Z0-9_-]*$') regex = re.compile(r'^([\.\/:a-zA-Z0-9#_?=&-]+%\(([a-zA-Z0-9_-]+)\)s)+[/a-zA-Z0-9#_?=&-]*$')
if not regex.match(value): if not regex.match(value):
raise ValidationError(_('Invalid URL format string.')) raise ValidationError(_('Invalid URL format string.'))

View File

@@ -58,25 +58,25 @@ class RealmFilterTest(ZulipTestCase):
self.assertIsNotNone(re.match(data['pattern'], 'ZUL2-15')) self.assertIsNotNone(re.match(data['pattern'], 'ZUL2-15'))
data['pattern'] = r'_code=(?P<id>[0-9a-zA-Z]+)' data['pattern'] = r'_code=(?P<id>[0-9a-zA-Z]+)'
data['url_format_string'] = 'https://realm.com/my_realm_filter/?value=%(id)s' data['url_format_string'] = 'https://example.com/product/%(id)s/details'
result = self.client_post("/json/realm/filters", info=data) result = self.client_post("/json/realm/filters", info=data)
self.assert_json_success(result) self.assert_json_success(result)
self.assertIsNotNone(re.match(data['pattern'], '_code=123abcdZ')) self.assertIsNotNone(re.match(data['pattern'], '_code=123abcdZ'))
data['pattern'] = r'PR (?P<id>[0-9]+)' data['pattern'] = r'PR (?P<id>[0-9]+)'
data['url_format_string'] = 'https://realm.com/my_realm_filter/?value=%(id)s' data['url_format_string'] = 'https://example.com/web#view_type=type&model=model&action=12345&id=%(id)s'
result = self.client_post("/json/realm/filters", info=data) result = self.client_post("/json/realm/filters", info=data)
self.assert_json_success(result) self.assert_json_success(result)
self.assertIsNotNone(re.match(data['pattern'], 'PR 123')) self.assertIsNotNone(re.match(data['pattern'], 'PR 123'))
data['pattern'] = r'lp/(?P<id>[0-9]+)' data['pattern'] = r'lp/(?P<id>[0-9]+)'
data['url_format_string'] = 'https://realm.com/my_realm_filter/?value=%(id)s' data['url_format_string'] = 'https://realm.com/my_realm_filter/?value=%(id)s&sort=reverse'
result = self.client_post("/json/realm/filters", info=data) result = self.client_post("/json/realm/filters", info=data)
self.assert_json_success(result) self.assert_json_success(result)
self.assertIsNotNone(re.match(data['pattern'], 'lp/123')) self.assertIsNotNone(re.match(data['pattern'], 'lp/123'))
data['pattern'] = r'lp:(?P<id>[0-9]+)' data['pattern'] = r'lp:(?P<id>[0-9]+)'
data['url_format_string'] = 'https://realm.com/my_realm_filter/?value=%(id)s' data['url_format_string'] = 'https://realm.com/my_realm_filter/?sort=reverse&value=%(id)s'
result = self.client_post("/json/realm/filters", info=data) result = self.client_post("/json/realm/filters", info=data)
self.assert_json_success(result) self.assert_json_success(result)
self.assertIsNotNone(re.match(data['pattern'], 'lp:123')) self.assertIsNotNone(re.match(data['pattern'], 'lp:123'))

View File

@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from zerver.lib.response import json_success, json_error from zerver.lib.response import json_success, json_error
from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \ from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \
get_signed_upload_url, check_upload_within_quota get_signed_upload_url, check_upload_within_quota, INLINE_MIME_TYPES
from zerver.models import UserProfile, validate_attachment_request from zerver.models import UserProfile, validate_attachment_request
from django.conf import settings from django.conf import settings
from sendfile import sendfile from sendfile import sendfile
@@ -38,13 +38,11 @@ def serve_local(request: HttpRequest, path_id: str) -> HttpResponse:
# consistent format (urlquoted). For more details on filename* # consistent format (urlquoted). For more details on filename*
# and filename, see the below docs: # and filename, see the below docs:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
attachment = True mimetype, encoding = guess_type(local_path)
file_type = guess_type(local_path)[0] attachment = mimetype not in INLINE_MIME_TYPES
if file_type is not None and (file_type.startswith("image/") or
file_type == "application/pdf"):
attachment = False
return sendfile(request, local_path, attachment=attachment) return sendfile(request, local_path, attachment=attachment,
mimetype=mimetype, encoding=encoding)
def serve_file_backend(request: HttpRequest, user_profile: UserProfile, def serve_file_backend(request: HttpRequest, user_profile: UserProfile,
realm_id_str: str, filename: str) -> HttpResponse: realm_id_str: str, filename: str) -> HttpResponse: