Compare commits

...

13 Commits
2.1.0 ... 2.0.1

Author SHA1 Message Date
Tim Abbott
29b3dd0852 Release Zulip Server 2.0.1. 2019-03-04 17:39:57 -08:00
Tim Abbott
0ffc42083e i18n: Update translations from Transifex. 2019-03-04 17:28:30 -08:00
Tim Abbott
019e5a17f0 docs: Explain options for preventing changes during export.
This makes it a bit clearer that one doesn't need to deactivate a
realm just to export data.
2019-03-04 16:22:18 -08:00
Harshit Bansal
177673c84e portico: Refresh deactivated realm notice page every 60 seconds.
This helps avoid users being confused if a realm was temporarily
deactivated as part of getting a clean backup.

Fixes: #11757.
2019-03-04 16:22:10 -08:00
Harshit Bansal
f6c1a31988 auth: Remove invalid_subdomain restriction from LDAP backend.
Fixes: #11692.
2019-03-04 16:22:04 -08:00
Tim Abbott
870cd00f5f su_to_zulip: Fix detection of zulip user ID.
Apparently, while upgrade-zulip-from-git always ensures that zulip
deployment directories are owned by the Zulip user, unpack-zulip (aka
the tarball code path) has them owned by root.

The user ID detection logic in su_to_zulip's helper get_zulip_uid was
intended to support both development environments (where the user ID
might vary) and production environments.  For development
environments, the existing code is fine, but given this unpack-zulip
permissions issue, we need to have code to fallback to 'zulip' if the
detection logic detects the "zulip" user has having UID 0.
2019-03-04 16:21:53 -08:00
Aaron Raimist
7db599deaa docs: Fix Learn more about mentions link.
It seems like 1871d00bb2 renamed `/help/at-mention-a-user` to `/help/mention-a-user-or-group` but missed this link that shows up on the "You haven't been mentioned yet!" screen. Right now it leads to a "no such article page".
2019-03-04 11:12:56 -08:00
Tim Abbott
84d2be5e0c docs: Fix export/import manage.py instructions typos.
Fixes #11755.
2019-03-04 11:12:48 -08:00
Tim Abbott
d360833d7f nginx: Restructure how we manage uploaded file routes.
The overall goal of this change is to fix an issue where on Ubuntu
Trusty, we were accidentally overriding the configuration to serve
uploads from disk with the regular expressions for adding access
control headers.

However, while investigating this, it became clear that we could
considerably simplify the mental energy required to understand this
system by making the uploads-route file be unconditionally available
and included from `zulip-include/app` (which means the zulip_ops code
can share behavior here).

We also move the Access-Control-Allow-* headers to a separate include
file, to avoid duplicating it in 5 places.  Fixing this duplication
discovered a potential bug in the settings used for Tornado, where
DELETE was not allowed on a route that definitely expects DELETE.

Fixes #11758.
2019-03-04 11:12:44 -08:00
Tim Abbott
bc3db1701b realm_logo: Fix synchronization of realm night logo.
The night logo synchronization on the settings page was perfect, but
the actual display logic had a few problems:

* We were including the realm_logo in context_processors, even though
  it is only used in home.py.
* We used different variable names for the templating in navbar.html
  than anywhere else the codebase.

* The behavior that the night logo would default to the day logo if
  only one was uploaded was not correctly implemented for the navbar
  position, either in the synchronization for updates code or the
  logic in the navbar.html templates.
2019-03-04 11:12:36 -08:00
Rishi Gupta
e8aca7b723 help: Reorganize stream-permissions table. 2019-03-04 11:12:32 -08:00
Tim Abbott
7a72390710 copy: Fix extra space before > in copy-paste styling. 2019-03-04 11:12:11 -08:00
Boris Yankov
3ffe4ca3e5 user status: Make "unavailable" status circle grey.
After discussion, we decided that the red color is too distinct
and does not convey the idea of "almost offline".

This changes the new "unavailable" status circle's color from dark
red to grey, the same color used by the "offline" status circle.
2019-03-04 11:11:52 -08:00
31 changed files with 350 additions and 166 deletions

View File

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

View File

@@ -7,6 +7,19 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated This section lists notable unreleased changes; it is generally updated
in bursts. in bursts.
### 2.0.1 -- 2019-03-04
- Fixed handling of uploaded file routing on Ubuntu Trusty.
- Fixed buggy behavior of branding logos in night theme.
- Fixed handling of deployment directories being owned by root.
- The styling of "unavailable" status icons is now less prominent.
- The "deactivated realm" error page now auto-refreshes, to handle
realm reactivation.
- Updated documentation to avoid recommending realm deactivation as
a preferred approach to prepare for backups.
- Added support for using multiple organizations with same LDAP
backend configuration.
### 2.0.0 -- 2019-03-01 ### 2.0.0 -- 2019-03-01
**Highlights:** **Highlights:**

View File

@@ -31,19 +31,39 @@ process.
[backups]: ../production/maintain-secure-upgrade.html#backups [backups]: ../production/maintain-secure-upgrade.html#backups
## Export your Zulip data ## Preventing changes during the export
For best results, you'll want to shut down access to the organization For best results, you'll want to shut down access to the organization
you are exporting with `manage.py deactivate_realm` before exporting, before exporting, so that nobody can send new messages (etc.) while
so that nobody can send new messages (etc.) while you're exporting you're exporting data. There are two ways to do this:
data. We include that in the instructions below.
1. `supervisorctl stop all`, which stops the whole server. This is
preferred if you're not hosting multiple organizations, because it has
no side effects other than disabling the Zulip server for the
duration.
1. `manage.py deactivate_realm`, which deactivates the target
organization, logging out all active login sessions and preventing all
accounts in the from logging in or accessing the API. This is
preferred for environments like Zulip Cloud where you might want to
export a single organization without disrupting any other users, and
the intent is to move hosting of the organization (and forcing users
to re-login would be required as part of the hosting migrateion
anyway).
We include both options in the instructions below, commented out so
that neither runs (using the `# ` at the start of the lines). If
you'd like to use one of these options, remove the `# ` at the start
of the lines for the appropriate option.
## Export your Zulip data
Log in to a shell on your Zulip server as the `zulip` user. Run the Log in to a shell on your Zulip server as the `zulip` user. Run the
following commands: following commands:
``` ```
cd /home/zulip/deployments/current cd /home/zulip/deployments/current
./manage deactivate_realm -r '' # Deactivates the organization # ./manage.py deactivate_realm -r '' # Deactivates the organization
# supervisorctl stop all # Stops the Zulip server
./manage.py export -r '' # Exports the data ./manage.py export -r '' # Exports the data
``` ```
@@ -83,7 +103,8 @@ cd ~
tar -xf /path/to/export/file/zulip-export-zcmpxfm6.tar.gz tar -xf /path/to/export/file/zulip-export-zcmpxfm6.tar.gz
cd /home/zulip/deployments/current cd /home/zulip/deployments/current
./manage.py import '' ~/zulip-export-zcmpxfm6 ./manage.py import '' ~/zulip-export-zcmpxfm6
./manage reactivate_realm -r '' # Reactivates the organization # supervisorctl start all # Starts the Zulip server
# ./manage.py reactivate_realm -r '' # Reactivates the organization
``` ```
This could take several minutes to run, depending on how much data you're This could take several minutes to run, depending on how much data you're
@@ -99,7 +120,7 @@ root domain. Replace the last two lines above with the following, after replacin
``` ```
./manage.py import <subdomain> ~/zulip-export-zcmpxfm6 ./manage.py import <subdomain> ~/zulip-export-zcmpxfm6
./manage reactivate_realm -r <subdomain> # Reactivates the organization ./manage.py reactivate_realm -r <subdomain> # Reactivates the organization
``` ```
## Logging in ## Logging in

View File

@@ -0,0 +1,3 @@
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers Authorization;
add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD';

View File

@@ -27,15 +27,9 @@ location ~ /json/events {
# Send longpoll requests to Tornado # Send longpoll requests to Tornado
location /api/v1/events { location /api/v1/events {
include /etc/nginx/zulip-include/api_headers;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers Authorization;
add_header Access-Control-Allow-Methods 'GET, POST';
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers Authorization;
add_header Access-Control-Allow-Methods 'GET, POST';
add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0; add_header 'Content-Length' 0;
return 204; return 204;
@@ -60,15 +54,20 @@ location / {
uwsgi_pass django; uwsgi_pass django;
} }
# Certain Django routes not under /api are shared between mobile and # These Django routes not under /api are shared between mobile and
# web and thus need API headers added. We don't collapse this with the # web, and thus need API headers added. We can't easily collapse
# above block for /events, because regular expressions take priority over # these blocks with the /api block, because regular expressions take
# paths in nginx's order-of-operations, and we don't want to override the # priority over paths in nginx's order-of-operations, and we don't
# tornado stuff. # want to override the tornado configuration for /api/v1/events. The
location ~ ^/(user_uploads|avatar|thumbnail)/ { # last is handled via uploads-route.
add_header Access-Control-Allow-Origin *; location /thumbnail {
add_header Access-Control-Allow-Headers Authorization; include /etc/nginx/zulip-include/api_headers;
add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD';
include uwsgi_params;
uwsgi_pass django;
}
location /avatar {
include /etc/nginx/zulip-include/api_headers;
include uwsgi_params; include uwsgi_params;
uwsgi_pass django; uwsgi_pass django;
@@ -76,12 +75,11 @@ location ~ ^/(user_uploads|avatar|thumbnail)/ {
# Send all API routes not covered above to Django via uWSGI # Send all API routes not covered above to Django via uWSGI
location /api/ { location /api/ {
add_header Access-Control-Allow-Origin *; include /etc/nginx/zulip-include/api_headers;
add_header Access-Control-Allow-Headers Authorization;
add_header Access-Control-Allow-Methods 'GET, POST, DELETE, PUT, PATCH, HEAD';
include uwsgi_params; include uwsgi_params;
uwsgi_pass django; uwsgi_pass django;
} }
include /etc/nginx/zulip-include/uploads.route;
include /etc/nginx/zulip-include/app.d/*.conf; include /etc/nginx/zulip-include/app.d/*.conf;

View File

@@ -1,4 +1,10 @@
# This Django route not under /api is shared between mobile and web
# and thus needs API headers added, in addition to the configuration
# required to have it serve files directly.
location /user_uploads { location /user_uploads {
include /etc/nginx/zulip-include/api_headers;
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;"; add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;";
include /etc/nginx/zulip-include/uploads.types; include /etc/nginx/zulip-include/uploads.types;

View File

@@ -5,3 +5,14 @@ location /serve_uploads {
include /etc/nginx/zulip-include/uploads.types; include /etc/nginx/zulip-include/uploads.types;
alias /home/zulip/uploads/files; alias /home/zulip/uploads/files;
} }
# This Django route not under /api is shared between mobile and web
# and thus needs API headers added, in addition to the configuration
# required to have this URL be served by Django.
location /user_uploads {
include /etc/nginx/zulip-include/api_headers;
include uwsgi_params;
uwsgi_pass django;
}

View File

@@ -0,0 +1,10 @@
# This Django route not under /api is shared between mobile and web
# and thus needs API headers added, in addition to the configuration
# required to have this URL be served by Django.
location /user_uploads {
include /etc/nginx/zulip-include/api_headers;
include uwsgi_params;
uwsgi_pass django;
}

View File

@@ -41,6 +41,11 @@ class zulip::nginx {
'trusty' => 'puppet:///modules/zulip/nginx/zulip-include-maybe/uploads-route.direct', 'trusty' => 'puppet:///modules/zulip/nginx/zulip-include-maybe/uploads-route.direct',
default => 'puppet:///modules/zulip/nginx/zulip-include-maybe/uploads-route.internal', default => 'puppet:///modules/zulip/nginx/zulip-include-maybe/uploads-route.internal',
} }
$no_serve_uploads = zulipconf('application_server', 'no_serve_uploads', '')
if $no_serve_uploads != '' {
# If we're not serving uploads locally, set the appropriate API headers for it.
$uploads_route = 'puppet:///modules/zulip/nginx/zulip-include-maybe/uploads-route.noserve'
}
file { '/etc/nginx/zulip-include/uploads.route': file { '/etc/nginx/zulip-include/uploads.route':
ensure => file, ensure => file,

View File

@@ -37,7 +37,4 @@ server {
include /etc/nginx/zulip-include/certbot; include /etc/nginx/zulip-include/certbot;
include /etc/nginx/zulip-include/app; include /etc/nginx/zulip-include/app;
<% if @no_serve_uploads == '' -%>
include /etc/nginx/zulip-include/uploads.route;
<% end -%>
} }

View File

@@ -110,8 +110,15 @@ def subprocess_text_output(args):
# type: (Sequence[str]) -> str # type: (Sequence[str]) -> str
return subprocess.check_output(args, universal_newlines=True).strip() return subprocess.check_output(args, universal_newlines=True).strip()
def get_zulip_uid() -> int: def get_zulip_pwent() -> pwd.struct_passwd:
return os.stat(get_deploy_root()).st_uid deploy_root_uid = os.stat(get_deploy_root()).st_uid
if deploy_root_uid != 0:
return pwd.getpwuid(deploy_root_uid)
# In the case that permissions got messed up and the deployment
# directory is unexpectedly owned by root, we fallback to the
# `zulip` user as that's the correct value in production.
return pwd.getpwnam("zulip")
def su_to_zulip(save_suid=False): def su_to_zulip(save_suid=False):
# type: (bool) -> None # type: (bool) -> None
@@ -120,7 +127,7 @@ def su_to_zulip(save_suid=False):
installation). It should never be run from the installer or other installation). It should never be run from the installer or other
production contexts before /home/zulip/deployments/current is production contexts before /home/zulip/deployments/current is
created.""" created."""
pwent = pwd.getpwuid(get_zulip_uid()) pwent = get_zulip_pwent()
os.setgid(pwent.pw_gid) os.setgid(pwent.pw_gid)
if save_suid: if save_suid:
os.setresuid(pwent.pw_uid, pwent.pw_uid, os.getuid()) os.setresuid(pwent.pw_uid, pwent.pw_uid, os.getuid())
@@ -422,7 +429,7 @@ def is_root() -> bool:
def assert_not_running_as_root() -> None: def assert_not_running_as_root() -> None:
script_name = os.path.abspath(sys.argv[0]) script_name = os.path.abspath(sys.argv[0])
if is_root(): if is_root():
pwent = pwd.getpwuid(get_zulip_uid()) pwent = get_zulip_pwent()
msg = ("{shortname} should not be run as root. Use `su {user}` to switch to the 'zulip'\n" msg = ("{shortname} should not be run as root. Use `su {user}` to switch to the 'zulip'\n"
"user before rerunning this, or use \n su {user} -c '{name} ...'\n" "user before rerunning this, or use \n su {user} -c '{name} ...'\n"
"to switch users and run this as a single command.").format( "to switch users and run this as a single command.").format(

View File

@@ -75,7 +75,7 @@ var realm_logo = (function () {
$("#realm-settings-night-logo").attr("src", page_params.realm_night_logo_url); $("#realm-settings-night-logo").attr("src", page_params.realm_night_logo_url);
} }
if (page_params.night_mode) { if (page_params.night_mode && page_params.realm_night_logo_source !== 'D') {
$("#realm-logo").attr("src", page_params.realm_night_logo_url); $("#realm-logo").attr("src", page_params.realm_night_logo_url);
} else { } else {
$("#realm-logo").attr("src", page_params.realm_logo_url); $("#realm-logo").attr("src", page_params.realm_logo_url);

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-01 17:24+0000\n" "POT-Creation-Date: 2019-03-05 01:24+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -591,7 +591,8 @@ msgstr ""
#: templates/zerver/app/home.html:138 #: templates/zerver/app/home.html:138
msgid "" msgid ""
"\n" "\n"
" Learn more about mentions <a href=\"/help/at-mention-a-user\">\n" " Learn more about mentions <a href=\"/help/mention-a-user-or-group"
"\">\n"
" here</a>.\n" " here</a>.\n"
" " " "
msgstr "" msgstr ""
@@ -1406,11 +1407,11 @@ msgstr ""
msgid "Create organization" msgid "Create organization"
msgstr "" msgstr ""
#: templates/zerver/deactivated.html:9 #: templates/zerver/deactivated.html:15
msgid "Deactivated organization" msgid "Deactivated organization"
msgstr "" msgstr ""
#: templates/zerver/deactivated.html:14 #: templates/zerver/deactivated.html:20
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@@ -3245,7 +3246,7 @@ msgstr ""
msgid "Invalid type parameter" msgid "Invalid type parameter"
msgstr "" msgstr ""
#: zerver/lib/events.py:742 #: zerver/lib/events.py:745
msgid "Could not allocate event queue" msgid "Could not allocate event queue"
msgstr "" msgstr ""
@@ -3842,7 +3843,7 @@ msgstr ""
msgid "This user is not registered; do so from a browser." msgid "This user is not registered; do so from a browser."
msgstr "" msgstr ""
#: zerver/views/auth.py:798 zerver/views/auth.py:889 #: zerver/views/auth.py:798 zerver/views/auth.py:887
msgid "Your username or password is incorrect." msgid "Your username or password is incorrect."
msgstr "" msgstr ""
@@ -3854,7 +3855,7 @@ msgstr ""
msgid "Subdomain required" msgid "Subdomain required"
msgstr "" msgstr ""
#: zerver/views/auth.py:897 #: zerver/views/auth.py:895
msgid "GOOGLE_CLIENT_ID is not configured" msgid "GOOGLE_CLIENT_ID is not configured"
msgstr "" msgstr ""

View File

@@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: Zulip\n" "Project-Id-Version: Zulip\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-14 00:07+0000\n" "POT-Creation-Date: 2019-02-14 00:07+0000\n"
"PO-Revision-Date: 2019-02-14 00:08+0000\n" "PO-Revision-Date: 2019-03-05 00:19+0000\n"
"Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" "Last-Translator: Andrea <andrea.soccal@elmospa.com>\n"
"Language-Team: Italian (http://www.transifex.com/zulip/zulip/language/it/)\n" "Language-Team: Italian (http://www.transifex.com/zulip/zulip/language/it/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -226,7 +226,7 @@ msgstr ""
#: templates/corporate/upgrade.html:83 #: templates/corporate/upgrade.html:83
msgid "Most convenient" msgid "Most convenient"
msgstr "" msgstr "Più conveniente"
#: templates/corporate/upgrade.html:89 #: templates/corporate/upgrade.html:89
msgid "Manual" msgid "Manual"
@@ -423,7 +423,7 @@ msgstr "Aggiungi videochiamata"
#: templates/zerver/app/compose.html:102 #: templates/zerver/app/compose.html:102
msgid "Write" msgid "Write"
msgstr "" msgstr "Scrivi"
#: templates/zerver/app/compose.html:103 #: templates/zerver/app/compose.html:103
msgid "Preview" msgid "Preview"
@@ -641,12 +641,12 @@ msgstr "Uno o più indirizzi email..."
#: templates/zerver/app/invite_user.html:20 #: templates/zerver/app/invite_user.html:20
msgid "or" msgid "or"
msgstr "" msgstr "o"
#: templates/zerver/app/invite_user.html:20 #: templates/zerver/app/invite_user.html:20
#: templates/zerver/app/invite_user.html:26 #: templates/zerver/app/invite_user.html:26
msgid "Generate invite link" msgid "Generate invite link"
msgstr "" msgstr "Genera link di invito"
#: templates/zerver/app/invite_user.html:33 #: templates/zerver/app/invite_user.html:33
msgid "User(s) join as" msgid "User(s) join as"
@@ -662,7 +662,7 @@ msgstr "Amministratori dell'organizzazione"
#: templates/zerver/app/invite_user.html:40 #: templates/zerver/app/invite_user.html:40
msgid "Guests" msgid "Guests"
msgstr "" msgstr "Ospiti"
#: templates/zerver/app/invite_user.html:45 #: templates/zerver/app/invite_user.html:45
msgid "Streams they should join" msgid "Streams they should join"
@@ -670,7 +670,7 @@ msgstr "Canali che dovrebbero seguire"
#: templates/zerver/app/invite_user.html:52 #: templates/zerver/app/invite_user.html:52
msgid "Inviting..." msgid "Inviting..."
msgstr "" msgstr "Sto invitando..."
#: templates/zerver/app/invite_user.html:52 #: templates/zerver/app/invite_user.html:52
msgid "Invite" msgid "Invite"
@@ -1433,7 +1433,7 @@ msgstr "Utenti normali"
#: templates/zerver/emails/find_team.txt:1 #: templates/zerver/emails/find_team.txt:1
#, python-format #, python-format
msgid "Hi %(user_name)s," msgid "Hi %(user_name)s,"
msgstr "" msgstr "Ciao %(user_name)s,"
#: templates/zerver/emails/compiled/confirm_new_email.html:11 #: templates/zerver/emails/compiled/confirm_new_email.html:11
#: templates/zerver/emails/confirm_new_email.source.html:10 #: templates/zerver/emails/confirm_new_email.source.html:10
@@ -1443,7 +1443,7 @@ msgid ""
"We received a request to change the email address for the Zulip account on " "We received a request to change the email address for the Zulip account on "
"%(realm_uri)s from %(old_email)s to %(new_email)s. To confirm this change, " "%(realm_uri)s from %(old_email)s to %(new_email)s. To confirm this change, "
"please click below:" "please click below:"
msgstr "" msgstr "Abbiamo ricevuto una richiesta di modifica dell'indirizzo email per l'account Zulip su %(realm_uri)s da %(old_email)s a %(new_email)s. Per confermare questa modifica, per favore clicca sul link di seguito: "
#: templates/zerver/emails/compiled/confirm_new_email.html:12 #: templates/zerver/emails/compiled/confirm_new_email.html:12
#: templates/zerver/emails/confirm_new_email.source.html:11 #: templates/zerver/emails/confirm_new_email.source.html:11
@@ -1535,7 +1535,7 @@ msgstr ""
#: templates/zerver/emails/followup_day1.source.html:14 #: templates/zerver/emails/followup_day1.source.html:14
#, python-format #, python-format
msgid "You've joined the Zulip organization <b>%(realm_name)s</b>." msgid "You've joined the Zulip organization <b>%(realm_name)s</b>."
msgstr "" msgstr "Sei entrato nell'organizzazione Zulip <b>%(realm_name)s</b>."
#: templates/zerver/emails/compiled/followup_day1.html:20 #: templates/zerver/emails/compiled/followup_day1.html:20
#: templates/zerver/emails/followup_day1.source.html:19 #: templates/zerver/emails/followup_day1.source.html:19
@@ -1617,7 +1617,7 @@ msgid ""
"decoration:underline\">GitHub</a>, or chat with us live on the <a " "decoration:underline\">GitHub</a>, or chat with us live on the <a "
"href=\"https://chat.zulip.org\" style=\"color:#46aa8f; text-" "href=\"https://chat.zulip.org\" style=\"color:#46aa8f; text-"
"decoration:underline\">Zulip community server</a>!" "decoration:underline\">Zulip community server</a>!"
msgstr "" msgstr "PS: Seguici su <a href=\"https://twitter.com/zulip\" style=\"color:#46aa8f; text-decoration:underline\">Twitter</a>, seguici su <a href=\"https://github.com/zulip/zulip\" style=\"color:#46aa8f; text-decoration:underline\">GitHUb</a>, o chatta con noi live su <a href=\"https://chat.zulip.org\" style=\"color:#46aa8f; text-decoration:underline\">Zulip community server</a>!"
#: templates/zerver/emails/compiled/followup_day2.html:9 #: templates/zerver/emails/compiled/followup_day2.html:9
#: templates/zerver/emails/followup_day2.source.html:8 #: templates/zerver/emails/followup_day2.source.html:8
@@ -1693,7 +1693,7 @@ msgstr ""
#: templates/zerver/emails/compiled/followup_day2.html:35 #: templates/zerver/emails/compiled/followup_day2.html:35
#: templates/zerver/emails/followup_day2.source.html:34 #: templates/zerver/emails/followup_day2.source.html:34
msgid "Unsubscribe from welcome emails" msgid "Unsubscribe from welcome emails"
msgstr "" msgstr "Annulla l'iscrizione alle e-mail di benvenuto"
#: templates/zerver/emails/compiled/invitation.html:9 #: templates/zerver/emails/compiled/invitation.html:9
#: templates/zerver/emails/invitation.source.html:8 #: templates/zerver/emails/invitation.source.html:8
@@ -1767,7 +1767,7 @@ msgstr ""
msgid "" msgid ""
"Organization: %(organization_url)s Time: %(login_time)s Email: " "Organization: %(organization_url)s Time: %(login_time)s Email: "
"%(user_email)s" "%(user_email)s"
msgstr "" msgstr "ORganizzazione: %(organization_url)s Ora: %(login_time)s Email: %(user_email)s"
#: templates/zerver/emails/compiled/notify_new_login.html:13 #: templates/zerver/emails/compiled/notify_new_login.html:13
#: templates/zerver/emails/notify_new_login.source.html:12 #: templates/zerver/emails/notify_new_login.source.html:12
@@ -1831,7 +1831,7 @@ msgstr ""
#: templates/zerver/emails/notify_new_login.source.html:41 #: templates/zerver/emails/notify_new_login.source.html:41
#: templates/zerver/emails/notify_new_login.txt:20 #: templates/zerver/emails/notify_new_login.txt:20
msgid "Zulip Security" msgid "Zulip Security"
msgstr "" msgstr "Sicurezza Zulip"
#: templates/zerver/emails/compiled/notify_new_login.html:48 #: templates/zerver/emails/compiled/notify_new_login.html:48
#: templates/zerver/emails/notify_new_login.source.html:46 #: templates/zerver/emails/notify_new_login.source.html:46
@@ -1845,7 +1845,7 @@ msgstr "Annulla l'iscrizione delle notifiche del login"
msgid "" msgid ""
"Somebody (possibly you) requested a new password for the Zulip account " "Somebody (possibly you) requested a new password for the Zulip account "
"%(email)s on %(realm_uri)s." "%(email)s on %(realm_uri)s."
msgstr "" msgstr "Qualcuno (probabilmente tu) ha richiesto una nuova password per il tuo account Zulip %(email)sin %(realm_uri)s."
#: templates/zerver/emails/compiled/password_reset.html:14 #: templates/zerver/emails/compiled/password_reset.html:14
#: templates/zerver/emails/password_reset.source.html:13 #: templates/zerver/emails/password_reset.source.html:13
@@ -1855,7 +1855,7 @@ msgstr ""
#: templates/zerver/emails/compiled/password_reset.html:15 #: templates/zerver/emails/compiled/password_reset.html:15
#: templates/zerver/emails/password_reset.source.html:14 #: templates/zerver/emails/password_reset.source.html:14
msgid "Reset password" msgid "Reset password"
msgstr "" msgstr "Resetta la password"
#: templates/zerver/emails/compiled/password_reset.html:20 #: templates/zerver/emails/compiled/password_reset.html:20
#, python-format #, python-format
@@ -1871,7 +1871,7 @@ msgstr ""
#: templates/zerver/emails/password_reset.source.html:21 #: templates/zerver/emails/password_reset.source.html:21
#: templates/zerver/emails/password_reset.txt:10 #: templates/zerver/emails/password_reset.txt:10
msgid "You do not have an account in that Zulip organization." msgid "You do not have an account in that Zulip organization."
msgstr "" msgstr "Non hai un account in quella organizzazione in Zulip."
#: templates/zerver/emails/compiled/password_reset.html:27 #: templates/zerver/emails/compiled/password_reset.html:27
#: templates/zerver/emails/password_reset.source.html:26 #: templates/zerver/emails/password_reset.source.html:26
@@ -1933,7 +1933,7 @@ msgstr ""
msgid "" msgid ""
"If you did not request this change, please contact us immediately at <a " "If you did not request this change, please contact us immediately at <a "
"href=\"mailto:%(support_email)s\">%(support_email)s</a>." "href=\"mailto:%(support_email)s\">%(support_email)s</a>."
msgstr "" msgstr "Se non hai richiesto questo cambiamento, ti preghiamo di contattarci immediatamente a <a href=\"mailto:%(support_email)s\">%(support_email)s</a>."
#: templates/zerver/emails/confirm_new_email.subject.txt:1 #: templates/zerver/emails/confirm_new_email.subject.txt:1
msgid "Verify your new email address" msgid "Verify your new email address"
@@ -1981,7 +1981,7 @@ msgstr ""
msgid "" msgid ""
"(you'll need these to sign in to the <a " "(you'll need these to sign in to the <a "
"href=\"https://zulipchat.com/apps\">mobile and desktop</a> apps)" "href=\"https://zulipchat.com/apps\">mobile and desktop</a> apps)"
msgstr "" msgstr "(ne avrai bisogno per registrati alla app <a href=\"https://zulipchat.com/apps\">mobile e desktop</a>)"
#: templates/zerver/emails/followup_day1.source.html:35 #: templates/zerver/emails/followup_day1.source.html:35
#, python-format #, python-format
@@ -2010,22 +2010,22 @@ msgstr ""
#: templates/zerver/emails/followup_day1.subject.txt:2 #: templates/zerver/emails/followup_day1.subject.txt:2
#, python-format #, python-format
msgid "%(realm_name)s on Zulip: Your new organization details" msgid "%(realm_name)s on Zulip: Your new organization details"
msgstr "" msgstr "%(realm_name)s in Zulip: I dettagli della tua nuova organizzazione"
#: templates/zerver/emails/followup_day1.subject.txt:4 #: templates/zerver/emails/followup_day1.subject.txt:4
#, python-format #, python-format
msgid "%(realm_name)s on Zulip: Your new account details" msgid "%(realm_name)s on Zulip: Your new account details"
msgstr "" msgstr "%(realm_name)sin Zulip: I dettagli del tuo nuovo account"
#: templates/zerver/emails/followup_day1.txt:4 #: templates/zerver/emails/followup_day1.txt:4
#, python-format #, python-format
msgid "You've created the new Zulip organization %(realm_name)s." msgid "You've created the new Zulip organization %(realm_name)s."
msgstr "" msgstr "Hai creato la nuova organizzazione %(realm_name)sin Zulip. "
#: templates/zerver/emails/followup_day1.txt:6 #: templates/zerver/emails/followup_day1.txt:6
#, python-format #, python-format
msgid "You've joined the Zulip organization %(realm_name)s." msgid "You've joined the Zulip organization %(realm_name)s."
msgstr "" msgstr "Sei entrato nell'organizzazione Zulip %(realm_name)s."
#: templates/zerver/emails/followup_day1.txt:21 #: templates/zerver/emails/followup_day1.txt:21
msgid "" msgid ""
@@ -2182,12 +2182,12 @@ msgstr ""
#: templates/zerver/emails/notify_new_login.source.html:16 #: templates/zerver/emails/notify_new_login.source.html:16
#, python-format #, python-format
msgid "Organization: <a href=\"%(realm_uri)s\" target=\"_blank\">%(realm_uri)s</a>" msgid "Organization: <a href=\"%(realm_uri)s\" target=\"_blank\">%(realm_uri)s</a>"
msgstr "" msgstr "Organizzazione: <a href=\"%(realm_uri)s\" target=\"_blank\">%(realm_uri)s</a>"
#: templates/zerver/emails/notify_new_login.source.html:19 #: templates/zerver/emails/notify_new_login.source.html:19
#, python-format #, python-format
msgid "Email: <a href=\"mailto:%(user_email)s\" target=\"_blank\">%(user_email)s</a>" msgid "Email: <a href=\"mailto:%(user_email)s\" target=\"_blank\">%(user_email)s</a>"
msgstr "" msgstr "Email: <a href=\"mailto:%(user_email)s\" target=\"_blank\">%(user_email)s</a>"
#: templates/zerver/emails/notify_new_login.source.html:36 #: templates/zerver/emails/notify_new_login.source.html:36
#, python-format #, python-format
@@ -2201,12 +2201,12 @@ msgstr ""
#: templates/zerver/emails/notify_new_login.subject.txt:1 #: templates/zerver/emails/notify_new_login.subject.txt:1
#, python-format #, python-format
msgid "New login from %(device_browser)s on %(device_os)s" msgid "New login from %(device_browser)s on %(device_os)s"
msgstr "" msgstr "Nuovo accesso da %(device_browser)s in %(device_os)s"
#: templates/zerver/emails/notify_new_login.txt:3 #: templates/zerver/emails/notify_new_login.txt:3
#, python-format #, python-format
msgid "Organization: %(organization_url)s" msgid "Organization: %(organization_url)s"
msgstr "" msgstr "Organizzazione: %(organization_url)s"
#: templates/zerver/emails/notify_new_login.txt:5 #: templates/zerver/emails/notify_new_login.txt:5
#, python-format #, python-format
@@ -3304,7 +3304,7 @@ msgstr "Ogni messaggio ha un argomento, Gli argomenti rendono le conversazioni a
#: zerver/lib/hotspots.py:26 #: zerver/lib/hotspots.py:26
msgid "Go to Settings to configure your notifications and display settings." msgid "Go to Settings to configure your notifications and display settings."
msgstr "" msgstr "Vai a Impostazioni per configurare le notifiche e le impostazioni di visualizzazione."
#: zerver/lib/hotspots.py:30 #: zerver/lib/hotspots.py:30
msgid "Compose" msgid "Compose"
@@ -3470,7 +3470,7 @@ msgstr ""
#: zerver/lib/upload.py:120 zerver/lib/upload.py:135 zerver/lib/upload.py:187 #: zerver/lib/upload.py:120 zerver/lib/upload.py:135 zerver/lib/upload.py:187
msgid "Image size exceeds limit." msgid "Image size exceeds limit."
msgstr "" msgstr "Le dimensioni dell'immagine superano il limite."
#: zerver/lib/upload.py:288 #: zerver/lib/upload.py:288
msgid "Upload would exceed your organization's upload quota." msgid "Upload would exceed your organization's upload quota."
@@ -4418,4 +4418,4 @@ msgstr ""
#: zilencer/views.py:160 #: zilencer/views.py:160
msgid "Data is out of order." msgid "Data is out of order."
msgstr "" msgstr "I dati sono fuori servizio."

View File

@@ -80,7 +80,7 @@
"Are invitations required for joining the organization?": "E' richiesto un invito per entrare nell'organizzazione?", "Are invitations required for joining the organization?": "E' richiesto un invito per entrare nell'organizzazione?",
"Are you sure you want to create stream '__stream_name__' and subscribe __count__ users to it?": "Sei sicuro di voler creare il canale '__stream_name__' e sottoscrivere __count__ utenti?", "Are you sure you want to create stream '__stream_name__' and subscribe __count__ users to it?": "Sei sicuro di voler creare il canale '__stream_name__' e sottoscrivere __count__ utenti?",
"Are you sure you want to delete <b>__group_name__</b>?": "Sei sicuro di voler cancellare <b>__group_name__</b>?", "Are you sure you want to delete <b>__group_name__</b>?": "Sei sicuro di voler cancellare <b>__group_name__</b>?",
"Are you sure you want to delete all messages in <b>__topic_name__</b>?": "", "Are you sure you want to delete all messages in <b>__topic_name__</b>?": "Sei sicuro di voler cancellare tutti i messaggi in <b>__topic_name__</b>?",
"Are you sure you want to do this?": "Sei sicuro di volerlo fare?", "Are you sure you want to do this?": "Sei sicuro di volerlo fare?",
"Are you sure you want to mention all <strong>__count__</strong> people in this stream?": "Sei sicuro di voler menzionare tutte le <strong>__count__</strong> persone in questo canale?", "Are you sure you want to mention all <strong>__count__</strong> people in this stream?": "Sei sicuro di voler menzionare tutte le <strong>__count__</strong> persone in questo canale?",
"Are you sure you want to resend the invitation to <strong><span class=\"email\"></span></strong>?": "Sei sicuro di voler rispedire l'invito a <strong><span class=\"email\"></span></strong>?", "Are you sure you want to resend the invitation to <strong><span class=\"email\"></span></strong>?": "Sei sicuro di voler rispedire l'invito a <strong><span class=\"email\"></span></strong>?",
@@ -101,7 +101,7 @@
"By deactivating your account, you will be logged out immediately.": "Disattivando il tuo account, sarai scollegato immediatamente.", "By deactivating your account, you will be logged out immediately.": "Disattivando il tuo account, sarai scollegato immediatamente.",
"Cancel": "Cancella", "Cancel": "Cancella",
"Change": "Modifica", "Change": "Modifica",
"Change bot info and owner": "", "Change bot info and owner": "Modifica le info sul bot e il proprietario",
"Change email": "Modifica email", "Change email": "Modifica email",
"Change full name": "Modifica il nome", "Change full name": "Modifica il nome",
"Change later messages to this topic": "imposta a questo argomento i messaggi successivi", "Change later messages to this topic": "imposta a questo argomento i messaggi successivi",
@@ -156,12 +156,12 @@
"Deactivated users": "Utenti disattivati", "Deactivated users": "Utenti disattivati",
"Deactivation encountered an error. Please reload and try again.": "Errore durante la disattivazione: per favore ricarica e riprova.", "Deactivation encountered an error. Please reload and try again.": "Errore durante la disattivazione: per favore ricarica e riprova.",
"Default language": "Lingua di default", "Default language": "Lingua di default",
"Default settings for new users joining this organization.": "", "Default settings for new users joining this organization.": "Impostazioni di default per i nuovi utenti che entrano in questa organizzazione.",
"Default streams": "Canali di default", "Default streams": "Canali di default",
"Default user settings": "Impostazioni utente di default", "Default user settings": "Impostazioni utente di default",
"Delete": "Cancella", "Delete": "Cancella",
"Delete alert word": "Cancella parola allarme", "Delete alert word": "Cancella parola allarme",
"Delete all messages in <b>__topic_name__</b>": "", "Delete all messages in <b>__topic_name__</b>": "Cancella tutti i messaggi in <b>__topic_name__</b>",
"Delete avatar": "Cancella avatar", "Delete avatar": "Cancella avatar",
"Delete bot": "Cancella bot", "Delete bot": "Cancella bot",
"Delete draft": "Cancella bozza", "Delete draft": "Cancella bozza",
@@ -169,7 +169,7 @@
"Delete icon": "Cancella icona", "Delete icon": "Cancella icona",
"Delete logo": "Cancella logo", "Delete logo": "Cancella logo",
"Delete message": "Cancella messaggio", "Delete message": "Cancella messaggio",
"Delete messages": "", "Delete messages": "Cancella messaggi",
"Delete stream": "Cancella canale", "Delete stream": "Cancella canale",
"Delete topic": "Cancella argomento", "Delete topic": "Cancella argomento",
"Delete user group": "Cancella gruppo di utenti", "Delete user group": "Cancella gruppo di utenti",
@@ -246,7 +246,7 @@
"Forgotten it?": "Dimenticato?", "Forgotten it?": "Dimenticato?",
"Formatting": "Sto formattando", "Formatting": "Sto formattando",
"Full name": "Nome", "Full name": "Nome",
"Generate invite link": "", "Generate invite link": "Genera link di invito",
"Generate new API key": "Genera nuova chiave API", "Generate new API key": "Genera nuova chiave API",
"Generating link...": "Generazione del link...", "Generating link...": "Generazione del link...",
"Generic": "Generico", "Generic": "Generico",
@@ -254,7 +254,7 @@
"Go back": "Indietro", "Go back": "Indietro",
"Got it!": "Capito!", "Got it!": "Capito!",
"Guest": "Ospite", "Guest": "Ospite",
"Guests cannot edit custom emoji.": "", "Guests cannot edit custom emoji.": "Gli ospiti non posso modificare le emoji custom.",
"High contrast mode": "Modalità ad alto conrasto", "High contrast mode": "Modalità ad alto conrasto",
"Hint": "Suggerimento", "Hint": "Suggerimento",
"Hint (up to 80 characters)": "Suggerimento (fino a 80 caratteri)", "Hint (up to 80 characters)": "Suggerimento (fino a 80 caratteri)",
@@ -268,16 +268,16 @@
"Interface": "Interfaccia", "Interface": "Interfaccia",
"Invalid slash command. Check if you are missing a space after the command.": "Comando slash invalido. Verifica se manca uno spazio dopo il comando", "Invalid slash command. Check if you are missing a space after the command.": "Comando slash invalido. Verifica se manca uno spazio dopo il comando",
"Invalid stream id": "Identificativo canale non valido", "Invalid stream id": "Identificativo canale non valido",
"Invitation link: <a href=\"__link__\">__link__</a>": "", "Invitation link: <a href=\"__link__\">__link__</a>": "Link di invito:<a href=\"__link__\">__link__</a>",
"Invitations": "Inviti", "Invitations": "Inviti",
"Invite": "Invita", "Invite": "Invita",
"Invite more users": "Invita altri utenti", "Invite more users": "Invita altri utenti",
"Invited as": "", "Invited as": "Invitato come",
"Invited as administrator": "Invitato come amministratore", "Invited as administrator": "Invitato come amministratore",
"Invited at": "Invitato a", "Invited at": "Invitato a",
"Invited by": "Invitato da", "Invited by": "Invitato da",
"Invites": "Invita", "Invites": "Invita",
"Inviting...": "", "Inviting...": "Sto invitando...",
"It's been a while! Since you were last here, you received <b>__unread_count__</b> new messages.": "Manchi da un po'! Dalla tua ultima visita, hai ricevuto <b>__unread_count__</b> nuovi messaggi.", "It's been a while! Since you were last here, you received <b>__unread_count__</b> new messages.": "Manchi da un po'! Dalla tua ultima visita, hai ricevuto <b>__unread_count__</b> nuovi messaggi.",
"Joined": "Unito", "Joined": "Unito",
"Joining the organization": "Sto entrando nell'organizzazione", "Joining the organization": "Sto entrando nell'organizzazione",
@@ -397,7 +397,7 @@
"Organization settings": "Impostazione dell'organizzazione", "Organization settings": "Impostazione dell'organizzazione",
"Other notification settings": "Altre impostazioni delle notifiche", "Other notification settings": "Altre impostazioni delle notifiche",
"Other permissions": "Altri permessi", "Other permissions": "Altri permessi",
"Other settings": "", "Other settings": "Altre impostazioni",
"Outgoing webhook message format": "Formato messaggio webhook in uscita", "Outgoing webhook message format": "Formato messaggio webhook in uscita",
"Owner": "Proprietario", "Owner": "Proprietario",
"Password": "Password", "Password": "Password",
@@ -527,7 +527,7 @@
"Time settings": "Impostazioni dell'orario", "Time settings": "Impostazioni dell'orario",
"Time zone": "Fuso orario", "Time zone": "Fuso orario",
"Time's up!": "Tempo terminato!", "Time's up!": "Tempo terminato!",
"Tip: You can also send \"/poll Some question\"": "", "Tip: You can also send \"/poll Some question\"": "Tip: Puoi anche inviare \"/poll per Qualche domanda",
"Today": "Oggi", "Today": "Oggi",
"Toggle subscription": "Cambia sottoscrizione", "Toggle subscription": "Cambia sottoscrizione",
"Tomorrow": "Domani", "Tomorrow": "Domani",
@@ -600,7 +600,7 @@
"Who can create streams": "Chi può creare canali", "Who can create streams": "Chi può creare canali",
"Who can see users email addresses": "Chi può vedere gli indirizzi email degli utenti", "Who can see users email addresses": "Chi può vedere gli indirizzi email degli utenti",
"Working\u2026": "Elaborazione...", "Working\u2026": "Elaborazione...",
"Write": "", "Write": "Scrivi",
"Yes": "Sì", "Yes": "Sì",
"Yes, delete this stream": "Sì, cancella questo canale", "Yes, delete this stream": "Sì, cancella questo canale",
"Yes, send": "Sì, spedisci", "Yes, send": "Sì, spedisci",
@@ -629,9 +629,9 @@
"Your account": "Il tuo account", "Your account": "Il tuo account",
"Your bots": "I tuoi bot", "Your bots": "I tuoi bot",
"Your reminder note is empty!": "Il tuo promemoria è vuoto!", "Your reminder note is empty!": "Il tuo promemoria è vuoto!",
"Zoom API key (required)": "", "Zoom API key (required)": "Zoom API Key (richiesta)",
"Zoom API secret (required)": "", "Zoom API secret (required)": "Zoom API secret (richiesta)",
"Zoom user ID (required)": "", "Zoom user ID (required)": "Zoom user ID (richiesta)",
"[Condense this message]": "[Rimpicciolisci questo messaggio]", "[Condense this message]": "[Rimpicciolisci questo messaggio]",
"[Configure]": "[Configura]", "[Configure]": "[Configura]",
"[Disable]": "[Disabilita]", "[Disable]": "[Disabilita]",

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Zulip\n" "Project-Id-Version: Zulip\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-01 17:24+0000\n" "POT-Creation-Date: 2019-03-05 01:24+0000\n"
"PO-Revision-Date: 2018-04-11 21:06+0000\n" "PO-Revision-Date: 2018-04-11 21:06+0000\n"
"Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n" "Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n"
"Language-Team: Tamil (http://www.transifex.com/zulip/zulip/language/ta/)\n" "Language-Team: Tamil (http://www.transifex.com/zulip/zulip/language/ta/)\n"
@@ -599,7 +599,8 @@ msgstr ""
#: templates/zerver/app/home.html:138 #: templates/zerver/app/home.html:138
msgid "" msgid ""
"\n" "\n"
" Learn more about mentions <a href=\"/help/at-mention-a-user\">\n" " Learn more about mentions <a href=\"/help/mention-a-user-or-group"
"\">\n"
" here</a>.\n" " here</a>.\n"
" " " "
msgstr "" msgstr ""
@@ -1420,11 +1421,11 @@ msgstr ""
msgid "Create organization" msgid "Create organization"
msgstr "" msgstr ""
#: templates/zerver/deactivated.html:9 #: templates/zerver/deactivated.html:15
msgid "Deactivated organization" msgid "Deactivated organization"
msgstr "" msgstr ""
#: templates/zerver/deactivated.html:14 #: templates/zerver/deactivated.html:20
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@@ -3273,7 +3274,7 @@ msgstr ""
msgid "Invalid type parameter" msgid "Invalid type parameter"
msgstr "" msgstr ""
#: zerver/lib/events.py:742 #: zerver/lib/events.py:745
msgid "Could not allocate event queue" msgid "Could not allocate event queue"
msgstr "" msgstr ""
@@ -3875,7 +3876,7 @@ msgstr ""
msgid "This user is not registered; do so from a browser." msgid "This user is not registered; do so from a browser."
msgstr "" msgstr ""
#: zerver/views/auth.py:798 zerver/views/auth.py:889 #: zerver/views/auth.py:798 zerver/views/auth.py:887
msgid "Your username or password is incorrect." msgid "Your username or password is incorrect."
msgstr "" msgstr ""
@@ -3887,7 +3888,7 @@ msgstr ""
msgid "Subdomain required" msgid "Subdomain required"
msgstr "" msgstr ""
#: zerver/views/auth.py:897 #: zerver/views/auth.py:895
msgid "GOOGLE_CLIENT_ID is not configured" msgid "GOOGLE_CLIENT_ID is not configured"
msgstr "" msgstr ""

View File

@@ -38,11 +38,11 @@
} }
.user_circle_empty_line { .user_circle_empty_line {
border-color: hsl(0, 90%, 40%); border-color: hsl(0, 0%, 50%);
&::after { &::after {
content: ''; content: '';
background: hsl(0, 90%, 40%); background: hsl(0, 0%, 50%);
height: 1.5px; // 1px is too thin, 2px is too thick height: 1.5px; // 1px is too thin, 2px is too thick
width: 6px; width: 6px;
display: block; display: block;

View File

@@ -11,8 +11,7 @@
{{#if invite_only}} {{#if invite_only}}
<i class="fa fa-lock invite-stream-icon" title="{{t 'This is a private stream' }}" aria-label="{{t 'This is a private stream' }}"></i> <i class="fa fa-lock invite-stream-icon" title="{{t 'This is a private stream' }}" aria-label="{{t 'This is a private stream' }}"></i>
{{/if}} {{/if}}
{{display_recipient}} {{display_recipient}}</a>
</a>
{{! hidden narrow icon for copy-pasting }} {{! hidden narrow icon for copy-pasting }}
<span class="copy-paste-text">&gt;</span> <span class="copy-paste-text">&gt;</span>

View File

@@ -136,7 +136,7 @@
<p> <p>
{% trans %} {% trans %}
Learn more about mentions <a href="/help/at-mention-a-user"> Learn more about mentions <a href="/help/mention-a-user-or-group">
here</a>. here</a>.
{% endtrans %} {% endtrans %}
</p> </p>

View File

@@ -31,7 +31,7 @@
<nav class="header-main rightside-userlist" id="top_navbar"> <nav class="header-main rightside-userlist" id="top_navbar">
<div class="column-left"> <div class="column-left">
<a class="brand no-style" href="#"> <a class="brand no-style" href="#">
<img id="realm-logo" src="{% if night_mode %} {{ realm_night_logo }}{% else %} {{ realm_logo }} {% endif %}" alt="" class="nav-logo no-drag"/> <img id="realm-logo" src="{{ navbar_logo_url }}" alt="" class="nav-logo no-drag"/>
</a> </a>
</div> </div>
<div class="column-middle" id="navbar-middle"> <div class="column-middle" id="navbar-middle">

View File

@@ -1,4 +1,10 @@
{% extends "zerver/portico_signup.html" %} {% extends "zerver/portico_signup.html" %}
{% block customhead %}
{{ super() }}
<meta http-equiv="refresh" content="60;URL='/'">
{% endblock %}
{% block portico_content %} {% block portico_content %}
<div class="app portico-page"> <div class="app portico-page">

View File

@@ -43,45 +43,51 @@ private stream messages:
### Public streams ### Public streams
| | Org admins | Stream members | Org members | Guests | | Org admins | Members | Guests
|--- |--- |--- |--- |--- |--- |--- |--- |---
| Join | &#10004; | &mdash; | &#10004; | | Join | &#10004; | &#10004; |
| Add others | &#10004; | [1] | &#10004; | | Unsubscribe | &#9726; | &#9726; | &#9726;
| See subscriber list | &#10004; | &#10004; | &#10004; | | Add others | &#10004; | &#10004; |
| See full history | &#10004; | &#10004; | &#10004; | | See subscriber list | &#10004; | &#10004; | &#9726;
| See estimated traffic | &#10004; | &#10004; | &#10004; | | See full history | &#10004; | &#10004; | &#9726;
| Post | &#10004; | [2] | | | See estimated traffic | &#10004; | &#10004; | &#9726;
| Change the privacy | &#10004; | | | | Post | &#10004; | &#10038; | &#10038;
| Rename | &#10004; | | | | Change the privacy | &#10004; | |
| Edit the description | &#10004; | | | | Rename | &#10004; | |
| Remove others | &#10004; | | | | Edit the description | &#10004; | |
| Delete | &#10004; | | | | Remove others | &#10004; | |
| Delete | &#10004; | |
[1] Yes, except for guests. &#10004; Always
&#9726; &nbsp; If subscribed to the stream
&#10038; Configurable. Org admins and Members can, by default, post to
any public stream, and Guests can only post to public streams if they
are subscribed. Additionally, streams can be configured to only allow
administrators to post.
[2] Configurable.
### Private streams ### Private streams
| | Org admins | Stream members | Org members | Guests
|--- |--- |--- |--- |---
| Join | | &mdash; | |
| Add others | | [1] | |
| See subscriber list | &#10004; | &#10004; | |
| See full history | | [3] | |
| See estimated traffic | &#10004; | &#10004; | |
| Post | &#10004; | [2] | |
| Change the privacy | [4] | | |
| Rename | &#10004; | | |
| Edit the description | &#10004; | | |
| Remove others | &#10004; | | |
| Delete | &#10004; | | |
[1] Yes, except for guests. | | Org admins | Members | Guests
|--- |--- |--- |---
| Join | | |
| Unsubscribe | &#9726; | &#9726; | &#9726;
| Add others | &#9726; | &#9726; |
| See subscriber list | &#10004; | &#9726; | &#9726;
| See full history | &#10038; | &#10038; | &#10038;
| See estimated traffic | &#10004; | &#9726; | &#9726;
| Post | &#9726; | &#10038; | &#10038;
| Change the privacy | &#9726; | |
| Rename | &#10004; | |
| Edit the description | &#10004; | |
| Remove others | &#10004; | |
| Delete | &#10004; | |
[2] Configurable. &#10004; Always
[3] Depends on the stream type. &#9726; &nbsp; If subscribed to the stream
[4] Yes, but only if subscribed. If you have a private stream without an &#10038; Configurable, but at minimum must be subscribed to the stream
admin, you'll have to add an admin in order to change the stream's privacy.

View File

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

View File

@@ -15,7 +15,6 @@ from zerver.lib.bugdown import convert as bugdown_convert
from zerver.lib.send_email import FromAddress from zerver.lib.send_email import FromAddress
from zerver.lib.subdomains import get_subdomain from zerver.lib.subdomains import get_subdomain
from zerver.lib.realm_icon import get_realm_icon_url from zerver.lib.realm_icon import get_realm_icon_url
from zerver.lib.realm_logo import get_realm_logo_url
from version import ZULIP_VERSION, LATEST_RELEASE_VERSION, \ from version import ZULIP_VERSION, LATEST_RELEASE_VERSION, \
LATEST_RELEASE_ANNOUNCEMENT, LATEST_MAJOR_VERSION LATEST_RELEASE_ANNOUNCEMENT, LATEST_MAJOR_VERSION
@@ -54,8 +53,6 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
realm_uri = settings.ROOT_DOMAIN_URI realm_uri = settings.ROOT_DOMAIN_URI
realm_name = None realm_name = None
realm_icon = None realm_icon = None
realm_logo = None
realm_night_logo = None
realm_description = None realm_description = None
realm_invite_required = False realm_invite_required = False
realm_plan_type = 0 realm_plan_type = 0
@@ -63,8 +60,6 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
realm_uri = realm.uri realm_uri = realm.uri
realm_name = realm.name realm_name = realm.name
realm_icon = get_realm_icon_url(realm) realm_icon = get_realm_icon_url(realm)
realm_logo = get_realm_logo_url(realm, night = False)
realm_night_logo = get_realm_logo_url(realm, night = True)
realm_description_raw = realm.description or "The coolest place in the universe." realm_description_raw = realm.description or "The coolest place in the universe."
realm_description = bugdown_convert(realm_description_raw, message_realm=realm) realm_description = bugdown_convert(realm_description_raw, message_realm=realm)
realm_invite_required = realm.invite_required realm_invite_required = realm.invite_required
@@ -119,8 +114,6 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
'realm_uri': realm_uri, 'realm_uri': realm_uri,
'realm_name': realm_name, 'realm_name': realm_name,
'realm_icon': realm_icon, 'realm_icon': realm_icon,
'realm_logo': realm_logo,
'realm_night_logo': realm_night_logo,
'realm_description': realm_description, 'realm_description': realm_description,
'realm_plan_type': realm_plan_type, 'realm_plan_type': realm_plan_type,
'root_domain_uri': settings.ROOT_DOMAIN_URI, 'root_domain_uri': settings.ROOT_DOMAIN_URI,

View File

@@ -107,6 +107,13 @@ def get_raw_user_data(realm_id: int, client_gravatar: bool) -> Dict[int, Dict[st
for row in user_dicts for row in user_dicts
} }
def add_realm_logo_fields(state: Dict[str, Any], realm: Realm) -> None:
state['realm_logo_url'] = realm_logo_url(realm, night = False)
state['realm_logo_source'] = realm.logo_source
state['realm_night_logo_url'] = realm_logo_url(realm, night = True)
state['realm_night_logo_source'] = realm.night_logo_source
state['max_logo_file_size'] = settings.MAX_LOGO_FILE_SIZE
def always_want(msg_type: str) -> bool: def always_want(msg_type: str) -> bool:
''' '''
This function is used as a helper in This function is used as a helper in
@@ -184,11 +191,7 @@ def fetch_initial_state_data(user_profile: UserProfile,
state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_url'] = realm_icon_url(realm)
state['realm_icon_source'] = realm.icon_source state['realm_icon_source'] = realm.icon_source
state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE
state['realm_logo_url'] = realm_logo_url(realm, night = False) add_realm_logo_fields(state, realm)
state['realm_logo_source'] = realm.logo_source
state['realm_night_logo_url'] = realm_logo_url(realm, night = True)
state['realm_night_logo_source'] = realm.night_logo_source
state['max_logo_file_size'] = settings.MAX_LOGO_FILE_SIZE
state['realm_bot_domain'] = realm.get_bot_domain() state['realm_bot_domain'] = realm.get_bot_domain()
state['realm_uri'] = realm.uri state['realm_uri'] = realm.uri
state['realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS state['realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS

View File

@@ -1718,8 +1718,6 @@ class FetchAuthBackends(ZulipTestCase):
('realm_name', check_string), ('realm_name', check_string),
('realm_description', check_string), ('realm_description', check_string),
('realm_icon', check_string), ('realm_icon', check_string),
('realm_logo', check_string),
('realm_night_logo', check_string),
]) ])
def test_fetch_auth_backend_format(self) -> None: def test_fetch_auth_backend_format(self) -> None:
@@ -2550,19 +2548,23 @@ class TestLDAP(ZulipLDAPTestCase):
self.assertIs(user_profile, None) self.assertIs(user_profile, None)
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)) @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_login_failure_due_to_wrong_subdomain(self) -> None: def test_login_success_with_different_subdomain(self) -> None:
self.mock_ldap.directory = { self.mock_ldap.directory = {
'uid=hamlet,ou=users,dc=zulip,dc=com': { 'uid=hamlet,ou=users,dc=zulip,dc=com': {
'userPassword': ['testing', ] 'fn': ['King Hamlet', ],
'sn': ['Hamlet', ],
'userPassword': ['testing', ],
} }
} }
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
with self.settings( with self.settings(
LDAP_APPEND_DOMAIN='zulip.com', LDAP_APPEND_DOMAIN='zulip.com',
AUTH_LDAP_BIND_PASSWORD='', AUTH_LDAP_BIND_PASSWORD='',
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'): AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com',
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing', AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
user_profile = self.backend.authenticate(self.example_email('hamlet'), 'testing',
realm=get_realm('zephyr')) realm=get_realm('zephyr'))
self.assertIs(user_profile, None) self.assertEqual(user_profile.email, self.example_email('hamlet'))
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)) @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_login_failure_due_to_invalid_subdomain(self) -> None: def test_login_failure_due_to_invalid_subdomain(self) -> None:

View File

@@ -10,6 +10,8 @@ import urllib
from typing import Any, Dict from typing import Any, Dict
from zerver.lib.actions import do_create_user from zerver.lib.actions import do_create_user
from zerver.lib.actions import do_change_logo_source
from zerver.lib.events import add_realm_logo_fields
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import ( from zerver.lib.test_helpers import (
HostRequestMock, queries_captured, get_user_messages HostRequestMock, queries_captured, get_user_messages
@@ -20,7 +22,7 @@ from zerver.models import (
get_realm, get_stream, get_user, UserProfile, get_realm, get_stream, get_user, UserProfile,
flush_per_request_caches, DefaultStream, Realm, flush_per_request_caches, DefaultStream, Realm,
) )
from zerver.views.home import home, sent_time_in_epoch_seconds from zerver.views.home import home, sent_time_in_epoch_seconds, compute_navbar_logo_url
from corporate.models import Customer, CustomerPlan from corporate.models import Customer, CustomerPlan
class HomeTest(ZulipTestCase): class HomeTest(ZulipTestCase):
@@ -722,6 +724,54 @@ class HomeTest(ZulipTestCase):
html = result.content.decode('utf-8') html = result.content.decode('utf-8')
self.assertIn('Apps for every platform.', html) self.assertIn('Apps for every platform.', html)
def test_compute_navbar_logo_url(self) -> None:
user_profile = self.example_user("hamlet")
page_params = {"night_mode": True}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/static/images/logo/zulip-org-logo.png?version=0")
page_params = {"night_mode": False}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/static/images/logo/zulip-org-logo.png?version=0")
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=False)
page_params = {"night_mode": True}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/user_avatars/1/realm/logo.png?version=2")
page_params = {"night_mode": False}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/user_avatars/1/realm/logo.png?version=2")
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=True)
page_params = {"night_mode": True}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/user_avatars/1/realm/night_logo.png?version=2")
page_params = {"night_mode": False}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/user_avatars/1/realm/logo.png?version=2")
# This configuration isn't super supported in the UI and is a
# weird choice, but we have a test for it anyway.
do_change_logo_source(user_profile.realm, Realm.LOGO_DEFAULT, night=False)
page_params = {"night_mode": True}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/user_avatars/1/realm/night_logo.png?version=2")
page_params = {"night_mode": False}
add_realm_logo_fields(page_params, user_profile.realm)
self.assertEqual(compute_navbar_logo_url(page_params),
"/static/images/logo/zulip-org-logo.png?version=0")
def test_generate_204(self) -> None: def test_generate_204(self) -> None:
email = self.example_email("hamlet") email = self.example_email("hamlet")
self.login(email) self.login(email)

View File

@@ -38,6 +38,7 @@ from zerver.lib.actions import (
get_stream, get_stream,
do_create_default_stream_group, do_create_default_stream_group,
do_add_default_stream, do_add_default_stream,
do_create_realm,
) )
from zerver.lib.send_email import send_email, send_future_email, FromAddress from zerver.lib.send_email import send_email, send_future_email, FromAddress
from zerver.lib.initial_password import initial_password from zerver.lib.initial_password import initial_password
@@ -2686,6 +2687,49 @@ class UserSignUpTest(InviteUserBase):
self.assertEqual(birthday_field_value.value, '1990-12-19') self.assertEqual(birthday_field_value.value, '1990-12-19')
self.assertEqual(phone_number_field_value.value, 'a-new-number') self.assertEqual(phone_number_field_value.value, 'a-new-number')
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_ldap_registration_multiple_realms(self) -> None:
password = "testing"
email = "newuser@zulip.com"
ldap_user_attr_map = {
'full_name': 'fn',
'short_name': 'sn',
}
full_name = 'New LDAP fullname'
mock_directory = {
'uid=newuser,ou=users,dc=zulip,dc=com': {
'userPassword': ['testing', ],
'fn': [full_name],
'sn': ['shortname'],
}
}
init_fakeldap(mock_directory)
do_create_realm('test', 'test', False)
with self.settings(
POPULATE_PROFILE_VIA_LDAP=True,
LDAP_APPEND_DOMAIN='zulip.com',
AUTH_LDAP_BIND_PASSWORD='',
AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
subdomain = "zulip"
self.login_with_return(email, password,
HTTP_HOST=subdomain + ".testserver")
user_profile = UserProfile.objects.get(email=email, realm=get_realm('zulip'))
self.assertEqual(user_profile.email, email)
self.logout()
# Test registration in another realm works.
subdomain = "test"
self.login_with_return(email, password,
HTTP_HOST=subdomain + ".testserver")
user_profile = UserProfile.objects.get(email=email, realm=get_realm('test'))
self.assertEqual(user_profile.email, email)
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend', @override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',
'zproject.backends.ZulipDummyBackend')) 'zproject.backends.ZulipDummyBackend'))
def test_ldap_registration_when_names_changes_are_disabled(self) -> None: def test_ldap_registration_when_names_changes_are_disabled(self) -> None:

View File

@@ -871,8 +871,6 @@ def api_get_server_settings(request: HttpRequest) -> HttpResponse:
"realm_uri", "realm_uri",
"realm_name", "realm_name",
"realm_icon", "realm_icon",
"realm_logo",
"realm_night_logo",
"realm_description"]: "realm_description"]:
if context[settings_item] is not None: if context[settings_item] is not None:
result[settings_item] = context[settings_item] result[settings_item] = context[settings_item]

View File

@@ -1,4 +1,4 @@
from typing import List, Dict, Optional from typing import Any, List, Dict, Optional
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
@@ -73,6 +73,13 @@ def get_bot_types(user_profile: UserProfile) -> List[Dict[str, object]]:
}) })
return bot_types return bot_types
def compute_navbar_logo_url(page_params: Dict[str, Any]) -> str:
if page_params["night_mode"] and page_params["realm_night_logo_source"] != Realm.LOGO_DEFAULT:
navbar_logo_url = page_params["realm_night_logo_url"]
else:
navbar_logo_url = page_params["realm_logo_url"]
return navbar_logo_url
def home(request: HttpRequest) -> HttpResponse: def home(request: HttpRequest) -> HttpResponse:
if (settings.DEVELOPMENT and not settings.TEST_SUITE and if (settings.DEVELOPMENT and not settings.TEST_SUITE and
os.path.exists('var/handlebars-templates/compile.error')): os.path.exists('var/handlebars-templates/compile.error')):
@@ -270,6 +277,9 @@ def home_real(request: HttpRequest) -> HttpResponse:
# include (and thus how to display emojis in the emoji picker # include (and thus how to display emojis in the emoji picker
# and composebox typeahead). # and composebox typeahead).
emojiset = UserProfile.GOOGLE_BLOB_EMOJISET emojiset = UserProfile.GOOGLE_BLOB_EMOJISET
navbar_logo_url = compute_navbar_logo_url(page_params)
response = render(request, 'zerver/app/index.html', response = render(request, 'zerver/app/index.html',
context={'user_profile': user_profile, context={'user_profile': user_profile,
'emojiset': emojiset, 'emojiset': emojiset,
@@ -286,6 +296,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
'is_admin': user_profile.is_realm_admin, 'is_admin': user_profile.is_realm_admin,
'is_guest': user_profile.is_guest, 'is_guest': user_profile.is_guest,
'night_mode': user_profile.night_mode, 'night_mode': user_profile.night_mode,
'navbar_logo_url': navbar_logo_url,
'show_webathena': user_profile.realm.webathena_enabled, 'show_webathena': user_profile.realm.webathena_enabled,
'enable_feedback': settings.ENABLE_FEEDBACK, 'enable_feedback': settings.ENABLE_FEEDBACK,
'embedded': narrow_stream is not None, 'embedded': narrow_stream is not None,

View File

@@ -451,12 +451,11 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
raise ZulipLDAPException("Realm has been deactivated") raise ZulipLDAPException("Realm has been deactivated")
if return_data.get("inactive_user"): if return_data.get("inactive_user"):
raise ZulipLDAPException("User has been deactivated") raise ZulipLDAPException("User has been deactivated")
if return_data.get("invalid_subdomain"): # An invalid_subdomain `return_data` value here is ignored,
# TODO: Implement something in the caller for this to # since that just means we're trying to create an account in a
# provide a nice user-facing error message for this # second realm on the server (`ldap_auth_enabled(realm)` would
# situation (right now it just acts like any other auth # have been false if this user wasn't meant to have an account
# failure). # in this second realm).
raise ZulipLDAPException("Wrong subdomain")
if self._realm.deactivated: if self._realm.deactivated:
# This happens if no account exists, but the realm is # This happens if no account exists, but the realm is
# deactivated, so we shouldn't create a new user account # deactivated, so we shouldn't create a new user account