mirror of
https://github.com/zulip/zulip.git
synced 2025-10-27 18:13:58 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3819a376b5 | ||
|
|
6749817f3e | ||
|
|
f86e22a443 | ||
|
|
bd55825ab8 | ||
|
|
0a827064ba | ||
|
|
01902fa648 | ||
|
|
cbb9ea6b49 | ||
|
|
d163143f12 | ||
|
|
c21c8dcd95 | ||
|
|
82d2960ad1 | ||
|
|
fa07539016 | ||
|
|
6d0c39fd7e | ||
|
|
2e2004b6c3 | ||
|
|
620e98860e | ||
|
|
83380b4296 | ||
|
|
e88aac5105 | ||
|
|
6046ea8014 | ||
|
|
ba8ee93fae | ||
|
|
e682ea189a | ||
|
|
148ea9fe48 | ||
|
|
31a34836d3 | ||
|
|
309266376e | ||
|
|
ef194171f7 | ||
|
|
66fa35f5ac |
@@ -5,3 +5,4 @@
|
||||
__revision__ = '$Id: settings.py 12 2008-11-23 19:38:52Z jarek.zgoda $'
|
||||
|
||||
STATUS_ACTIVE = 1
|
||||
STATUS_REVOKED = 2
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"> :{{this.emoji_name}}:</div>
|
||||
{{else}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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 @@
|
||||
×
|
||||
</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' }}" />
|
||||
|
||||
@@ -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="" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) }}">
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="title">{{ _('Pan & 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">×</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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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\') }}" />'],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
),
|
||||
]
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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])})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user