Compare commits

...

24 Commits
2.1.4 ... 2.1.8

Author SHA1 Message Date
Alex Vandiver
3819a376b5 Release Zulip Server 2.1.8. 2021-08-12 00:25:29 +00:00
Iam-VM
6749817f3e migrations: Fix possible 0257_fix_has_link_attribute.py failure.
While it should be an invariant that message.rendered_content is never
None for a row saved to the database, it is possible for that
invariant to be violated, likely including due to bugs in previous
versions of data import/export tools.

While it'd be ideal for such messages to be rendered to fix the
invariant, it doesn't make sense for this has_link migration to crash
because of such a corrupted row, so we apply the similar policy we
already have for rendered_content="".
2021-08-04 12:48:15 -07:00
Tim Abbott
f86e22a443 Release Zulip Server 2.1.7. 2020-06-25 17:11:53 -07:00
Anders Kaseorg
bd55825ab8 CVE-2020-15070: Replace eval with ast.literal_eval.
This eval function performs the inverse of the implicit
stringification that’s implied by this type-incorrect assignment in
do_update_user_custom_profile_data_if_changed:

field_value.value = field['value']

We believe there’s sufficient validation for the data being passed to
this eval that it could only have been exploited by a PostgreSQL
administrator editing the database manually.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-25 17:02:32 -07:00
Anders Kaseorg
0a827064ba memcached: Change the default MEMCACHED_USERNAME to zulip@localhost.
This prevents memcached from automatically appending the hostname to
the username, which was a source of problems on servers where the
hostname was changed.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-19 20:21:54 -07:00
Tim Abbott
01902fa648 Release Zulip Server 2.1.6. 2020-06-17 00:27:37 -07:00
Tim Abbott
cbb9ea6b49 auth: Fix Python style not supported on Python 3.5.
This bug broke the 2.1.5 release on Ubuntu Xenial.
2020-06-17 00:24:42 -07:00
Tim Abbott
d163143f12 Release Zulip Server 2.1.5. 2020-06-16 23:16:06 -07:00
Tim Abbott
c21c8dcd95 CVE-2020-14215: Add migration to clear INVITED_AS_REALM_ADMIN.
This migration fixes any PreregistrationUser objects that might have
been already corrupted to have the administrator role by the buggy
original version of migration 0198_preregistrationuser_invited_as.

Since invitations that create new users as administrators are rare, it
is cleaner to just remove the role from all PreregistrationUser
objects than to filter for just those older invitation objects that
could have been corrupted by the original migration.
2020-06-16 23:16:06 -07:00
Tim Abbott
82d2960ad1 CVE-2020-14215: Fix migration 0198_preregistrationuser_invited_as.
This migration incorrectly swapped the role associated with invitation
objects between members and organization administrators, resulting in
most invitation objects that existed before the upgrade to Zulip
2.0.0-rc1 or later to be incorrectly administrator invitations.

Fixing the migration is safe and will help those installations
upgrading directly from 1.9.x to 2.1.5 or later.

A migration to fix the corrupted records will appear in an upcoming
commit.
2020-06-16 23:16:06 -07:00
Mateusz Mandera
fa07539016 CVE-2020-14215: Fix validation in PreregistrationUser queries.
The most import change here is the one in maybe_send_to_registration
codepath, as the insufficient validation there could lead to fetching
an expired PreregistrationUser that was invited as an administrator
admin even years ago, leading to this registration ending up in the
new user being a realm administrator.

Combined with the buggy migration in
0198_preregistrationuser_invited_as.py, this led to users incorrectly
joining as organizations administrators by accident.  But even without
that bug, this issue could have allowed a user who was invited as an
administrator but then had that invitation expire and then joined via
social authentication incorrectly join as an organization administrator.

The second change is in ConfirmationEmailWorker, where this wasn't a
security problem, but if the server was stopped for long enough, with
some invites to send out email for in the queue, then after starting it
up again, the queue worker would send out emails for invites that
had already expired.

Backported to the 2.1.x series by tabbott.
2020-06-16 23:16:06 -07:00
Tim Abbott
6d0c39fd7e CVE-2020-14194: Use noopener/noreferrer for external links.
We fixed the main issue of this form in CVE-2020-9444, but the audit
done at that time only included links found in rendered_markdown; this
change completes our audit for links with target=_blank anywhere in
the codebase.
2020-06-16 23:16:05 -07:00
Tim Abbott
2e2004b6c3 templates: Fix missing quoting of attributes in HTML templates.
This fixes a bundle of issues where we were missing "" around
attributes coming from variables.  In most cases, the variables were
integers or fixed constants from the Zulip codebase (E.g. the name of
an installed integration), but in at least one case it was
user-provided data that could potentially have security impact.
2020-06-16 23:12:41 -07:00
Anders Kaseorg
620e98860e auth: Accept next as POST parameter in POST requests.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-16 23:12:41 -07:00
Anders Kaseorg
83380b4296 CVE-2020-12759: Fix reflected XSS vulnerability in Dropbox webhook.
Also check the challenge argument’s presence before using it.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-16 23:12:40 -07:00
arpit551
e88aac5105 provision: Rename --production-test-suite option in provision.
Since we use this option in our docker-zulip project also
so rather than using it as a test suite option we made it
more specific i.e. --build-release-tarball-only.
2020-06-07 11:19:25 -07:00
Tim Abbott
6046ea8014 settings: Fix fetching API key with password auth disabled.
To the extent that the previous logic worked, it relied on an unlikely
race where the click handler had been setup before.
2020-06-05 11:37:26 -07:00
Tim Abbott
ba8ee93fae help: Suggest restarting server during Slack import.
This reduces the risk of folks running into OOM kills when going
through the data import process on servers with a minimal 2GB of RAM.
2020-05-12 22:17:06 -07:00
Rohitt Vashishtha
e682ea189a slack-import: Update docs to reflect the removal of Slack legacy tokens.
This commit deatails how users can generate the new type of APi tokens
by creating a new slack app with the correct scopes specified.

Fixes #14963.
2020-05-12 22:17:06 -07:00
Tim Abbott
148ea9fe48 slack import: Fix DefaultStream import of deactivated #random.
If the #random channel in Slack is deactivated, we should follow
Zulip's data model of not allowing deactivated, default streams.

This had apparently happened in zulipchat.com for a few organizations,
resulting in weird exceptions trying to invite new users.
2020-05-12 22:17:06 -07:00
Rohitt Vashishtha
31a34836d3 slack-import: Downgrade Slack legacy-token check failure to warning.
Slack has disabled creation of legacy tokens, which means we have to use other
tokens for importing the data. Thus, we shouldn't throw an error if the token
doesn't match the legacy token format.

Since we do not have any other validation for those tokens yet, we log a warning
but still try to continue with the import assuming that the token has the right
scopes.

See https://api.slack.com/changelog/2020-02-legacy-test-token-creation-to-retire.
2020-05-12 22:17:06 -07:00
Anders Kaseorg
309266376e version: Update for Zulip Desktop v5.2.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-05-07 11:27:08 -07:00
pemontto
ef194171f7 puppet: Allow /etc/zulip to be a symlink.
This PR updates the puppet manifest to allow /etc/zulip to be a symlink. The current behaviour overwrites /etc/zulip if it is link to another directory, which is problematic with docker-zulip and 
in particular the `LINK_SETTINGS_TO_DATA` setting.
2020-04-17 12:45:25 -07:00
Tim Abbott
66fa35f5ac test_i18n: Update test for new translation string data. 2020-04-16 16:42:03 -07:00
78 changed files with 440 additions and 231 deletions

View File

@@ -5,3 +5,4 @@
__revision__ = '$Id: settings.py 12 2008-11-23 19:38:52Z jarek.zgoda $'
STATUS_ACTIVE = 1
STATUS_REVOKED = 2

View File

@@ -7,6 +7,52 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated
in bursts.
### 2.1.8 -- 2021-08-11
- Fixed possible `0257_fix_has_link_attribute.py` database migration
failure, which would cause errors during the upgrade process.
### 2.1.7 -- 2020-06-25
- CVE-2020-15070: Fix privilege escalation vulnerability with custom
profile fields and direct write access to Zulip's postgres database.
- Changed default memcached authentication username to zulip@localhost,
fixing authentication problems when servers change their hostname.
### 2.1.6 -- 2020-06-17
- Fixed use of Python 3.6+ syntax in 2.1.5 release that prevented
installation on Ubuntu Xenial.
### 2.1.5 -- 2020-06-16
- CVE-2020-12759: Fix reflected XSS vulnerability in Dropbox webhook.
- CVE-2020-14194: Prevent reverse tabnapping via topic header links.
- CVE-2020-14215: Fixed use of invitation role data from expired
invitations on signup via external authentication methods.
- CVE-2020-14215: Fixed buggy `0198_preregistrationuser_invited_as`
database migration from the 2.0.0-rc1 release, which incorrectly added
the administrator role to invitations.
- CVE-2020-14215: Added migration to clear the administrator role from
any invitation objects already corrupted by the buggy version of the
`0198_preregistrationuser_invited_as` migration.
- Fixed missing quoting of certain attributes in HTML templates.
- Allow /etc/zulip to be a symlink (for docker-zulip).
- Disabled access from insecure Zulip Desktop releases below version 5.2.0.
- Adjusted Slack import documentation to help administrators avoid OOM
kills when doing Slack import on low-RAM systems.
- Fixed a race condition fetching users' personal API keys.
- Fixed a few bugs with Slack data import.
Administrators of servers originally installed with Zulip 1.9 or older
should audit for unexpected [organization
administrators][audit-org-admin] following this upgrade, as it is
possible CVE-2020-14215 caused a user to incorrectly join as an
organization administrator in the past. See the release blog post for
details.
[audit-org-admin]: https://zulip.com/help/change-a-users-role
### 2.1.4 -- 2020-04-16
- Fixed a regression in 2.1.3 that impacted creating the very first

View File

@@ -112,6 +112,7 @@ class zulip::base {
mode => '0644',
owner => 'zulip',
group => 'zulip',
links => 'follow',
}
file { ['/etc/zulip/zulip.conf', '/etc/zulip/settings.py']:
ensure => 'file',

View File

@@ -21,17 +21,29 @@ class zulip::memcached {
content => zulipsecret('secrets', 'memcached_password', ''),
notify => Exec[generate_memcached_sasldb2],
}
file { '/var/lib/zulip/memcached-sasldb2.stamp':
owner => 'root',
group => 'root',
mode => '0644',
content => '1',
notify => Exec[generate_memcached_sasldb2],
}
exec { 'generate_memcached_sasldb2':
require => [
Package[$memcached_packages],
Package[$zulip::sasl_modules::sasl_module_packages],
File['/etc/sasl2/memcached-zulip-password'],
],
refreshonly => true,
# Pass the hostname explicitly because otherwise saslpasswd2
# lowercases it and memcached does not.
command => "bash -c 'saslpasswd2 -p -f /etc/sasl2/memcached-sasldb2 \
-a memcached -u \"\$HOSTNAME\" zulip < /etc/sasl2/memcached-zulip-password'",
# Use localhost for the currently recommended MEMCACHED_USERNAME =
# "zulip@localhost" and the hostname for compatibility with
# MEMCACHED_USERNAME = "zulip".
command => "bash -euc '
rm -f /etc/sasl2/memcached-sasldb2
saslpasswd2 -p -f /etc/sasl2/memcached-sasldb2 \
-a memcached -u localhost zulip < /etc/sasl2/memcached-zulip-password
saslpasswd2 -p -f /etc/sasl2/memcached-sasldb2 \
-a memcached -u \"\$HOSTNAME\" zulip < /etc/sasl2/memcached-zulip-password
'",
}
file { '/etc/sasl2/memcached-sasldb2':
require => Exec[generate_memcached_sasldb2],

View File

@@ -1,4 +1,4 @@
const ELECTRON_APP_VERSION = "4.0.0";
const ELECTRON_APP_VERSION = "5.2.0";
const ELECTRON_APP_URL_LINUX = "https://github.com/zulip/zulip-desktop/releases/download/v" + ELECTRON_APP_VERSION + "/Zulip-" + ELECTRON_APP_VERSION + "-x86_64.AppImage";
const ELECTRON_APP_URL_MAC = "https://github.com/zulip/zulip-desktop/releases/download/v" + ELECTRON_APP_VERSION + "/Zulip-" + ELECTRON_APP_VERSION + ".dmg";
const ELECTRON_APP_URL_WINDOWS = "https://github.com/zulip/zulip-desktop/releases/download/v" + ELECTRON_APP_VERSION + "/Zulip-Web-Setup-" + ELECTRON_APP_VERSION + ".exe";

View File

@@ -261,21 +261,7 @@ exports.set_up = function () {
$("#account-settings-status").hide();
const setup_api_key_modal = _.once(function () {
$('.account-settings-form').append(render_settings_api_key_modal());
$("#api_key_value").text("");
$("#show_api_key").hide();
if (page_params.realm_password_auth_enabled === false) {
// Skip the password prompt step, since the user doesn't have one.
$("#get_api_key_button").click();
}
$("#get_api_key_button").on("click", function (e) {
const data = {};
e.preventDefault();
e.stopPropagation();
data.password = $("#get_api_key_password").val();
function request_api_key(data) {
channel.post({
url: '/json/fetch_api_key',
data: data,
@@ -295,7 +281,25 @@ exports.set_up = function () {
$("#api_key_modal").show();
},
});
});
}
$('.account-settings-form').append(render_settings_api_key_modal());
$("#api_key_value").text("");
$("#show_api_key").hide();
if (page_params.realm_password_auth_enabled === false) {
// Skip the password prompt step, since the user doesn't have one.
request_api_key({});
} else {
$("#get_api_key_button").on("click", function (e) {
const data = {};
e.preventDefault();
e.stopPropagation();
data.password = $("#get_api_key_password").val();
request_api_key(data);
});
}
$("#show_api_key").on("click", "button.regenerate_api_key", function (e) {
channel.post({

View File

@@ -5,7 +5,7 @@
</td>
<td>
<span class="emoji_image">
<a href="{{source_url}}" target="_blank">
<a href="{{source_url}}" target="_blank" rel="noopener noreferrer">
<img src="{{source_url}}" alt="{{display_name}}" />
</a>
</span>

View File

@@ -2,7 +2,11 @@
<tr class="invite_row">
<td>
{{#if is_multiuse}}
<span class="email"><a href="{{link_url}}" target="_blank">{{t 'Invite link'}}</a></span>
<span class="email">
<a href="{{link_url}}" target="_blank" rel="noopener noreferrer">
{{t 'Invite link'}}
</a>
</span>
{{else}}
<span class="email">{{email}}</span>
{{/if}}

View File

@@ -1,6 +1,6 @@
<div class="emoji-popover">
<div class="emoji-popover-top input-append">
<input class="emoji-popover-filter" type="text" autofocus placeholder={{t 'Search' }} />
<input class="emoji-popover-filter" type="text" autofocus placeholder="{{t 'Search' }}" />
<i class="fa fa-search" aria-hidden="true"></i>
</div>
<div class="emoji-popover-category-tabs">

View File

@@ -1,5 +1,5 @@
{{#with emoji_dict}}
<div class="emoji-popover-emoji {{#if ../message_id }}{{#if has_reacted}} reacted {{/if}} reaction {{else}} composition {{/if}}" data-emoji-name={{name}} tabindex="0" data-emoji-id="{{../type}},{{../section}},{{../index}}">
<div class="emoji-popover-emoji {{#if ../message_id }}{{#if has_reacted}} reacted {{/if}} reaction {{else}} composition {{/if}}" data-emoji-name="{{name}}" tabindex="0" data-emoji-id="{{../type}},{{../section}},{{../index}}">
{{#if is_realm_emoji}}
<img src="{{url}}" class="emoji"/>
{{else}}

View File

@@ -1,6 +1,6 @@
<div class='pill' data-id='{{ id }}' tabindex=0>
{{#if has_image}}
<img class="pill-image" src={{img_src}} />
<img class="pill-image" src="{{img_src}}" />
{{/if}}
<span class="pill-value">{{ display_value }}</span>
<div class="exit">

View File

@@ -1,4 +1,4 @@
<div class="{{this.class}}" aria-label="{{this.label}}" data-reaction-id={{this.local_id}}>
<div class="{{this.class}}" aria-label="{{this.label}}" data-reaction-id="{{this.local_id}}">
{{#if this.emoji_alt_code}}
<div class="emoji_alt_code">&nbsp:{{this.emoji_name}}:</div>
{{else}}

View File

@@ -32,7 +32,7 @@
</span><span class="recipient_bar_controls no-select">
{{! exterior links (e.g. to a trac ticket) }}
{{#each topic_links}}
<a href="{{this}}" target="_blank" class="no-underline">
<a href="{{this}}" target="_blank" rel="noopener noreferrer" class="no-underline">
<i class="fa fa-external-link-square" aria-label="{{t 'External link' }}"></i>
</a>
{{/each}}

View File

@@ -39,7 +39,7 @@
{{#if page_params.two_fa_enabled }}
<p for="two_factor_auth" class="inline-block title">
{{t "Two factor authentication" }}: {{#if page_params.two_fa_enabled_user }}{{t "Enabled" }}{{else}}{{t "Disabled" }}{{/if}}
<a target="_blank" id="two_factor_auth" href="/account/two_factor/" title="{{t 'Setup two factor authentication' }}">[{{t "Setup" }}]</a>
<a target="_blank" rel="noopener noreferrer" id="two_factor_auth" href="/account/two_factor/" title="{{t 'Setup two factor authentication' }}">[{{t "Setup" }}]</a>
</p>
{{/if}}
@@ -99,7 +99,7 @@
<label for="old_password" class="title">{{t "Old password" }}</label>
<input type="password" autocomplete="off" name="old_password" id="old_password" class="w-200 inline-block" value="" />
<div class="info">
<a href="/accounts/password/reset/" class="sea-green" target="_blank">{{t "Forgotten it?" }}</a>
<a href="/accounts/password/reset/" class="sea-green" target="_blank" rel="noopener noreferrer">{{t "Forgotten it?" }}</a>
</div>
</div>
@@ -160,7 +160,7 @@
&times;
</span>
<div id="user-avatar-source">
<a href="https://en.gravatar.com/" target="_blank">{{t "Avatar from Gravatar" }}</a>
<a href="https://en.gravatar.com/" target="_blank" rel="noopener noreferrer">{{t "Avatar from Gravatar" }}</a>
</div>
</div>
<input type="file" name="user_avatar_file_input" class="notvisible" id="user_avatar_file_input" value="{{t 'Upload profile picture' }}" />

View File

@@ -11,7 +11,10 @@
<div id="password_confirmation">
<form id="api_key_form">
<p>{{t "Please re-enter your password to confirm your identity." }}
<a href="/accounts/password/reset/" target="_blank">{{t "Never had one? Forgotten it?" }}</a></p>
<a href="/accounts/password/reset/" target="_blank" rel="noopener noreferrer">
{{t "Never had one? Forgotten it?" }}
</a>
</p>
<div class="control-group">
<label for="password" class="control-label">{{t "Current password" }}</label>
<input type="password" autocomplete="off" name="password" id="get_api_key_password" value="" />

View File

@@ -2,7 +2,7 @@
<div class="bot-settings-form">
{{#unless page_params.is_guest}}
<div class="tip">
{{#tr this}}Looking for our <a href="/integrations" target="_blank">Integrations</a> or <a href="/api" target="_blank">API</a> documentation?{{/tr}}
{{#tr this}}Looking for our <a href="/integrations" target="_blank" rel="noopener noreferrer">Integrations</a> or <a href="/api" rel="noopener noreferrer" target="_blank">API</a> documentation?{{/tr}}
</div>
<div class="tip bot-settings-tip"></div>

View File

@@ -1,6 +1,6 @@
<div id="data-exports" class="settings-section" data-name="data-exports-admin">
<h3>{{t "Data exports" }}
<a href="/help/export-your-organization" target="_blank">
<a href="/help/export-your-organization" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
@@ -10,7 +10,7 @@
{{t 'Depending on the size of your organization, an export can take anywhere from seconds to an hour.' }}
</p>
<p>
{{#tr this}}<a href="/help/export-your-organization" target="_blank">Click here</a> to learn about exporting private streams and messages.{{/tr}}
{{#tr this}}<a href="/help/export-your-organization" target="_blank" rel="noopener noreferrer">Click here</a> to learn about exporting private streams and messages.{{/tr}}
{{t 'Note that organizations are limited to five exports per week.' }}
</p>

View File

@@ -1,6 +1,6 @@
<div id="admin-deactivated-users-list" class="settings-section" data-name="deactivated-users-admin">
<h3 class="inline-block">{{t "Deactivated users" }}
<a href="/help/deactivate-or-reactivate-a-user" target="_blank">
<a href="/help/deactivate-or-reactivate-a-user" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>

View File

@@ -31,7 +31,7 @@
<div class="input-group">
<label for="demote_inactive_streams" class="dropdown-title">{{t "Demote inactive streams" }}
<a href="/help/manage-inactive-streams" target="_blank">
<a href="/help/manage-inactive-streams" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>

View File

@@ -36,7 +36,7 @@
</ul>
<p>
{{#tr this}}
More details are available <a href="/help/add-a-custom-linkification-filter" target="_blank">in the Help Center article</a>.
More details are available <a href="/help/add-a-custom-linkification-filter" target="_blank" rel="noopener noreferrer">in the Help Center article</a>.
{{/tr}}
</p>

View File

@@ -68,7 +68,7 @@
<div class="input-group">
<label for="realm_waiting_period_setting" class="dropdown-title">
{{t "Waiting period before new members turn into full members" }}
<a href="/help/restrict-permissions-of-new-members" target="_blank">
<a href="/help/restrict-permissions-of-new-members" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>
@@ -132,7 +132,7 @@
<div class="input-group">
<label for="realm_email_address_visibility">{{t "Who can access user email addresses" }}
<a href="/help/restrict-visibility-of-email-addresses" target="_blank">
<a href="/help/restrict-visibility-of-email-addresses" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>

View File

@@ -5,7 +5,7 @@
<div id="org-org-profile" class="org-subsection-parent">
<div class="subsection-header">
<h3>{{t "Organization profile" }}
<a href="/help/create-your-organization-profile" target="_blank">
<a href="/help/create-your-organization-profile" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
@@ -45,20 +45,20 @@
id="realm_icon_delete_button">{{t 'Delete profile picture' }}</button>
</div>
</div>
<a href="/login/?preview=true" target="_blank" class="button rounded sea-green w-200 block" id="id_org_profile_preview">
<a href="/login/?preview=true" target="_blank" rel="noopener noreferrer" class="button rounded sea-green w-200 block" id="id_org_profile_preview">
{{t 'Preview organization profile' }}
<i class="fa fa-external-link" aria-hidden="true" title="{{t 'Preview organization profile' }}"></i>
</a>
<div class="subsection-header">
<h3>{{t "Organization logo" }}
<a href="/help/create-your-organization-profile#add-a-wide-logo" target="_blank">
<a href="/help/create-your-organization-profile#add-a-wide-logo" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
<div>
{{#unless plan_includes_wide_organization_logo}}
<a href="/upgrade" class="upgrade-tip" target="_blank">
<a href="/upgrade" class="upgrade-tip" target="_blank" rel="noopener noreferrer">
{{upgrade_text_for_wide_organization_logo}}
</a>
{{/unless}}

View File

@@ -6,7 +6,7 @@
<div id="org-msg-editing" class="org-subsection-parent">
<div class="subsection-header">
<h3>{{t "Message editing" }}
<a href="/help/configure-message-editing-and-deletion" target="_blank">
<a href="/help/configure-message-editing-and-deletion" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
@@ -191,7 +191,7 @@
{{/each}}
</select>
<div id="google_hangouts_domain">
<label>{{t 'Domain for your <a href="https://gsuite.google.com" target="_blank">G Suite team</a> (required)' }}:</label>
<label>{{t 'Domain for your <a href="https://gsuite.google.com" target="_blank" rel="noopener noreferrer">G Suite team</a> (required)' }}:</label>
<input type="text" id="id_realm_google_hangouts_domain"
name="realm_google_hangouts_domain"
autocomplete="off"

View File

@@ -1,4 +1,4 @@
<span>
{{#tr this}}Organization using __percent_used__% of __upload_quota__.{{/tr}}
{{#if show_upgrade_message}}{{#tr this}}<a href="/upgrade" target="_blank">Upgrade</a> for more space.{{/tr}}{{/if}}
{{#if show_upgrade_message}}{{#tr this}}<a href="/upgrade" target="_blank" rel="noopener noreferrer">Upgrade</a> for more space.{{/tr}}{{/if}}
</span>

View File

@@ -24,7 +24,7 @@
{{t "Stream permissions" }}
</div>
<div class="stream-creation-info">
{{t 'These settings are explained in detail in the <a target="_blank" href="/help/stream-permissions">help center</a>.'}}
{{t 'These settings are explained in detail in the <a target="_blank" rel="noopener noreferrer" href="/help/stream-permissions">help center</a>.'}}
</div>
{{> stream_types is_public=true }}

View File

@@ -74,7 +74,7 @@
<div class="stream-email-box" {{#unless sub.email_address}}style="display: none;"{{/unless}}>
<label class="sub_settings_title">
{{t "Email address" }}
<a href="/help/message-a-stream-by-email" target="_blank">
<a href="/help/message-a-stream-by-email" target="_blank" rel="noopener noreferrer">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>

View File

@@ -31,7 +31,7 @@
<div class="nothing-selected">
{{#if can_create_streams}}
<button type="button" class="create_stream_button button small rounded">{{t 'Create stream' }}</button>
<span>{{t 'First time? Read our <a href="/help/getting-your-organization-started-with-zulip#create-streams" target="_blank">guidelines</a> for creating and naming streams.' }}</span>
<span>{{t 'First time? Read our <a href="/help/getting-your-organization-started-with-zulip#create-streams" target="_blank" rel="noopener noreferrer">guidelines</a> for creating and naming streams.' }}</span>
{{/if}}
</div>
<div class="settings" data-simplebar data-simplebar-auto-hide="false">

View File

@@ -1,7 +1,7 @@
{{#with attachment}}
<tr class="uploaded_file_row" id="{{name}}" data-attachment-id="{{id}}">
<td>
<a type="submit" href="/user_uploads/{{path_id}}" target="_blank" title="{{t 'View file' }}">
<a type="submit" href="/user_uploads/{{path_id}}" target="_blank" rel="noopener noreferrer" title="{{t 'View file' }}">
{{ name }}
</a>
</td>

View File

@@ -9,7 +9,7 @@
{{#unless is_guest}}
<p>
{{#tr this}}User groups allow you to <a href="/help/mention-a-user-or-group" target="_blank">mention</a> multiple users at once. When you mention a user group, everyone in the group is notified as if they were individually mentioned.{{/tr}}
{{#tr this}}User groups allow you to <a href="/help/mention-a-user-or-group" target="_blank" rel="noopener noreferrer">mention</a> multiple users at once. When you mention a user group, everyone in the group is notified as if they were individually mentioned.{{/tr}}
</p>
{{#if (or is_admin (eq realm_user_group_edit_policy USER_GROUP_EDIT_POLICY_MEMBERS))}}
<form class="form-horizontal admin-user-group-form">

View File

@@ -50,9 +50,9 @@
<div class="input" contenteditable="false" style="display: none;"></div>
</div>
{{else if this.is_link}}
<a href={{this.value}} target="_blank" class="value">{{this.value}}</a>
<a href="{{this.value}}" target="_blank" rel="noopener noreferrer" class="value">{{this.value}}</a>
{{else if this.is_external_account}}
<a href={{this.link}} target="_blank" class="value">{{this.value}}</a>
<a href="{{this.link}}" target="_blank" rel="noopener noreferrer" class="value">{{this.value}}</a>
{{else}}
{{#if this.rendered_value}}
<div class="value rendered_markdown">{{rendered_markdown this.rendered_value}}</div>

View File

@@ -1,8 +1,8 @@
<span class="label">realm</span>
<h3><img src="{{ realm_icon_url(realm) }}" class="support-realm-icon"> {{ realm.name }}</h3>
<b>URL</b>: <a target="_blank" href="{{ realm.uri }}">{{ realm.uri }}</a> |
<a target="_blank" href="/stats/realm/{{ realm.string_id }}/">stats</a> |
<a target="_blank" href="/realm_activity/{{ realm.string_id }}/">activity</a><br>
<b>URL</b>: <a target="_blank" rel="noopener noreferrer" href="{{ realm.uri }}">{{ realm.uri }}</a> |
<a target="_blank" rel="noopener noreferrer" href="/stats/realm/{{ realm.string_id }}/">stats</a> |
<a target="_blank" rel="noopener noreferrer" href="/realm_activity/{{ realm.string_id }}/">activity</a><br>
<b>Date created</b>: {{ realm.date_created|timesince }} ago<br>
<b>Admins</b>: {{ realm_admin_emails(realm) }}
<a title="Copy emails" class="copy-button" data-copytext="{{ realm_admin_emails(realm) }}">

View File

@@ -40,7 +40,7 @@ the registration flow has its own (nearly identical) copy of the fields below in
<input id="id_terms" class="required" type="checkbox" name="terms"
{% if form.terms.value() %}checked="checked"{% endif %} />
<span></span>
{% trans %}I agree to the <a href="{{ root_domain_uri }}/terms" target="_blank">Terms of Service</a>.{% endtrans %}
{% trans %}I agree to the <a href="{{ root_domain_uri }}/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a>.{% endtrans %}
</label>
{% if form.terms.errors %}
{% for error in form.terms.errors %}

View File

@@ -13,7 +13,7 @@
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
{% trans %}
Some older messages are unavailable.
<a href="/plans/" target="_blank">Upgrade your organization</a>
<a href="/plans/" target="_blank" rel="noopener noreferrer">Upgrade your organization</a>
to access your full message history.
{% endtrans %}
</p>
@@ -24,7 +24,7 @@
{% trans %}
End of results from your
<a href="/help/search-for-messages#searching-shared-history"
target="_blank">personal history</a>.
target="_blank" rel="noopener noreferrer">personal history</a>.
Consider <a class="search-shared-history" href="">searching all public streams</a>.
{% endtrans %}
</p>

View File

@@ -78,7 +78,7 @@
We recommend that
you <a class="webathena_login">give Zulip the ability to mirror the messages for you via
WebAthena</a>. If you'd prefer, you can instead
<a href="/zephyr-mirror" target="_blank">run the
<a href="/zephyr-mirror" target="_blank" rel="noopener noreferrer">run the
Zephyr mirror script yourself</a> in a screen
session.
</span>

View File

@@ -308,6 +308,6 @@
</table>
</div>
<hr/>
<a href="/help/keyboard-shortcuts" target="_blank">{% trans %}Detailed keyboard shortcuts documentation{% endtrans %}</a>
<a href="/help/keyboard-shortcuts" target="_blank" rel="noopener noreferrer">{% trans %}Detailed keyboard shortcuts documentation{% endtrans %}</a>
</div>
</div>

View File

@@ -10,7 +10,7 @@
<div class="title">{{ _('Pan &amp; Zoom') }}</div>
<div class="status" data-disabled="{{ _('Disabled') }}" data-enabled="{{ _('Enabled') }}"></div>
</div>
<a class="button small open" target="_blank">{{ _('Open') }}</a>
<a class="button small open" rel="noopener noreferrer" target="_blank">{{ _('Open') }}</a>
<a class="button small download" download>{{ _('Download') }}</a>
</div>
<div class="clear-float"></div>

View File

@@ -26,7 +26,7 @@
</tr>
<tr>
<td>[Zulip website](https://zulip.org) (or <kbd>Ctrl + Shift + L</kbd>)</td>
<td class="rendered_markdown"><a href="https://zulip.org" target="_blank">Zulip website</a></td>
<td class="rendered_markdown"><a href="https://zulip.org" target="_blank" rel="noopener noreferrer">Zulip website</a></td>
</tr>
<tr>
<td>* Milk<br/>
@@ -61,7 +61,7 @@
</td>
</tr>
<tr>
<td>:heart: (and <a href="http://www.emoji-cheat-sheet.com/" target="_blank">many others</a>, from the <a href="https://code.google.com/p/noto/" target="_blank">Noto Project</a>)</td>
<td>:heart: (and <a href="http://www.emoji-cheat-sheet.com/" target="_blank" rel="noopener noreferrer">many others</a>, from the <a href="https://code.google.com/p/noto/" target="_blank" rel="noopener noreferrer">Noto Project</a>)</td>
<td class="rendered_markdown"><img alt=":heart:" class="emoji" src="/static/generated/emoji/images/emoji/heart.png" title=":heart:" /></td>
</tr>
<tr>
@@ -113,7 +113,7 @@ def zulip():
</tr>
<tr>
<td colspan="2">{% trans %}To add syntax highlighting to a multi-line code block,
add the language's <b>first</b> <a target="_blank" href="http://pygments.org/docs/lexers/">Pygments short name</a>
add the language's <b>first</b> <a target="_blank" rel="noopener noreferrer" href="http://pygments.org/docs/lexers/">Pygments short name</a>
after the first set of back-ticks.
You can also make a code block by indenting each line with 4 spaces.{% endtrans %}</td>
</tr>
@@ -143,9 +143,9 @@ Quoted block
</td>
</tr>
<tr>
<td class="rendered_markdown" colspan="2">{% trans %}You can also make <a target="_blank"
<td class="rendered_markdown" colspan="2">{% trans %}You can also make <a target="_blank" rel="noopener noreferrer"
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables">tables</a>
with this <a target="_blank"
with this <a target="_blank" rel="noopener noreferrer"
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables">Markdown-ish
table syntax</a>.{% endtrans %}</td>
</tr>
@@ -153,6 +153,6 @@ Quoted block
</table>
</div>
<hr/>
<a href="/help/format-your-message-using-markdown" target="_blank">Detailed message formatting documentation</a>
<a href="/help/format-your-message-using-markdown" target="_blank" rel="noopener noreferrer">Detailed message formatting documentation</a>
</div>
</div>

View File

@@ -80,7 +80,7 @@
</li>
<li class="divider"></li>
<li role="presentation">
<a href="/help" target="_blank" role="menuitem">
<a href="/help" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-question-circle" aria-hidden="true"></i> {{ _('Help center') }}
</a>
</li>
@@ -101,23 +101,23 @@
</li>
<li class="divider" role="presentation"></li>
<li role="presentation">
<a href="{{ apps_page_url }}" target="_blank" role="menuitem">
<a href="{{ apps_page_url }}" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-desktop" aria-hidden="true"></i> {{ _('Desktop & mobile apps') }}
</a>
</li>
<li role="presentation">
<a href="/integrations" target="_blank" role="menuitem">
<a href="/integrations" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-github" aria-hidden="true"></i> {{ _('Integrations') }}
</a>
</li>
<li role="presentation">
<a href="/api" target="_blank" role="menuitem">
<a href="/api" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-sitemap" aria-hidden="true"></i> {{ _('API documentation') }}
</a>
</li>
{% if not is_guest %}
<li role="presentation">
<a href="/stats" target="_blank" role="menuitem">
<a href="/stats" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-bar-chart" aria-hidden="true"></i>
<span>{{ _('Statistics') }}</span>
</a>
@@ -125,14 +125,14 @@
{% endif %}
{% if show_plans %}
<li role="presentation">
<a href="/plans" target="_blank" role="menuitem">
<a href="/plans" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-rocket" aria-hidden="true"></i> {{ _('Plans and pricing') }}
</a>
</li>
{% endif %}
{% if show_billing %}
<li role="presentation">
<a href="/billing" target="_blank" role="menuitem">
<a href="/billing" target="_blank" rel="noopener noreferrer" role="menuitem">
<i class="fa fa-credit-card" aria-hidden="true"></i> {{ _('Billing') }}
</a>
</li>

View File

@@ -21,7 +21,7 @@
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
{% trans %}Zulip needs to send email to confirm users' addresses and send notifications.{% endtrans %}
<a class="alert-link" href="https://zulip.readthedocs.io/en/latest/production/email.html" target="_blank">
<a class="alert-link" href="https://zulip.readthedocs.io/en/latest/production/email.html" target="_blank" rel="noopener noreferrer">
{% trans %}See how to configure email.{% endtrans %}
</a>
</div>
@@ -31,7 +31,7 @@
<div data-step="1">
{% trans %}
You are using an old version of the Zulip desktop app with known security bugs.
<a class="alert-link" href="https://zulipchat.com/apps" target="_blank">
<a class="alert-link" href="https://zulipchat.com/apps" target="_blank" rel="noopener noreferrer">
Download the latest version.
</a>
{% endtrans %}

View File

@@ -113,6 +113,6 @@
{% endtrans %}
</p>
<hr/>
<a href="/help/search-for-messages#search-operators" target="_blank">{% trans %}Detailed search operators documentation{% endtrans %}</a>
<a href="/help/search-for-messages#search-operators" target="_blank" rel="noopener noreferrer">{% trans %}Detailed search operators documentation{% endtrans %}</a>
</div>
</div>

View File

@@ -22,7 +22,7 @@
<div class="cta">
<h1>Zulip for <span class="platform"></span></h1>
<p class="description"></p>
<p class="download-instructions">For download instructions, go to the <a class="silver bold" href="/help/desktop-app-install-guide" target="_blank">desktop app install guide</a>.</p>
<p class="download-instructions">For download instructions, go to the <a class="silver bold" href="/help/desktop-app-install-guide" target="_blank" rel="noopener noreferrer">desktop app install guide</a>.</p>
<a class="link no-action" href=""><span class="button green">Download Zulip for <span class="platform"></span></span></a>
<span id="download-android-apk"><a href="https://github.com/zulip/zulip-mobile/releases/latest">or manually download APK</a></span>
</div>

View File

@@ -5,7 +5,7 @@
<span class="message_sender{% if status_message %} sender_info_hover{% endif %} no-select">
{% if include_sender %}
<div class="inline_profile_picture">
<img src={{ avatar_url }} alt="" class="no-drag"/>
<img src="{{ avatar_url }}" alt="" class="no-drag"/>
</div>
{% if status_message %}
<span class="sender-status">

View File

@@ -37,7 +37,7 @@
{% else %}
<p>
Please have a look at our
<a target="_blank" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing">
<a target="_blank" rel="noopener noreferrer" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing">
setup guide</a> for forwarding emails sent in development
environment to an email account.
</p>

View File

@@ -15,13 +15,14 @@ page can be easily identified in it's respective JavaScript file -->
{% endif %}
<p class="devlogin_subheader">(Or visit the <a href="/login/">normal login page</a>.)</p>
<form name="direct_login_form" id="direct_login_form" method="post" class="login-form">
<input type="hidden" name="next" value="{{ next }}" />
<div class="control-group">
<div class="controls">
<div class="group">
<h2>{{ _('Administrators') }}</h2>
{% if direct_admins %}
{% for direct_admin in direct_admins %}
<p><input type="submit" formaction="{{ direct_admin.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}?next={{ next }}"
<p><input type="submit" formaction="{{ direct_admin.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}"
name="direct_email" class="btn-direct btn-admin" value="{{ direct_admin.delivery_email }}" /></p>
{% endfor %}
{% else %}
@@ -30,7 +31,7 @@ page can be easily identified in it's respective JavaScript file -->
<h2>{{ _('Guest users') }}</h2>
{% if guest_users %}
{% for guest_user in guest_users %}
<p><input type="submit" formaction="{{ guest_user.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}?next={{ next }}"
<p><input type="submit" formaction="{{ guest_user.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}"
name="direct_email" class="btn-direct btn-admin" value="{{ guest_user.delivery_email }}" /></p>
{% endfor %}
{% else %}
@@ -42,7 +43,7 @@ page can be easily identified in it's respective JavaScript file -->
<h2>{{ _('Normal users') }}</h2>
{% if direct_users %}
{% for direct_user in direct_users %}
<p><input type="submit" formaction="{{ direct_user.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}?next={{ next }}"
<p><input type="submit" formaction="{{ direct_user.realm.uri }}{{ url('zerver.views.auth.dev_direct_login') }}"
name="direct_email" class="btn-direct btn-admin" value="{{ direct_user.delivery_email }}" /></p>
{% endfor %}
{% else %}

View File

@@ -49,7 +49,7 @@
<br/>
<div class="alert alert-info">
You must setup SMTP as described
<a target="_blank" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing">
<a target="_blank" rel="noopener noreferrer" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing">
here</a> first before enabling this.
</div>
</form>

View File

@@ -94,7 +94,7 @@
<p>Communicate as efficiently as you use your favorite
text editor. Anything you can do with a mouse, you
can do even faster from the keyboard.
<a class="cta" href="/help/keyboard-shortcuts" target="_blank">
<a class="cta" href="/help/keyboard-shortcuts" target="_blank" rel="noopener noreferrer">
Learn more about keyboard shortcuts.</a>
</p>
</div>
@@ -104,7 +104,7 @@
<section>
<h2>Apps, Integrations, and API</h2>
<a class="feature-block" href="/integrations" target="_blank">
<a class="feature-block" href="/integrations" target="_blank" rel="noopener noreferrer">
<h3>INTEGRATIONS</h3>
<p>
Get alerts and updates from your favorite services with
@@ -112,7 +112,7 @@
Jenkins, and more.
</p>
</a>
<a class="feature-block" href="/api" target="_blank">
<a class="feature-block" href="/api" target="_blank" rel="noopener noreferrer">
<h3>API</h3>
<p>
Want to roll your own notifications? We've got a
@@ -120,12 +120,12 @@
integrations—both sending and receiving—a snap!
</p>
</a>
<a class="feature-block" href="/apps" target="_blank">
<a class="feature-block" href="/apps" target="_blank" rel="noopener noreferrer">
<h3>MOBILE APPS</h3>
<p>Keep up while on the go with our native quality iOS and
Android apps.</p>
</a>
<a class="feature-block" href="/apps" target="_blank">
<a class="feature-block" href="/apps" target="_blank" rel="noopener noreferrer">
<h3>DESKTOP APPS</h3>
<p>Prefer Zulip in its own window and rich, OS-level
notifications? Enjoy Zulip on your desktop.</p>
@@ -139,14 +139,14 @@
<section>
<h2>And everything else you need...</h2>
<a class="feature-block" href="/security" target="_blank">
<a class="feature-block" href="/security" target="_blank" rel="noopener noreferrer">
<h3>ENTERPRISE-GRADE SECURITY</h3>
<p>
Zulip is used by some of the most security-conscious
organizations in the world.
</p>
</a>
<a class="feature-block" href="/help/search-for-messages" target="_blank">
<a class="feature-block" href="/help/search-for-messages" target="_blank" rel="noopener noreferrer">
<h3>FULL-TEXT FULL-HISTORY SEARCH</h3>
<p>
Search is both snappy and smart, helping you look for
@@ -154,31 +154,31 @@
search operators for fine-grained control.
</p>
</a>
<a class="feature-block" href="/help/stream-permissions" target="_blank">
<a class="feature-block" href="/help/stream-permissions" target="_blank" rel="noopener noreferrer">
<h3>HISTORY</h3>
<p>Join a stream and see its history, so even new team
members are never out of the loop.</p>
</a>
<a class="feature-block" href="/help/star-a-message" target="_blank">
<a class="feature-block" href="/help/star-a-message" target="_blank" rel="noopener noreferrer">
<h3>STARRED MESSAGES</h3>
<p>Keep a todo list of messages to come back to, or keep
track of interesting conversations.</p>
</a>
<a class="feature-block" href="/help/analytics" target="_blank">
<a class="feature-block" href="/help/analytics" target="_blank" rel="noopener noreferrer">
<h3>STATISTICS</h3>
<p>Zulip has a powerful set of analytics available to
help you see how your organization communicates.</p>
</a>
<a class="feature-block" href="/help/private-messages" target="_blank">
<a class="feature-block" href="/help/private-messages" target="_blank" rel="noopener noreferrer">
<h3>ONE-ON-ONE AND GROUP PRIVATE CONVERSATIONS</h3>
<p>Lightweight private conversations with one or as many people as you need.</p>
</a>
<a class="feature-block" href="/help/status-and-availability" target="_blank">
<a class="feature-block" href="/help/status-and-availability" target="_blank" rel="noopener noreferrer">
<h3>TEAM AVAILABILITY</h3>
<p>See who is currently online at a glance.</p>
</a>
<a class="feature-block" href="/help/stream-permissions" target="_blank">
<a class="feature-block" href="/help/stream-permissions" target="_blank" rel="noopener noreferrer">
<h3>PRIVATE STREAMS</h3>
<p>Enjoy the benefits of threaded conversations while
controlling your audience and privacy.</p>
@@ -188,7 +188,7 @@
<p>We're always receiving messages for you, even when
you're logged out or away from your computer.</p>
</div>
<a class="feature-block" href="/help/edit-or-delete-a-message" target="_blank">
<a class="feature-block" href="/help/edit-or-delete-a-message" target="_blank" rel="noopener noreferrer">
<h3>MESSAGE EDITING</h3>
<p>Don't worry, you can always fix that typo, either in
the body of message or its topic.</p>
@@ -197,12 +197,12 @@
<h3>TYPING NOTIFICATIONS</h3>
<p>Know when other users are composing messages to you.</p>
</div>
<a class="feature-block" href="/help/view-and-edit-your-message-drafts" target="_blank">
<a class="feature-block" href="/help/view-and-edit-your-message-drafts" target="_blank" rel="noopener noreferrer">
<h3>SAVED DRAFTS</h3>
<p>Zulip's drafts make it easy to write longer messages
without worrying about losing your work.</p>
</a>
<a class="feature-block" href="https://zulip.readthedocs.io/en/latest/contributing/accessibility.html" target="_blank">
<a class="feature-block" href="https://zulip.readthedocs.io/en/latest/contributing/accessibility.html" target="_blank" rel="noopener noreferrer">
<h3>ACCESSIBILITY</h3>
<p>
Zulip follows best practices for accessibility, and has
@@ -210,22 +210,22 @@
tools.
</p>
</a>
<a class="feature-block" href="/help/about-streams-and-topics" target="_blank">
<a class="feature-block" href="/help/about-streams-and-topics" target="_blank" rel="noopener noreferrer">
<h3>CONVERSATIONS THREADED BY TOPIC</h3>
<p>Participate in several conversations with the same
group at once, without getting lost or overwhelmed.</p>
</a>
<a class="feature-block" href="/help/reading-strategies" target="_blank">
<a class="feature-block" href="/help/reading-strategies" target="_blank" rel="noopener noreferrer">
<h3>CATCH UP IN NO TIME</h3>
<p>With topics, hotkeys and snappy performance, usefully
reviewing hundreds of messages takes just minutes.</p>
</a>
<a class="feature-block" href="/help/change-your-language" target="_blank">
<a class="feature-block" href="/help/change-your-language" target="_blank" rel="noopener noreferrer">
<h3>FULLY INTERNATIONALIZED</h3>
<p>The Zulip UI is fully internationalized and has been
translated into over a dozen languages.</p>
</a>
<a class="feature-block" href="/help/configure-authentication-methods" target="_blank">
<a class="feature-block" href="/help/configure-authentication-methods" target="_blank" rel="noopener noreferrer">
<h3>CUSTOMIZABLE LOGIN AND REGISTRATION</h3>
<p>
Customize the available authentication methods and
@@ -233,35 +233,35 @@
organization using Markdown.
</p>
</a>
<a class="feature-block" href="/help/start-a-call" target="_blank">
<a class="feature-block" href="/help/start-a-call" target="_blank" rel="noopener noreferrer">
<h3>VIDEO CALLS</h3>
<p>
Create and join video calls with a single click. Powered
by your choice of Zoom, Jitsi Meet, or Google Hangouts.
</p>
</a>
<a class="feature-block" href="/help/configure-authentication-methods" target="_blank">
<a class="feature-block" href="/help/configure-authentication-methods" target="_blank" rel="noopener noreferrer">
<h3>FLEXIBLE AUTHENTICATION</h3>
<p>
Supported authentication providers include LDAP, SAML,
Google, GitHub, and more.
</p>
</a>
<a class="feature-block" href="/help/import-from-slack" target="_blank">
<a class="feature-block" href="/help/import-from-slack" target="_blank" rel="noopener noreferrer">
<h3>DATA IMPORT</h3>
<p>
Import an existing Slack, Mattermost, HipChat, Stride,
or Gitter workspace into Zulip.
</p>
</a>
<a class="feature-block" href="/help/add-custom-profile-fields" target="_blank">
<a class="feature-block" href="/help/add-custom-profile-fields" target="_blank" rel="noopener noreferrer">
<h3>CUSTOM PROFILE FIELDS</h3>
<p>
Use Zulip to store directory information, links to social
media profiles, food preferences, or anything else.
</p>
</a>
<a class="feature-block" href="/help/roles-and-permissions" target="_blank">
<a class="feature-block" href="/help/roles-and-permissions" target="_blank" rel="noopener noreferrer">
<h3>GUESTS</h3>
<p>
Guests cannot see or join streams unless they are explicitly
@@ -269,33 +269,33 @@
contractors.
</p>
</a>
<a class="feature-block" href="/help/create-your-organization-profile" target="_blank">
<a class="feature-block" href="/help/create-your-organization-profile" target="_blank" rel="noopener noreferrer">
<h3>CUSTOM BRANDING</h3>
<p>
Use your logo instead of Zulip's in the desktop and webapp.
</p>
</a>
<a class="feature-block" href="/integrations/communication" target="_blank">
<a class="feature-block" href="/integrations/communication" target="_blank" rel="noopener noreferrer">
<h3>INTEGRATE WITH IRC, MATRIX, OR SLACK</h3>
<p>
Two way integrations with IRC and Matrix, and one way
integration with Slack.
</p>
</a>
<a class="feature-block" href="/help/moderating-open-organizations" target="_blank">
<a class="feature-block" href="/help/moderating-open-organizations" target="_blank" rel="noopener noreferrer">
<h3>MODERATION</h3>
<p>
A full suite of tools for moderating open communities.
</p>
</a>
<a class="feature-block" href="/help/export-your-organization" target="_blank">
<a class="feature-block" href="/help/export-your-organization" target="_blank" rel="noopener noreferrer">
<h3>DATA EXPORTS</h3>
<p>
No vendor lock-in. Export your hosted Zulip to an
on-premises installation at any time.
</p>
</a>
<a class="feature-block" href="https://github.com/zulip/zulip/" target="_blank">
<a class="feature-block" href="https://github.com/zulip/zulip/" target="_blank" rel="noopener noreferrer">
<h3>YOUR FEATURE HERE</h3>
<p>Zulip is open source, so if something important for
your use case is missing, you can make it happen!</p>

View File

@@ -50,12 +50,16 @@ the most common configuration, run the following commands, replacing
```
cd /home/zulip/deployments/current
supervisorctl stop all # Stop the Zulip server
./manage.py convert_gitter_data gitter_data.json --output converted_gitter_data
./manage.py import '' converted_gitter_data
./scripts/restart-server
```
This could take several minutes to run, depending on how much data you're
importing.
This could take several minutes to run, depending on how much data
you're importing. The server stop/restart is only necessary when
importing on a server with minimal RAM, where an OOM kill might
otherwise occur.
**Import options**

View File

@@ -108,13 +108,17 @@ password you set during the HipChat export.
```
cd /home/zulip/deployments/current
supervisorctl stop all # Stop the Zulip server
openssl aes-256-cbc -d -in <exported_file> -out hipchat.tar.gz -md md5 -pass pass:<password>
./manage.py convert_hipchat_data hipchat.tar.gz --output converted_hipchat_data
./manage.py import '' converted_hipchat_data
./scripts/restart-server
```
This could take several minutes to run, depending on how much data you're
importing.
This could take several minutes to run, depending on how much data
you're importing. The server stop/restart is only necessary when
importing on a server with minimal RAM, where an OOM kill might
otherwise occur.
**Import options**

View File

@@ -149,12 +149,16 @@ the most common configuration, run the following commands, replacing
cd /home/zulip
tar -xzvf export.tar.gz
cd /home/zulip/deployments/current
supervisorctl stop all # Stop the Zulip server
./manage.py convert_mattermost_data /home/zulip/mattermost --output /home/zulip/converted_mattermost_data
./manage.py import "" /home/zulip/converted_mattermost_data/<team-name>
./scripts/restart-server
```
This could take several minutes to run, depending on how much data you're
importing.
This could take several minutes to run, depending on how much data
you're importing. The server stop/restart is only necessary when
importing on a server with minimal RAM, where an OOM kill might
otherwise occur.
**Import options**

View File

@@ -17,30 +17,45 @@ into an existing Zulip organization.
First, export your data from Slack.
### Export your Slack data
!!! warn ""
**Note:** Only Slack owners and admins can export data from Slack.
See Slack's
[guide to data exports](https://get.slack.help/hc/en-us/articles/201658943-Export-data-and-message-history)
for more information.
#### Get a Slack API token.
It will be a long string starting with `xoxb-`. It is required to
fetch data that Slack doesn't include in their data exports, like
email addresses.
{start_tabs}
1. [Generate a Slack Legacy API
token](https://api.slack.com/custom-integrations/legacy-tokens).
It will be a long string starting with `xoxp-`. It is required to
fetch data that Slack doesn't include in their data exports, like
email addresses.
1. [Create a new Slack app](https://api.slack.com/apps).
2. [Export your Slack data](https://my.slack.com/services/export). You will
receive a zip file `slack_data.zip`.
2. [Add OAuth scopes](https://api.slack.com/authentication/basics#scopes)
to your app. We need the following 'bot token scopes':
- `emoji:read`
- `users:read`
- `users:read.email`
- `team:read`
!!! warn ""
**Note:** Only Slack owners and admins can export data from Slack.
See Slack's
[guide to data exports](https://get.slack.help/hc/en-us/articles/201658943-Export-data-and-message-history)
for more information.
This step will also generate a different token starting with
`xoxe-`; you don't need it.
3. [Install the app](https://api.slack.com/authentication/basics#installing)
to your workspace. You will get an API token that you can now use to fetch
data from your slack workspace.
{end_tabs}
### Export your Slack data
Now, [Export your Slack data](https://my.slack.com/services/export). You will
receive a zip file `slack_data.zip`.
This step will also generate a different token starting with
`xoxe-`; you don't need it.
### Import into zulipchat.com
Email support@zulipchat.com with `slack_data.zip`, the Slack API token
@@ -64,12 +79,16 @@ the most common configuration, run the following commands, replacing
```
cd /home/zulip/deployments/current
supervisorctl stop all # Stop the Zulip server
./manage.py convert_slack_data slack_data.zip --token <token> --output converted_slack_data
./manage.py import '' converted_slack_data
./scripts/restart-server
```
This could take several minutes to run, depending on how much data you're
importing.
This could take several minutes to run, depending on how much data
you're importing. The server stop/restart is only necessary when
importing on a server with minimal RAM, where an OOM kill might
otherwise occur.
**Import options**

View File

@@ -26,7 +26,7 @@
{% endif %}
<p>
<a href="https://zulipchat.com/apps" target="_blank">
<a href="https://zulipchat.com/apps" target="_blank" rel="noopener noreferrer">
{{ _("Download the latest release.") }}
</a>
</p>

View File

@@ -144,7 +144,7 @@
{% for integration in integrations_dict.values() %}
{% if integration.is_enabled() %}
<div id={{ integration.name }} class="integration-instructions">
<div id="{{ integration.name }}" class="integration-instructions">
<div class="help-content"></div>
<p style="font-size:11px; font-style:italic;">
Logos are trademarks of their respective owners.

View File

@@ -41,7 +41,8 @@ page can be easily identified in it's respective JavaScript file. -->
{% else %}
{% if password_auth_enabled %}
<form name="login_form" id="login_form" method="post" class="login-form"
action="{{ url('django.contrib.auth.views.login') }}?next={{ next }}">
action="{{ url('django.contrib.auth.views.login') }}">
<input type="hidden" name="next" value="{{ next }}">
{% if two_factor_authentication_enabled %}
{{ wizard.management_form }}

View File

@@ -150,7 +150,7 @@
<div class="pricing-details">
Pricing varies with support required
</div>
<a href="mailto:sales@zulipchat.com" target="_blank" class="no-action button green">
<a href="mailto:sales@zulipchat.com" target="_blank" rel="noopener noreferrer" class="no-action button green">
Contact sales
</a>
</div>

View File

@@ -32,7 +32,7 @@
<button id="enter-realm-button" type="submit">{{ _('Next') }}</button>
<p class="bottom-text">
{{ _("Don't know your organization URL?") }}
<a target="_blank" href="/accounts/find/">{{ _("Find your organization.") }}</a>
<a target="_blank" rel="noopener noreferrer" href="/accounts/find/">{{ _("Find your organization.") }}</a>
</p>
</div>
</form>
@@ -40,7 +40,7 @@
</div>
<div class="bottom-text">
{{ _("Need to get your group started on Zulip?") }} <a target="_blank" href="/new/">{{ _("Create a new organization.") }}</a>
{{ _("Need to get your group started on Zulip?") }} <a target="_blank" rel="noopener noreferrer" href="/new/">{{ _("Create a new organization.") }}</a>
</div>
</div>

View File

@@ -28,7 +28,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<input id="id_team_name" class="required" type="text"
placeholder="Acme or Aκμή"
value="{% if form.realm_name.value() %}{{ form.realm_name.value() }}{% endif %}"
name="realm_name" maxlength={{ MAX_REALM_NAME_LENGTH }} required />
name="realm_name" maxlength="{{ MAX_REALM_NAME_LENGTH }}" required />
</div>
<label for="id_team_name" class="inline-block label-title">{{ _('Organization name') }}</label>
{% if form.realm_name.errors %}
@@ -63,7 +63,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
class="{% if root_domain_landing_page %}required{% endif %} subdomain" type="text"
placeholder="acme"
value="{% if form.realm_subdomain.value() %}{{ form.realm_subdomain.value() }}{% endif %}"
name="realm_subdomain" maxlength={{ MAX_REALM_SUBDOMAIN_LENGTH }}
name="realm_subdomain" maxlength="{{ MAX_REALM_SUBDOMAIN_LENGTH }}"
{% if root_domain_landing_page %}required{% endif %} />
<label for="id_team_subdomain" class="realm_subdomain_label">.{{ external_host }}</label>
<p id="id_team_subdomain_error_client" class="error help-inline text-error"></p>
@@ -124,7 +124,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
{% else %}
<input id="id_full_name" class="required" type="text" name="full_name"
value="{% if full_name %}{{ full_name }}{% elif form.full_name.value() %}{{ form.full_name.value() }}{% endif %}"
maxlength={{ MAX_NAME_LENGTH }} placeholder="{% trans %}Full name or 名前{% endtrans %}" required />
maxlength="{{ MAX_NAME_LENGTH }}" placeholder="{% trans %}Full name or 名前{% endtrans %}" required />
<label for="id_full_name" class="inline-block label-title">{{ _('Full name') }}</label>
{% if form.full_name.errors %}
{% for error in form.full_name.errors %}
@@ -151,7 +151,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<div class="input-box">
<input id="id_password" class="required" type="password" name="password"
value="{% if form.password.value() %}{{ form.password.value() }}{% endif %}"
maxlength={{ MAX_PASSWORD_LENGTH }}
maxlength="{{ MAX_PASSWORD_LENGTH }}"
data-min-length="{{password_min_length}}"
data-min-guesses="{{password_min_guesses}}" required />
<label for="id_password" class="inline-block">{{ _('Password') }}</label>
@@ -217,7 +217,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<input id="id_terms" class="required" type="checkbox" name="terms"
{% if form.terms.value() %}checked="checked"{% endif %} />
<span></span>
{% trans %}I agree to the <a href="{{ root_domain_uri }}/terms" target="_blank">Terms of Service</a>.{% endtrans %}
{% trans %}I agree to the <a href="{{ root_domain_uri }}/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a>.{% endtrans %}
</label>
{% if form.terms.errors %}
{% for error in form.terms.errors %}

View File

@@ -123,7 +123,7 @@
<!-- Compiled using underscore -->
<script type="text/template" id="contributors-template">
<div class="person">
<a href="https://github.com/<%= name %>" target="_blank" class="no-underline">
<a href="https://github.com/<%= name %>" target="_blank" rel="noopener noreferrer" class="no-underline">
<div class="avatar">
<img class="avatar_img" src="<%= avatar %>" alt="{{ _('Avatar') }}" />
</div>

View File

@@ -29,11 +29,11 @@ sudo rm -f /etc/apt/sources.list.d/mongodb*.list
# Provisioning may fail due to many issues but most of the times a network
# connection issue is the reason. So we are going to retry entire provisioning
# once again if that fixes our problem.
tools/provision --production-travis || {
tools/provision --build-release-tarball-only || {
ret=$?
if [ "$ret" = 1 ]; then
echo "\`provision\`: Something went wrong with the provisioning, might be a network issue, Retrying to provision..."
tools/provision --production-travis
tools/provision --build-release-tarball-only
else
echo "\`provision\`: Something REALLY BAD went wrong with the provisioning, not retrying."
exit "$ret"

View File

@@ -364,7 +364,7 @@ def main(options):
run_as_root(["cp", REPO_STOPWORDS_PATH, TSEARCH_STOPWORDS_PATH])
if is_circleci or (is_travis and not options.is_production_travis):
if is_circleci or (is_travis and not options.is_build_release_tarball_only):
run_as_root(["service", "rabbitmq-server", "restart"])
run_as_root(["service", "redis-server", "restart"])
run_as_root(["service", "memcached", "restart"])
@@ -390,7 +390,7 @@ def main(options):
[
provision_inner,
*(["--force"] if options.is_force else []),
*(["--production-travis"] if options.is_production_travis else []),
*(["--build-release-tarball-only"] if options.is_build_release_tarball_only else []),
]
)
@@ -401,10 +401,10 @@ if __name__ == "__main__":
default=False,
help="Ignore all provisioning optimizations.")
parser.add_argument('--production-travis', action='store_true',
dest='is_production_travis',
parser.add_argument('--build-release-tarball-only', action='store_true',
dest='is_build_release_tarball_only',
default=False,
help="Provision for Travis with production settings.")
help="Provision needed to build release tarball.")
options = parser.parse_args()
main(options)

View File

@@ -140,11 +140,9 @@ def main(options: argparse.Namespace) -> int:
else:
print("No need to run `scripts/setup/inline-email-css`.")
if not options.is_production_travis:
# The following block is skipped for the production Travis
# suite, because that suite doesn't make use of these elements
# of the development environment (it just uses the development
# environment to build a release tarball).
if not options.is_build_release_tarball_only:
# The following block is skipped when we just need the development
# environment to build a release tarball.
# Need to set up Django before using template_database_status
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zproject.settings")
@@ -243,10 +241,10 @@ if __name__ == "__main__":
default=False,
help="Ignore all provisioning optimizations.")
parser.add_argument('--production-travis', action='store_true',
dest='is_production_travis',
parser.add_argument('--build-release-tarball-only', action='store_true',
dest='is_build_release_tarball_only',
default=False,
help="Provision for Travis with production settings.")
help="Provision needed to build release tarball.")
options = parser.parse_args()
sys.exit(main(options))

View File

@@ -619,6 +619,10 @@ html_rules = whitespace_rules + prose_style_rules + [
'exclude': set(["templates/analytics/support.html"]),
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],
'bad_lines': ['<input placeholder="foo">']},
{'pattern': '={',
'description': "Likely missing quoting in HTML attribute",
'good_lines': ['<a href="{{variable}}">'],
'bad_lines': ['<a href={{variable}}>']},
{'pattern': "placeholder='[^{]",
'description': "`placeholder` value should be translatable.",
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],

View File

@@ -1,6 +1,6 @@
import os
ZULIP_VERSION = "2.1.4"
ZULIP_VERSION = "2.1.8"
# Add information on number of commits and commit hash to version, if available
zulip_git_version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'zulip-git-version')
if os.path.exists(zulip_git_version_file):
@@ -10,7 +10,7 @@ if os.path.exists(zulip_git_version_file):
ZULIP_VERSION = version
LATEST_MAJOR_VERSION = "2.1"
LATEST_RELEASE_VERSION = "2.1.4"
LATEST_RELEASE_VERSION = "2.1.8"
LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/12/13/zulip-2-1-released/"
# Versions of the desktop app below DESKTOP_MINIMUM_VERSION will be
@@ -18,7 +18,7 @@ LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/12/13/zulip-2-1-relea
# DESKTOP_MINIMUM_VERSION but below DESKTOP_WARNING_VERSION will have
# a banner at the top of the page asking the user to upgrade.
DESKTOP_MINIMUM_VERSION = "5.0.0"
DESKTOP_WARNING_VERSION = "5.0.0"
DESKTOP_WARNING_VERSION = "5.2.0"
# 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

@@ -378,7 +378,7 @@ def channels_to_zerver_stream(slack_data_dir: str, realm_id: int,
realm["zerver_stream"].append(stream)
slack_default_channels = ['general', 'random']
if channel['name'] in slack_default_channels:
if channel['name'] in slack_default_channels and not stream['deactivated']:
defaultstream = build_defaultstream(realm_id, stream_id,
defaultstream_id)
realm["zerver_defaultstream"].append(defaultstream)
@@ -993,6 +993,8 @@ def do_convert_data(slack_zip_file: str, output_dir: str, token: str, threads: i
realm_id = 0
domain_name = settings.EXTERNAL_HOST
log_token_warning(token)
slack_data_dir = slack_zip_file.replace('.zip', '')
if not os.path.exists(slack_data_dir):
os.makedirs(slack_data_dir)
@@ -1055,15 +1057,16 @@ def get_data_file(path: str) -> Any:
data = ujson.load(fp)
return data
def log_token_warning(token: str) -> None:
if not token.startswith("xoxp-"):
logging.info('Not a Slack legacy token.\n'
' This token might not have all the needed scopes. We need the following scopes:\n'
' - emoji:read\n - users:read\n - users:read.email\n - team:read')
def get_slack_api_data(slack_api_url: str, get_param: str, **kwargs: Any) -> Any:
if not kwargs.get("token"):
raise AssertionError("Slack token missing in kwargs")
token = kwargs["token"]
if not token.startswith("xoxp-"):
raise Exception('Invalid Slack legacy token.\n'
' You must pass a Slack "legacy token" starting with "xoxp-".\n'
' Create one at https://api.slack.com/custom-integrations/legacy-tokens')
data = requests.get("{}?{}".format(slack_api_url, urlencode(kwargs)))
if data.status_code == requests.codes.ok:

View File

@@ -98,7 +98,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
ScheduledEmail, MAX_TOPIC_NAME_LENGTH, \
MAX_MESSAGE_LENGTH, get_client, get_stream, get_personal_recipient, \
get_user_profile_by_id, PreregistrationUser, \
get_stream_recipient, \
get_stream_recipient, filter_to_valid_prereg_users, \
email_allowed_for_realm, email_to_username, \
get_user_by_delivery_email, get_stream_cache_key, active_non_guest_user_ids, \
UserActivityInterval, active_user_ids, get_active_streams, \
@@ -128,7 +128,6 @@ from django.utils.timezone import now as timezone_now
from confirmation.models import Confirmation, create_confirmation_link, generate_key, \
confirmation_url
from confirmation import settings as confirmation_settings
from zerver.lib.bulk_create import bulk_create_users
from zerver.lib.timestamp import timestamp_to_datetime, datetime_to_timestamp
@@ -5170,13 +5169,8 @@ def do_invite_users(user_profile: UserProfile,
notify_invites_changed(user_profile)
def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
days_to_activate = settings.INVITATION_LINK_VALIDITY_DAYS
active_value = getattr(confirmation_settings, 'STATUS_ACTIVE', 1)
lowest_datetime = timezone_now() - datetime.timedelta(days=days_to_activate)
prereg_users = PreregistrationUser.objects.exclude(status=active_value).filter(
invited_at__gte=lowest_datetime,
referred_by__realm=user_profile.realm)
prereg_users = filter_to_valid_prereg_users(
PreregistrationUser.objects.filter(referred_by__realm=user_profile.realm))
invites = []
@@ -5188,6 +5182,7 @@ def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
invited_as=invitee.invited_as,
is_multiuse=False))
lowest_datetime = timezone_now() - datetime.timedelta(days=settings.INVITATION_LINK_VALIDITY_DAYS)
multiuse_confirmation_objs = Confirmation.objects.filter(realm=user_profile.realm,
type=Confirmation.MULTIUSE_INVITE,
date_sent__gte=lowest_datetime)

View File

@@ -10,15 +10,15 @@ def set_initial_value_for_invited_as(apps: StateApps, schema_editor: DatabaseSch
PreregistrationUser = apps.get_model("zerver", "PreregistrationUser")
for user in PreregistrationUser.objects.all():
if user.invited_as_admin:
user.invited_as = 1 # PreregistrationUser.INVITE_AS['REALM_ADMIN']
user.invited_as = 2 # PreregistrationUser.INVITE_AS['REALM_ADMIN']
else:
user.invited_as = 2 # PreregistrationUser.INVITE_AS['MEMBER']
user.invited_as = 1 # PreregistrationUser.INVITE_AS['MEMBER']
user.save(update_fields=["invited_as"])
def reverse_code(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
PreregistrationUser = apps.get_model("zerver", "PreregistrationUser")
for user in PreregistrationUser.objects.all():
if user.invited_as == 1: # PreregistrationUser.INVITE_AS['REALM_ADMIN']
if user.invited_as == 2: # PreregistrationUser.INVITE_AS['REALM_ADMIN']
user.invited_as_admin = True
else: # PreregistrationUser.INVITE_AS['MEMBER']
user.invited_as_admin = False

View File

@@ -13,9 +13,9 @@ BATCH_SIZE = 1000
def process_batch(apps: StateApps, id_start: int, id_end: int, last_id: int) -> None:
Message = apps.get_model('zerver', 'Message')
for message in Message.objects.filter(id__gte=id_start, id__lte=id_end).order_by("id"):
if message.rendered_content == "":
if message.rendered_content in ["", None]:
# There have been bugs in the past that made it possible
# for a message to have "" as its rendered_content; we
# for a message to have "" or None as its rendered_content; we
# need to skip those because lxml won't process them.
#
# They should safely already have the correct state

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.26 on 2020-06-16 22:26
from __future__ import unicode_literals
from django.db import migrations
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.db.migrations.state import StateApps
def clear_preregistrationuser_invited_as_admin(
apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
"""This migration fixes any PreregistrationUser objects that might
have been already corrupted to have the administrator role by the
buggy original version of migration
0198_preregistrationuser_invited_as.
Since invitations that create new users as administrators are
rare, it is cleaner to just remove the role from all
PreregistrationUser objects than to filter for just those older
invitation objects that could have been corrupted by the original
migration, which would have been possible using the
django_migrations table to check the date when the buggy migration
was run.
"""
INVITED_AS_MEMBER = 1
INVITED_AS_REALM_ADMIN = 2
PreregistrationUser = apps.get_model("zerver", "PreregistrationUser")
PreregistrationUser.objects.filter(
invited_as=INVITED_AS_REALM_ADMIN).update(
invited_as=INVITED_AS_MEMBER)
class Migration(migrations.Migration):
dependencies = [
('zerver', '0260_missed_message_addresses_from_redis_to_db'),
]
operations = [
migrations.RunPython(
clear_preregistrationuser_invited_as_admin,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -26,6 +26,8 @@ from django.utils.timezone import now as timezone_now
from zerver.lib.timestamp import datetime_to_timestamp
from django.db.models.signals import post_save, post_delete
from django.utils.translation import ugettext_lazy as _
from confirmation import settings as confirmation_settings
from zerver.lib import cache
from zerver.lib.validator import check_int, \
check_short_string, check_long_string, validate_choice_field, check_date, \
@@ -40,6 +42,7 @@ from bitfield import BitField
from bitfield.types import BitHandler
from collections import defaultdict
from datetime import timedelta
import ast
import re
import sre_constants
import time
@@ -1284,6 +1287,14 @@ class PreregistrationUser(models.Model):
)
invited_as = models.PositiveSmallIntegerField(default=INVITE_AS['MEMBER']) # type: int
def filter_to_valid_prereg_users(query: QuerySet) -> QuerySet:
days_to_activate = settings.INVITATION_LINK_VALIDITY_DAYS
active_value = confirmation_settings.STATUS_ACTIVE
revoked_value = confirmation_settings.STATUS_REVOKED
lowest_datetime = timezone_now() - datetime.timedelta(days=days_to_activate)
return query.exclude(status__in=[active_value, revoked_value]).filter(
invited_at__gte=lowest_datetime)
class MultiuseInvite(models.Model):
referred_by = models.ForeignKey(UserProfile, on_delete=CASCADE) # Optional[UserProfile]
streams = models.ManyToManyField('Stream') # type: Manager
@@ -2727,7 +2738,7 @@ class CustomProfileField(models.Model):
(CHOICE, str(_('List of options')), validate_choice_field, str, "CHOICE"),
] # type: List[ExtendedFieldElement]
USER_FIELD_TYPE_DATA = [
(USER, str(_('Person picker')), check_valid_user_ids, eval, "USER"),
(USER, str(_('Person picker')), check_valid_user_ids, ast.literal_eval, "USER"),
] # type: List[UserFieldElement]
CHOICE_FIELD_VALIDATORS = {

View File

@@ -49,7 +49,7 @@ from zerver.lib.test_classes import (
from zerver.models import \
get_realm, email_to_username, CustomProfileField, CustomProfileFieldValue, \
UserProfile, PreregistrationUser, Realm, RealmDomain, get_user, MultiuseInvite, \
clear_supported_auth_backends_cache, PasswordTooWeakError
clear_supported_auth_backends_cache, PasswordTooWeakError, get_user_by_delivery_email
from zerver.signals import JUST_CREATED_THRESHOLD
from confirmation.models import Confirmation, create_confirmation_link
@@ -1066,6 +1066,29 @@ class SocialAuthBase(ZulipTestCase):
self.assertEqual(result.status_code, 302)
self.assertIn('login', result.url)
@override_settings(TERMS_OF_SERVICE=None)
def test_social_auth_invited_as_admin_but_expired(self) -> None:
iago = self.example_user("iago")
email = self.nonreg_email("alice")
name = 'Alice Jones'
do_invite_users(iago, [email], [], invite_as=PreregistrationUser.INVITE_AS['REALM_ADMIN'])
expired_date = timezone_now() - datetime.timedelta(days=settings.INVITATION_LINK_VALIDITY_DAYS + 1)
PreregistrationUser.objects.filter(email=email).update(invited_at=expired_date)
subdomain = 'zulip'
realm = get_realm("zulip")
account_data_dict = self.get_account_data_dict(email=email, name=name)
result = self.social_auth_test(account_data_dict,
expect_choose_email_screen=True,
subdomain=subdomain, is_signup='1')
self.stage_two_of_registration(result, realm, subdomain, email, name, name,
self.BACKEND_CLASS.full_name_validated)
# The invitation is expired, so the user should be created as normal member only.
created_user = get_user_by_delivery_email(email, realm)
self.assertEqual(created_user.role, UserProfile.ROLE_MEMBER)
class SAMLAuthBackendTest(SocialAuthBase):
__unittest_skip__ = False
@@ -2108,7 +2131,7 @@ class TestDevAuthBackend(ZulipTestCase):
with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
result = self.client_post('/accounts/login/local/', data)
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, 'http://zulip.testserver')
self.assertEqual(result.url, 'http://zulip.testserver/')
self.assert_logged_in_user_id(user_profile.id)
self.assertIn('otp_device_id', list(self.client.session.keys()))
@@ -2120,7 +2143,7 @@ class TestDevAuthBackend(ZulipTestCase):
res = do_local_login('/accounts/login/local/')
self.assertEqual(res.status_code, 302)
self.assertEqual(res.url, 'http://zulip.testserver')
self.assertEqual(res.url, 'http://zulip.testserver/')
res = do_local_login('/accounts/login/local/?next=/user_uploads/path_to_image')
self.assertEqual(res.status_code, 302)

View File

@@ -106,7 +106,7 @@ class CompatibilityTest(ZulipTestCase):
with mock.patch('zerver.views.compatibility.DESKTOP_MINIMUM_VERSION', '4.0.3'):
self.assertEqual(is_outdated_desktop_app('ZulipElectron/4.0.3 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36'), (True, False, False))
self.assertEqual(is_outdated_desktop_app('ZulipElectron/5.0.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36'), (False, False, False))
self.assertEqual(is_outdated_desktop_app('ZulipElectron/5.2.0 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/5.2.0 Chrome/80.0.3987.165 Electron/8.2.5 Safari/537.36'), (False, False, False))
self.assertEqual(is_outdated_desktop_app('Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'), (False, False, False))

View File

@@ -46,7 +46,7 @@ class EmailTranslationTestCase(ZulipTestCase):
# Also remove the "nocoverage" from check_translation above.
# check_translation("Viele Grüße", "patch", "/json/settings", {"email": "hamlets-new@zulip.com"})
check_translation("Incrível!", "post", "/accounts/home/", {"email": "new-email@zulip.com"}, HTTP_ACCEPT_LANGUAGE="pt")
check_translation("Danke, dass Du", "post", '/accounts/find/', {'emails': hamlet.email})
check_translation("Danke, dass du", "post", '/accounts/find/', {'emails': hamlet.email})
check_translation("Hallo", "post", "/json/invites", {"invitee_emails": "new-email@zulip.com",
"stream_ids": ujson.dumps([stream.id])})

View File

@@ -94,11 +94,6 @@ class SlackImporter(ZulipTestCase):
get_slack_api_data(slack_user_list_url, "members", token=token)
self.assertEqual(invalid.exception.args, ('Error accessing Slack API: invalid_auth',),)
token = 'xoxe-invalid-token'
with self.assertRaises(Exception) as invalid:
get_slack_api_data(slack_user_list_url, "members", token=token)
self.assertTrue(invalid.exception.args[0].startswith("Invalid Slack legacy token.\n"))
with self.assertRaises(Exception) as invalid:
get_slack_api_data(slack_user_list_url, "members")
self.assertEqual(invalid.exception.args, ('Slack token missing in kwargs',),)
@@ -427,9 +422,11 @@ class SlackImporter(ZulipTestCase):
| set(test_added_mpims.keys())
self.assertDictEqual(test_added_channels, added_channels)
# zerver defaultstream already tested in helper functions
# zerver defaultstream already tested in helper functions.
# Note that the `random` stream is archived and thus should
# not be created as a DefaultStream.
self.assertEqual(realm["zerver_defaultstream"],
[{'id': 0, 'realm': 3, 'stream': 0}, {'id': 1, 'realm': 3, 'stream': 1}])
[{'id': 0, 'realm': 3, 'stream': 1}])
self.assertDictEqual(test_added_mpims, added_mpims)
self.assertDictEqual(test_dm_members, dm_members)

View File

@@ -34,7 +34,7 @@ from zerver.lib.user_agent import parse_user_agent
from zerver.lib.users import get_api_key
from zerver.lib.validator import validate_login_email
from zerver.models import PreregistrationUser, UserProfile, remote_user_to_email, Realm, \
get_realm
get_realm, filter_to_valid_prereg_users
from zerver.signals import email_on_new_login
from zproject.backends import password_auth_enabled, dev_auth_enabled, \
ldap_auth_enabled, ZulipLDAPConfigurationError, ZulipLDAPAuthBackend, \
@@ -112,8 +112,8 @@ def maybe_send_to_registration(request: HttpRequest, email: str, full_name: str=
# creation or confirm-continue-registration depending on
# is_signup.
try:
prereg_user = PreregistrationUser.objects.filter(
email__iexact=email, realm=realm).latest("invited_at")
prereg_user = filter_to_valid_prereg_users(PreregistrationUser.objects.filter(
email__iexact=email, realm=realm)).latest("invited_at")
# password_required and full_name data passed here as argument should take precedence
# over the defaults with which the existing PreregistrationUser that we've just fetched
@@ -251,8 +251,11 @@ def login_or_register_remote_user(request: HttpRequest, remote_username: str,
@log_view_func
@has_request_variables
def remote_user_sso(request: HttpRequest,
mobile_flow_otp: Optional[str]=REQ(default=None)) -> HttpResponse:
def remote_user_sso(
request: HttpRequest,
mobile_flow_otp: Optional[str] = REQ(default=None),
next: str = REQ(default="/"),
) -> HttpResponse:
subdomain = get_subdomain(request)
try:
realm = get_realm(subdomain) # type: Optional[Realm]
@@ -286,11 +289,9 @@ def remote_user_sso(request: HttpRequest,
else:
user_profile = authenticate(remote_user=remote_user, realm=realm)
redirect_to = request.GET.get('next', '')
return login_or_register_remote_user(request, remote_user, user_profile,
mobile_flow_otp=mobile_flow_otp,
redirect_to=redirect_to)
redirect_to=next)
@csrf_exempt
@log_view_func
@@ -338,9 +339,15 @@ def remote_user_jwt(request: HttpRequest) -> HttpResponse:
return login_or_register_remote_user(request, email, user_profile, remote_user)
def oauth_redirect_to_root(request: HttpRequest, url: str,
sso_type: str, is_signup: bool=False,
extra_url_params: Dict[str, str]={}) -> HttpResponse:
@has_request_variables
def oauth_redirect_to_root(
request: HttpRequest,
url: str,
sso_type: str,
is_signup: bool=False,
extra_url_params: Dict[str, str]={},
next: Optional[str] = REQ(default=None),
) -> HttpResponse:
main_site_uri = settings.ROOT_DOMAIN_URI + url
if settings.SOCIAL_AUTH_SUBDOMAIN is not None and sso_type == 'social':
main_site_uri = (settings.EXTERNAL_URI_SCHEME +
@@ -363,7 +370,6 @@ def oauth_redirect_to_root(request: HttpRequest, url: str,
raise JsonableError(_("Invalid OTP"))
params['mobile_flow_otp'] = mobile_flow_otp
next = request.GET.get('next')
if next:
params['next'] = next
@@ -588,7 +594,9 @@ class TwoFactorLoginView(BaseTwoFactorLoginView):
realm = get_realm_from_request(self.request)
redirect_to = realm.uri if realm else '/'
context['next'] = self.request.GET.get('next', redirect_to)
context['next'] = self.request.POST.get(
'next', self.request.GET.get('next', redirect_to),
)
return context
def done(self, form_list: List[Form], **kwargs: Any) -> HttpResponse:
@@ -609,7 +617,9 @@ class TwoFactorLoginView(BaseTwoFactorLoginView):
with patch.object(settings, 'LOGIN_REDIRECT_URL', realm_uri):
return super().done(form_list, **kwargs)
def login_page(request: HttpRequest, **kwargs: Any) -> HttpResponse:
@has_request_variables
def login_page(request: HttpRequest,
next: str = REQ(default="/"), **kwargs: Any) -> HttpResponse:
# To support previewing the Zulip login pages, we have a special option
# that disables the default behavior of redirecting logged-in users to the
# logged-in app.
@@ -630,6 +640,7 @@ def login_page(request: HttpRequest, **kwargs: Any) -> HttpResponse:
return redirect_to_deactivation_notice()
extra_context = kwargs.pop('extra_context', {})
extra_context["next"] = next
if dev_auth_enabled() and kwargs.get("template_name") == "zerver/dev_login.html":
if 'new_realm' in request.POST:
try:
@@ -701,7 +712,11 @@ def start_two_factor_auth(request: HttpRequest,
return two_fa_view(request, **kwargs)
@csrf_exempt
def dev_direct_login(request: HttpRequest, **kwargs: Any) -> HttpResponse:
@has_request_variables
def dev_direct_login(
request: HttpRequest,
next: str = REQ(default="/"),
) -> HttpResponse:
# This function allows logging in without a password and should only be called
# in development environments. It may be called if the DevAuthBackend is included
# in settings.AUTHENTICATION_BACKENDS
@@ -717,7 +732,6 @@ def dev_direct_login(request: HttpRequest, **kwargs: Any) -> HttpResponse:
return HttpResponseRedirect(reverse('dev_not_supported'))
do_login(request, user_profile)
next = request.GET.get('next', '')
redirect_to = get_safe_redirect_to(next, user_profile.realm.uri)
return HttpResponseRedirect(redirect_to)

View File

@@ -21,8 +21,13 @@ class DropboxHookTests(WebhookTestCase):
def test_verification_request(self) -> None:
self.subscribe(self.test_user, self.STREAM_NAME)
get_params = {'stream_name': self.STREAM_NAME,
'challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E',
'api_key': get_api_key(self.test_user)}
result = self.client_get(self.url, get_params)
self.assert_json_error(result, "Missing 'challenge' argument", 400)
get_params['challenge'] = '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E'
result = self.client_get(self.url, get_params)
self.assertEqual(result.status_code, 200)
self.assertEqual(result["Content-Type"], "text/plain; charset=UTF-8")
self.assert_in_response('9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', result)

View File

@@ -1,4 +1,8 @@
from typing import Optional
from django.http import HttpRequest, HttpResponse
from zerver.lib.request import REQ, RequestVariableMissingError
from zerver.lib.response import json_success
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.decorator import has_request_variables, api_key_only_webhook_view
@@ -6,11 +10,17 @@ from zerver.models import UserProfile
@api_key_only_webhook_view('Dropbox', notify_bot_owner_on_invalid_json=False)
@has_request_variables
def api_dropbox_webhook(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
def api_dropbox_webhook(
request: HttpRequest,
user_profile: UserProfile,
challenge: Optional[str]=REQ(default=None),
) -> HttpResponse:
if request.method == 'POST':
topic = 'Dropbox'
check_send_webhook_message(request, user_profile, topic,
"File has been updated on Dropbox!")
return json_success()
else:
return HttpResponse(request.GET['challenge'])
if challenge is None:
raise RequestVariableMissingError("challenge")
return HttpResponse(challenge, content_type="text/plain; charset=UTF-8")

View File

@@ -17,7 +17,7 @@ from django.core.handlers.base import BaseHandler
from zerver.models import \
get_client, get_system_bot, PreregistrationUser, \
get_user_profile_by_id, Message, Realm, UserMessage, UserProfile, \
Client
Client, filter_to_valid_prereg_users
from zerver.lib.context_managers import lockfile
from zerver.lib.error_notify import do_report_error
from zerver.lib.queue import SimpleQueueClient, queue_json_publish, retry_event
@@ -215,10 +215,13 @@ class ConfirmationEmailWorker(QueueProcessingWorker):
if "email" in data:
# When upgrading from a version up through 1.7.1, there may be
# existing items in the queue with `email` instead of `prereg_id`.
invitee = PreregistrationUser.objects.filter(
email__iexact=data["email"].strip()).latest("invited_at")
invitee = filter_to_valid_prereg_users(
PreregistrationUser.objects.filter(email__iexact=data["email"].strip())
).latest("invited_at")
else:
invitee = PreregistrationUser.objects.filter(id=data["prereg_id"]).first()
invitee = filter_to_valid_prereg_users(
PreregistrationUser.objects.filter(id=data["prereg_id"])
).first()
if invitee is None:
# The invitation could have been revoked
return

View File

@@ -105,7 +105,7 @@ BOT_CONFIG_SIZE_LIMIT = 10000
# External service configuration
CAMO_URI = ''
MEMCACHED_LOCATION = '127.0.0.1:11211'
MEMCACHED_USERNAME = None if get_secret("memcached_password") is None else "zulip"
MEMCACHED_USERNAME = None if get_secret("memcached_password") is None else "zulip@localhost"
RABBITMQ_HOST = '127.0.0.1'
RABBITMQ_USERNAME = 'zulip'
REDIS_HOST = '127.0.0.1'

View File

@@ -566,8 +566,8 @@ CAMO_URI = '/external_content/'
# Format HOST:PORT
# MEMCACHED_LOCATION = 127.0.0.1:11211
# To authenticate to memcached, set memcached_password in zulip-secrets.conf,
# and optionally change the default username 'zulip' here.
# MEMCACHED_USERNAME = 'zulip'
# and optionally change the default username 'zulip@localhost' here.
# MEMCACHED_USERNAME = 'zulip@localhost'
# Redis configuration
#