Compare commits

...

132 Commits

Author SHA1 Message Date
Tim Abbott
d22cb7d01f Release Zulip Server 2.1.3. 2020-04-01 13:35:31 -07:00
Anders Kaseorg
76ce370181 frontend: Defensively filter unsafe links that may come from bugdown.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-01 13:35:31 -07:00
Anders Kaseorg
64856d858e CVE-2020-10935: Fix XSS vulnerability in local link rewriting.
Make sure rewrite_local_links_to_relative does not accidentally change
the meaning of links.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-01 13:35:31 -07:00
Anders Kaseorg
c9796ba7f7 CVE-2020-9444: Prevent reverse tabnabbing attacks.
While we could fix this issue by changing the markdown processor,
doing so is not a robust solution, because even a momentary bug in the
markdown processor could allow cached messages that do not follow our
security policy.

This change ensures that even if our markdown processor has bugs that
result in rendered content that does not properly follow our policy of
using rel="noopener noreferrer" on links, we'll still do something
reasonable.

Co-authored-by: Tim Abbott <tabbott@zulipchat.com>
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-01 13:35:31 -07:00
Tim Abbott
b21117954d CVE-2020-9445: Remove unused and insecure modal_link feature.
Zulip's modal_link markdown feature has not been used since 2017; it
was a hack used for a 2013-era tutorial feature and was never used
outside that use case.

Unfortunately, it's sloppy implementation was exposed in the markdown
processor for all users, not just the tutorial use case.

More importantly, it was buggy, in that it did not validate the link
using the standard validation approach used by our other code
interacting with links.

The right solution is simply to remove it.
2020-04-01 13:35:31 -07:00
Mateusz Mandera
59f5ca713f auth: Fix error on startup in django-two-factor-auth in Django 2.1+.
https://github.com/Bouke/django-two-factor-auth/issues/297
This setting was added in 1.9 version of the app and can be used
harmleslly in our current Django 1.11-based code and will prevent an
error on Django 2.1+ when we move there.
2020-04-01 13:35:31 -07:00
Tim Abbott
67da8e8431 version: Move minimum desktop version configuration to version.py.
This makes it relatively easy for a system administrator to
temporarily override these values after a desktop app security
release that they want to ensure all of their users take.

We're not putting this in settings, since we don't want to encourage
accidental long-term overrides of these important-to-security values.
2020-04-01 13:23:53 -07:00
Mateusz Mandera
b79fbf9239 requirements: Bump python-social-auth to 3.3.2. 2020-03-26 23:35:56 +00:00
Tom Daff
f1f937e4ea monitoring: Fix check-rabbitmq-consumers.
Missing commas in the definition of all the queues to check meant that it would be looking for queues with concatenated names, rather than the correct ones. Added the commas.
2020-03-25 17:19:55 -07:00
Chris Heald
68628149db integrations: Add AlertManager webhook. 2020-03-25 11:39:05 -07:00
Anders Kaseorg
f247721a2d tests: Fix test_banned_desktop_app_versions for 2.1.x.
ZulipTestCase.login_user was not added until commit
1b16693526 (#14176).

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-03-25 00:39:55 -07:00
Tim Abbott
e3d6b4f210 compatibility: Add more strict desktop app blocking.
This allows us to block use of the desktop app with insecure versions
(we simply fail to load the Zulip webapp at all, instead rendering an
error page).

For now we block only versions that are known to be both insecure and
not auto-updating, but we can easily adjust these parameters in the
future.
2020-03-24 20:35:21 -07:00
YashRE42
ea8e6149da templates: Extract navbar_alerts to seperate file.
This is a prep-commit for the new navbar style, seperating
navbar_alerts.html from navbar.html in order to make the structure and
styles of navbar.html  easier to tweak.
These templates have very little to do with each other to begin with
appart from the fact that they are both rendered at the top of the app.
2020-03-24 20:35:17 -07:00
Rohitt Vashishtha
376cd88a83 tests: Treat github.com/zulip links as external.
Tests for these links often result in rate-limiting from GitHub,
leading to the builds failing in Circle CI. We temporarily mark
github.com/zulip links as external to keep the builds passing.
2020-03-19 17:26:53 +01:00
Mateusz Mandera
bfd92260fd requirements: Bump python-social-auth version. 2020-03-19 16:58:57 +01:00
Mateusz Mandera
217431d0c4 auth: Monkey patch a fix for Github deprecation notice spam.
This is a way to monkey-patch a fix for
https://github.com/python-social-auth/social-core/issues/430
Changes from this commit should be reverted once the issue is fixed
upstream.
2020-03-03 15:49:18 -08:00
Mateusz Mandera
30cc6798b3 auth: Fix Github auth with organization/team membership restriction.
We need to request access to read:org scope to be able to check org/team
membership. Without it SOCIAL_AUTH_GITHUB_ORG_NAME and
SOCIAL_AUTH_GITHUB_TEAM_ID settings don't work and simply lead to all
auth attempts failing.
Tested manually.
2020-03-01 15:30:10 +01:00
Tim Abbott
677ad69555 docs: Update draft changelog for 2.1.3. 2020-02-28 17:06:22 -08:00
Mateusz Mandera
95118d860d home: Don't assume user agent header is set for insecure_desktop_app.
The header may not be set - this leads to CI failures on 2.1.x branch,
but in any case is a real bug.
2020-02-28 17:01:26 -08:00
Tim Abbott
b8888c801b panels: Show a banner for users with legacy desktop apps.
Users who are using ZulipDesktop or haven't managed to auto-update to
ZulipElectron should be strongly encouraged to upgrade.

We'll likely want to move to something even stricter that blocks
loading the app at all, but this is a good start.
2020-02-28 05:29:25 -08:00
Vishnu KS
7a9251a3e1 actions: Make do_change_plan_type support changing plan to SELF_HOSTED.
Credits to @xpac1985 for reporting, debugging and proposing fix to the
issue. The proposed fix was modified slightly by @hackerkid to set the
correct value for max_invites and upload_quota_gb. Tests added by
@hackerkid.

Fixes #13974
2020-02-25 16:16:48 -08:00
Pragati Agrawal
64ec413940 settings user groups: Fix organization admin can not create user groups.
The bug was in complex `if` condition, which should mean that users should
be allowed to create a User group only when they are either admin or user
group creation policy is set to everyone.

Fixes: #13909.
2020-02-24 12:16:36 -08:00
Mateusz Mandera
147c3998de tests: Adjust failing test on 2.1.x branch.
The KeyError is getting formatted a bit differently on the 2.1.x branch.
2020-02-24 12:11:59 -08:00
Mateusz Mandera
79fc9c3281 saml: Add SOCIAL_AUTH_SAML_SECURITY_CONFIG to default_settings.
SOCIAL_AUTH_SAML_SECURITY_CONFIG["authnRequestsSigned"] override in
settings.py in a previous commit wouldn't work on servers old enough to
not have the SAML settings in their settings.py - due to
SOCIAL_AUTH_SAML_SECURITY_CONFIG being undefined.
This commit fixes that.
2020-02-21 09:30:36 -08:00
Mateusz Mandera
a33d7f0400 saml: Make the bad idp param KeyError log message more verbose.
Original idea was that KeyError was only going to happen there in case
of user passing bad input params to the endpoint, so logging a generic
message seemed sufficient. But this can also happen in case of
misconfiguration, so it's worth logging more info as it may help in
debugging the configuration.
2020-02-20 14:49:41 -08:00
Mateusz Mandera
2471f6ad83 saml: Use rsa-sha256 as the default signature algorithm.
python3-saml uses the insecure rsa-sha1 as default.
2020-02-20 14:47:51 -08:00
Vishnu KS
19d1ca3a1d management: Make backup command work when DB is not in localhost.
This is useful preparatory work for supporting the backup management
command inside docker-zulip.
2020-02-19 14:22:47 -08:00
Anders Kaseorg
9fcbc3a49b puppet: Fix regeneration of memcached-sasldb2 on password changes.
Puppet doesn’t re-run an exec blocks that’s declared as creating an
existing file, even if it’s notified.  Remove the creates declaration.

Fixes #13730.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-19 14:21:39 -08:00
Anders Kaseorg
1413fda773 restore-backup: Run generate_secrets.py.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-19 14:21:38 -08:00
Tim Abbott
494e596be8 Draft release notes for 2.1.3. 2020-02-19 12:28:27 -08:00
Tim Abbott
4cc25f8e84 i18n: Add missing translation tags to typing notifications.
Thanks to Andrea Soc for the report.
2020-02-19 12:28:27 -08:00
Tim Abbott
19ab295172 email_notifications: Fix missing translation tags on sender. 2020-02-19 12:28:26 -08:00
Tim Abbott
31f02cd926 test_fixtures: Fix buggy reuse of status_dir between databases.
Apparently, the arguments passed to template_database_status were
incorrect for the manual testing development database, in that we
didn't pass a status_dir when calling into that code from provision.

The result was that provisioning before running `test-backend` would
ignore changes to the list of check_files (etc.) made after rebasing,
and vice versa.

The cleanest fix is to compute status_dir from other values passed in;
I'm also going to open a follow-up issue for creating a better overall
interface here.
2020-02-19 12:28:26 -08:00
Tim Abbott
266c7c83e0 test_fixtures: Note populate_db depends on server_initialization.py.
This should ensure that folks rebasing past this commit from an older
database model get their database rebuilt in the way that will
match the test_subs.py query count of 40.
2020-02-19 12:08:55 -08:00
Hemanth V. Alluri
dd198fd06e webhooks/ansibletower: Update for AWX 9.1.1.
Add a simple compatibility function for AWX 9.x.x. Before AWX 9.x.x
a "friendly_name" key was sent by default. Afterwards it was removed
from being a default key but we can still more or less determine if
the triggering event was a job from the REST-style URL.

Note: It is also technically possible to add the key back by defining
a custom notification template in AWX/Tower.

Resolves #13295.
2020-02-19 12:08:55 -08:00
xpac1985
10e8928b0f docs: Add info about zulip-announce RSS feed to install docs.
The mailing list can also be subscribed to via RSS/Atom feeds, I just wanted to make that information easier accessible.
2020-02-19 12:08:55 -08:00
Ray Kraesig
bc81275d3c register: Ensure future client_capabilities fields are optional.
The `notification_settings_null` field of the `client_capabilities`
parameter is, apparently unintentionally, required.

This is mostly harmless. However, if any _future_ fields are made
required, all existing clients using this parameter will break, and it
will be needlessly difficult for new clients to specify new
capabilities in a backwards-compatible way.

Attempt to stave that possibility off with warnings.

(No functional changes.)
2020-02-19 12:08:55 -08:00
Tim Abbott
6c8c3cd3dc settings: Fix copy-from-clipboard behavior for bot tokens.
We do this by cleaning up the API for generate_zuliprc_content,
allowing us to deduplicate the previously incorrect code.
2020-02-19 12:08:55 -08:00
Vishnu KS
1783515794 emails: Use the word email instead of message in do not reply sentence.
Fixes #13693
2020-02-19 12:08:55 -08:00
Vishnu KS
21026d984b emails: Remove unecessary call to message_content_allowed_in_missedmessage_emails. 2020-02-19 12:08:55 -08:00
Vishnu KS
66fe724c8a emails: Show proper message when email content is not shown. 2020-02-19 12:08:55 -08:00
Vishnu KS
282d6edf2e tests: Check whether body include multiple strings in _test_cases. 2020-02-19 12:08:55 -08:00
Mateusz Mandera
785a7ec9e7 email_mirror: Handle encoded attachment filenames. 2020-02-19 12:08:55 -08:00
Mateusz Mandera
c44d9f9b1b email_mirror: Extract handle_header_content function. 2020-02-19 12:08:55 -08:00
Tim Abbott
0d5d3c4912 email_mirror: Rewrite docstrings to focus on current reality.
These docstrings hadn't been properly updated in years, and bad an
awkward mix of a bad version of the user-facing documentation and
details that are no longer true (e.g. references to "Voyager").

(One important detail is that we have real documentation for this
system now).
2020-02-19 12:08:55 -08:00
Mateusz Mandera
ef793590c1 email_mirror: Parse encoded From headers with show_sender=True. 2020-02-19 12:08:55 -08:00
Tim Abbott
3032ba15cf soft_deactivation: Fix incorrect logging function.
Using logging.info() rather than logger.info() meant that our
zulip.soft_deactivation logger configuration (which, in particular,
included not logging to the console) was not active on this log line,
resulting in the `manage.py soft_deactivate_users` cron job sending
emails every time it ran.

Fixes #13750.
2020-02-19 12:08:55 -08:00
Tim Abbott
96a2ddffe7 docs: Add link from LDAP docs to invitation docs.
This addresses confusion we had with some organizations where they
were surprised that with only LDAP enabled, the "invite more users"
feature was available.

Fixes #11685.
2020-02-19 12:08:55 -08:00
Tim Abbott
2794362214 slack import: Fix handling of messages sent by user U00. 2020-02-19 12:08:55 -08:00
Vishnu KS
9b3e1e2c97 emails: Set alt attribute to empty for leading images.
The alt text of the leading images were displayed as preview
content in inbox by email clients like gmail. Since the leading
images were used mostly for decoration this made the preview
content gibberish. It's fine to set the alt attributes to empty
from accessibility point of view since the old alt attributes
did't added any meaningful information.
2020-02-19 12:08:55 -08:00
orientor
ae44fdd7cc settings: Fix buggy emoji format loading spinner.
When a user clicked the current emoji format in "display settings",
we'd show an infinite loading spinner (basically as a side effect of
trying to tell the server to change the emoji format to what it
already was).

Fix this by aborting early if the emoji format is already the option
that the user clicked.

Fixes #13684.
2020-02-19 12:08:55 -08:00
Tim Abbott
b45cce61e7 message_list_view: Fix handling of links to deleted streams.
Previously, links to deleted streams would be incorrectly rendered as
stream's name).

Fixes an issue that was reported where after deleting the "general"
stream, the welcome turtle messages might appear as links to
2020-02-19 12:08:55 -08:00
Tim Abbott
2e923a0eb5 slack import: Improve error messages around invalid tokens.
This updates our error handling of invalid Slack API tokens (and other
networking error handling) to mostly make sense:
* A token that doesn't start with `xoxp-` gives an extended error early.
* An AssertionError for the codebase is correctly declared as such.
* We check for token shape errors before querying the Slack API.

We could still do useful work to raise custom exception classes here.

Thanks to @stavrospat for raising this issue.
2020-02-19 12:08:55 -08:00
Mateusz Mandera
f538f34d95 email_mirror: Use .walk() to search all MIME parts for attachments.
Fixes #13416

We used to search only one level in depth through the MIME structure,
and thus would miss attachments that were nested deeper (which can
happen with some email clients). We can take advantage of message.walk()
to iterate through each MIME part.
2020-02-19 12:08:55 -08:00
Mateusz Mandera
5d2befdc54 send_to_email_mirror: Fix loop setting recipient-like headers.
return in that loop was a bug, which would lead to the To: header not
being set even though data['recipient'] = str(message['To']) is being
run next, thus requiring the header. We can remove the return
statement and now the loop will overwrite all the potentially
troublesome headers.
2020-02-19 12:08:55 -08:00
Mateusz Mandera
cc8b83b261 email_mirror: Insert a new line before attachment links. 2020-02-19 12:08:55 -08:00
Mateusz Mandera
ac8f4aaa93 email_mirror: Check address usability in get_missed_message_address. 2020-02-19 12:08:55 -08:00
Mateusz Mandera
843c148c59 email_mirror: Give extract_and_validate a more descriptive name. 2020-02-19 12:08:55 -08:00
Mateusz Mandera
d39bcf2264 email_mirror: Reuse exception messages in mirror_email_message. 2020-02-19 12:08:55 -08:00
Tim Abbott
ce64a6b163 default stream groups: Fix broken registration UI.
The default stream groups feature (#6693) was never fully implemented;
this fixes a key detail (the registration UI being broken).
2020-02-19 12:08:55 -08:00
Tim Abbott
7875196783 default stream groups: Fix buggy LDAP behavior.
With LDAP authentication, we don't currently have a good way to
support the default stream groups feature.

The old behavior was just to assume a user select every default stream
group, which seems wrong; since we didn't prompt the user about these,
we should just ignore the feature.
2020-02-19 12:08:55 -08:00
Mateusz Mandera
56c1ad1a3d install: Don't create internal realm in the installation process. 2020-02-19 17:05:28 +01:00
Tim Abbott
d9aa4161f8 install: Remove references to "Zulip Voyager".
"Zulip Voyager" was a name invented during the Hack Week to open
source Zulip for what a single-system Zulip server might be called, as
a Star Trek pun on the code it was based on, "Zulip Enterprise".

At the time, we just needed a name quickly, but it was never a good
name, just a placeholder.  This removes that placeholder name from
much of the codebase.  A bit more work will be required to transition
the `zulip::voyager` Puppet class, as that has some migration work
involved.
2020-02-19 17:00:17 +01:00
Mateusz Mandera
728155afee server_initialization: Add server_initialized function. 2020-02-19 16:59:56 +01:00
Mateusz Mandera
660501c782 test_classes: Fix bug where UserProfile could be passed to client_post.
It would cause JSON overflow error while producing URL coverage report.
2020-02-19 16:59:14 +01:00
Mateusz Mandera
ad974c3ae3 initialize_voyager_db: Deduplicate create_internal_realm logic.
zerver.lib.server_initialization.create_internal has precisely the same
code (you can copy-and-paste swap them, with one level of indentation
adjustment, without generating any diff) so they can be trivially
deduplicated.
2020-02-19 16:57:44 +01:00
Mateusz Mandera
bc4029deae initialize_voyager_db: Deduplicate create_users.
zerver.lib.server_initialization.create_users has precisely the same
code (you can copy-and-paste swap them without generating any diff) so
they can be trivially deduplicated.
2020-02-19 16:54:39 +01:00
Mateusz Mandera
218ca61dd0 server_initialization: Rename some variables.
This makes the code of create_internal_realm identical to the
corresponding block in initialize_voyager_db.py.
2020-02-19 16:43:18 +01:00
Mateusz Mandera
3419908f39 initialize_voyager_db: Add comment above default client creation block. 2020-02-19 16:42:57 +01:00
Mateusz Mandera
af67990f14 server_initialization: Set internal bots owners to themselves. 2020-02-19 16:42:39 +01:00
Mateusz Mandera
e6cf30fc22 server_initialization: Remove unnecessary type annotation. 2020-02-19 16:42:15 +01:00
Mateusz Mandera
e2ccbe7c80 initialize_voyager_db: Add bot_owner argument to create_users.
This doesn't change any behavior, the purpose of this is to make the
function identical to what we have in server_initialization.py so that
it can be deduplicated in follow-up commits.
2020-02-19 16:41:56 +01:00
Mateusz Mandera
8b31387670 server_initialization: Use tos_version argument in create_users. 2020-02-19 16:41:31 +01:00
Mateusz Mandera
501eb09716 populate_db: Extract default client creation to server_initialization. 2020-02-19 16:25:30 +01:00
Mateusz Mandera
280d9db26d populate_db: Extract some functions to server_initialization.py. 2020-02-19 16:23:51 +01:00
Vishnu KS
cee6227f53 bots: Remove feedback cross realm bot.
This completes the remaining pieces of removing this missed in
d70e799466 (mostly in tests).
Backported to 2.1.x branch.
2020-02-19 16:21:02 +01:00
Mateusz Mandera
cae803e8a9 bots: Remove FEEDBACK_BOT implementation.
This legacy cross-realm bot hasn't been used in several years, as far
as I know.  If we wanted to re-introduce it, I'd want to implement it
as an embedded bot using those common APIs, rather than the totally
custom hacky code used for it that involves unnecessary queue workers
and similar details.
Backported to the 2.1.x branch.
2020-02-19 15:26:08 +01:00
Tim Abbott
ba598366e9 Release Zulip Server 2.1.2. 2020-01-16 12:26:14 -08:00
Steve Howell
d452ad31e0 server: Sort user_ids in recent PM conversations.
This change should prevent test flakes, plus
it's more deterministic behavior for clients,
who will generally comma-join the ids into
a key for their internal data structures.

I was able to verify test coverage on this
by making the sort reversed, which would
cause test_huddle_send_message_events to
fail.
2020-01-16 12:25:11 -08:00
Steve Howell
aed813f44c bug fix: Fix huddles in "Private Messages".
If two user_ids in a recent huddle have ids
that sort lexically differently than numerically,
such as 7 and 66, then we were creating two
different buckets in pm_conversations.

This regression was introduced in
263ac0eb45 on
November 21, 2019.
2020-01-16 12:25:11 -08:00
Steve Howell
71dae1b92a refactor: Have pm_conversations take user_ids.
Instead of having our callers pass in a possibly
non-canonical version of a user_ids_string, just
have them pass in a list.

The next commit will canonicalize the sort.
2020-01-16 12:25:11 -08:00
Steve Howell
629ec1aa8b tests: Use tricky server data in unit tests.
The server may send us ids in the order
[11, 2], instead of [2, 11].  We don't want
to rely on server behavior, regardless, for
the sort.

Our tests now show we process that data.

The current code is is still buggy and causes
us to show the same huddle two different times
for situations where the lexical sort doesn't
match the numerical sort.

This happens on czo often, where Tim is user
7, and his id sorts lexically after ids like
58, 622, 4444, etc.
2020-01-16 12:25:11 -08:00
Anders Kaseorg
87d60a1fff thumbnail: Tighten fix for CVE-2019-19775 open redirect.
Due to a known but unfixed bug in the Python standard library’s
urllib.parse module (CVE-2015-2104), a crafted URL could bypass the
validation in the previous patch and still achieve an open redirect.

https://bugs.python.org/issue23505

Switch to using django.utils.http.is_safe_url, which already contains
a workaround for this bug.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-16 11:41:12 -08:00
Tim Abbott
98eef54e4f i18n: Update translation data from Transifex. 2020-01-16 11:41:12 -08:00
Tim Abbott
235ba339d0 filter: Allow marking is:mentioned messages as read.
We may revisit this in the future, but similar to is:private, the
current Zulip user experience makes users expect that in the
is:mentioned view, they should really be able to mark messages as
read.

Further, the practice use case for not marking them as read is very
low, since it's rare for someone to have so many mentions that
revisiting the mentions view isn't sufficient to see everything that
needs their attention.
2020-01-16 11:15:46 -08:00
Tim Abbott
e5320cc1f6 filter: Add streams:public to sorted_term_types.
This is for consistency with in:, has:, and similar values where
there's a fixed set of RHS entries.
2020-01-16 11:15:04 -08:00
Rohitt Vashishtha
1d72ea2fd5 filter: Remove is_exactly().
Previously, is_exactly() had already been repalced with can_bucket_by().
This commit removes is_exactly() and replaces its usage in our tests
with can_bucket_by().
2020-01-16 11:14:44 -08:00
Steve Howell
c7948a7960 filter: Remove redundant is:private operators.
If we have a pm-with, then is:private is redundant
and just forces us to write confusing/verbose code
in various places.
2020-01-16 11:14:18 -08:00
Rohitt Vashishtha
04bb26be3a unreads: Remove is_reading_mode().
This was a part of an experiment we ran on chat.zulip.org in Jul 2018
and surrounding code that used it never got merged to master.

See: https://chat.zulip.org/#narrow/stream/2-general/topic/un-narrow.20view/near/609506
and c407ba5175.
2020-01-16 11:13:34 -08:00
Rohitt Vashishtha
7f45ca9b22 filter: Add 'in:*' to sorted_term_types.
This simplifies our handling of in-home and in-all cases in
can_mark_messages_read().
2020-01-16 11:13:16 -08:00
Steve Howell
1bedb965e9 refactor: Clean up can_mark_messages_read.
We now explicitly enumerate various cases, which
should make it easier to change this code.
2020-01-16 11:13:11 -08:00
Anders Kaseorg
bc752188e7 create-db.sql: Start by dropping the zulip database if needed.
At some point the PostgreSQL Docker image started creating the zulip
database for us, which caused our CREATE DATABASE to fail.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-15 18:12:35 -08:00
Anders Kaseorg
b0ea81fe16 create-db.sql: Handle exception if zulip user already exists.
Fixes #13530.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-15 18:12:31 -08:00
Anders Kaseorg
358ab821c4 generate_secrets: Enable redis authentication in production.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-15 14:03:15 -08:00
Anders Kaseorg
97322dd195 generate_secrets: Enable memcached authentication in production.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-15 14:03:15 -08:00
Anders Kaseorg
1ba48a04da settings: Support optional memcached authentication.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-15 14:03:15 -08:00
Tim Abbott
e8377b605f migrations: Fix zulipinternal migration corner case.
It's theoretically possible to have configured a Zulip server where
the system bots live in the same realm as normal users (and may have
in fact been the default in early Zulip releases?  Unclear.).  We
should handle these without the migration intended to clean up naming
for the system bot realm crashing.

Fixes #13660.
2020-01-15 14:02:07 -08:00
Tim Abbott
830f1e9f3f populate_db: Fix cache flushing when rebuilding test database.
This fixes a similar problem to the last commit; we don't use
memcached with the test database, so we don't need to flush memcached
when rebuilding it.

(And if we try, we'll get exceptions trying to access the relevant
settings).
2020-01-13 18:23:48 -08:00
Tim Abbott
037b87b580 populate_db: Fix handling of memcached flushing.
Our recent fixes to using the system's configured memcached settings
broke populate_db, because its hacky clear_database helper is called
with a hacked-up settings module.

We fix this by first moving this out-of-place code from models.py into
populate_db, and then saving the settings required to access memcached
so that we can use them in clear_database.

We also fix a mypy erorr in flush-memcached that matches the same
issue fixed in clear_database.
2020-01-13 18:23:44 -08:00
Anders Kaseorg
82a6e77301 flush-memcached: Use pylibmc.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-13 17:39:25 -08:00
Anders Kaseorg
9efb90510c clear_database: Respect MEMCACHED_LOCATION.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-13 17:39:22 -08:00
Anders Kaseorg
b255c8b8a6 puppet: Fix zuli-redis.conf path typo.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-13 17:37:28 -08:00
Anders Kaseorg
03e8e8be9d puppet: Delete legacy rediscleanup code.
It was added in commit 9afb1c7a71 from
before 1.4.0.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-13 17:37:25 -08:00
Ray Kraesig
2932d9cd28 docs: link to more-currently-maintained fork of GitX
The well-known rowanj/gitx repository hasn't been updated since 2014.
Preferentially direct new contributors to gitx/gitx instead.

(We retain the rowanj repo as a fallback, since it has precompiled
releases available.)
2020-01-13 17:36:47 -08:00
Tim Abbott
0baa205ad3 find_team: Send find team emails from the support address.
This is for consistency with the email's body, which claims replying
directly will work.
2020-01-13 17:36:37 -08:00
Tim Abbott
a8d8500c46 design: Fix missing rendered_markdown class on /me content.
There may be a deeper issue that various JavaScript logic expects
every message to have a `.message_content` element, but we definitely
should have the `.rendered_markdown` class on any markdown content.

Fixes #13634.
2020-01-13 17:36:20 -08:00
Mateusz Mandera
aa19f43f0b email_mirror: Move send_to_mm_address code to process_missed_message.
process_missed_message did nothing other than calling
send_to_missed_message_address with the same arguments, so there's no
reason to have these as separate functions.
2020-01-13 17:35:41 -08:00
Mateusz Mandera
0974b0130d email_mirror: Migrate missed message addresses from redis to database.
Addresses point 1 of #13533.

MissedMessageEmailAddress objects get tied to the specific that was
missed by the user. A useful benefit of that is that email message sent
to that address will handle topic changes - if the message that was
missed gets its topic changed, the email response will get posted under
the new topic, while in the old model it would get posted under the
old topic, which could potentially be confusing.

Migrating redis data to this new model is a bit tricky, so the migration
code has comments explaining some of the compromises made there, and
test_migrations.py tests handling of the various possible cases that
could arise.
2020-01-13 17:35:37 -08:00
Mateusz Mandera
8a1d2bb5b6 models: Add MissedMessageEmailAddress class.
Preparatory commit for making the email mirror use the database instead
of redis for missed message addresses.

This model will represent missed message email addresses, which
currently have their data stored in redis.
The redis data will be converted and migrated into these models and
the email mirror will start using them in the main commit.
2020-01-13 17:35:34 -08:00
Tim Abbott
a38976f25d slack import: Clarify confusion around xoxe- tokens. 2020-01-13 17:35:25 -08:00
Anders Kaseorg
fccfc02981 install: Run generate_secrets.py before zulip-puppet-apply.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-01-13 17:35:16 -08:00
Mateusz Mandera
929847ae2d test_helpers: Set Recipient class attrs in use_db_models.
Model classes fetched through apps.get_model don't get methods or class
attributes. It's not feasible to add them to all these objects in
use_db_models, but Recipient.PERSONAL etc. are worth setting, since
doing that increases the range of functions that can successfully be
imported and called in test_migrations.py.
2020-01-13 17:34:40 -08:00
Mateusz Mandera
a3338f3735 test_email_notifs: Clean up mocking.
These tests had a lot of very repetetive, identical mocking, in some
tests without even doing anything with the mocks. It's cleaner to put
the mock in the one relevant, common place for all the tests that need
it, and remove it from tests who had no use for the mocking.
2020-01-13 17:34:04 -08:00
Mateusz Mandera
f377ef6dd7 api: Return a JsonableError if API key of invalid format is given. 2020-01-13 17:34:01 -08:00
Mateusz Mandera
4c9997a523 utils: Add a function to check if string can be an API key. 2020-01-13 17:33:48 -08:00
Mateusz Mandera
2470fba95c cache: Validate keys before passing them to memcached.
Fixes #13504.

This commit is purely an improvement in error handling.

We used to not do any validation on keys before passing them to
memcached, which meant for invalid keys, memcached's own key
validation would throw an exception.  Unfortunately, the resulting
error messages are super hard to read; the traceback structure doesn't
even show where the call into memcached happened.

In this commit we add validation to all the basic cache_* functions, and
appropriate handling in their callers.

We also add a lot of tests for the new behavior, which has the nice
effect of giving us decent coverage of all these core caching
functions which previously had been primarily tested manually.
2020-01-13 17:33:41 -08:00
Mateusz Mandera
2a6145f7fb default_settings: Fix inaccurate "below" phrase in comments.
These are leftovers from where we had default settings in the
settings.py file. Now that the files are separate those references to
"below" are not correct.
2020-01-03 16:54:23 -08:00
Mateusz Mandera
7036fea97b docs: Fix missing apostrophe in EMAIL_HOST_USER value. 2020-01-03 16:54:21 -08:00
Mateusz Mandera
05a42fb8df docs: Fix incorrect path to get-django-setting script. 2020-01-03 16:54:17 -08:00
Mateusz Mandera
cd0b14ce2f docs: Add some troubleshooting notes for ldap. 2020-01-03 16:54:15 -08:00
Mateusz Mandera
a1fc8fb079 ldap: Protect against troublesome deactivations in ldap sync.
If ldap sync is run while ldap is misconfigured, it can end up causing
troublesome deactivations due to not finding users in ldap -
deactivating all users, or deactivating all administrators of a realm,
which then will require manual intervention to reactivate at least one
admin in django shell.
This change prevents such potential troublesome situations which are
overwhelmingly likely to be unintentional. If intentional, --force
option can be used to remove the protection.
2020-01-03 16:54:11 -08:00
Mateusz Mandera
e147ee2087 docs: Include suggested USERNAME_ATTR in example AD ldap configs. 2020-01-03 16:54:08 -08:00
Mateusz Mandera
61180020c1 ldap: Improve logging.
Our ldap integration is quite sensitive to misconfigurations, so more
logging is better than less to help debug those issues.
Despite the following docstring on ZulipLDAPException:

"Since this inherits from _LDAPUser.AuthenticationFailed, these will
be caught and logged at debug level inside django-auth-ldap's
authenticate()"

We weren't actually logging anything, because debug level messages were
ignored due to our general logging settings. It is however desirable to
log these errors, as they can prove useful in debugging configuration
problems. The django_auth_ldap logger can get fairly spammy on debug
level, so we delegate ldap logging to a separate file
/var/log/zulip/ldap.log to avoid spamming server.log too much.
2019-12-30 10:24:01 -08:00
Mateusz Mandera
2a473c57f4 ldap: Use a cleaner super().authenticate() call in ZulipLDAPAuthBackend. 2019-12-30 10:23:58 -08:00
Anders Kaseorg
c0980e3e9e templates: Correct sample Google authorized redirect URI.
The required URI was changed in #11450.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-12-30 10:23:43 -08:00
Anders Kaseorg
035d4c57be test-install: Use lxc-destroy -f instead of lxc-stop.
Fixes this error after rebooting the host:

$ sudo ./destroy-all  -f
zulip-install-bionic-41MM2
lxc-stop: zulip-install-bionic-41MM2: tools/lxc_stop.c: main: 191 zulip-install-bionic-41MM2 is not running

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-12-30 10:23:39 -08:00
Anders Kaseorg
fcbd24e72c test-install: Run lxc-attach with --clear-env.
The host environment variables (especially PATH) should not be allowed
to pollute the test and could interfere with it.

This allows test-install to run on a NixOS host.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-12-30 10:23:35 -08:00
Tim Abbott
29babba85a ldap: Fix bad interaction between EMAIL_ADDRESS_VISIBILITY and LDAP sync.
A block of LDAP integration code related to data synchronization did
not correctly handle EMAIL_ADDRESS_VISIBILITY_ADMINS, as it was
accessing .email, not .delivery_email, both for logging and doing the
mapping between email addresses and LDAP users.

Fixes #13539.
2019-12-30 10:23:18 -08:00
Tim Abbott
49ff894d6a Release Zulip Server 2.1.1. 2019-12-13 16:36:21 -08:00
Tim Abbott
f3e75b6b5f docs: Rewrite LDAP discussion of AUTH_LDAP_REVERSE_EMAIL_SEARCH.
This moves the mandatory configuration for options A/B/C into a single
bulleted list for each option, rather than split across two steps; I
think the result is significantly more readable.

It also fixes a bug where we suggested setting
AUTH_LDAP_REVERSE_EMAIL_SEARCH = AUTH_LDAP_USER_SEARCH in some cases,
whereas in fact it will never work because the parameters are
`%(email)s`, not `%(user)s`.

Also, now that one needs to set AUTH_LDAP_REVERSE_EMAIL_SEARCH, it
seems worth adding values for that to the Active Directory
instructions.  Thanks to @alfonsrv for the suggestion.
2019-12-13 16:32:56 -08:00
Vishnu KS
6b9f37dc8f install: Use crudini for storing value of POSTGRES_MISSING_DICTIONARIES.
This simplifies the RDS installation process to avoid awkwardly
requiring running the installer twice, and also is significantly more
robust in handling issues around rerunning the installer.

Finally, the answer for whether dictionaries are missing is available
to Django for future use in warnings/etc. around full-text search not
being great with this configuration, should they be required.
2019-12-13 16:32:48 -08:00
Mateusz Mandera
cd926b8aae migrations: Avoid triggering backend initalization in migration 0209.
Fixes #13528.
The email_auth_enabled check caused all enabled backends to get
initialized, and thus if LDAP was enabled the check_ldap_config()
check would cause an error if LDAP was misconfigured
(for example missing the new settings).
2019-12-13 10:57:38 -08:00
186 changed files with 3429 additions and 2119 deletions

View File

@@ -39,7 +39,7 @@ something goes wrong, this helps you figure out when and why.
If you don't already have one installed, here are some suggestions:
- macOS: [GitX-dev][gitgui-gitxdev]
- macOS: [GitX][gitgui-gitx] (previously [GitX-dev][gitgui-gitxdev])
- Ubuntu/Linux: [git-cola][gitgui-gitcola], [gitg][gitgui-gitg], [gitk][gitgui-gitk]
- Windows: [SourceTree][gitgui-sourcetree]
@@ -61,6 +61,7 @@ And, if none of the above are to your liking, try [one of these][gitbook-guis].
[gitgui-gitcola]: http://git-cola.github.io/
[gitgui-gitg]: https://wiki.gnome.org/Apps/Gitg
[gitgui-gitk]: https://git-scm.com/docs/gitk
[gitgui-gitx]: https://github.com/gitx/gitx/
[gitgui-gitxdev]: https://rowanj.github.io/gitx/
[gitgui-sourcetree]: https://www.sourcetreeapp.com/
[github-help-add-ssh-key]: https://help.github.com/en/articles/adding-a-new-ssh-key-to-your-github-account

View File

@@ -7,6 +7,69 @@ 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.3 -- 2020-04-01
- CVE-2020-9444: Prevent reverse tabnapping attacks.
- CVE-2020-9445: Remove unused and insecure modal_link feature.
- CVE-2020-10935: Fix XSS vulnerability in local link rewriting.
- Blocked access from Zulip Desktop versions below 5.0.0. This
behavior can be adjusted by editing `DESKTOP_*_VERSION`
in `/home/zulip/deployments/current/version.py`.
- Restructured server initialization to simplify initialization of
Docker containers (eliminating common classes of user error).
- Removed buggy feedback bot (`ENABLE_FEEDBACK`).
- Migrated GitHub authentication to use the current encoding.
- Fixed support for restoring a backup on a different minor release
(in the common case they have the same database schema).
- Fixed restoring backups with memcached authentication enabled.
- Fixed preview content (preheaders) for many emails.
- Fixed buggy text in missed-message emails with PM content disabled.
- Fixed buggy loading spinner in "emoji format" widget.
- Fixed sorting and filtering users in organization settings.
- Fixed handling of links to deleted streams.
- Fixed check-rabbitmq-consumers monitoring.
- Fixed copy-to-clipboard button for outgoing webhook bots.
- Fixed logging spam from soft_deactivation cron job.
- Fixed email integration handling of emails with nested MIME structure.
- Fixed unicode bugs in incoming email integration.
- Fixed error handling for Slack data import.
- Fixed incoming webhook support for AWX 9.x.y.
- Fixed a couple missing translation tags.
- Fixed "User groups" settings UI bug for administrators.
- Fixed data import tool to reset resource limits after importing
data from a free plan organization on zulipchat.com.
- Changed the SAML default signature algorithm to SHA-256, overriding
the SHA-1 default used by python3-saml.
### 2.1.2 -- 2020-01-16
- Corrected fix for CVE-2019-19775 (the original fix was affected by
an unfixed security bug in Python's urllib, CVE-2015-2104).
- Migrated data for handling replies to missed-message emails from
semi-persistent redis to the fully persistent database.
- Added authentication for redis and memcached even in configurations
where these are running on localhost, for add hardening against
attacks from malicious processes running on the Zulip server.
- Improved logging for misconfigurations of LDAP authentication.
- Improved error handling for invalid LDAP configurations.
- Improved error tracebacks for invalid memcached keys.
- Fixed support for using LDAP with email address visibility
limited to administrators.
- Fixed styling of complex markup within /me messages.
- Fixed left sidebar duplicating some group private message threads.
- Fixed the "Mentions" narrow being unable to mark messages as read.
- Fixed error handling bug preventing rerunning the installer.
- Fixed a few minor issues with migrations for upgrading from 2.0.x.
### 2.1.1 -- 2019-12-13
- Fixed upgrading to 2.1.x with the LDAP integration enabled in a
configuration where `AUTH_LDAP_REVERSE_EMAIL_SEARCH` is newly
required, but is not set yet.
- Reimplemented --postgres-missing-dictionaries installer option,
used with our new support for a DBaaS managed database.
- Improved documentation for `AUTH_LDAP_REVERSE_EMAIL_SEARCH`.
### 2.1.0 -- 2019-12-12
**Highlights:**

View File

@@ -51,7 +51,7 @@ it as follows:
The `Entity ID` should match the value of
`SOCIAL_AUTH_SAML_SP_ENTITY_ID` computed in the Zulip settings.
You can get the correct value by running the following:
`/home/zulip/deployments/current/scripts/setup/get-django-setting
`/home/zulip/deployments/current/scripts/get-django-setting
SOCIAL_AUTH_SAML_SP_ENTITY_ID`.
* **SSO URL**:
@@ -176,29 +176,33 @@ In either configuration, you will need to do the following:
the form it needs for authentication. There are three supported
ways to set up the username and/or email mapping:
(A) Using email addresses as usernames, if LDAP has each user's
email address. To do this, just set `AUTH_LDAP_USER_SEARCH` to
query by email address.
(A) Using email addresses as Zulip usernames, if LDAP has each
user's email address:
* Make `AUTH_LDAP_USER_SEARCH` a query by email address.
* Set `AUTH_LDAP_REVERSE_EMAIL_SEARCH` to the same query with
`%(email)s` rather than `%(user)s` as the search parameter.
* Set `AUTH_LDAP_USERNAME_ATTR` to the name of the LDAP
attribute for the user's LDAP username in the search result
for `AUTH_LDAP_REVERSE_EMAIL_SEARCH`.
(B) Using LDAP usernames as Zulip usernames, with email addresses
formed consistently like `sam` -> `sam@example.com`. To do
this, set `AUTH_LDAP_USER_SEARCH` to query by LDAP username, and
`LDAP_APPEND_DOMAIN = "example.com"`.
formed consistently like `sam` -> `sam@example.com`:
* Set `AUTH_LDAP_USER_SEARCH` to query by LDAP username
* Set `LDAP_APPEND_DOMAIN = "example.com"`.
(C) Using LDAP usernames as Zulip usernames, with email addresses
taken from some other attribute in LDAP (for example, `email`).
To do this, set `AUTH_LDAP_USER_SEARCH` to query by LDAP
username, and `LDAP_EMAIL_ATTR = "email"`.
1. In configurations (A) and (C), you need to tell Zulip how to look
up a user's LDAP data given their user's email address:
* Set `AUTH_LDAP_REVERSE_EMAIL_SEARCH` to a query that will find an
LDAP user given their email address. Generally, this will be
`AUTH_LDAP_USER_SEARCH` in configuration (A) or a search by
`LDAP_EMAIL_ATTR` in configuration (C).
* Set `AUTH_LDAP_USERNAME_ATTR` to the name of the LDAP attribute
for the user's LDAP username in that search result.
taken from some other attribute in LDAP (for example, `mail`):
* Set `AUTH_LDAP_USER_SEARCH` to query by LDAP username
* Set `LDAP_EMAIL_ATTR = "mail"`.
* Set `AUTH_LDAP_REVERSE_EMAIL_SEARCH` to a query that will find
an LDAP user given their email address (i.e. a search by
`LDAP_EMAIL_ATTR`). For example:
```
AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(mail=%(email)s)")
```
* Set `AUTH_LDAP_USERNAME_ATTR` to the name of the LDAP
attribute for the user's LDAP username in that search result.
You can quickly test whether your configuration works by running:
@@ -210,28 +214,42 @@ from the root of your Zulip installation. If your configuration is
working, that will output the full name for your user (and that user's
email address, if it isn't the same as the "Zulip username").
**Active Directory**: For Active Directory, one typically sets
`AUTH_LDAP_USER_SEARCH` to one of:
**Active Directory**: Most Active Directory installations will use one
of the following configurations:
* To access by Active Directory username:
```
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)")
AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(mail=%(email)s)")
AUTH_LDAP_USERNAME_ATTR = "sAMAccountName"
```
* To access by Active Directory email address:
```
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(mail=%(user)s)")
AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(mail=%(email)s)")
AUTH_LDAP_USERNAME_ATTR = "mail"
```
**If you are using LDAP for authentication**: you will need to enable
the `zproject.backends.ZulipLDAPAuthBackend` auth backend, in
`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`. After doing
so (and as always [restarting the Zulip server](settings.md) to ensure
`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`. After doing so
(and as always [restarting the Zulip server](settings.md) to ensure
your settings changes take effect), you should be able to log into
Zulip by entering your email address and LDAP password on the Zulip
login form.
You may also want to configure Zulip's settings for [inviting new
users](https://zulipchat.com/help/invite-new-users). If LDAP is the
only enabled authentication method, the main use case for Zulip's
invitation feature is selecting the initial streams for invited users
(invited users will still need to use their LDAP password to create an
account).
### Synchronizing data
Zulip can automatically synchronize data declared in
@@ -368,6 +386,26 @@ details.
[upstream-ldap-groups]: https://django-auth-ldap.readthedocs.io/en/latest/groups.html#limiting-access
### Troubleshooting
Most issues with LDAP authentication are caused by misconfigurations of
the user and email search settings. Some things you can try to get to
the bottom of the problem:
* Review the instructions for the LDAP configuration type you're
using: (A), (B) or (C) (described above), and that you have
configured all of the required settings documented in the
instructions for that configuration type.
* Use the `manage.py query_ldap` tool to verify your configuration.
The output of the command will usually indicate the cause of any
configuration problem. For the LDAP integration to work, this
command should be able to successfully fetch a complete, correct set
of data for the queried user.
* You can find LDAP-specific logs in `/var/log/zulip/ldap.log`. If
you're asking for help with your setup, please provide logs from
this file (feel free to anonymize any email addresses to
`username@example.com`) in your report.
## Apache-based SSO with `REMOTE_USER`
If you have any existing SSO solution where a preferred way to deploy

View File

@@ -134,14 +134,11 @@ follows:
postgres_password = abcd1234
```
Now complete the installation by running the following command to ask
the Zulip installer to initialize the postgres database. (Note: The
options are different from before).
Now complete the installation by running the following commands.
```
./zulip-server-*/scripts/setup/install --certbot \
--email=YOUR_EMAIL --hostname=YOUR_HOSTNAME \
--remote-postgres --postgres-missing-dictionaries
# Ask Zulip installer to initialize the postgres database.
su zulip -c '/home/zulip/deployments/current/scripts/setup/initialize-database'
# And then generate a realm creation link:
su zulip -c '/home/zulip/deployments/current/manage.py generate_realm_creation_link'

View File

@@ -62,7 +62,7 @@ find the service's provided "SMTP credentials", and configure Zulip as
follows:
* The hostname like `EMAIL_HOST = 'smtp.mailgun.org'` in `/etc/zulip/settings.py`
* The username like `EMAIL_HOST_USER = 'username@example.com` in
* The username like `EMAIL_HOST_USER = 'username@example.com'` in
`/etc/zulip/settings.py`.
* The TLS setting as `EMAIL_USE_TLS = True` in
`/etc/zulip/settings.py`, for most providers

View File

@@ -106,7 +106,8 @@ Learning more:
* Subscribe to the
[Zulip announcements email list](https://groups.google.com/forum/#!forum/zulip-announce)
for server administrators. This extremely low-traffic list is for
important announcements, including new releases and security issues.
important announcements, including new releases and security issues. You can also use the
[RSS feed](https://groups.google.com/forum/#!aboutgroup/zulip-announce).
* Follow [Zulip on Twitter](https://twitter.com/zulip).
* Learn how to [configure your Zulip server settings](settings.md).
* Learn about [Backups, export and import](../production/export-and-import.md)

View File

@@ -56,7 +56,6 @@ zulip-workers:zulip-events-confirmation-emails RUNNING pid 21
zulip-workers:zulip-events-digest_emails RUNNING pid 2205, uptime 1:13:11
zulip-workers:zulip-events-email_mirror RUNNING pid 2203, uptime 1:13:11
zulip-workers:zulip-events-error_reports RUNNING pid 2200, uptime 1:13:11
zulip-workers:zulip-events-feedback_messages RUNNING pid 2207, uptime 1:13:11
zulip-workers:zulip-events-missedmessage_mobile_notifications RUNNING pid 2204, uptime 1:13:11
zulip-workers:zulip-events-missedmessage_reminders RUNNING pid 2206, uptime 1:13:11
zulip-workers:zulip-events-signups RUNNING pid 2198, uptime 1:13:11

View File

@@ -6,7 +6,7 @@ There are three disjoint sets of users you care about
for typical Zulip realms:
- active users in your realm
- cross-realm users like feedback@zulip.com
- cross-realm users like welcome-bot@zulip.com
- deactivated users in your realm
You can also think in terms of these user populations:

View File

@@ -2,6 +2,8 @@ var util = require("util");
var test_credentials = require('../../var/casper/test_credentials.js').test_credentials;
casper.options.clientScripts.push("frontend_tests/casper_lib/polyfill.js");
function timestamp() {
return new Date().getTime();
}

View File

@@ -0,0 +1,12 @@
/* eslint-env browser */
// PhantomJS doesnt support new DOMParser().parseFromString(…, "text/html").
var real_parseFromString = DOMParser.prototype.parseFromString;
DOMParser.prototype.parseFromString = function (string, type) {
if (type === "text/html") {
var doc = document.implementation.createHTMLDocument("");
doc.documentElement.innerHTML = string;
return doc;
}
return real_parseFromString.apply(this, arguments);
};

View File

@@ -143,7 +143,7 @@ casper.then(function () {
casper.then(function () {
casper.click('*[title="Narrow to your private messages with Cordelia Lear"]');
});
casper.waitUntilVisible('li[data-user-ids-string="9"].expanded_private_message.active-sub-filter', function () {
casper.waitUntilVisible('li[data-user-ids-string="8"].expanded_private_message.active-sub-filter', function () {
casper.page.sendEvent('keypress', 'c');
});

View File

@@ -1,3 +1,5 @@
const { JSDOM } = require("jsdom");
set_global('bridge', false);
set_global('blueslip', global.make_zblueslip({
@@ -7,6 +9,7 @@ set_global('blueslip', global.make_zblueslip({
const noop = function () {};
set_global('$', global.make_zjquery());
set_global('DOMParser', new JSDOM().window.DOMParser);
set_global('i18n', global.stub_i18n);
const _navigator = {
@@ -263,15 +266,15 @@ run_test('validate', () => {
});
run_test('get_invalid_recipient_emails', () => {
const feedback_bot = {
email: 'feedback@example.com',
const welcome_bot = {
email: 'welcome-bot@example.com',
user_id: 124,
full_name: 'Feedback Bot',
full_name: 'Welcome Bot',
};
page_params.cross_realm_bots = [feedback_bot];
page_params.cross_realm_bots = [welcome_bot];
page_params.user_id = 30;
people.initialize();
compose_state.private_message_recipient('feedback@example.com');
compose_state.private_message_recipient('welcome-bot@example.com');
assert.deepEqual(compose.get_invalid_recipient_emails(), []);
});

View File

@@ -7,8 +7,9 @@ set_global('compose_ui', {});
const { JSDOM } = require("jsdom");
const { window } = new JSDOM('<!DOCTYPE html><p>Hello world</p>');
const { document } = window;
const { DOMParser, document } = window;
set_global('$', require('jquery')(window));
set_global('DOMParser', DOMParser);
set_global('document', document);
const copy_and_paste = zrequire('copy_and_paste');

View File

@@ -86,7 +86,8 @@ run_test('basics', () => {
assert(!filter.contains_only_private_messages());
assert(!filter.allow_use_first_unread_when_narrowing());
assert(!filter.can_apply_locally());
assert(!filter.is_exactly('stream'));
assert(filter.can_bucket_by('stream'));
assert(filter.can_bucket_by('stream', 'topic'));
// If our only stream operator is negated, then for all intents and purposes,
// we don't consider ourselves to have a stream operator, because we don't
@@ -97,6 +98,7 @@ run_test('basics', () => {
filter = new Filter(operators);
assert(!filter.contains_only_private_messages());
assert(!filter.has_operator('stream'));
assert(!filter.can_mark_messages_read());
// Negated searches are just like positive searches for our purposes, since
// the search logic happens on the back end and we need to have can_apply_locally()
@@ -108,6 +110,7 @@ run_test('basics', () => {
assert(!filter.contains_only_private_messages());
assert(filter.has_operator('search'));
assert(!filter.can_apply_locally());
assert(!filter.can_mark_messages_read());
// Similar logic applies to negated "has" searches.
operators = [
@@ -117,6 +120,7 @@ run_test('basics', () => {
assert(filter.has_operator('has'));
assert(!filter.can_apply_locally());
assert(!filter.includes_full_stream_history());
assert(!filter.can_mark_messages_read());
operators = [
{operator: 'streams', operand: 'public', negated: true},
@@ -124,6 +128,7 @@ run_test('basics', () => {
filter = new Filter(operators);
assert(!filter.contains_only_private_messages());
assert(!filter.has_operator('streams'));
assert(!filter.can_mark_messages_read());
assert(filter.has_negated_operand('streams', 'public'));
assert(!filter.can_apply_locally());
@@ -133,6 +138,7 @@ run_test('basics', () => {
filter = new Filter(operators);
assert(!filter.contains_only_private_messages());
assert(filter.has_operator('streams'));
assert(!filter.can_mark_messages_read());
assert(!filter.has_negated_operand('streams', 'public'));
assert(!filter.can_apply_locally());
assert(filter.includes_full_stream_history());
@@ -142,6 +148,16 @@ run_test('basics', () => {
];
filter = new Filter(operators);
assert(filter.contains_only_private_messages());
assert(filter.can_mark_messages_read());
assert(!filter.has_operator('search'));
assert(filter.can_apply_locally());
operators = [
{operator: 'is', operand: 'mentioned'},
];
filter = new Filter(operators);
assert(!filter.contains_only_private_messages());
assert(filter.can_mark_messages_read());
assert(!filter.has_operator('search'));
assert(filter.can_apply_locally());
@@ -200,7 +216,11 @@ function assert_not_mark_read_with_is_operands(additional_operators_to_test) {
is_operator = [{ operator: 'is', operand: 'mentioned' }];
filter = new Filter(additional_operators_to_test.concat(is_operator));
assert(!filter.can_mark_messages_read());
if (additional_operators_to_test.length === 0) {
assert(filter.can_mark_messages_read());
} else {
assert(!filter.can_mark_messages_read());
}
is_operator = [{ operator: 'is', operand: 'mentioned', negated: true }];
filter = new Filter(additional_operators_to_test.concat(is_operator));
@@ -275,11 +295,17 @@ run_test('can_mark_messages_read', () => {
{ operator: 'pm-with', operand: 'joe@example.com,' },
];
const pm_with_negated = [
{ operator: 'pm-with', operand: 'joe@example.com,', negated: true},
];
const group_pm = [
{ operator: 'pm-with', operand: 'joe@example.com,STEVE@foo.com' },
];
filter = new Filter(pm_with);
assert(filter.can_mark_messages_read());
filter = new Filter(pm_with_negated);
assert(!filter.can_mark_messages_read());
filter = new Filter(group_pm);
assert(filter.can_mark_messages_read());
assert_not_mark_read_with_is_operands(group_pm);
@@ -310,11 +336,28 @@ run_test('can_mark_messages_read', () => {
const in_home = [
{ operator: 'in', operand: 'home' },
];
const in_home_negated = [
{ operator: 'in', operand: 'home', negated: true },
];
filter = new Filter(in_home);
assert(filter.can_mark_messages_read());
assert_not_mark_read_with_is_operands(in_home);
assert_not_mark_read_with_has_operands(in_home);
assert_not_mark_read_when_searching(in_home);
filter = new Filter(in_home_negated);
assert(!filter.can_mark_messages_read());
// Do not mark messages as read when in an unsupported 'in:*' filter.
const in_random = [
{ operator: 'in', operand: 'xxxxxxxxx' },
];
const in_random_negated = [
{ operator: 'in', operand: 'xxxxxxxxx', negated: true },
];
filter = new Filter(in_random);
assert(!filter.can_mark_messages_read());
filter = new Filter(in_random_negated);
assert(!filter.can_mark_messages_read());
});
run_test('show_first_unread', () => {
@@ -369,7 +412,7 @@ run_test('new_style_operators', () => {
const filter = new Filter(operators);
assert.deepEqual(filter.operands('stream'), ['foo']);
assert(filter.is_exactly('stream'));
assert(filter.can_bucket_by('stream'));
});
run_test('public_operators', () => {
@@ -381,7 +424,7 @@ run_test('public_operators', () => {
let filter = new Filter(operators);
assert_same_operators(filter.public_operators(), operators);
assert(!filter.is_exactly('stream'));
assert(filter.can_bucket_by('stream'));
global.page_params.narrow_stream = 'default';
operators = [
@@ -391,6 +434,28 @@ run_test('public_operators', () => {
assert_same_operators(filter.public_operators(), []);
});
run_test('redundancies', () => {
let terms;
let filter;
terms = [
{ operator: 'pm-with', operand: 'joe@example.com,' },
{ operator: 'is', operand: 'private' },
];
filter = new Filter(terms);
assert(filter.can_bucket_by('pm-with'));
terms = [
{ operator: 'pm-with',
operand: 'joe@example.com,',
negated: true,
},
{ operator: 'is', operand: 'private' },
];
filter = new Filter(terms);
assert(filter.can_bucket_by('is-private', 'not-pm-with'));
});
run_test('canonicalizations', () => {
assert.equal(Filter.canonicalize_operator('Is'), 'is');
assert.equal(Filter.canonicalize_operator('Stream'), 'stream');
@@ -980,14 +1045,14 @@ run_test('describe', () => {
assert.equal(Filter.describe(narrow), string);
});
run_test('is_functions', () => {
run_test('can_bucket_by', () => {
let terms = [
{operator: 'stream', operand: 'My Stream'},
];
let filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), true);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
assert.equal(filter.can_bucket_by('stream'), true);
assert.equal(filter.can_bucket_by('stream', 'topic'), false);
assert.equal(filter.can_bucket_by('pm-with'), false);
terms = [
// try a non-orthodox ordering
@@ -997,9 +1062,6 @@ run_test('is_functions', () => {
filter = new Filter(terms);
assert.equal(filter.can_bucket_by('stream'), true);
assert.equal(filter.can_bucket_by('stream', 'topic'), true);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), true);
assert.equal(filter.is_exactly('pm-with'), false);
assert.equal(filter.can_bucket_by('pm-with'), false);
terms = [
@@ -1009,49 +1071,45 @@ run_test('is_functions', () => {
filter = new Filter(terms);
assert.equal(filter.can_bucket_by('stream'), false);
assert.equal(filter.can_bucket_by('stream', 'topic'), false);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
assert.equal(filter.can_bucket_by('pm-with'), false);
terms = [
{operator: 'pm-with', operand: 'foo@example.com', negated: true},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), false);
assert.equal(filter.can_bucket_by('stream'), false);
assert.equal(filter.can_bucket_by('stream', 'topic'), false);
assert.equal(filter.can_bucket_by('pm-with'), false);
terms = [
{operator: 'pm-with', operand: 'foo@example.com,bar@example.com'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('stream'), false);
assert.equal(filter.is_exactly('stream', 'topic'), false);
assert.equal(filter.is_exactly('pm-with'), true);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
assert.equal(filter.can_bucket_by('stream'), false);
assert.equal(filter.can_bucket_by('stream', 'topic'), false);
assert.equal(filter.can_bucket_by('pm-with'), true);
assert.equal(filter.can_bucket_by('is-mentioned'), false);
assert.equal(filter.can_bucket_by('is-private'), false);
terms = [
{operator: 'is', operand: 'private'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), true);
assert.equal(filter.can_bucket_by('is-mentioned'), false);
assert.equal(filter.can_bucket_by('is-private'), true);
terms = [
{operator: 'is', operand: 'mentioned'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), true);
assert.equal(filter.is_exactly('is-private'), false);
assert.equal(filter.can_bucket_by('is-mentioned'), true);
assert.equal(filter.can_bucket_by('is-private'), false);
terms = [
{operator: 'is', operand: 'mentioned'},
{operator: 'is', operand: 'starred'},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
assert.equal(filter.can_bucket_by('is-mentioned'), true);
assert.equal(filter.can_bucket_by('is-private'), false);
@@ -1064,8 +1122,8 @@ run_test('is_functions', () => {
{operator: 'is', operand: 'mentioned', negated: true},
];
filter = new Filter(terms);
assert.equal(filter.is_exactly('is-mentioned'), false);
assert.equal(filter.is_exactly('is-private'), false);
assert.equal(filter.can_bucket_by('is-mentioned'), false);
assert.equal(filter.can_bucket_by('is-private'), false);
});
run_test('term_type', () => {
@@ -1081,7 +1139,7 @@ run_test('term_type', () => {
};
}
assert_term_type(term('streams', 'public'), 'streams');
assert_term_type(term('streams', 'public'), 'streams-public');
assert_term_type(term('stream', 'whatever'), 'stream');
assert_term_type(term('pm-with', 'whomever'), 'pm-with');
assert_term_type(term('pm-with', 'whomever', true), 'not-pm-with');

View File

@@ -30,7 +30,6 @@ run_test('stream', () => {
['search', 'yo'],
]);
assert(narrow_state.active());
assert(!narrow_state.is_reading_mode());
assert.equal(narrow_state.stream(), 'Test');
assert.equal(narrow_state.stream_id(), test_stream.stream_id);
@@ -59,7 +58,6 @@ run_test('narrowed', () => {
assert(!narrow_state.narrowed_to_topic());
assert(!narrow_state.narrowed_by_stream_reply());
assert.equal(narrow_state.stream_id(), undefined);
assert(narrow_state.is_reading_mode());
set_filter([['stream', 'Foo']]);
assert(!narrow_state.narrowed_to_pms());
@@ -69,7 +67,6 @@ run_test('narrowed', () => {
assert(!narrow_state.narrowed_to_search());
assert(!narrow_state.narrowed_to_topic());
assert(narrow_state.narrowed_by_stream_reply());
assert(narrow_state.is_reading_mode());
set_filter([['pm-with', 'steve@zulip.com']]);
assert(narrow_state.narrowed_to_pms());
@@ -79,7 +76,6 @@ run_test('narrowed', () => {
assert(!narrow_state.narrowed_to_search());
assert(!narrow_state.narrowed_to_topic());
assert(!narrow_state.narrowed_by_stream_reply());
assert(narrow_state.is_reading_mode());
set_filter([['stream', 'Foo'], ['topic', 'bar']]);
assert(!narrow_state.narrowed_to_pms());
@@ -89,7 +85,6 @@ run_test('narrowed', () => {
assert(!narrow_state.narrowed_to_search());
assert(narrow_state.narrowed_to_topic());
assert(!narrow_state.narrowed_by_stream_reply());
assert(narrow_state.is_reading_mode());
set_filter([['search', 'grail']]);
assert(!narrow_state.narrowed_to_pms());
@@ -99,7 +94,6 @@ run_test('narrowed', () => {
assert(narrow_state.narrowed_to_search());
assert(!narrow_state.narrowed_to_topic());
assert(!narrow_state.narrowed_by_stream_reply());
assert(!narrow_state.is_reading_mode());
});
run_test('operators', () => {

View File

@@ -18,7 +18,7 @@ zrequire("people");
run_test('insert_recent_private_message', () => {
set_global('page_params', {
recent_private_conversations: [
{user_ids: [1, 2],
{user_ids: [11, 2],
max_message_id: 150,
},
{user_ids: [1],
@@ -33,24 +33,24 @@ run_test('insert_recent_private_message', () => {
pmc.recent.initialize();
assert.deepEqual(pmc.recent.get(), [
{user_ids_string: '1,2', max_message_id: 150},
{user_ids_string: '2,11', max_message_id: 150},
{user_ids_string: '1', max_message_id: 111},
{user_ids_string: '15', max_message_id: 7},
]);
pmc.recent.insert('1', 1001);
pmc.recent.insert('2', 2001);
pmc.recent.insert('1', 3001);
pmc.recent.insert([1], 1001);
pmc.recent.insert([2], 2001);
pmc.recent.insert([1], 3001);
// try to backdate user1's latest message
pmc.recent.insert('1', 555);
pmc.recent.insert([1], 555);
assert.deepEqual(pmc.recent.get(), [
{user_ids_string: '1', max_message_id: 3001},
{user_ids_string: '2', max_message_id: 2001},
{user_ids_string: '1,2', max_message_id: 150},
{user_ids_string: '2,11', max_message_id: 150},
{user_ids_string: '15', max_message_id: 7},
]);
assert.deepEqual(pmc.recent.get_strings(), ['1', '2', '1,2', '15']);
assert.deepEqual(pmc.recent.get_strings(), ['1', '2', '2,11', '15']);
});

View File

@@ -73,9 +73,8 @@ run_test('build_private_messages_list', () => {
const active_conversation_2 = 'me@zulip.com,alice@zulip.com';
let max_conversations = 5;
const user_ids_string = '101,102';
const timestamp = 0;
pm_conversations.recent.insert(user_ids_string, timestamp);
pm_conversations.recent.insert([101, 102], timestamp);
global.unread.num_unread_for_person = function () {
return 1;
@@ -125,9 +124,8 @@ run_test('build_private_messages_list_bot', () => {
const active_conversation_1 = 'outgoingwebhook@zulip.com';
const max_conversations = 5;
const user_ids_string = '314';
const timestamp = 0;
pm_conversations.recent.insert(user_ids_string, timestamp);
pm_conversations.recent.insert([314], timestamp);
global.unread.num_unread_for_person = function () {
return 1;

View File

@@ -41,13 +41,10 @@ run_test('generate_zuliprc_uri', () => {
});
run_test('generate_zuliprc_content', () => {
const user = {
email: "admin12@chatting.net",
api_key: "nSlA0mUm7G42LP85lMv7syqFTzDE2q34",
};
const content = settings_bots.generate_zuliprc_content(user.email, user.api_key);
const expected = "[api]\nemail=admin12@chatting.net\n" +
"key=nSlA0mUm7G42LP85lMv7syqFTzDE2q34\n" +
const bot_user = bot_data.get(1);
const content = settings_bots.generate_zuliprc_content(bot_user);
const expected = "[api]\nemail=error-bot@zulip.org\n" +
"key=QadL788EkiottHmukyhHgePUFHREiu8b\n" +
"site=https://chat.example.com\n";
assert.equal(content, expected);

View File

@@ -9,6 +9,7 @@ zrequire('stream_edit');
const { JSDOM } = require("jsdom");
const { window } = new JSDOM();
global.$ = require('jquery')(window);
set_global('DOMParser', window.DOMParser);
// When writing these tests, the following command might be helpful:
// ./tools/get-handlebar-vars static/templates/*.hbs
@@ -1450,7 +1451,7 @@ run_test('typing_notifications', () => {
html += '</ul>';
const li = $(html).find('li').first();
assert.equal(li.text(), 'Hamlet is typing...');
assert.equal(li.text(), 'translated: Hamlet is typing...');
});

View File

@@ -1,4 +1,7 @@
const { JSDOM } = require("jsdom");
set_global('$', global.make_zjquery());
set_global('DOMParser', new JSDOM().window.DOMParser);
set_global('blueslip', global.make_zblueslip({}));
set_global('document', {});
@@ -301,3 +304,19 @@ run_test('move_array_elements_to_front', () => {
assert(emails_actual[i] === emails_expected[i]);
}
});
run_test("clean_user_content_links", () => {
window.location.href = "http://zulip.zulipdev.com/";
assert.equal(
util.clean_user_content_links(
'<a href="http://example.com">good</a> ' +
'<a href="http://localhost:NNNN">invalid</a> ' +
'<a href="javascript:alert(1)">unsafe</a> ' +
'<a href="/#fragment" target="_blank">fragment</a>'
),
'<a href="http://example.com" target="_blank" rel="noopener noreferrer">good</a> ' +
'<a>invalid</a> ' +
'<a>unsafe</a> ' +
'<a href="/#fragment">fragment</a>'
);
});

View File

@@ -7,6 +7,7 @@
# Ansgar Hofmann <ansgar@trollgames.de>, 2016
# Ansgar Hofmann <ansgar@trollgames.de>, 2017,2019
# n3w2oo <kontakt@n3w2oo.net>, 2016
# Christian Spaan, 2019
# Daniel W. <daniel.wos.86@googlemail.com>, 2015
# Dante Cassius <exaltedunmelody@gmail.com>, 2016
# Dennis Stengele <dennis@stengele.me>, 2019
@@ -22,6 +23,7 @@
# BornToBeRoot, 2015
# n3w2oo <kontakt@n3w2oo.net>, 2016
# Niklas P <niklaspotthoff@gmail.com>, 2016
# Philipp Mysz <philipp_mysz@gmx.de>, 2019
# Philip Steffan <philip.steffan@okfn.de>, 2019
# Robert Hönig <robhoenig@gmail.com>, 2017-2019
# Robin Richtsfeld <robin.richtsfeld@gmail.com>, 2018
@@ -32,8 +34,8 @@ msgstr ""
"Project-Id-Version: Zulip\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 03:00+0000\n"
"PO-Revision-Date: 2019-12-13 03:01+0000\n"
"Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n"
"PO-Revision-Date: 2019-12-14 23:42+0000\n"
"Last-Translator: Christian Spaan\n"
"Language-Team: German (http://www.transifex.com/zulip/zulip/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -499,7 +501,7 @@ msgid ""
" target=\"_blank\">personal history</a>.\n"
" Consider <a class=\"search-shared-history\" href=\"\">searching all public streams</a>.\n"
" "
msgstr ""
msgstr "\n Keine weiteren Ergebnisse in Ihrer\n <a href=\"/help/search-for-messages#searching-shared-history\"\n target=\"_blank\">persönlichen Chronik</a>.\n Versuchen Sie, <a class=\"search-shared-history\" href=\"\">alle öffentlichen Streams zu durchsuchen</a>.\n "
#: templates/zerver/app/home.html:35
msgid "Welcome to Zulip."
@@ -3776,7 +3778,7 @@ msgstr "%s ist kein Integer"
#: zerver/lib/validator.py:101
#, python-format
msgid "Invalid %s"
msgstr ""
msgstr "Ungültig %s"
#: zerver/lib/validator.py:108
#, python-format

View File

@@ -10,9 +10,9 @@
"1 hour": "1 Stunde",
"1 week": "1 Woche",
"10 minutes": "10 Minuten",
"12-hour clock (5:00 PM)": "",
"12-hour clock (5:00 PM)": "12-Stunden Format (5:00 PM)",
"2 minutes": "2 Minuten",
"24-hour clock (17:00)": "",
"24-hour clock (17:00)": "24-Stunden Format (17:00)",
"3 days": "3 Tage",
"<a href=\"/help/export-your-organization\" target=\"_blank\">Click here</a> to learn about exporting private streams and messages.": "<a href=\"/help/export-your-organization\" target=\"_blank\">Hier klicken</a>, um zu erfahren, wie man private Streams und Nachrichten exportiert.",
"<a href=\"/upgrade\" target=\"_blank\">Upgrade</a> for more space.": "<a href=\"/upgrade\" target=\"_blank\">Upgrade</a> für mehr Platz.",
@@ -312,7 +312,7 @@
"Language settings": "Spracheinstellungen",
"Large number of subscribers": "Hohe Anzahl an Mitgliedern",
"Last active": "Zuletzt aktiv",
"Last active: __last_seen__": "",
"Last active: __last_seen__": "Zuletzt aktiv: __last_seen__",
"Last modified": "Zuletzt geändert",
"Local time": "Lokale Zeit",
"Looking for our <a href=\"/integrations\" target=\"_blank\">Integrations</a> or <a href=\"/api\" target=\"_blank\">API</a> documentation?": "Suchst Du unsere Dokumentation für <a href=\"/integrations\" target=\"_blank\">Integrationen</a> oder die <a href=\"/api\" target=\"_blank\">API</a>?",
@@ -384,7 +384,7 @@
"No invites match your current filter.": "Keine Einladung passt zu diesem Filter.",
"No linkifiers set.": "Keine Linkifiers gesetzt.",
"No more topics.": "Keine weiteren Themen.",
"No owner": "",
"No owner": "Kein Besitzer",
"No restrictions": "Keine Beschränkungen",
"No users match your current filter.": "Dein aktueller Filter stimmt mit keinem Nutzer überein.",
"None": "Nichts",
@@ -393,7 +393,7 @@
"Nothing to preview": "Keine Vorschau vorhanden",
"Notification sound": "Benachrichtigungston",
"Notifications": "Benachrichtigungen",
"Notifications for @all/@everyone mentions": "",
"Notifications for @all/@everyone mentions": "Benachrichtigungen für @all/@everyone Erwähnungen",
"Notifications stream changed!": "Stream für Benachrichtigungen wurde geändert!",
"Notifications stream disabled!": "Stream für Benachrichtungen wurde deaktiviert!",
"Offline": "Offline",
@@ -403,7 +403,7 @@
"Only organization administrators can add custom emoji in this organization.": "In dieser Organisation können nur Administratoren eigene\nEmojis hinzufügen.",
"Only organization administrators can add generic bots": "Nur Administratoren dieser Organisation können Standard-Bots hinzufügen",
"Only organization administrators can edit these settings.": "Diese Einstellungen können nur von Administratoren dieser Organisation bearbeitet werden.",
"Only organization administrators can modify user groups in this organization.": "",
"Only organization administrators can modify user groups in this organization.": "Nur Organisationsadministratoren können Nutzergruppen in dieser Organisation ändern.",
"Only organization administrators can post.": "Nur Administratoren dieser Organisation können posten.",
"Only organization admins are allowed to post to this stream.": "Nur Administratoren dieser Organisation können in diesen Stream schreiben.",
"Only stream members can add users to a private stream": "Nur Stream-Mitglieder können Benutzer zu einem privaten Stream hinzufügen",
@@ -424,7 +424,7 @@
"Other settings": "Andere Einstellung",
"Outgoing webhook message format": "Outgoing Webhook Nachrichtenformat",
"Owner": "Besitzer",
"Owner: __name__": "",
"Owner: __name__": "Besitzer: __name__",
"Password": "Passwort",
"Password is too weak": "Zu schwaches Passwort",
"Password should be at least __length__ characters long": "Passwort sollte mindestens __length__ Zeichen enthalten",
@@ -477,7 +477,7 @@
"Revoke invitation to __email__": "Einladung an __email__ widerrufen",
"Revoke now": "Jetzt widerrufen",
"Role": "Rolle",
"SAVING": "",
"SAVING": "SPEICHERN",
"Saturday": "Samstag",
"Save": "Speichern",
"Save changes": "Änderungen speichern",
@@ -566,7 +566,7 @@
"This stream is reserved for <strong>announcements</strong>. <br /> Are you sure you want to message all <strong>__count__</strong> people in this stream?": "Dieser Stream ist auschließlich für <strong>Ankündigungen</strong>. <br />Bist Du sicher, dass Du eine Nachricht an alle <strong>__count__</strong> Nutzer in diesem Stream versenden möchtest?",
"Thursday": "Donnerstag",
"Time": "Zeit",
"Time format": "",
"Time format": "Zeitformat",
"Time settings": "Zeiteinstellungen",
"Time zone": "Zeitzone",
"Time's up!": "Zeit vorbei!",
@@ -585,7 +585,7 @@
"URL pattern": "URL-Muster",
"Un-collapse": "Aufklappen",
"Unable to upload that many files at once.": "Zu viele Dateien, um sie auf einmal hochzuladen.",
"Unavailable": "",
"Unavailable": "Nicht verfügbar",
"Uncheck all": "Alles abwählen",
"Unknown": "Unbekannt",
"Unless I say otherwise for a particular stream, I want:": "Wenn Du es nicht anders einstellst, erhältst Du:",
@@ -642,11 +642,11 @@
"We are about to have a poll. Please wait for the question.": "Wir haben gleich eine Umfrage. Bitte warte auf die Frage.",
"We recommend against deleting topics unless needed for security reasons or managing abuse. Deleted messages can be confusing for users who may later visit the topic via notifications.": "Wir empfehlen, keine Themen zu löschen, es sei denn aus Sicherheitsgründen oder um Missbrauch einzudämmen. Gelöschte Nachrichten können verwirrend für andere Nutzer sein, wenn sie versuchen, das Thema über eine Benachrichtigung zu öffnen.",
"Wednesday": "Mittwoch",
"Who can access user email addresses": "",
"Who can access user email addresses": "Kann auf Nutzer-Email-Adressen zugreifen",
"Who can add bots": "Wer kann Bots hinzufügen",
"Who can add custom emoji": "Wer kann eigene Emojis hinzufügen",
"Who can add users to streams": "Wer kann Nutzer zu Streams hinzufügen",
"Who can create and manage user groups": "",
"Who can create and manage user groups": "Kann Nutzergruppen erstellen und verwalten",
"Who can create streams": "Wer kann Streams erstellen",
"Working\u2026": "Arbeitet…",
"Would you like to unstar all starred messages? This action cannot be undone.": "Möchtest du alle markierten Nachrichten aufheben? Dieser Vorgang kann nicht rückgängig gemacht werden.",

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 04:30+0000\n"
"POT-Creation-Date: 2020-01-16 19:35+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: analytics/views.py:80 zerver/decorator.py:522 zerver/decorator.py:531
#: analytics/views.py:80 zerver/decorator.py:525 zerver/decorator.py:534
msgid "Not allowed for guest users"
msgstr ""
@@ -1335,7 +1335,7 @@ msgstr ""
msgid "Organization permissions"
msgstr ""
#: templates/zerver/app/settings_overlay.html:72 zerver/models.py:1769
#: templates/zerver/app/settings_overlay.html:72 zerver/models.py:1767
msgid "Custom emoji"
msgstr ""
@@ -2925,35 +2925,35 @@ msgstr ""
msgid "Account is not associated with this subdomain"
msgstr ""
#: zerver/decorator.py:471 zerver/decorator.py:533
#: zerver/decorator.py:474 zerver/decorator.py:536
msgid "This endpoint does not accept bot requests."
msgstr ""
#: zerver/decorator.py:513
#: zerver/decorator.py:516
msgid "Must be an server administrator"
msgstr ""
#: zerver/decorator.py:588
#: zerver/decorator.py:591
msgid "This endpoint requires HTTP basic authentication."
msgstr ""
#: zerver/decorator.py:591
#: zerver/decorator.py:594
msgid "Invalid authorization header for basic auth"
msgstr ""
#: zerver/decorator.py:593
#: zerver/decorator.py:596
msgid "Missing authorization header for basic auth"
msgstr ""
#: zerver/decorator.py:670
#: zerver/decorator.py:673
msgid "Not logged in"
msgstr ""
#: zerver/decorator.py:681
#: zerver/decorator.py:684
msgid "Webhook bots can only access webhooks"
msgstr ""
#: zerver/decorator.py:740
#: zerver/decorator.py:743
msgid "Access denied"
msgstr ""
@@ -3376,7 +3376,7 @@ msgstr ""
msgid "Must be an organization administrator or emoji author"
msgstr ""
#: zerver/lib/emoji.py:109 zerver/models.py:601
#: zerver/lib/emoji.py:109 zerver/models.py:599
msgid "Invalid characters in emoji name"
msgstr ""
@@ -3428,7 +3428,11 @@ msgstr ""
msgid "Invalid API key"
msgstr ""
#: zerver/lib/exceptions.py:229
#: zerver/lib/exceptions.py:222
msgid "Malformed API key"
msgstr ""
#: zerver/lib/exceptions.py:234
#, python-brace-format
msgid ""
"The '{event_type}' event isn't currently supported by the {webhook_name} "
@@ -3870,71 +3874,71 @@ msgstr ""
msgid "API usage exceeded rate limit"
msgstr ""
#: zerver/models.py:255
#: zerver/models.py:253
msgid "stream events"
msgstr ""
#: zerver/models.py:274
#: zerver/models.py:272
msgid "Available on Zulip Standard. Upgrade to access."
msgstr ""
#: zerver/models.py:664
#: zerver/models.py:662
#, python-format
msgid "Invalid filter pattern. Valid characters are %s."
msgstr ""
#: zerver/models.py:680
#: zerver/models.py:678
msgid "Invalid URL format string."
msgstr ""
#: zerver/models.py:1768
#: zerver/models.py:1766
msgid "Unicode emoji"
msgstr ""
#: zerver/models.py:1770
#: zerver/models.py:1768
msgid "Zulip extra emoji"
msgstr ""
#: zerver/models.py:2676
#: zerver/models.py:2690
#, python-format
msgid "Invalid user ID: %d"
msgstr ""
#: zerver/models.py:2680
#: zerver/models.py:2694
#, python-format
msgid "User with ID %d is deactivated"
msgstr ""
#: zerver/models.py:2683
#: zerver/models.py:2697
#, python-format
msgid "User with ID %d is a bot"
msgstr ""
#: zerver/models.py:2713
#: zerver/models.py:2727
msgid "List of options"
msgstr ""
#: zerver/models.py:2716
#: zerver/models.py:2730
msgid "Person picker"
msgstr ""
#: zerver/models.py:2728
#: zerver/models.py:2742
msgid "Short text"
msgstr ""
#: zerver/models.py:2729
#: zerver/models.py:2743
msgid "Long text"
msgstr ""
#: zerver/models.py:2730
#: zerver/models.py:2744
msgid "Date picker"
msgstr ""
#: zerver/models.py:2731
#: zerver/models.py:2745
msgid "Link"
msgstr ""
#: zerver/models.py:2732
#: zerver/models.py:2746
msgid "External account"
msgstr ""

View File

@@ -4,17 +4,17 @@
#
# Translators:
# Alessio Schreiber <alessio.schreiber@gmail.com>, 2019
# Andrea <andrea.soccal@elmospa.com>, 2018-2019
# Andrea <andrea.soccal@elmospa.com>, 2018-2020
# Biagio Laquale <infingerprinting@gmail.com>, 2019
# Marcello Nuccio <marcello.nuccio@nuccioservizi.it>, 2019
# 8d821b430c6446b8644f76c07c39dc73, 2019
# Paolo Midali <pmidali@latek.it>, 2018-2019
msgid ""
msgstr ""
"Project-Id-Version: Zulip\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 03:00+0000\n"
"PO-Revision-Date: 2019-12-13 03:01+0000\n"
"Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n"
"PO-Revision-Date: 2020-01-15 15:02+0000\n"
"Last-Translator: Andrea <andrea.soccal@elmospa.com>\n"
"Language-Team: Italian (http://www.transifex.com/zulip/zulip/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -334,7 +334,7 @@ msgstr "Si, Grazie!"
#: templates/zerver/app/bankruptcy.html:15
msgid "No, I'll catch up."
msgstr "No, mi raggiungo."
msgstr "No grazie, me li guardo tutti."
#: templates/zerver/app/compose.html:12 templates/zerver/app/compose.html:13
#: templates/zerver/app/compose.html:108
@@ -800,11 +800,11 @@ msgstr "Limitando"
#: templates/zerver/app/keyboard_shortcuts.html:155
msgid "Narrow to stream"
msgstr ""
msgstr "Limita al canale"
#: templates/zerver/app/keyboard_shortcuts.html:159
msgid "Narrow to topic or PM conversation"
msgstr ""
msgstr "Limita al canale o alle conversazioni dei messaggi privati"
#: templates/zerver/app/keyboard_shortcuts.html:163
msgid "Narrow to all private messages"
@@ -1198,7 +1198,7 @@ msgstr "Limita per ID di messaggio"
#: templates/zerver/app/search_operators.html:45
msgid "Search all public streams in the organization."
msgstr ""
msgstr "Cerca in tutti i canali dell'organizzazione."
#: templates/zerver/app/search_operators.html:49
msgid "Narrow to messages with alert words."
@@ -1368,11 +1368,11 @@ msgstr "Inviti"
#: templates/zerver/app/settings_overlay.html:150
msgid "Data exports"
msgstr ""
msgstr "Esportazioni di dati"
#: templates/zerver/app/settings_overlay.html:156
msgid "Show more"
msgstr ""
msgstr "Mostra di più"
#: templates/zerver/config_error.html:14
msgid ""
@@ -1517,7 +1517,7 @@ msgstr "Se non hai richiesto questo cambiamento, ti preghiamo di contattarci imm
#: templates/zerver/emails/invitation.source.html:4
#: templates/zerver/emails/realm_reactivation.source.html:4
msgid "Turtle with envelope"
msgstr ""
msgstr "Tartaruga con busta"
#: templates/zerver/emails/compiled/confirm_registration.html:10
#: templates/zerver/emails/confirm_registration.source.html:9
@@ -1552,7 +1552,7 @@ msgstr "Contattaci in qualsiasi momento a<a href=\"mailto:%(support_email)s\" st
#: templates/zerver/emails/compiled/email_base_default.html:87
#: templates/zerver/emails/email_base_default.source.html:30
msgid "Swimming fish"
msgstr ""
msgstr "Pesce che nuota"
#: templates/zerver/emails/compiled/find_team.html:11
#: templates/zerver/emails/find_team.source.html:10
@@ -1687,7 +1687,7 @@ msgstr "PS: Seguici su <a href=\"https://twitter.com/zulip\" style=\"color:#46aa
#: templates/zerver/emails/compiled/followup_day2.html:4
#: templates/zerver/emails/followup_day2.source.html:4
msgid "Octopus box with heart"
msgstr ""
msgstr "Scatola di polpo con cuore"
#: templates/zerver/emails/compiled/followup_day2.html:9
#: templates/zerver/emails/followup_day2.source.html:8
@@ -1706,7 +1706,7 @@ msgstr "Volevo condividere un'ultima cosa con te: alcuni suggerimenti sugli argo
#: templates/zerver/emails/compiled/followup_day2.html:13
#: templates/zerver/emails/followup_day2.source.html:12
msgid "Examples of short topics"
msgstr ""
msgstr "Esempi di brevi argomenti"
#: templates/zerver/emails/compiled/followup_day2.html:15
msgid ""
@@ -1732,7 +1732,7 @@ msgstr "Non consigliati: \"Cosa pensa la gente di questo nuovo modello di design
#: templates/zerver/emails/compiled/followup_day2.html:22
#: templates/zerver/emails/followup_day2.source.html:21
msgid "Example of a topic that is too long"
msgstr ""
msgstr "Esempio di argomento troppo lungo"
#: templates/zerver/emails/compiled/followup_day2.html:24
msgid ""
@@ -1800,7 +1800,7 @@ msgstr "Per iniziare, clicca sul bottone sotto."
#: templates/zerver/emails/compiled/invitation_reminder.html:4
#: templates/zerver/emails/invitation_reminder.source.html:4
msgid "Mailbox with envelope"
msgstr ""
msgstr "Cassetta postale con busta"
#: templates/zerver/emails/compiled/invitation_reminder.html:9
#: templates/zerver/emails/invitation_reminder.source.html:8
@@ -2434,7 +2434,7 @@ msgstr "La migliore chat per luoghi di lavoro"
#: templates/zerver/for-open-source.html:23
msgid "Zulip for open source"
msgstr ""
msgstr "Zulip per open source"
#: templates/zerver/for-working-groups-and-communities.html:18
msgid "The best chat for working groups and communities"
@@ -2542,7 +2542,7 @@ msgstr "Integrazioni personalizzate"
#: templates/zerver/integrations/index.html:64
#: templates/zerver/integrations/index.html:91
msgid "Incoming webhooks"
msgstr ""
msgstr "Webhook in arrivo"
#: templates/zerver/integrations/index.html:67
#: templates/zerver/integrations/index.html:94 zerver/lib/integrations.py:46
@@ -2552,7 +2552,7 @@ msgstr "Bots interattivi"
#: templates/zerver/integrations/index.html:70
#: templates/zerver/integrations/index.html:97
msgid "REST API"
msgstr ""
msgstr "REST API"
#: templates/zerver/integrations/index.html:77
msgid "Categories"
@@ -3431,11 +3431,11 @@ msgstr "L'evento '{event_type}' non è attualmente supportato dal webhook {webho
#: zerver/lib/external_accounts.py:47
msgid "Custom external account must define url pattern"
msgstr ""
msgstr "L'account esterno personalizzato deve definire il modello di URL"
#: zerver/lib/external_accounts.py:49
msgid "Invalid external account type"
msgstr ""
msgstr "Tipo di account esterno non valido"
#: zerver/lib/hotspots.py:12
msgid "Reply to a message"
@@ -3558,12 +3558,12 @@ msgstr "Token inesistente"
#: zerver/lib/push_notifications.py:595
#, python-format
msgid "%(full_name)s mentioned you:"
msgstr ""
msgstr "%(full_name)sti ha menzionato:"
#: zerver/lib/push_notifications.py:597
#, python-format
msgid "%(full_name)s mentioned everyone:"
msgstr ""
msgstr "%(full_name)sha menzionato tutti:"
#: zerver/lib/remote_server.py:80
#, python-format
@@ -3678,16 +3678,16 @@ msgstr "Nome o nome utente non buono"
#: zerver/lib/users.py:62
#, python-format
msgid "Invalid integration '%s'."
msgstr ""
msgstr "Integrazione non valida '%s'."
#: zerver/lib/users.py:66
#, python-format
msgid "Missing configuration parameters: %s"
msgstr ""
msgstr "Parametri di configurazione mancanti: %s"
#: zerver/lib/users.py:73
msgid "Invalid {} value {} ({})"
msgstr ""
msgstr "Non valido {} valore {} ({})"
#: zerver/lib/users.py:88
msgid "Invalid configuration data!"
@@ -3757,7 +3757,7 @@ msgstr "%s non è un numero intero"
#: zerver/lib/validator.py:101
#, python-format
msgid "Invalid %s"
msgstr ""
msgstr "%snon valido"
#: zerver/lib/validator.py:108
#, python-format
@@ -3816,7 +3816,7 @@ msgstr "%snon è un url"
#: zerver/lib/validator.py:249
msgid "Malformed URL pattern."
msgstr ""
msgstr "Pattern URL non valido."
#: zerver/lib/validator.py:269
#, python-brace-format
@@ -3831,12 +3831,12 @@ msgstr "'{value}' non è una scelta valida per '{field_name}'."
#: zerver/lib/validator.py:348
#, python-format
msgid "%s is not a string or an integer list"
msgstr ""
msgstr "%snon è una stringa o un elenco intero"
#: zerver/lib/validator.py:356
#, python-format
msgid "%s is not a string or integer"
msgstr ""
msgstr "%snon è una stringa o un numero intero"
#: zerver/lib/webhooks/common.py:51
#, python-brace-format
@@ -3867,11 +3867,11 @@ msgstr "L'utilizzo dell'API ha superato il limite di velocità"
#: zerver/models.py:255
msgid "stream events"
msgstr ""
msgstr "Eventi di canale"
#: zerver/models.py:274
msgid "Available on Zulip Standard. Upgrade to access."
msgstr ""
msgstr "Disponibile su Zulip Standard. Aggiorna per accedere."
#: zerver/models.py:664
#, python-format
@@ -3960,12 +3960,12 @@ msgstr "Non sei autorizzato a ricevere eventi da questa coda"
#: zerver/tornado/event_queue.py:536
#, python-format
msgid "An event newer than %s has already been pruned!"
msgstr ""
msgstr "Un evento più recente di %s è già stato eliminato!"
#: zerver/tornado/event_queue.py:542
#, python-format
msgid "Event %s was not in this queue"
msgstr ""
msgstr "L'evento %s non era in questa coda"
#: zerver/tornado/exceptions.py:14
#, python-brace-format
@@ -4067,7 +4067,7 @@ msgstr "User-Agent header mancante dalla richiesta"
#: zerver/views/custom_profile_fields.py:37
msgid "Label cannot be blank."
msgstr ""
msgstr "L'etichetta non può essere vuota."
#: zerver/views/custom_profile_fields.py:53
msgid "Field must have at least one choice."
@@ -4080,11 +4080,11 @@ msgstr "Tipo campo non valido."
#: zerver/views/custom_profile_fields.py:112
#: zerver/views/custom_profile_fields.py:150
msgid "A field with that label already exists."
msgstr ""
msgstr "Un campo con quell'etichetta esiste già."
#: zerver/views/custom_profile_fields.py:143
msgid "Default custom field cannot be updated."
msgstr ""
msgstr "Il campo personalizzato predefinito non può essere aggiornato."
#: zerver/views/hotspots.py:17
#, python-format
@@ -4268,7 +4268,7 @@ msgstr "Almeno un metodo di autenticazione deve essere abilitato."
#: zerver/views/realm.py:95
msgid "Invalid video_chat_provider {}"
msgstr ""
msgstr "video_chat_provider {}non valido"
#: zerver/views/realm.py:100 zerver/views/realm_domains.py:28
msgid "Invalid domain: {}"
@@ -4322,16 +4322,16 @@ msgstr "Caricamento immagine fallito."
#: zerver/views/realm_export.py:40
msgid "Exceeded rate limit."
msgstr ""
msgstr "Limite di velocità superato."
#: zerver/views/realm_export.py:47
#, python-format
msgid "Please request a manual export from %s."
msgstr ""
msgstr "Si prega di richiedere un'esportazione manuale da %s."
#: zerver/views/realm_export.py:76
msgid "Invalid data export ID"
msgstr ""
msgstr "ID esportazione dati non valido"
#: zerver/views/realm_export.py:80
msgid "Export already deleted"
@@ -4505,7 +4505,7 @@ msgstr "Password errata!"
#: zerver/views/user_settings.py:75
msgid "New password is too weak!"
msgstr ""
msgstr "La nuova password è troppo semplice!"
#: zerver/views/user_settings.py:104
msgid "Check your email for a confirmation link. "
@@ -4626,7 +4626,7 @@ msgstr "Input JSON rovinato"
#: zerver/webhooks/papertrail/view.py:25
msgid "Missing expected keys"
msgstr ""
msgstr "Chiavi previste mancanti"
#: zerver/webhooks/pivotal/view.py:178
msgid "Unable to handle Pivotal payload"
@@ -4675,4 +4675,4 @@ msgstr "I dati sono fuori servizio."
#: zilencer/views.py:181
msgid "Invalid data."
msgstr ""
msgstr "Dati non validi."

View File

@@ -156,7 +156,7 @@
"Custom filter added!": "Aggiunto filtro personalizzato!",
"Custom profile fields": "Campi profilo personalizzati",
"Customize profile picture": "",
"Data exports": "",
"Data exports": "Esportazioni di dati",
"Date uploaded": "Data di caricamento",
"Day": "Giorno",
"Day mode": "Modalità giorno",
@@ -513,7 +513,7 @@
"Show API key": "",
"Show counts for starred messages": "Mostra conteggi sui messaggi preferiti",
"Show fewer": "",
"Show more": "",
"Show more": "Mostra di più",
"Show previews of linked websites": "Mostra anteprime dei siti collegati",
"Show previews of uploaded and linked images": "Mostra anteprime delle immagini caricate e collegate",
"Show starred message count": "",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,51 @@
{
"\"__file_name__\" was too large; the maximum file size is __file_size__MB.": "",
"\"__file_name__\" was too large; the maximum file size is __file_size__MB.": "\"__file_name__\" слишком большой файл; максимальный размер файлов __file_size__мб.",
"(This user has been deactivated)": "(Этот пользователь был отключен)",
"(no topic)": "(без темы)",
"(unavailable)": "",
"(unavailable)": "(недоступно)",
"(you)": "(вы)",
"/me is excited (Display action text)": "",
"/poll Where should we go to lunch today? (Create a poll)": "",
"/me is excited (Display action text)": "/me в восторге (показать текст действия)",
"/poll Where should we go to lunch today? (Create a poll)": "/poll Куда нам сегодня пойти пообедать? (провести голосование)",
"1 day": "1 день",
"1 hour": "1 час",
"1 week": "1 неделя",
"10 minutes": "10 минут",
"12-hour clock (5:00 PM)": "",
"12-hour clock (5:00 PM)": "12-часовые часы (5:00 PM)",
"2 minutes": "2 минуты",
"24-hour clock (17:00)": "",
"3 days": "",
"<a href=\"/help/export-your-organization\" target=\"_blank\">Click here</a> to learn about exporting private streams and messages.": "",
"<a href=\"/upgrade\" target=\"_blank\">Upgrade</a> for more space.": "",
"24-hour clock (17:00)": "24-часовые часы (17:00)",
"3 days": "3 дня",
"<a href=\"/help/export-your-organization\" target=\"_blank\">Click here</a> to learn about exporting private streams and messages.": "<a href=\"/help/export-your-organization\" target=\"_blank\">Нажмите тут</a>, чтобы узнать об экспорте закрытых каналов и сообщений.",
"<a href=\"/upgrade\" target=\"_blank\">Upgrade</a> for more space.": "<a href=\"/upgrade\" target=\"_blank\">Апгрейд</a>, чтобы получить больше места.",
"<b>Private, protected history:</b> must be invited by a member; new members can only see messages sent after they join; hidden from non-administrator users": "<b>Закрытый, защищенная переписка:</b> должен быть приглашён участником; новые пользователи могут видеть только сообщения, отправленные после их подключения; скрыт от всех пользователей не администраторов",
"<b>Private, shared history:</b> must be invited by a member; new members can view complete message history; hidden from non-administrator users": "<b>Закрытый, открытая переписка:</b> должен быть приглашен участником; новые пользователи могут видеть всю историю переписки; скрыт от всех пользователей не администраторов",
"<b>Public:</b> anyone can join; anyone can view complete message history without joining": "<b>Открытый:</b> любой может присоединиться; любой может читать историю сообщения не присоединяясь",
"<p>Stream will be announced in <b>#__notifications_stream__</b>.</p>": "Канал будет анонсирован в <b>#__notifications_stream__</b>\\.</p>",
"<p>The stream <b>__stream_name__</b> does not exist.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>": "<p>Канал <b>__stream_name__</b> не существует\\.</p><p>Управляй своими подписками на <a href='#streams/all'>своей странице Каналы</a>\\.</p> ",
"<strong>__name__</strong> is not subscribed to this stream. They will not be notified if you mention them.": "<strong>__name__</strong> не подписан(-ы) на этот канал. Они не будут уведомлены, если Вы упомянете их.",
"<strong>__name__</strong> is not subscribed to this stream. They will not be notified unless you subscribe them.": "<strong>__name__</strong> не подписан(ы) на этот канал. Они не будут получать уведомления, пока вы их не подпишете.",
"A stream needs to have a name": "Укажите название канала",
"<strong>__name__</strong> is not subscribed to this stream. They will not be notified if you mention them.": "<strong>__name__</strong> не подписан на этот канал. Пользователь не будет уведомлен, если вы упомянете его.",
"<strong>__name__</strong> is not subscribed to this stream. They will not be notified unless you subscribe them.": "<strong>__name__</strong> не подписан на этот канал. Пользователь не будет получать уведомления, пока вы его не подпишете.",
"A stream needs to have a name": "Канал должен иметь название",
"A stream with this name already exists": "Канал с таким именем уже существует",
"A wide image for the upper left corner of the app.": "",
"API key": "API ключ",
"A wide image for the upper left corner of the app.": "Широкое изображение для верхнего левого угла приложения.",
"API key": "API-ключ",
"Action": "Действие",
"Actions": "Действия",
"Active": "Активный",
"Active bots": "Активные боты",
"Active now": "Активно сейчас",
"Active now": "Сейчас активен",
"Add": "Добавить",
"Add a new alert word": "Добавить новое сигнальное слово",
"Add a new bot": "Добавить нового бота",
"Add a new emoji": "Добавить эмодзи",
"Add a new linkifier": "",
"Add a new linkifier": "Добавить новый фильтр",
"Add a new profile field": "Добавить новое поле профиля",
"Add a new user group": "Добавить новую группу пользователей",
"Add alert word": "Добавить сигнальное слово",
"Add choice": "",
"Add choice": "Новый выбор",
"Add emoji": "Добавить эмодзи",
"Add emoji reaction": "Добавить эмодзи реакцию",
"Add emoji reaction (:)": "Добавить эмодзи реакцию (:)",
"Add emoji reaction": "Добавить эмодзи-реакцию",
"Add emoji reaction (:)": "Добавить эмодзи-реакцию (:)",
"Add extra emoji for members of the __realm_name__ organization.": "Добавить дополнительные эмодзи для участников организации __realm_name__.",
"Add linkifier": "",
"Add linkifier": "Добавить фильтр",
"Add member...": "Добавить участника...",
"Add new default stream": "Добавить новый канал по умолчанию",
"Add profile field": "Добавить поле профиля",
@@ -55,47 +55,47 @@
"Add video call": "Добавить видеовызов",
"Added successfully!": "Добавлено успешно!",
"Administrator": "Администратор",
"Administrators can always delete any message.": "Администраторы могут всегда удалить любое сообщение.",
"Admins": "",
"Admins and full members": "",
"Admins and members": "",
"Admins and members, but only admins can add generic bots": "",
"Administrators can always delete any message.": "Администраторы всегда могут удалять любое сообщение.",
"Admins": "Администраторы",
"Admins and full members": "Администраторы и полноценные участники",
"Admins and members": "Администраторы и участники",
"Admins and members, but only admins can add generic bots": "Администраторы и участники, но только администраторы могут добавлять общие боты",
"Admins only": "Только администраторы",
"Admins, members, and guests": "",
"Admins, members, and guests": "Администраторы, участники и гости",
"Alert word": "Сигнальное слово",
"Alert word added successfully!": "Сигнальное слово успешно добавлено!",
"Alert word already exists!": "Сигнальное слово уже существует!",
"Alert word can't be empty!": "Сигнальное слово не может быть пустым!",
"Alert word removed successfully!": "Сигнальное слово успешно удалено!",
"Alert words allow you to be notified as if you were @-mentioned when certain words or phrases are used in Zulip. Alert words are not case sensitive.": "",
"All stream members can post.": "",
"Alert words allow you to be notified as if you were @-mentioned when certain words or phrases are used in Zulip. Alert words are not case sensitive.": "Слова предупреждения позволяют получать уведомления, как если бы вы упоминали @ при использовании определенных слов или фраз в Zulip. Оповещения не чувствительны к регистру.",
"All stream members can post.": "Все участники потока могут оставлять сообщения.",
"All streams": "Все каналы",
"All unreads": "",
"Allow message content in missed message emails": "",
"All unreads": "Все непрочтенные",
"Allow message content in missed message emails": "Допускать содержание сообщений в уведомлениях о пропущенных сообщениях по электронной почте",
"Allow message deleting": "Разрешить удаление сообщений",
"Allow message editing": "Разрешить редактирование сообщений",
"Allow subdomains": "Разрешать поддомены",
"Allowed domains": "Разрешенные домены",
"Allowed domains: __domains__": "Разрешенные домены: __domains__",
"Already subscribed to __stream__": "Уже подписан на __stream__",
"Always": "",
"An API key can be used to programmatically access a Zulip account. Anyone with access to your API key has the ability to read your messages, send messages on your behalf, and otherwise impersonate you on Zulip, so you should guard your API key as carefully as you guard your password. <br /> We recommend creating bots and using the bots' accounts and API keys to access the Zulip API, unless the task requires access to your account.": "API ключ может быть использован для программного доступа к аккаунту Zulip. Обладающий вашим API ключом имеет возможность читать ваши сообщения, посылать сообщения от вашего имени и иначе представлять вас в Zulip. Вам следует защищать API ключ так же как и ваш пароль. <br /> Мы рекомендуем использовать для ботов специальные аккаунты и API ключи для доступа к Zulip API, в случае если задача не требует доступа именно к вашему аккаунту.",
"Always": "Всегда",
"An API key can be used to programmatically access a Zulip account. Anyone with access to your API key has the ability to read your messages, send messages on your behalf, and otherwise impersonate you on Zulip, so you should guard your API key as carefully as you guard your password. <br /> We recommend creating bots and using the bots' accounts and API keys to access the Zulip API, unless the task requires access to your account.": "API-ключ может быть использован для программного доступа к учетной записи Zulip. Обладающий вашим API-ключом имеет возможность читать ваши сообщения, посылать сообщения от вашего имени и иначе представлять вас в Zulip. Вам следует защищать API-ключ так же, как и ваш пароль. <br /> Мы рекомендуем использовать для ботов специальные учетные записи и API-ключи для доступа к Zulip API, в случае если задача не требует доступа именно к вашей учетной записи.",
"An hour ago": "Час назад",
"An unknown error occurred.": "Произошла неизвестная ошибка.",
"Announce stream": "Анонсировать новый канал",
"Any member of this organization can add custom emoji.": "",
"Any organization administrator can conduct an export.": "",
"Any member of this organization can add custom emoji.": "Любой член этой организации может добавить пользовательские эмодзи.",
"Any organization administrator can conduct an export.": "Любой администратор организации может экспортировать данные.",
"Any time": "Любое время",
"Anyone in this organization can add bots": "Любой в этой организации может добавлять ботов",
"Are invitations required for joining the organization?": "Необходимо ли приглашение чтобы присоединиться к организации?",
"Are invitations required for joining the organization?": "Необходимо ли приглашение, чтобы присоединиться к организации?",
"Are you sure you want to create stream '__stream_name__' and subscribe __count__ users to it?": "Вы уверены, что хотите создать канал '__stream_name__' и подписать на него __count__ пользователей?",
"Are you sure you want to delete <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 <b>__group_name__</b>?": "Вы уверены, что хотите удалить <b>__group_name__</b>?",
"Are you sure you want to delete all messages in <b>__topic_name__</b>?": "Вы уверены, что хотите удалить все сообщения в <b>1__topic_name__</b>?",
"Are you sure you want to do this?": "Вы уверены, что хотите сделать это?",
"Are you sure you want to mention all <strong>__count__</strong> people in this stream?": "Вы уверены, что хотите упомянуть всех <strong>__count__</strong> участников канала?",
"Are you sure you want to resend the invitation to <strong><span class=\"email\"></span></strong>?": "Вы действительно хотите отправить повторное приглашение <strong><span class=\\\\\"email\\\\\"></span></strong>?",
"Are you sure you want to revoke the invitation to <strong>__email__</strong>?": "",
"Are you sure you want to revoke this invitation link created by <strong>__referred_by__</strong>?": "",
"Are you sure you want to resend the invitation to <strong><span class=\"email\"></span></strong>?": "Вы действительно хотите отправить повторное приглашение <strong><span class=\"email\"></span></strong>?",
"Are you sure you want to revoke the invitation to <strong>__email__</strong>?": "Вы уверены, что хотите отозвать приглашение для <strong>__email__</strong>?,",
"Are you sure you want to revoke this invitation link created by <strong>__referred_by__</strong>?": "Вы уверены, что хотите отозвать приглашение по ссылке <strong>__referred_by__</strong>?",
"Attach files": "Прикрепить файлы",
"Attachment deleted": "Прикрепленный файл удален",
"Audible desktop notifications": "Звуковые уведомления",
@@ -104,16 +104,16 @@
"Automatic": "Автоматически",
"Avatar from Gravatar": "Аватар из Gravatar",
"Bot": "Бот",
"Bot email": "Email бота",
"Bot email (a-z, 0-9, and dashes only)": "",
"Bot email": "Адрес электронной почты бота",
"Bot email (a-z, 0-9, and dashes only)": "Адрес электронной почты бота (только a-z, 0-9 и тире)",
"Bot type": "Тип бота",
"Bots": "Боты",
"By deactivating <strong><span class=\"user_name\"></span></strong> &lt;<span class=\"email\"></span>&gt;, they will be logged out immediately.": "Если вы отключите учетную запись <strong><span class=\"user_name\"></span></strong> &lt;<span class=\"email\"></span>&gt;, они автоматически выйдут из системы.",
"By deactivating <strong><span class=\"user_name\"></span></strong> &lt;<span class=\"email\"></span>&gt;, they will be logged out immediately.": "Если вы отключите учетную запись <strong><span class=\"user_name\"></span></strong> &lt;<span class=\"email\"></span>&gt;, пользователь немедленно будет отключен от системы.",
"By deactivating your account, you will be logged out immediately.": "Отключив свою учетную запись, вы автоматически выйдете из системы.",
"Cancel": "Отмена",
"Change": "Изменить",
"Change bot info and owner": "",
"Change email": "Изменить email",
"Change bot info and owner": "Изменить информацию и владельца бота",
"Change email": "Изменить адрес электронной почты",
"Change full name": "Изменить полное имя",
"Change later messages to this topic": "Изменить последующие сообщения в этой теме",
"Change notification settings for individual streams on your <a href=\"/#streams\">Streams page</a>.": "Измените настройки уведомлений для отдельных каналов на странице <a href=\"/#streams\">Каналы</a>.",
@@ -123,76 +123,76 @@
"Change stream permissions for #": "Изменить разрешения канала #",
"Change user info and roles": "Изменить информацию о пользователе и его функции",
"Check all": "Выбрать все",
"Check your email (%s) to confirm the new address.": "",
"Check your email (%s) to confirm the new address.": "Для подтверждения нового адреса проверьте свою электронную почту (%s) ",
"Choose avatar": "Выберите аватар",
"Choose custom color": "Выберите произвольный цвет",
"Clear emoji image": "Очистить изображение эмодзи",
"Clear profile picture": "",
"Clear profile picture": "Очистить картинку профиля",
"Click anywhere on a message to reply.": "Кликните в любое место сообщения чтобы ответить.",
"Click outside the input box to save. We\\'ll automatically notify anyone that was added or removed.": "Для сохранения нажмите вне поля ввода. Мы автоматически уведомим всех, кто был добавлен или удален.",
"Close": "Закрыть",
"Collapse": "Свернуть",
"Compose your message here": "Введите ваше сообщение здесь",
"Condense message (-)": "",
"Condense message (-)": "Сжать сообщение (-)",
"Configure regular expression patterns that will be automatically linkified when used in Zulip message bodies or topics. For example to automatically linkify commit IDs and issue numbers (e.g. #123) to the corresponding items in a GitHub project, you could use the following:": "Настройте шаблоны регулярных выражений, которые будут автоматически вставлять ссылки при использовании в телах или темах сообщений Zulip. Например, чтобы автоматически привязывать идентификаторы коммитов и номера проблем (например, #123) к соответствующим элементам проекта GitHub, вы можете использовать следующее:",
"Configure the authentication methods for your organization.": "Настроить метод аутентификации для вашей организации",
"Configure the default streams new users are subscribed to when joining your organization.": "Настроить каналы по умолчанию на которые будут подписаны новые пользователи вашей организации.",
"Convert emoticons before sending (<code>:)</code> becomes \ud83d\ude03)": "Превращать эмотиконы перед отправкой (<code>:)</code> становится 😃)",
"Cookie Bot": "Cookie бот",
"Convert emoticons before sending (<code>:)</code> becomes \ud83d\ude03)": "Превращать эмодзи перед отправкой (<code>:)</code> становится 😃)",
"Cookie Bot": "Cookie-бот",
"Copied!": "Скопировано!",
"Copy and close": "Скопировать и закрыть",
"Copy from stream": "Копировать из канала",
"Copy link to conversation": "Скопировать ссылку в беседу",
"Copy zuliprc": "Копировать zuliprc",
"Create": "Создать",
"Create bot": "Создать бота",
"Create bot": "Создать бот",
"Create new stream": "Создать новый канал",
"Create stream": "Создать канал",
"Creating bot": "Создаю бота",
"Creating bot": "Создаю бот",
"Creating stream...": "Создаю канал...",
"Current password": "Текущий пароль",
"Custom": "",
"Custom": "Свое",
"Custom emoji added!": "Дополнительный эмодзи добавлен!",
"Custom filter added!": "Дополнительный фильтр добавлен!",
"Custom profile fields": "Дополнительные поля профиля",
"Customize profile picture": "",
"Data exports": "",
"Customize profile picture": "Настроить изображение профиля",
"Data exports": "Выгрузка данных",
"Date uploaded": "Дата загрузки",
"Day": "День",
"Day mode": "Дневной режим",
"Day of the week to send digests": "",
"Day of the week to send digests": "День недели для отправки дайджеста",
"Deactivate": "Отключить",
"Deactivate account": "Отключить учетную запись",
"Deactivate now": "Отключить сейчас",
"Deactivate organization": "Отключить организацию",
"Deactivate your account": "Отключить вашу учетную запись",
"Deactivated": "",
"Deactivated": "Отключено",
"Deactivated users": "Отключенные пользователи",
"Deactivation encountered an error. Please reload and try again.": "Ошибка деактивации. Перезагрузите и попробуйте еще раз.",
"Default language": "Язык по умолчанию",
"Default settings for new users joining this organization.": "",
"Default settings for new users joining this organization.": "Настройки по умолчанию для новых пользователей этой организации.",
"Default user settings": "Настройки пользователя по умолчанию",
"Delete": "Удалить",
"Delete alert word": "Удалить сигнальное слово",
"Delete all messages in <b>__topic_name__</b>": "",
"Delete bot": "Удалить бота",
"Delete all messages in <b>__topic_name__</b>": "Удалить все сообщения в <b>__topic_name__</b>",
"Delete bot": "Удалить бот",
"Delete draft": "Удалить черновик",
"Delete file": "Удалить файл",
"Delete logo": "Удалить логотоп",
"Delete message": "Удалить сообщение",
"Delete messages": "Удалить сообщения",
"Delete profile picture": "",
"Delete profile picture": "Удалить картинку профиля",
"Delete stream": "Удалить канал",
"Delete topic": "Удалить тему",
"Delete user group": "Удалить группу пользователей",
"Deleted successfully!": "Удалено успешно!",
"Deleting this stream will immediately unsubscribe everyone, and the stream's content will not be recoverable.": "Удаление этого канала моментально отпишет от него всех участников, а сообщения в канале будут безвозвратно удалены.",
"Demote inactive streams": "",
"Demote inactive streams": "Убирать неактивные каналы",
"Dense mode": "Сжатый режим",
"Depending on the size of your organization, an export can take anywhere from seconds to an hour.": "",
"Depending on the size of your organization, an export can take anywhere from seconds to an hour.": "В зависимости от размера вашей организации, экспорт может занять от нескольких секунд до часа.",
"Description": "Описание",
"Desktop": "",
"Desktop notifications are triggered for messages that are offscreen when they arrive. Mobile and email notifications are triggered once you have been away from Zulip for a few minutes.": "",
"Desktop": "Рабочий стол",
"Desktop notifications are triggered for messages that are offscreen when they arrive. Mobile and email notifications are triggered once you have been away from Zulip for a few minutes.": "Всплывающие уведомления на рабочем столе вызываются сообщениями, которые в момент поступления не отображаются на экране. Уведомления на мобильных устройствах и по электронной почте вызываются, когда вы уже несколько минут не присутствуете в Zulip.",
"Disabled": "Отключен",
"Discard": "Сбросить",
"Discard changes": "Отменить изменения",
@@ -204,23 +204,23 @@
"Download": "Скачать",
"Download .zuliprc": "Скачать .zuliprc",
"Download botserverrc": "Скачать botserverrc",
"Download config of all active outgoing webhook bots in Zulip Botserver format.": "Загрузить конфигурацию всех исходящих вебхук ботов в формате Zulip Botserver",
"Download config of all active outgoing webhook bots in Zulip Botserver format.": "Загрузить конфигурацию всех исходящих вебхук-ботов в формате Zulip Botserver",
"Download file": "Скачать файл",
"Download zuliprc": "Скачать zuliprc",
"Drafts": "Черновики",
"Drafts older than <strong>__draft_lifetime__</strong> days are automatically removed.": "",
"Drafts older than <strong>__draft_lifetime__</strong> days are automatically removed.": "Черновики старше<strong>__draft_lifetime__</strong> дней автоматически удаляются.",
"EDITED": "ИЗМЕНЕНО",
"Edit": "Изменить",
"Edit bot": "Изменить бота",
"Edit bot": "Изменить бот",
"Edit status message": "Редактировать статус",
"Edit user": "Изменить пользователя",
"Edit your profile": "Редактировать свой профиль",
"Edited (__last_edit_timestr__)": "Изменено (__last_edit_timestr__)",
"Email": "Адрес email",
"Email address": "Адрес email",
"Email address changes are disabled in this organization.": "Изменение адреса email отключено в этой организации.",
"Email": "Адрес электронной почты",
"Email address": "Адрес электронной почты",
"Email address changes are disabled in this organization.": "Изменение адреса электронной почты отключено в этой организации.",
"Email copied": "Электронная почта скопирована",
"Email notifications": "Уведомления на email",
"Email notifications": "Уведомления на электронную почту",
"Emoji name": "Название эмодзи",
"Emojiset changed successfully!": "Набор эмодзи изменен успешно.",
"Enable message edit history": "Включить историю редактирования сообщений",
@@ -239,13 +239,13 @@
"Error removing subscription": "Ошибка удаления подписки",
"Error removing user from this stream.": "Ошибка удаления пользователя с канала.",
"Error saving edit": "Ошибка сохранения изменений",
"Error: Cannot deactivate the only organization administrator.": "",
"Error: Cannot deactivate the only organization administrator.": "Ошибка: Нельзя деактивировать единственного администратора организации.",
"Estimated messages per week": "Примерно сообщений в неделю",
"Expand message (-)": "",
"Export failed": "",
"Export started. Check back in a few minutes.": "",
"Exports all users, settings, and all data visible in public streams.": "",
"External account type": "",
"Expand message (-)": "Развернуть сообщения (-)",
"Export failed": "Ошибка экспорта",
"Export started. Check back in a few minutes.": "Экспорт данных начат. Проверьте через пару минут.",
"Exports all users, settings, and all data visible in public streams.": "Выгрузка всех пользователей, настроек, и всех данных, доступных в публичных каналах.",
"External account type": "Тип внешнего аккаунта",
"External link": "Внешняя ссылка",
"Failed": "Не удалось",
"Failed to change notifications stream!": "Не удалось изменить канал для уведомлений!",
@@ -254,57 +254,57 @@
"Failed!": "Не удалось!",
"Field choices": "Выбор поля",
"File": "Файл",
"File and image uploads have been disabled for this organization.": "",
"File and image uploads have been disabled for this organization.": "Для данной организации отключена возможность закачки файлов и изображений.",
"File type is not supported.": "Тип файла не поддерживается",
"File upload is not yet available for your browser.": "Загрузка файлов не доступна в вашем браузере.",
"Filter": "Фильтр",
"Filter bots": "Фильтр ботов",
"Filter deactivated users": "Фильтр отключенных пользователей",
"Filter emojis": "",
"Filter exports": "",
"Filter emojis": "Фильтр эмодзи",
"Filter exports": "Отфильтровать",
"Filter invites": "Фильтр приглашений",
"Filter linkifiers": "",
"Filter linkifiers": "Отфильтровать",
"Filter streams": "Фильтр каналов",
"Filter users": "Фильтр пользователей",
"First time? Read our <a href=\"/help/getting-your-organization-started-with-zulip#create-streams\" target=\"_blank\">guidelines</a> for creating and naming streams.": "",
"First time? Read our <a href=\"/help/getting-your-organization-started-with-zulip#create-streams\" target=\"_blank\">guidelines</a> for creating and naming streams.": "Это ваш первый раз? Прочитайте наше <a href=\"/help/getting-your-organization-started-with-zulip#create-streams\" target=\"_blank\">руководство</a> по созданию и наименованию каналов.",
"Forgotten it?": "Забыли?",
"Formatting": "Форматирование текста",
"Friday": "",
"Friday": "Пятница",
"Full name": "Полное имя",
"Generate invite link": "Создать ссылку для приглашения",
"Generate new API key": "Сгенерировать новый API-ключ",
"Generating link...": "",
"Generating link...": "Сгенерировать ссылку",
"Generic": "Общий",
"Get API key": "Получить API-ключ",
"Go back": "Вернуться",
"Got it!": "Понял!",
"Guest": "Гость",
"Guests cannot edit custom emoji.": "",
"Hide starred message count": "",
"Guests cannot edit custom emoji.": "Гости не могут редактировать собственные эмодзи.",
"Hide starred message count": "Скрыть количество отмеченных сообщений",
"High contrast mode": "Высоко-контрастный режим",
"Hint": "Совет",
"Hint (up to 80 characters)": "Подсказка (до 80 символов)",
"Idle": "Ожидающий",
"Image": "Изображение",
"Inactive bots": "Неактивные боты",
"Include content of private messages in desktop notifications": "Получать содержимое личных сообщений во всплывающих оповещениях",
"Include content of private messages in desktop notifications": "Получать содержимое личных сообщений во всплывающих уведомлениях",
"Include message content in missed message emails": "Включить содержимое сообщения в пропущенные сообщения электронной почты",
"Include organization name in subject of missed message emails": "Включить название организации в тему пропущенных сообщений электронной почты",
"Incoming webhooks can only send messages.": "Входящий вебхук может только отправлять сообщения.",
"Interface": "Интерфейс",
"Invalid slash command. Check if you are missing a space after the command.": "Неверная команда косой черты. Проверьте, возможно вы пропустили пробел после команды.",
"Invalid stream id": "Неверный код канала",
"Invitation link: <a href=\"__link__\">__link__</a>": "",
"Invitation link: <a href=\"__link__\">__link__</a>": "Ссылка приглашения: <a href=\"__link__\">__link__</a>",
"Invite": "Пригласить",
"Invite link": "",
"Invite link": "Ссылка приглашения",
"Invite more users": "Пригласить еще пользователей",
"Invited as": "",
"Invited as": "Приглашен как",
"Invited at": "Приглашен в",
"Invited by": "Пригласил",
"Invites": "",
"Invites": "Приглашения",
"Inviting...": "Приглашаю...",
"It's been a while! Since you were last here, you received <b>__unread_count__</b> new messages.": "С возвращением! Пока вас не было, для вас накопилось <b>__unread_count__</b> новых сообщений.",
"Joined": "",
"Joined": "Подключился",
"Joining the organization": "Присоединение к организации",
"Just now": "Только что",
"Keyboard shortcuts": "Горячие клавиши",
@@ -312,7 +312,7 @@
"Language settings": "Языковые настройки",
"Large number of subscribers": "Большое количество подписчиков",
"Last active": "Последняя активность",
"Last active: __last_seen__": "",
"Last active: __last_seen__": "Последняя активность: __last_seen__",
"Last modified": "Последнее изменение",
"Local time": "Местное время",
"Looking for our <a href=\"/integrations\" target=\"_blank\">Integrations</a> or <a href=\"/api\" target=\"_blank\">API</a> documentation?": "Ищите нашу документацию по <a href=\"/integrations\" target=\"_blank\">интеграции</a> или <a href=\"/api\" target=\"_blank\">API</a>? ",
@@ -322,22 +322,22 @@
"Mark all messages in <b>__stream.name__</b> as read": "Отметить все сообщения в <b>__stream.name__</b> как прочитанные",
"Mark all messages in <b>__topic_name__</b> as read": "Отметить все сообщения в <b>__topic_name__</b> как прочитанные",
"Marketing team": "Команда по маркетингу",
"Marking all messages as read\u2026": "Пометить все сообщения как прочитанные",
"Marking all messages as read\u2026": "Пометить все сообщения как прочитанные...",
"Member": "Участник",
"Mentioned in": "Упомянут в",
"Message #__- stream_name__": "",
"Message __- recipient_names__": "",
"Message #__- stream_name__": "Сообщение в #__- stream_name__",
"Message __- recipient_names__": "Сообщение __- recipient_names__",
"Message actions": "Действия с сообщением",
"Message editing": "Редактирование сообщений",
"Message formatting": "Форматирование сообщений",
"Message sent when you were not subscribed": "Сообщение было отправлено, когда вы были не подписаны на канал",
"Messages retention period in days (blank means messages are retained forever)": "Период хранения сообщений в днях (пустое значение означает сохранять навсегда)",
"Method": "Метод",
"Mobile": "",
"Mobile": "Мобильный",
"Mobile notifications": "Мобильные уведомления",
"Mobile push notifications are not configured on this server.": "Мобильные push-оповещения не настроены на этом сервере.",
"Monday": "",
"More details are available <a href=\"/help/add-a-custom-linkification-filter\" target=\"_blank\">in the Help Center article</a>.": "",
"Mobile push notifications are not configured on this server.": "Мобильные push-уведомления не настроены на этом сервере.",
"Monday": "Понедельник",
"More details are available <a href=\"/help/add-a-custom-linkification-filter\" target=\"_blank\">in the Help Center article</a>.": "Более подробная информация доступна <a href=\"/help/add-a-custom-linkification-filter\" target=\"_blank\">в статье в справочном центре</a>.",
"More than 2 weeks ago": "Более 2-х недель назад",
"Mute stream": "Заглушить канал",
"Mute the stream <b>__stream.name__</b>": "Заглушить поток <b>__stream.name__</b>",
@@ -347,11 +347,11 @@
"Muted streams don't show up in \\\"All messages\\\" or generate notifications unless you are mentioned.": "Заглушенный каналы не отображаются в \\\"Все сообщения\\\" и не показывают уведомления если вы не упомянуты.",
"N": "N",
"Name": "Имя",
"Name changes are disabled in this organization. Contact an administrator to change your name.": "",
"Name or email": "Имя или email",
"Narrow to __- message_recipient__": "",
"Name changes are disabled in this organization. Contact an administrator to change your name.": "Изменения имени отключено для данной организации. Чтобы изменить имя, пожалуйста, обратитесь к администротору.",
"Name or email": "Имя или адрес электронной почты",
"Narrow to __- message_recipient__": "Показать только получателя __- message_recipient__",
"Narrow to stream &quot;__display_recipient__&quot;": "Показать только канал &quot;__display_recipient__&quot;",
"Narrow to stream &quot;__display_recipient__&quot;, topic &quot;__topic__&quot;": "",
"Narrow to stream &quot;__display_recipient__&quot;, topic &quot;__topic__&quot;": "Показать только тему &quot;__topic__&quot; в канале &quot;__display_recipient__&quot;",
"Narrow to topic <b>__topic_name__</b>": "Показать только тему <b>__topic_name__</b>",
"Narrow to your private messages with __display_reply_to__": "Показать только личную переписку с __display_reply_to__",
"Never": "Никогда",
@@ -359,7 +359,7 @@
"New": "Новый",
"New alert word": "Новое сигнальное слово",
"New choice": "Новый выбор",
"New email": "Новый email адрес",
"New email": "Новый адрес электронной почты",
"New full name": "Новое полное имя",
"New members can only see messages sent after they join.": "Новые пользователи видят сообщения отправленные после присоединения",
"New members can view complete message history.": "Новые пользователи видят всю историю сообщений",
@@ -375,27 +375,27 @@
"Night": "Ночь",
"Night mode": "Ночной режим",
"No": "Нет",
"No bots match your current filter.": "Нет ботов попадающих под текущий фильтр.",
"No custom emoji.": "",
"No default streams match you current filter.": "Нет каналов по умолчанию, попадающих под текущий фильтр.",
"No bots match your current filter.": "Нет ботов, подпадающих под текущий фильтр.",
"No custom emoji.": "Нет дополнительных эмодзи.",
"No default streams match you current filter.": "Нет каналов по умолчанию, подпадающих под текущий фильтр.",
"No description.": "Нет описания.",
"No drafts.": "Нет черновиков.",
"No exports.": "",
"No invites match your current filter.": "Нет приглашений подпадающих под текущий фильтр",
"No linkifiers set.": "",
"No exports.": "Нет выгрузок данных.",
"No invites match your current filter.": "Нет приглашений, подпадающих под текущий фильтр",
"No linkifiers set.": "Пока нет фильтров.",
"No more topics.": "Нет больше тем.",
"No owner": "",
"No owner": "Без владельца",
"No restrictions": "Нет ограничений",
"No users match your current filter.": "Нет пользователей попадающих под текущий фильтр.",
"No users match your current filter.": "Нет пользователей, подпадающих под текущий фильтр.",
"None": "Пусто",
"Note that any bots that you maintain will be disabled.": "Обратите внимание, все ваши боты будут отключены.",
"Note that organizations are limited to five exports per week.": "",
"Nothing to preview": "Пустое сообщение",
"Notification sound": "",
"Notifications": "Оповещения",
"Notifications for @all/@everyone mentions": "",
"Notifications stream changed!": "Оповещения канала изменены!",
"Notifications stream disabled!": "Оповещения канала отключены!",
"Note that any bots that you maintain will be disabled.": "Обратите внимание, что все ваши боты будут отключены.",
"Note that organizations are limited to five exports per week.": "Пожалуйста, учтите, что организации ограничены пятью выгрузками данных за неделю.",
"Nothing to preview": "Ничего нет для предпросмотра",
"Notification sound": "Звук уведомления",
"Notifications": "Уведомления",
"Notifications for @all/@everyone mentions": "Уведомления про упоминание всех через @all/@everyone",
"Notifications stream changed!": "Уведомления канала изменены!",
"Notifications stream disabled!": "Уведомления канала отключены!",
"Offline": "Не в сети",
"Old password": "Старый пароль",
"Only group members and organization administrators can modify a group.": "Только участники группы и администраторы организации могут редактировать группу.",
@@ -403,12 +403,12 @@
"Only organization administrators can add custom emoji in this organization.": "Только администраторы могут добавлять пользовательские эмодзи в эту организацию.",
"Only organization administrators can add generic bots": "Только админы могут добавлять общих ботов",
"Only organization administrators can edit these settings.": "Только администраторы организации могут изменять эти настройки.",
"Only organization administrators can modify user groups in this organization.": "",
"Only organization administrators can modify user groups in this organization.": "Только администраторы организации могут изменять группы пользователей в этой организации.",
"Only organization administrators can post.": "Только администраторы организации могут писать",
"Only organization admins are allowed to post to this stream.": "Только администратор организации может отправлять сообщения в этот канал.",
"Only stream members can add users to a private stream": "",
"Only stream members can add users to a private stream": "Только подписчики канала могут добавлять пользователей к закрытому каналу.",
"Optional": "Необязательно",
"Or, to automatically linkify GitHub's <code>org/repo#1234</code> syntax:": "",
"Or, to automatically linkify GitHub's <code>org/repo#1234</code> syntax:": "Или автоматически фильтровать синтаксис GitHub <code>org/repo#1234</code>:",
"Organization": "Организация",
"Organization administrator": "Администратор организации",
"Organization administrators can change this in the organization settings.": "Администраторы организации могут изменить это в настройках организации.",
@@ -416,15 +416,15 @@
"Organization logo": "Логотип организации",
"Organization name": "Название организации",
"Organization profile": "Профиль организации",
"Organization profile picture": "",
"Organization profile picture": "Картинка профиля организации",
"Organization settings": "Настройки организации",
"Organization using __percent_used__% of __upload_quota__.": "",
"Other notification settings": "Другие настройки оповещени",
"Organization using __percent_used__% of __upload_quota__.": "Организация использует __percent_used__% своих __upload_quota__.",
"Other notification settings": "Другие настройки уведомлений",
"Other permissions": "Другие разрешения",
"Other settings": "Другие настройки",
"Outgoing webhook message format": "Формат сообщения исходящего вебхука",
"Owner": "Владелец",
"Owner: __name__": "",
"Owner: __name__": "Владелец:__name__",
"Password": "Пароль",
"Password is too weak": "Пароль слишком простой",
"Password should be at least __length__ characters long": "Длина пароля должна быть не менее __length__ символов",
@@ -439,18 +439,18 @@
"Please specify a stream": "Укажите канал",
"Please specify a topic": "Укажите тему",
"Please specify at least one valid recipient": "Укажите хотя бы одного получателя",
"Press > for list of topics": "",
"Prevent users from changing their avatar": "",
"Prevent users from changing their email address": "Запретить пользователям изменять свой email адрес",
"Press > for list of topics": "Нажмите > для списка тем",
"Prevent users from changing their avatar": "Запретить пользователям изменять свой аватар",
"Prevent users from changing their email address": "Запретить пользователям изменять свой адрес электронной почты",
"Prevent users from changing their name": "Запретить пользователям изменять свое имя",
"Preview": "Предпросмотр",
"Preview organization profile": "",
"Preview organization profile": "Предпросмотр профиля организации",
"Preview profile": "Предпросмотр профиля",
"Private messages and mentions": "",
"Private messages, @-mentions, and alert words": "",
"Pro tip: You can use 'd' to open your drafts.": "",
"Private messages and mentions": "Личные сообщения и упоминания",
"Private messages, @-mentions, and alert words": "Личные сообщения, упоминания через @ и сигнальные слова",
"Pro tip: You can use 'd' to open your drafts.": "Совет профессионала: вы можете нажать клавишу 'd', чтобы открыть свои черновики.",
"Profile": "Профиль",
"Profile picture": "",
"Profile picture": "Картинка профиля",
"Quote and reply": "Ответить с цитированием",
"Reactivate": "Активировать",
"Reactivate bot": "Включить бота",
@@ -461,7 +461,7 @@
"Remove from default": "Удалить из списка по умолчанию",
"Reply (r)": "Ответ (r)",
"Reply mentioning user": "Ответить упомянув пользователя",
"Requesting user": "",
"Requesting user": "Запрашиваем пользователя",
"Require topics in stream messages": "Требовать тему в сообщениях канала",
"Resend": "Переслать",
"Resend invitation to <span class=\"email\"></span>": "Повторно пригласить <span class=\"email\"></span>",
@@ -473,12 +473,12 @@
"Restrict to a list of domains": "Ограничить список доменов",
"Retry": "Повторить",
"Revoke": "Отозвать",
"Revoke invitation link": "",
"Revoke invitation to __email__": "",
"Revoke invitation link": "Отозвать ссылку для приглашения",
"Revoke invitation to __email__": "Отозвать приглашение __email__",
"Revoke now": "Отозвать сейчас",
"Role": "Роль",
"SAVING": "",
"Saturday": "",
"SAVING": "СОХРАНЕНИЕ",
"Saturday": "Суббота",
"Save": "Сохранить",
"Save changes": "Сохранить изменения",
"Save failed": "Ошибка сохранения",
@@ -493,30 +493,30 @@
"Select date and time": "Выбрать дату и время",
"Select default language": "Выбрать язык по умолчанию",
"Send digest emails when I'm away": "Отправлять дайджест на почту, когда меня нет",
"Send email notifications for new logins to my account": "Посылать email-уведомления о новых логинах на мою учётную запись",
"Send email notifications for new logins to my account": "Посылать уведомления о новых логинах на мою учётную запись по электронной почте",
"Send emails introducing Zulip to new users": "Отправлять ознакомительное письмо о Zulip новым пользователям",
"Send mobile notifications even if I'm online (useful for testing)": "",
"Send mobile notifications even if I'm online (useful for testing)": "Посылать уведомления на мобильные устройства, даже если я онлайн (полезно для тестирования)",
"Send private message": "Отправить личное сообщение",
"Send weekly digest emails to inactive users": "",
"Send weekly digest emails to inactive users": "Посылать еженедельный обзор по электронной почте неактивным пользователям",
"Sent!": "Отправлено!",
"Sent! Scroll down to view your message.": "",
"Sent! Your message is outside your current narrow.": "",
"Sent! Your message was sent to a stream you have muted.": "",
"Sent! Your message was sent to a topic you have muted.": "",
"Sent! Your recent message is outside the current search.": "",
"Sent! Scroll down to view your message.": "Выслано! Прокрутите вниз, чтобы посмотреть свое сообщение.",
"Sent! Your message is outside your current narrow.": "Выслано! Ваше сообщение, в данный момент, вне области вашей текущей настройки фильтра.",
"Sent! Your message was sent to a stream you have muted.": "Выслано! Ваше сообщение было послано в канал, который вы заглушили.",
"Sent! Your message was sent to a topic you have muted.": "Выслано! Ваше сообщение было послано в тему, которую вы заглушили.",
"Sent! Your recent message is outside the current search.": "Выслано! Ваше сообщение находится вне пределах вашего текущего поиска.",
"Set a status message": "Установить статус",
"Set yourself as active": "",
"Set yourself as unavailable": "",
"Set yourself as active": "Переключить свой статус на \"активен\"",
"Set yourself as unavailable": "Переключить свой статус на \"недоступен\"",
"Settings": "Настройки",
"Setup": "Настройка",
"Setup two factor authentication": "Настройка двухфакторной аутентификации",
"Show API key": "",
"Show API key": "Показать API ключ",
"Show counts for starred messages": "Показывать счётчик помеченных сообщений",
"Show fewer": "",
"Show fewer": "Показать меньше",
"Show more": "Показать еще",
"Show previews of linked websites": "Показать предпросмотр сайтов",
"Show previews of uploaded and linked images": "Показать предпросмотр загруженных изображений и ссылок на них",
"Show starred message count": "",
"Show starred message count": "Показать количество избранных сообщений",
"Show/change your API key": "Показать/изменить ваш API-ключ",
"Signup notifications stream changed!": "Канал уведомлений о регистрации изменен!",
"Signup notifications stream disabled!": "Канал уведомлений о регистрации отключен!",
@@ -525,7 +525,7 @@
"Slack's outgoing webhooks": "Исходящие вебхуки Slack",
"Sorry, the file was too large.": "Извините, файл слишком большой.",
"Star": "Отметить",
"Start public export": "",
"Start public export": "Начать публичную выгрузку",
"Stream": "Канал",
"Stream color": "Цвет канала",
"Stream created recently": "Недавно созданные каналы",
@@ -544,33 +544,33 @@
"Subscribed successfully!": "Подписан успешно!",
"Subscriber count": "Количество подписчиков",
"Subscribers": "Подписчики",
"Sunday": "",
"System bot": "",
"Sunday": "Воскресенье",
"System bot": "Системный бот",
"Task already exists": "Задача уже существует",
"Text": "Текст",
"The export URL is not yet available... Check back soon.": "",
"The export URL is not yet available... Check back soon.": "Ссылка выгрузки пока еще не доступна... Возвращайтесь через несколько минут.",
"The recipient __recipient__ is not valid": "Получатель __recipient__ не является допустимым",
"The recipients __recipients__ are not valid": "Получатели __recipients__ не являются допустимыми",
"The stream description cannot contain newline characters.": "",
"The stream description cannot contain newline characters.": "Описание канала не должна содержать перенос строки.",
"The stream description has been updated!": "Описание канала обновлено!",
"The stream has been renamed!": "Канал переименован!",
"Their password will be cleared from our systems, and any bots they maintain will be disabled.": "Их пароли будут стерты в нашей системе, а все их боты будут отключены.",
"There are no messages to reply to.": "Нет сообщений для ответа.",
"These settings are explained in detail in the <a target=\"_blank\" href=\"/help/stream-permissions\">help center</a>.": "Эти настройки подробно освещены в <a target=\"_blank\" href=\"/help/stream-permissions\">центре помощи</a>.",
"This action is permanent and cannot be undone. All users will permanently lose access to their Zulip accounts.": "Это действие является перманентным и не может быть отменено. Все пользователи потеряют доступ к своим учетным записям Zulip.",
"This is a <i class=\"fa fa-globe\" aria-hidden=\"true\"></i> <b>web public stream</b>. Any member of the organization can join without an invitation and anyone on the internet can read the content published.": "",
"This is a <i class=\"hash\" aria-hidden=\"true\"></i> <b>public stream</b>. Any member of the organization can join without an invitation.": "",
"This is a <i class=\"fa fa-globe\" aria-hidden=\"true\"></i> <b>web public stream</b>. Any member of the organization can join without an invitation and anyone on the internet can read the content published.": "Этот канал является <i class=\"fa fa-globe\" aria-hidden=\"true\"></i> <b>открытым сетевым каналом</b>. Любой член организации может к нему присоединиться без приглашения, и любой пользователь интернета может читать все, что в нем опубликовано.",
"This is a <i class=\"hash\" aria-hidden=\"true\"></i> <b>public stream</b>. Any member of the organization can join without an invitation.": "Этот канал является <i class=\"hash\" aria-hidden=\"true\"></i> <b>открытым каналом</b>. Любой член организации может к нему присоединиться без приглашения.",
"This is a <span class=\"fa fa-lock\" aria-hidden=\"true\"></span> <b>private stream</b>. Only people who have been invited can access its content, but any member of the stream can invite others.": "Это <span class=\"fa fa-lock\" aria-hidden=\"true\"></span> <b>закрытый канал</b>. Только приглашенные люди имеют доступ к его содержанию, но любой участник канала может пригласить других.",
"This is a private stream": "Это закрытый канал",
"This organization is configured to restrict editing of message content to __minutes_to_edit__ minutes after it is sent.": "Согласно настройкам этой организации, редактировать сообщение можно только в течение __minutes_to_edit__ минут после отправки.",
"This stream is reserved for <strong>announcements</strong>. <br /> Are you sure you want to message all <strong>__count__</strong> people in this stream?": "Этот канал зарезервирован для <strong>анонсов</strong>. <br /> Вы уверены, что хотите отправить сообщение всем <strong>__count__</strong> пользователям в этом канале?",
"Thursday": "",
"Time": "",
"Time format": "",
"Thursday": "Четверг",
"Time": "Время",
"Time format": "Формат времени",
"Time settings": "Настройки времени",
"Time zone": "Часовой пояс",
"Time's up!": "Время вышло!",
"Tip: You can also send \"/poll Some question\"": "",
"Tip: You can also send \"/poll Some question\"": "Совет: вы также можете начать голосование через \"/poll какой-либо вопрос\"",
"Today": "Сегодня",
"Toggle subscription": "Переключить подписку",
"Tomorrow": "Завтра",
@@ -578,14 +578,14 @@
"Topic editing only": "Доступно только редактирование темы",
"Topic muted": "Тема заглушена",
"Try again": "Попробуйте еще",
"Tuesday": "",
"Tuesday": "Вторник",
"Two factor authentication": "Двухфакторная аутентификация",
"Type": "Тип",
"URL format string": "Строка формата URL",
"URL pattern": "",
"URL pattern": "Шаблон URL",
"Un-collapse": "Развернуть",
"Unable to upload that many files at once.": "Не могу загрузить столько много файлов за раз.",
"Unavailable": "",
"Unavailable": "Недоступно",
"Uncheck all": "Снять отметки со всего",
"Unknown": "Неизвестный",
"Unless I say otherwise for a particular stream, I want:": "Если не указано иначе для конкретного канала, я хочу:",
@@ -594,9 +594,9 @@
"Unmute the topic <b>__topic__</b>": "Включить оповещения темы <b>__topic__</b>",
"Unmute the topic <b>__topic_name__</b>": "Включить оповещения из темы <b>__topic_name__</b>",
"Unpin stream <b>__stream.name__</b> from top": "Открепить канал <b>__stream.name__</b> сверху списка",
"Unread count summary (appears in desktop sidebar and browser tab)": "",
"Unread count summary (appears in desktop sidebar and browser tab)": "Кол-во непрочитанных (отображается в боковой панели на рабочем столе и во вкладке браузера)",
"Unstar": "Снять отметку",
"Unstar all messages": "",
"Unstar all messages": "Снять отметку со всех отмеченных сообщений",
"Unsubscribe": "Отписаться",
"Unsubscribed successfully!": "Отписан успешно!",
"Up to N minutes after posting": "До N минут после публикации",
@@ -606,19 +606,19 @@
"Upload image or GIF": "Загрузить изображение или GIF",
"Upload logo": "Загрузить логотип",
"Upload new logo": "Загрузить новый логотип",
"Upload new profile picture": "",
"Upload profile picture": "",
"Uploading logo.": "",
"Uploading profile picture.": "",
"Upload new profile picture": "Загрузить новое фото профиля",
"Upload profile picture": "Загрузить фото профиля",
"Uploading logo.": "Загрузка логотипа",
"Uploading profile picture.": "Загрузка фото профиля",
"Uploading\u2026": "Загрузка\\u2026",
"Use full width on wide screens": "",
"Use full width on wide screens": "Использовать широкий формат отображения рабочей области",
"User already subscribed.": "Пользователь уже подписан.",
"User group added!": "Группа пользователей добавлена!",
"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.": "",
"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.": "Группы пользователей позволяют вам <a href=\"/help/mention-a-user-or-group\" target=\"_blank\">упоминать</a> несколько пользователей сразу. Если вы упоминаете группу пользователей, каждый, находящийся в данной группе, получает уведомление, будто упомянули лично его.",
"User identity": "Идентификатор пользователя",
"User is already not subscribed.": "Пользователь уже отписан.",
"User is deactivated": "Пользователь отключен",
"User list on left sidebar in narrow windows": "Список пользователей в левой боковой панели",
"User list on left sidebar in narrow windows": "Список пользователей в левой боковой панели на узких окнах",
"User role": "Роль пользователя",
"User settings": "Настройки пользователя",
"User(s) invited successfully.": "Пользователи успешно приглашены.",
@@ -627,7 +627,7 @@
"Video chat provider": "Провайдер видеочата",
"View edit history": "Показать историю редактирования",
"View file": "Показать файл",
"View full profile": "",
"View full profile": "Просмотр полного профиля",
"View messages sent": "Просмотр отправленных сообщений",
"View private messages": "Показать личные сообщения",
"View private messages to myself": "Показать личные сообщения для меня",
@@ -635,21 +635,21 @@
"View source / Edit topic": "Исходный текст / Редактирование темы",
"View stream": "Просмотр канала",
"View your profile": "Отрыть профиль пользователя",
"Visual desktop notifications": "Визуальные оповещения на рабочем столе",
"Waiting period (days)": "",
"Waiting period before new members turn into full members": "",
"Visual desktop notifications": "Визуальные уведомления на рабочем столе",
"Waiting period (days)": "Период ожидания (дни)",
"Waiting period before new members turn into full members": "Период ожидания, пока из новых членов становятся полноценные члены",
"Warning: <strong>__stream_name__</strong> is a private stream.": "Предупреждение: <strong> __ stream_name __ </ strong> - закрытый канал.",
"We are about to have a poll. Please wait for the question.": "",
"We recommend against deleting topics unless needed for security reasons or managing abuse. Deleted messages can be confusing for users who may later visit the topic via notifications.": "",
"Wednesday": "",
"Who can access user email addresses": "",
"We are about to have a poll. Please wait for the question.": "У нас будет голосование. Пожалуйста, подождите, пока появится вопрос.",
"We recommend against deleting topics unless needed for security reasons or managing abuse. Deleted messages can be confusing for users who may later visit the topic via notifications.": "Мы не советуем удалять темы, за исключением, если это необходимо по соображением безопасности или борьбы со злоупотреблением. Удаленные сообщения могут смущать пользователей, которые будут позже просматривать тему по уведомлениям.",
"Wednesday": "Среда",
"Who can access user email addresses": "Кто имеет доступ к адресам электронной почты пользователей",
"Who can add bots": "Кто может добавлять ботов",
"Who can add custom emoji": "Кто может добавлять эмодзи",
"Who can add users to streams": "",
"Who can create and manage user groups": "",
"Who can add users to streams": "Кто имеет право добавлять пользователей к каналам",
"Who can create and manage user groups": "Кто имеет право создавать и управлять группами пользователей",
"Who can create streams": "Кто может создавать каналы",
"Working\u2026": "Работаем\\u2026",
"Would you like to unstar all starred messages? This action cannot be undone.": "",
"Would you like to unstar all starred messages? This action cannot be undone.": "Вы хотите снять отмметку со всех отмеченных сообщений? Это действие нельзя будет отменить.",
"Write": "Редактирование",
"Yes, delete this stream": "Да, удалить этот канал",
"Yes, send": "Да, отправить",
@@ -661,9 +661,9 @@
"You and __recipients__": "Вы и __recipients__",
"You are not currently subscribed to this stream.": "Вы не подписаны на этот канал.",
"You are not subscribed to stream __stream__": "Вы не подписаны на канал __stream__",
"You are searching for messages that are sent by more than one person, which is not possible.": "",
"You are searching for messages that belong to more than one stream, which is not possible.": "",
"You are searching for messages that belong to more than one topic, which is not possible.": "",
"You are searching for messages that are sent by more than one person, which is not possible.": "Вы ищете сообщения, которые были посланы более чем одним человеком, а это невозможно.",
"You are searching for messages that belong to more than one stream, which is not possible.": "Вы ищете сообщения, которые относятся к более чем одному каналу, а это невозможно.",
"You are searching for messages that belong to more than one topic, which is not possible.": "Вы ищете сообщения, которые относятся к более чем одной теме, а это невозможно.",
"You cannot create a stream with no subscribers!": "Вы не можете создать канал без подписчиков!",
"You have muted the topic <span class=\"topic\"></span> under the <span class=\"stream\"></span> stream.": "Вы заглушили тему <span class=\"topic\"></span> в канале <span class=\"stream\"></span>.",
"You have no active bots.": "У вас нет активных ботов.",
@@ -673,37 +673,37 @@
"You have nothing to send!": "Нечего отправлять!",
"You must be an organization administrator to create a stream without subscribing.": "Вы должны быть администратором, чтобы создавать каналы не подписываясь.",
"You need to be running Zephyr mirroring in order to send messages!": "Зеркалирование Zephyr должно быть включено для возможности отправки сообщений!",
"You searched for:": "",
"You searched for:": "Вы искали:",
"You subscribed to stream __stream__": "Вы подписаны на канал __stream__",
"You unsubscribed from stream __stream__": "Вы отписаны от канала __stream__",
"You're not subscribed to this stream. You will not be notified if other users reply to your message.": "Вы не подписаны на этот канал. Вы не получите уведомление если кто-то ответит на ваше сообщение.",
"Your API key:": "Ваш API-ключ:",
"Your reminder note is empty!": "Текст вашего напоминания пуст!",
"Zoom API key (required)": "",
"Zoom API secret (required if changed)": "",
"Zoom user ID or email address (required)": "",
"[Condense message]": "",
"Zoom API key (required)": "API-ключ Zoom (необходим)",
"Zoom API secret (required if changed)": "Секретный API-ключ Zoom (необходим, если изменился)",
"Zoom user ID or email address (required)": "ID пользователя Zoom или адрес электронной почты (необходим)",
"[Condense message]": "[Свернуть сообщение]",
"[Configure]": "[Настроить]",
"[Disable]": "[Отключить]",
"[More...]": "[Еще...]",
"__days__ days ago": "",
"__days__ days ago": "__days__ дней тому назад",
"__hours__ hours ago": "__hours__ часов назад",
"__last_active_date__": "",
"__last_active_date__": "__last_active_date__",
"__minutes__ min to edit": "осталось __minutes__ мин. для изменения",
"__minutes__ minutes ago": "__minutes__ минут назад",
"__seconds__ sec to edit": "осталось __seconds__ сек. для изменения",
"__starred_status__ this message": "__starred_status__ сообщение",
"__wildcard_mention_token__ (Notify stream)": "__wildcard_mention_token__ (канал уведомлений)",
"and": "и",
"clear": "",
"clear": "очистить",
"cookie": "куки",
"group private messages with __recipient__": "",
"group private messages with __recipient__": "группировать личные сообщения с __recipient__",
"in 1 hour": "через 1 час",
"in 20 minutes": "через 20 минут",
"in 3 hours": "через 3 часа",
"leafy green vegetable": "листвяной зеленый овощ",
"marketing": "маркетинг",
"more topics": "еще темы",
"private messages with __recipient__": "",
"private messages with yourself": ""
"private messages with __recipient__": "личные сообщения с __recipient__",
"private messages with yourself": "личные сообщения с собой"
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Zulip\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 04:30+0000\n"
"POT-Creation-Date: 2020-01-16 19:35+0000\n"
"PO-Revision-Date: 2018-04-11 21:06+0000\n"
"Last-Translator: Tim Abbott <tabbott@kandralabs.com>\n"
"Language-Team: Tamil (http://www.transifex.com/zulip/zulip/language/ta/)\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: analytics/views.py:80 zerver/decorator.py:522 zerver/decorator.py:531
#: analytics/views.py:80 zerver/decorator.py:525 zerver/decorator.py:534
msgid "Not allowed for guest users"
msgstr ""
@@ -1351,7 +1351,7 @@ msgstr ""
msgid "Organization permissions"
msgstr ""
#: templates/zerver/app/settings_overlay.html:72 zerver/models.py:1769
#: templates/zerver/app/settings_overlay.html:72 zerver/models.py:1767
msgid "Custom emoji"
msgstr ""
@@ -2957,35 +2957,35 @@ msgstr ""
msgid "Account is not associated with this subdomain"
msgstr ""
#: zerver/decorator.py:471 zerver/decorator.py:533
#: zerver/decorator.py:474 zerver/decorator.py:536
msgid "This endpoint does not accept bot requests."
msgstr ""
#: zerver/decorator.py:513
#: zerver/decorator.py:516
msgid "Must be an server administrator"
msgstr ""
#: zerver/decorator.py:588
#: zerver/decorator.py:591
msgid "This endpoint requires HTTP basic authentication."
msgstr ""
#: zerver/decorator.py:591
#: zerver/decorator.py:594
msgid "Invalid authorization header for basic auth"
msgstr ""
#: zerver/decorator.py:593
#: zerver/decorator.py:596
msgid "Missing authorization header for basic auth"
msgstr ""
#: zerver/decorator.py:670
#: zerver/decorator.py:673
msgid "Not logged in"
msgstr ""
#: zerver/decorator.py:681
#: zerver/decorator.py:684
msgid "Webhook bots can only access webhooks"
msgstr ""
#: zerver/decorator.py:740
#: zerver/decorator.py:743
msgid "Access denied"
msgstr ""
@@ -3408,7 +3408,7 @@ msgstr ""
msgid "Must be an organization administrator or emoji author"
msgstr ""
#: zerver/lib/emoji.py:109 zerver/models.py:601
#: zerver/lib/emoji.py:109 zerver/models.py:599
msgid "Invalid characters in emoji name"
msgstr ""
@@ -3462,7 +3462,11 @@ msgstr ""
msgid "Invalid API key"
msgstr ""
#: zerver/lib/exceptions.py:229
#: zerver/lib/exceptions.py:222
msgid "Malformed API key"
msgstr ""
#: zerver/lib/exceptions.py:234
#, python-brace-format
msgid ""
"The '{event_type}' event isn't currently supported by the {webhook_name} "
@@ -3910,73 +3914,73 @@ msgstr ""
msgid "API usage exceeded rate limit"
msgstr ""
#: zerver/models.py:255
#: zerver/models.py:253
#, fuzzy
#| msgid "Username"
msgid "stream events"
msgstr "பயனர்பெயர்"
#: zerver/models.py:274
#: zerver/models.py:272
msgid "Available on Zulip Standard. Upgrade to access."
msgstr ""
#: zerver/models.py:664
#: zerver/models.py:662
#, python-format
msgid "Invalid filter pattern. Valid characters are %s."
msgstr ""
#: zerver/models.py:680
#: zerver/models.py:678
msgid "Invalid URL format string."
msgstr ""
#: zerver/models.py:1768
#: zerver/models.py:1766
msgid "Unicode emoji"
msgstr ""
#: zerver/models.py:1770
#: zerver/models.py:1768
msgid "Zulip extra emoji"
msgstr ""
#: zerver/models.py:2676
#: zerver/models.py:2690
#, python-format
msgid "Invalid user ID: %d"
msgstr ""
#: zerver/models.py:2680
#: zerver/models.py:2694
#, python-format
msgid "User with ID %d is deactivated"
msgstr ""
#: zerver/models.py:2683
#: zerver/models.py:2697
#, python-format
msgid "User with ID %d is a bot"
msgstr ""
#: zerver/models.py:2713
#: zerver/models.py:2727
msgid "List of options"
msgstr ""
#: zerver/models.py:2716
#: zerver/models.py:2730
msgid "Person picker"
msgstr ""
#: zerver/models.py:2728
#: zerver/models.py:2742
msgid "Short text"
msgstr ""
#: zerver/models.py:2729
#: zerver/models.py:2743
msgid "Long text"
msgstr ""
#: zerver/models.py:2730
#: zerver/models.py:2744
msgid "Date picker"
msgstr ""
#: zerver/models.py:2731
#: zerver/models.py:2745
msgid "Link"
msgstr ""
#: zerver/models.py:2732
#: zerver/models.py:2746
msgid "External account"
msgstr ""

View File

@@ -0,0 +1,2 @@
mech_list: plain
sasldb_path: /etc/sasl2/memcached-sasldb2

View File

@@ -3,6 +3,7 @@
class zulip::app_frontend_base {
include zulip::common
include zulip::nginx
include zulip::sasl_modules
include zulip::supervisor
$web_packages = [
@@ -149,4 +150,15 @@ class zulip::app_frontend_base {
mode => '0755',
source => 'puppet:///modules/zulip/nagios_plugins/zulip_app_frontend',
}
if $::osfamily == 'debian' {
# The pylibmc wheel looks for SASL plugins in the wrong place.
file { '/usr/lib64':
ensure => directory,
}
file { '/usr/lib64/sasl2':
ensure => link,
target => "/usr/lib/${::rubyplatform}/sasl2",
}
}
}

View File

@@ -76,7 +76,6 @@ class zulip::base {
'embed_links',
'embedded_bots',
'error_reports',
'feedback_messages',
'invites',
'missedmessage_email_senders',
'email_senders',

View File

@@ -14,5 +14,9 @@ class zulip::camo {
group => 'root',
mode => '0644',
content => template('zulip/camo_defaults.template.erb'),
notify => Service[camo],
}
service { 'camo':
ensure => running,
}
}

View File

@@ -1,11 +1,58 @@
class zulip::memcached {
$memcached_packages = ['memcached']
include zulip::sasl_modules
$memcached_packages = $::osfamily ? {
'debian' => [ 'memcached', 'sasl2-bin' ],
'redhat' => [ 'memcached' ],
}
package { $memcached_packages: ensure => 'installed' }
$memcached_memory = zulipconf('memcached', 'memory', $zulip::base::total_memory_mb / 8)
file { '/etc/sasl2':
ensure => directory,
}
file { '/etc/sasl2/memcached-zulip-password':
# We cache the password in this file so we can check whether it
# changed and avoid running saslpasswd2 if it didn't.
require => File['/etc/sasl2'],
owner => 'root',
group => 'root',
mode => '0600',
content => zulipsecret('secrets', 'memcached_password', ''),
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'",
}
file { '/etc/sasl2/memcached-sasldb2':
require => Exec[generate_memcached_sasldb2],
owner => 'memcache',
group => 'memcache',
mode => '0600',
}
file { '/etc/sasl2/memcached.conf':
require => File['/etc/sasl2'],
owner => 'root',
group => 'root',
mode => '0644',
source => 'puppet:///modules/zulip/sasl2/memcached.conf',
notify => Service[memcached],
}
file { '/etc/memcached.conf':
ensure => file,
require => Package[memcached],
require => [
Package[$memcached_packages],
Package[$zulip::sasl_modules::sasl_module_packages]
],
owner => 'root',
group => 'root',
mode => '0644',

View File

@@ -19,7 +19,7 @@ class zulip::redis {
package { $redis_packages: ensure => 'installed' }
$file = "${redis_dir}/redis.conf"
$zulip_redisconf = "${redis_dir}/zuli-redis.conf"
$zulip_redisconf = "${redis_dir}/zulip-redis.conf"
$line = "include ${zulip_redisconf}"
exec { 'redis':
unless => "/bin/grep -Fxqe '${line}' '${file}'",
@@ -27,18 +27,32 @@ class zulip::redis {
command => "bash -c \"(/bin/echo; /bin/echo '# Include Zulip-specific configuration'; /bin/echo '${line}') >> '${file}'\"",
require => [Package[$redis],
File[$zulip_redisconf],
Exec['rediscleanup']],
Exec['rediscleanup-zuli-redis']],
}
exec { 'rediscleanup':
onlyif => "echo '80a4cee76bac751576c3db8916fc50a6ea319428 ${file}' | sha1sum -c",
command => "head -n-3 ${file} | sponge ${file}",
# Fix the typo in the path to $zulip_redisconf introduced in
# 071e32985c1207f20043e1cf28f82300d9f23f31 without triggering a
# redis restart.
$legacy_wrong_filename = "${redis_dir}/zuli-redis.conf"
exec { 'rediscleanup-zuli-redis':
onlyif => "test -e ${legacy_wrong_filename}",
command => "
mv ${legacy_wrong_filename} ${zulip_redisconf}
perl -0777 -pe '
if (m|^\\Q${line}\\E\$|m) {
s|^\\n?(:?# Include Zulip-specific configuration\\n)?include \\Q${legacy_wrong_filename}\\E\\n||m;
} else {
s|^include \\Q${legacy_wrong_filename}\\E\$|${line}|m;
}
' -i /etc/redis/redis.conf
",
provider => shell,
}
$redis_password = zulipsecret('secrets', 'redis_password', '')
file { $zulip_redisconf:
ensure => file,
require => Package[$redis],
require => [Package[$redis], Exec['rediscleanup-zuli-redis']],
owner => 'redis',
group => 'redis',
mode => '0640',

View File

@@ -0,0 +1,7 @@
class zulip::sasl_modules {
$sasl_module_packages = $::osfamily ? {
'debian' => [ 'libsasl2-modules' ],
'redhat' => [ 'cyrus-sasl-plain' ],
}
package { $sasl_module_packages: ensure => 'installed' }
}

View File

@@ -27,7 +27,7 @@ logfile /var/log/memcached.log
# Run the daemon as root. The start-memcached will default to running as root if no
# -u command is present in this config file
-u nobody
-u memcache
# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that memcached has, so make sure
@@ -50,3 +50,6 @@ logfile /var/log/memcached.log
# Maximize core file limit
# -r
# Enable SASL authentication
-S

View File

@@ -1,6 +1,6 @@
# This file is managed by puppet; local changes will be overridden.
smtpd_banner = $myhostname ESMTP $mail_name (Zulip Voyager)
smtpd_banner = $myhostname ESMTP $mail_name (Zulip)
biff = no
# appending .domain is the MUA's job.

View File

@@ -469,17 +469,6 @@ define service {
contact_groups admins
}
define service {
use generic-service
service_description Check rabbitmq feedback messages consumers
check_command check_rabbitmq_consumers!feedback_messages
# Workaround weird checks 40s after first error causing alerts
# from a single failure because cron hasn't run again yet
max_check_attempts 3
hostgroup_name frontends
contact_groups admins
}
define service {
use generic-service
service_description Check rabbitmq message sender consumers
@@ -589,7 +578,7 @@ define service {
define service {
use generic-service
hostgroup_name staging_frontends
service_description Check email deliverer process which is only used on Zulip Voyager
service_description Check email deliverer process
check_command check_email_deliverer_process
contact_groups admins
}
@@ -597,7 +586,7 @@ define service {
define service {
use generic-service
hostgroup_name staging_frontends
service_description Check email deliverer backlog which is only used on Zulip Voyager
service_description Check email deliverer backlog
check_command check_email_deliverer_backlog
contact_groups admins
}

View File

@@ -219,7 +219,7 @@ cryptography==2.8 \
--hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \
--hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \
--hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 \
# via apns2, moto, pyopenssl, requests, scrapy, service-identity, sshpubkeys
# via apns2, moto, pyopenssl, requests, scrapy, service-identity, social-auth-core, sshpubkeys
cssselect==1.1.0 \
--hash=sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf \
--hash=sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc \
@@ -757,10 +757,10 @@ social-auth-app-django==3.1.0 \
--hash=sha256:6d0dd18c2d9e71ca545097d57b44d26f59e624a12833078e8e52f91baf849778 \
--hash=sha256:9237e3d7b6f6f59494c3b02e0cce6efc69c9d33ad9d1a064e3b2318bcbe89ae3 \
--hash=sha256:f151396e5b16e2eee12cd2e211004257826ece24fc4ae97a147df386c1cd7082
social-auth-core==3.2.0 \
--hash=sha256:47cd2458c8fefd02466b0c514643e02ad8b61d8b4b69f7573e80882e3a97b0f0 \
--hash=sha256:8320666548a532eb158968eda542bbe1863682357c432d8c4e28034a7f1e3b58 \
--hash=sha256:d81ed681e3c0722300b61a0792c5db5d21206793f95ca810f010c1cc931c8d89 \
social-auth-core==3.3.2 \
--hash=sha256:1ce0f672827465df416b7170536cf6ac2415158fe993acc227aec1ead5d429ee \
--hash=sha256:3d04148d3f01d163cbf893d35250abe86e3e759203bd6f3036fdb85f89f85109 \
--hash=sha256:6320ff4644eece77dd8cec7939361918e26a877fc282974071f9a8892fd6df7e \
# via social-auth-app-django
sockjs-tornado==1.0.6 \
--hash=sha256:ec12b0c37723b0aac56610fb9b6aa68390720d0c9c2a10461df030c3a1d9af95

View File

@@ -139,7 +139,7 @@ cryptography==2.8 \
--hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \
--hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \
--hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 \
# via apns2, pyopenssl, requests
# via apns2, pyopenssl, requests, social-auth-core
cssselect==1.1.0 \
--hash=sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf \
--hash=sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc \
@@ -505,10 +505,10 @@ social-auth-app-django==3.1.0 \
--hash=sha256:6d0dd18c2d9e71ca545097d57b44d26f59e624a12833078e8e52f91baf849778 \
--hash=sha256:9237e3d7b6f6f59494c3b02e0cce6efc69c9d33ad9d1a064e3b2318bcbe89ae3 \
--hash=sha256:f151396e5b16e2eee12cd2e211004257826ece24fc4ae97a147df386c1cd7082
social-auth-core==3.2.0 \
--hash=sha256:47cd2458c8fefd02466b0c514643e02ad8b61d8b4b69f7573e80882e3a97b0f0 \
--hash=sha256:8320666548a532eb158968eda542bbe1863682357c432d8c4e28034a7f1e3b58 \
--hash=sha256:d81ed681e3c0722300b61a0792c5db5d21206793f95ca810f010c1cc931c8d89 \
social-auth-core==3.3.2 \
--hash=sha256:1ce0f672827465df416b7170536cf6ac2415158fe993acc227aec1ead5d429ee \
--hash=sha256:3d04148d3f01d163cbf893d35250abe86e3e759203bd6f3036fdb85f89f85109 \
--hash=sha256:6320ff4644eece77dd8cec7939361918e26a877fc282974071f9a8892fd6df7e \
# via social-auth-app-django
sockjs-tornado==1.0.6 \
--hash=sha256:ec12b0c37723b0aac56610fb9b6aa68390720d0c9c2a10461df030c3a1d9af95

View File

@@ -1,31 +1,33 @@
#!/usr/bin/env python3
"""
Forward messages sent to the configured email gateway to Zulip.
"""Postfix implementation of the incoming email gateway's helper for
forwarding emails into Zulip.
For zulip.com, messages to that address go to the Inbox of emailgateway@zulip.com.
Zulip voyager configurations will differ.
https://zulip.readthedocs.io/en/latest/production/settings.html#email-gateway
Messages meant for Zulip have a special recipient form of
The email gateway supports two major modes of operation: An email
server (using postfix) where the email address configured in
EMAIL_GATEWAY_PATTERN delivers emails directly to Zulip (this) or a
cron job that connects to an IMAP inbox (which receives the emails)
periodically.
<stream name>+<regenerable stream token>@streams.zulip.com
This pattern is configurable via the EMAIL_GATEWAY_PATTERN settings.py
variable.
Configure your MTA to execute this script on message
receipt with the contents of the message piped to standard input. The
script will queue the message for processing. In this mode of invocation,
you should pass the destination email address in the ORIGINAL_RECIPIENT
environment variable.
Zulip's puppet configuration takes care of configuring postfix to
execute this script when emails are received by postfix, piping the
email content via standard input (and the destination email address in
the ORIGINAL_RECIPIENT environment variable).
In Postfix, you can express that via an /etc/aliases entry like this:
|/home/zulip/deployments/current/scripts/lib/email-mirror-postfix -r ${original_recipient}
To manage DoS issues, this script does very little work (just sending
an HTTP request to queue the message for processing) to avoid
importing expensive libraries.
Also you can use optional keys to configure the script and change default values:
-s SHARED_SECRET For adding shared secret key if it is not contained in
"/etc/zulip/zulip-secrets.conf".
"/etc/zulip/zulip-secrets.conf". This key is used to authenticate
the HTTP requests made by this tool.
-d HOST Destination Zulip host for email uploading. Address must contain type of
HTTP protocol, i.e "https://example.com". Default value: "https://127.0.0.1".
@@ -36,6 +38,7 @@ Also you can use optional keys to configure the script and change default values
self-signed certificates. Default value: False.
-t Disable sending request to the Zulip server. Default value: False.
"""
import os

View File

@@ -259,11 +259,26 @@ EOF
fi
) > /etc/zulip/zulip.conf
case ",$PUPPET_CLASSES," in
*,zulip::voyager,* | *,zulip::dockervoyager,* | *,zulip::app_frontend,*)
if [ -z "$NO_OVERWRITE_SETTINGS" ] || ! [ -e "/etc/zulip/settings.py" ]; then
cp -a "$ZULIP_PATH"/zproject/prod_settings_template.py /etc/zulip/settings.py
if [ -n "$EXTERNAL_HOST" ]; then
sed -i "s/^EXTERNAL_HOST =.*/EXTERNAL_HOST = '$EXTERNAL_HOST'/" /etc/zulip/settings.py
fi
if [ -n "$ZULIP_ADMINISTRATOR" ]; then
sed -i "s/^ZULIP_ADMINISTRATOR =.*/ZULIP_ADMINISTRATOR = '$ZULIP_ADMINISTRATOR'/" /etc/zulip/settings.py
fi
fi
ln -nsf /etc/zulip/settings.py "$ZULIP_PATH"/zproject/prod_settings.py
"$ZULIP_PATH"/scripts/setup/generate_secrets.py --production
;;
esac
"$ZULIP_PATH"/scripts/zulip-puppet-apply -f
# Detect which features were selected for the below
set +e
[ -e "/etc/init.d/camo" ]; has_camo=$?
[ -e "/etc/init.d/nginx" ]; has_nginx=$?
[ -e "/etc/supervisor/conf.d/zulip.conf" ]; has_appserver=$?
[ -e "/etc/cron.d/rabbitmq-numconsumers" ]; has_rabbit=$?
@@ -272,7 +287,6 @@ set -e
# Docker service setup is done in the docker config, not here
if [ "$DEPLOYMENT_TYPE" = "dockervoyager" ]; then
has_camo=1
has_nginx=1
has_appserver=0
has_rabbit=1
@@ -280,7 +294,7 @@ if [ "$DEPLOYMENT_TYPE" = "dockervoyager" ]; then
fi
if [ -n "$POSTGRES_MISSING_DICTIONARIES" ]; then
export POSTGRES_MISSING_DICTIONARIES="true"
crudini --set /etc/zulip/zulip.conf postgresql missing_dictionaries true
fi
if [ -n "$REMOTE_POSTGRES" ]; then
@@ -310,29 +324,6 @@ EOF
service nginx restart
fi
if [ "$has_appserver" = 0 ]; then
"$ZULIP_PATH"/scripts/setup/generate_secrets.py --production
if [ -z "$NO_OVERWRITE_SETTINGS" ] || ! [ -e "/etc/zulip/settings.py" ]; then
cp -a "$ZULIP_PATH"/zproject/prod_settings_template.py /etc/zulip/settings.py
if [ -n "$EXTERNAL_HOST" ]; then
sed -i "s/^EXTERNAL_HOST =.*/EXTERNAL_HOST = '$EXTERNAL_HOST'/" /etc/zulip/settings.py
fi
if [ -n "$ZULIP_ADMINISTRATOR" ]; then
sed -i "s/^ZULIP_ADMINISTRATOR =.*/ZULIP_ADMINISTRATOR = '$ZULIP_ADMINISTRATOR'/" /etc/zulip/settings.py
fi
fi
ln -nsf /etc/zulip/settings.py "$ZULIP_PATH"/zproject/prod_settings.py
fi
# Restart camo since generate_secrets.py likely replaced its secret key
if [ "$has_camo" = 0 ]; then
# Cut off stdin because a bug in the Debian packaging for camo
# causes our stdin to leak to the daemon, which can cause tools
# invoking the installer to hang.
# TODO: fix in Debian too.
service camo restart </dev/null
fi
if [ "$has_rabbit" = 0 ]; then
if ! rabbitmqctl status >/dev/null; then
set +x

View File

@@ -47,13 +47,12 @@ consumers = defaultdict(int) # type: Dict[str, int]
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
queues = {
'deferred_work'
'deferred_work',
'digest_emails',
'email_mirror',
'embed_links',
'embedded_bots',
'error_reports',
'feedback_messages',
'invites',
'message_sender',
'missedmessage_emails',
@@ -63,7 +62,7 @@ queues = {
'outgoing_webhooks',
'signups',
'slow_queries',
'user_activity'
'user_activity',
'user_activity_interval',
'user_presence',
# These queues may not be present if settings.TORNADO_PROCESSES > 1

View File

@@ -1,4 +1,10 @@
CREATE USER zulip;
\connect postgres
DROP DATABASE IF EXISTS zulip;
DO $$BEGIN
CREATE USER zulip;
EXCEPTION WHEN duplicate_object THEN
RAISE NOTICE 'zulip user already exists';
END$$;
ALTER ROLE zulip SET search_path TO zulip,public;
CREATE DATABASE zulip OWNER=zulip;
\connect zulip

View File

@@ -1,26 +1,19 @@
#!/usr/bin/env python3
import os
import socket
import sys
from urllib.parse import urlsplit
BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")
sys.path.append(BASE_DIR)
import scripts.lib.setup_path_on_import
from zproject import settings
import pylibmc
url = urlsplit("//" + settings.MEMCACHED_LOCATION)
assert url.port is not None
print("Flushing memcached...")
with socket.create_connection((url.hostname, url.port)) as f:
f.sendall(b"flush_all\r\n")
response = b""
while b"\n" not in response:
response += f.recv(4096)
if response != b"OK\r\n":
print(response, file=sys.stderr)
print("Failed to flush memcached", file=sys.stderr)
sys.exit(1)
pylibmc.Client(
[settings.MEMCACHED_LOCATION],
binary=True,
username=settings.MEMCACHED_USERNAME,
password=settings.MEMCACHED_PASSWORD,
behaviors=settings.CACHES["default"]["OPTIONS"] # type: ignore # settings not typed properly
).flush_all()

View File

@@ -17,11 +17,10 @@ import argparse
import uuid
import configparser
from zerver.lib.utils import generate_random_token
from zproject import settings
os.chdir(os.path.join(os.path.dirname(__file__), '..', '..'))
CAMO_CONFIG_FILENAME = '/etc/default/camo'
# Standard, 64-bit tokens
AUTOGENERATED_SETTINGS = [
'avatar_salt',
@@ -30,18 +29,6 @@ AUTOGENERATED_SETTINGS = [
'thumbor_key',
]
# TODO: We can eliminate this function if we refactor the install
# script to run generate_secrets before zulip-puppet-apply.
def generate_camo_config_file(camo_key):
# type: (str) -> None
camo_config = """ENABLED=yes
PORT=9292
CAMO_KEY=%s
""" % (camo_key,)
with open(CAMO_CONFIG_FILENAME, 'w') as camo_file:
camo_file.write(camo_config)
print("Generated Camo config file %s" % (CAMO_CONFIG_FILENAME,))
def generate_django_secretkey():
# type: () -> str
"""Secret key generation taken from Django's startproject.py"""
@@ -89,11 +76,52 @@ def generate_secrets(development=False):
add_secret("local_database_password", generate_random_token(64))
if need_secret('secret_key'):
add_secret('secret_key', generate_django_secretkey())
secret_key = generate_django_secretkey()
add_secret('secret_key', secret_key)
# To prevent Django ImproperlyConfigured error
settings.SECRET_KEY = secret_key
if need_secret('camo_key'):
add_secret('camo_key', get_random_string(64))
if (
not development
and settings.MEMCACHED_LOCATION == "127.0.0.1:11211"
and need_secret("memcached_password")
):
add_secret("memcached_password", generate_random_token(64))
if (
not development
and settings.REDIS_HOST == "127.0.0.1"
and need_secret("redis_password")
):
# To prevent Puppet from restarting Redis, which would lose
# data because we configured Redis to disable persistence, set
# the Redis password on the running server and edit the config
# file directly.
import redis
from zerver.lib.redis_utils import get_redis_client
redis_password = generate_random_token(64)
for filename in ["/etc/redis/zuli-redis.conf", "/etc/redis/zulip-redis.conf"]:
if os.path.exists(filename):
with open(filename, "a") as f:
f.write(
"# Set a Redis password based on zulip-secrets.conf\n"
"requirepass '%s'\n" % (redis_password,)
)
break
try:
get_redis_client().config_set("requirepass", redis_password)
except redis.exceptions.ConnectionError:
pass
add_secret("redis_password", redis_password)
# zulip_org_key is generated using os.urandom().
# zulip_org_id does not require a secure CPRNG,
# it only needs to be unique.
@@ -102,10 +130,6 @@ def generate_secrets(development=False):
if need_secret('zulip_org_id'):
add_secret('zulip_org_id', str(uuid.uuid4()))
if not development:
# Write the Camo config file directly
generate_camo_config_file(current_conf['camo_key'])
if len(lines) == 0:
print("generate_secrets: No new secrets to generate.")
return

View File

@@ -32,17 +32,6 @@ cd "$THIS_DIR/../.."
./manage.py migrate --noinput
./manage.py createcachetable third_party_api_results
if ! ./manage.py initialize_voyager_db; then
set +x
echo
echo -e '\033[32mPopulating default database failed.'
echo "After you fix the problem, you will need to do the following before rerunning this:"
echo " * supervisorctl stop all # to stop all services that might be accessing the database"
echo " * scripts/setup/postgres-init-db # run as root to drop and re-create the database"
echo -e '\033[0m'
exit 1
fi
# Check if the supervisor socket exists. If not, it could be:
#
# A) A normal installation went bad (supervisor hasn't started)

View File

@@ -44,10 +44,6 @@ source "$(dirname "$0")/terminate-psql-sessions" postgres zulip zulip_base
# Make sure the current working directory is readable by postgres
cd /
su "$POSTGRES_USER" -c 'psql -v ON_ERROR_STOP=1 -e' <<EOF
DROP DATABASE IF EXISTS zulip;
EOF
su "$POSTGRES_USER" -c 'psql -v ON_ERROR_STOP=1 -e' < "$(dirname "$0")/create-db.sql"
)

View File

@@ -100,6 +100,13 @@ def restore_backup(tarball_file):
run(as_postgres + ["createdb", "-O", "zulip", "-T", "template0", "--", db_name])
if settings.PRODUCTION:
# In case we are restoring a backup from an older Zulip
# version, there may be new secrets to generate.
subprocess.check_call([
os.path.join(settings.DEPLOY_ROOT, "scripts", "setup", "generate_secrets.py"),
"--production",
])
# If there is a local rabbitmq, we need to reconfigure it
# to ensure the rabbitmq password matches the value in the
# restored zulip-secrets.conf. We need to be careful to

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1 @@
<svg height="64" viewBox="-4.649 -0.667 64 64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M27.35-.667c-17.673 0-32 14.326-32 32s14.328 32 32 32 32-14.327 32-32-14.328-32-32-32zm0 59.89c-5.028 0-9.105-3.36-9.105-7.5h18.2c0 4.142-4.077 7.5-9.105 7.5zM42.4 49.24H12.31v-5.454H42.4zm-.108-8.26H12.397l-.297-.344c-3.08-3.74-3.804-5.7-4.508-7.68-.012-.066 3.734.766 6.39 1.363 0 0 1.367.316 3.364.68-1.918-2.25-3.057-5.107-3.057-8.03 0-6.415 4.92-12.02 3.145-16.55 1.728.14 3.575 3.646 3.7 9.126 1.837-2.538 2.605-7.172 2.605-10.014 0-2.942 1.94-6.36 3.878-6.477-1.73 2.85.448 5.29 2.382 11.35.726 2.276.633 6.106 1.193 8.535.186-5.045 1.053-12.405 4.254-14.946-1.412 3.2.21 7.205 1.318 9.13 1.79 3.106 2.873 5.46 2.873 9.9 0 2.984-1.102 5.793-2.96 8 2.113-.397 3.572-.754 3.572-.754l6.862-1.34c0-.001-.997 4.1-4.828 8.05z" fill="#da4e31"/></svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@@ -679,27 +679,6 @@ exports.initialize = function () {
stream_list.toggle_filter_displayed(e);
});
// FEEDBACK
// Keep these 2 feedback bot triggers separate because they have to
// propagate the event differently.
$('.feedback').click(function () {
compose_actions.start('private', {
private_message_recipient: 'feedback@zulip.com',
trigger: 'feedback menu item'});
});
$('#feedback_button').click(function (e) {
e.stopPropagation();
popovers.hide_all();
compose_actions.start('private', {
private_message_recipient: 'feedback@zulip.com',
trigger: 'feedback button'});
});
// WEBATHENA
$('body').on('click', '.webathena_login', function (e) {

View File

@@ -736,7 +736,7 @@ exports.render_and_show_preview = function (preview_spinner, preview_content_box
rendered_preview_html = rendered_content;
}
preview_content_box.html(rendered_preview_html);
preview_content_box.html(util.clean_user_content_links(rendered_preview_html));
if (page_params.emojiset === "text") {
preview_content_box.find(".emoji").replaceWith(function () {
const text = $(this).attr("title");

View File

@@ -152,7 +152,7 @@ function Filter(operators) {
if (operators === undefined) {
this._operators = [];
} else {
this._operators = this._canonicalize_operators(operators);
this._operators = this.fix_operators(operators);
}
}
@@ -382,13 +382,42 @@ Filter.prototype = {
},
can_mark_messages_read: function () {
return _.every(this._operators, function (elem) {
return (_.contains(['stream', 'topic', 'pm-with'], elem.operator)
|| elem.operator === 'is' && elem.operand === 'private'
|| elem.operator === 'in' && elem.operand === 'all'
|| elem.operator === 'in' && elem.operand === 'home')
&& !elem.negated;
});
const term_types = this.sorted_term_types();
if (_.isEqual(term_types, ['stream', 'topic'])) {
return true;
}
if (_.isEqual(term_types, ['pm-with'])) {
return true;
}
// TODO: Some users really hate it when Zulip marks messages as read
// in interleaved views, so we will eventually have a setting
// that early-exits before the subsequent checks.
if (_.isEqual(term_types, ['stream'])) {
return true;
}
if (_.isEqual(term_types, ['is-private'])) {
return true;
}
if (_.isEqual(term_types, ['is-mentioned'])) {
return true;
}
if (_.isEqual(term_types, [])) {
// All view
return true;
}
if (term_types.length === 1 && _.contains(['in-home', 'in-all'], term_types[0])) {
return true;
}
return false;
},
allow_use_first_unread_when_narrowing: function () {
@@ -429,6 +458,26 @@ Filter.prototype = {
return true;
},
fix_operators: function (operators) {
operators = this._canonicalize_operators(operators);
operators = this._fix_redundant_is_private(operators);
return operators;
},
_fix_redundant_is_private: function (terms) {
const is_pm_with = (term) => {
return Filter.term_type(term) === 'pm-with';
};
if (!_.any(terms, is_pm_with)) {
return terms;
}
return _.reject(terms, (term) => {
return Filter.term_type(term) === 'is-private';
});
},
_canonicalize_operators: function (operators_mixed_case) {
return _.map(operators_mixed_case, function (tuple) {
return Filter.canonicalize_term(tuple);
@@ -457,37 +506,6 @@ Filter.prototype = {
return sorted_terms;
},
is_exactly: function () {
// TODO: in ES6 use spread operator
//
// Examples calls:
// filter.is_exactly('stream', 'topic')
// filter.is_exactly('pm-with')
const wanted_term_types = [].slice.call(arguments);
const term_types = this.sorted_term_types();
return _.isEqual(term_types, wanted_term_types);
},
is_reading_mode: function () {
// We only turn on "reading mode" for filters that
// have contiguous messages for a narrow, as opposed
// to "random access" queries like search:<keyword>
// or id:<number> that jump you to parts of the message
// view where you might only care about reading the
// current message.
const term_types = this.sorted_term_types();
const wanted_list = [
['stream'],
['stream', 'topic'],
['is-private'],
['pm-with'],
];
return _.any(wanted_list, function (wanted_types) {
return _.isEqual(wanted_types, term_types);
});
},
can_bucket_by: function () {
// TODO: in ES6 use spread operator
//
@@ -573,7 +591,7 @@ Filter.term_type = function (term) {
result += operator;
if (_.contains(['is', 'has'], operator)) {
if (_.contains(['is', 'has', 'in', 'streams'], operator)) {
result += '-' + operand;
}
@@ -582,6 +600,7 @@ Filter.term_type = function (term) {
Filter.sorted_term_types = function (term_types) {
const levels = [
'in',
'streams',
'stream', 'topic',
'pm-with', 'group-pm-with', 'sender',

View File

@@ -590,7 +590,13 @@ MessageListView.prototype = {
if (stream_id && !$(this).find(".highlight").length) {
// Display the current name for stream if it is not
// being displayed in search highlight.
$(this).text("#" + stream_data.maybe_get_stream_name(stream_id));
const stream_name = stream_data.maybe_get_stream_name(stream_id);
if (stream_name !== undefined) {
// If the stream has been deleted,
// stream_data.maybe_get_stream_name might return
// undefined. Otherwise, display the current stream name.
$(this).text("#" + stream_name);
}
}
});
@@ -601,7 +607,13 @@ MessageListView.prototype = {
// being displayed in search highlight.
const text = $(this).text();
const topic = text.split('>', 2)[1];
$(this).text("#" + stream_data.maybe_get_stream_name(stream_id) + ' > ' + topic);
const stream_name = stream_data.maybe_get_stream_name(stream_id);
if (stream_name !== undefined) {
// If the stream has been deleted,
// stream_data.maybe_get_stream_name might return
// undefined. Otherwise, display the current stream name.
$(this).text("#" + stream_name + ' > ' + topic);
}
}
});

View File

@@ -54,9 +54,7 @@ exports.process_message_for_recent_private_messages = function (message) {
pm_conversations.set_partner(user_id);
});
const user_ids_string = user_ids.join(',');
pm_conversations.recent.insert(user_ids_string, message.id);
pm_conversations.recent.insert(user_ids, message.id);
};
exports.set_message_booleans = function (message) {

View File

@@ -18,14 +18,6 @@ exports.filter = function () {
return current_filter;
};
exports.is_reading_mode = function () {
if (current_filter === undefined) {
return true;
}
return current_filter.is_reading_mode();
};
exports.operators = function () {
if (current_filter === undefined) {
return new Filter(page_params.narrow).operators();

View File

@@ -19,7 +19,9 @@ const get_step = function ($process) {
exports.initialize = function () {
// if email has not been set up and the user is the admin, display a warning
// to tell them to set up an email server.
if (page_params.warn_no_email === true && page_params.is_admin) {
if (page_params.insecure_desktop_app) {
exports.open($("[data-process='insecure-desktop-app']"));
} else if (page_params.warn_no_email === true && page_params.is_admin) {
exports.open($("[data-process='email-server']"));
} else {
exports.open($("[data-process='notifications']"));

View File

@@ -18,13 +18,14 @@ exports.recent = (function () {
const recent_message_ids = new Dict({fold_case: true}); // key is user_ids_string
const recent_private_messages = [];
self.insert = function (user_ids_string, message_id) {
if (user_ids_string === '') {
// The API uses '' for self-PMs; convert it to the string
// containing the current user's ID, which is the format
// the webapp expects.
user_ids_string = people.my_current_user_id().toString();
self.insert = function (user_ids, message_id) {
if (user_ids.length === 0) {
// The server sends [] for self-PMs.
user_ids = [people.my_current_user_id()];
}
user_ids.sort((a, b) => a - b);
const user_ids_string = user_ids.join(',');
let conversation = recent_message_ids.get(user_ids_string);
if (conversation === undefined) {
@@ -71,8 +72,7 @@ exports.recent = (function () {
self.initialize = function () {
_.each(page_params.recent_private_conversations, function (conversation) {
const user_ids_string = conversation.user_ids.join(",");
self.insert(user_ids_string, conversation.max_message_id);
self.insert(conversation.user_ids, conversation.max_message_id);
});
delete page_params.recent_private_messages;
};

View File

@@ -70,7 +70,6 @@ function get_new_heights() {
const usable_height = viewport_height
- parseInt($("#right-sidebar").css("marginTop"), 10)
- $("#feedback_section").safeOuterHeight(true)
- parseInt(buddy_list_wrapper.css("marginTop"), 10)
- parseInt(buddy_list_wrapper.css("marginBottom"), 10)
- $("#userlist-header").safeOuterHeight(true)

View File

@@ -312,8 +312,12 @@ exports.set_up = function () {
});
$("#download_zuliprc").on("click", function () {
const data = settings_bots.generate_zuliprc_content(people.my_current_email(),
$("#api_key_value").text());
const bot_object = {
user_id: people.my_current_user_id(),
email: people.my_current_email(),
api_key: $("#api_key_value").text(),
};
const data = settings_bots.generate_zuliprc_content(bot_object);
$(this).attr("href", settings_bots.encode_zuliprc_as_uri(data));
});
});

View File

@@ -115,13 +115,7 @@ exports.render_bots = function () {
exports.generate_zuliprc_uri = function (bot_id) {
const bot = bot_data.get(bot_id);
let token;
// For outgoing webhooks, include the token in the zuliprc.
// It's needed for authenticating to the Botserver.
if (bot.bot_type === 3) {
token = bot_data.get_services(bot_id)[0].token;
}
const data = exports.generate_zuliprc_content(bot.email, bot.api_key, token);
const data = exports.generate_zuliprc_content(bot);
return exports.encode_zuliprc_as_uri(data);
};
@@ -129,10 +123,16 @@ exports.encode_zuliprc_as_uri = function (zuliprc) {
return "data:application/octet-stream;charset=utf-8," + encodeURIComponent(zuliprc);
};
exports.generate_zuliprc_content = function (email, api_key, token) {
exports.generate_zuliprc_content = function (bot) {
let token;
// For outgoing webhooks, include the token in the zuliprc.
// It's needed for authenticating to the Botserver.
if (bot.bot_type === 3) {
token = bot_data.get_services(bot.user_id)[0].token;
}
return "[api]" +
"\nemail=" + email +
"\nkey=" + api_key +
"\nemail=" + bot.email +
"\nkey=" + bot.api_key +
"\nsite=" + page_params.realm_uri +
(token === undefined ? "" : "\ntoken=" + token) +
// Some tools would not work in files without a trailing new line.
@@ -491,10 +491,10 @@ exports.set_up = function () {
new ClipboardJS('#copy_zuliprc', {
text: function (trigger) {
const bot_info = trigger.closest(".bot-information-box");
const email = $(bot_info).find(".email .value").text();
const api_key = $(bot_info).find(".api_key .api-key-value-and-button .value").text();
const data = exports.generate_zuliprc_content(email.trim(), api_key.trim());
const bot_info = $(trigger).closest(".bot-information-box").find(".bot_info");
const bot_id = parseInt(bot_info.attr("data-user-id"), 10);
const bot = bot_data.get(bot_id);
const data = exports.generate_zuliprc_content(bot);
return data;
},
});

View File

@@ -137,6 +137,10 @@ exports.set_up = function () {
});
$(".emojiset_choice").click(function () {
const data = {emojiset: JSON.stringify($(this).val())};
const current_emojiset = JSON.stringify(page_params.emojiset);
if (current_emojiset === data.emojiset) {
return;
}
const spinner = $("#emoji-settings-status").expectOne();
loading.make_indicator(spinner, {text: settings_ui.strings.saving });

View File

@@ -1,3 +1,4 @@
const util = require("./util");
const render_settings_deactivation_stream_modal = require("../templates/settings/deactivation_stream_modal.hbs");
const render_stream_member_list_entry = require('../templates/stream_member_list_entry.hbs');
const render_subscription_settings = require('../templates/subscription_settings.hbs');
@@ -111,7 +112,9 @@ exports.update_stream_name = function (sub, new_name) {
exports.update_stream_description = function (sub) {
const stream_settings = exports.settings_for_sub(sub);
stream_settings.find('input.description').val(sub.description);
stream_settings.find('.stream-description-editable').html(sub.rendered_description);
stream_settings.find('.stream-description-editable').html(
util.clean_user_content_links(sub.rendered_description)
);
};
exports.invite_user_to_stream = function (user_email, sub, success, failure) {
@@ -458,7 +461,9 @@ exports.change_stream_description = function (e) {
$(".stream_change_property_info"));
},
error: function (xhr) {
sub_settings.find('.stream-description-editable').html(sub.rendered_description);
sub_settings.find('.stream-description-editable').html(
util.clean_user_content_links(sub.rendered_description)
);
ui_report.error(i18n.t("Error"), xhr, $(".stream_change_property_info"));
},
});

View File

@@ -1,3 +1,4 @@
const util = require('./util');
const render_subscription = require('../templates/subscription.hbs');
const render_subscription_settings = require('../templates/subscription_settings.hbs');
const render_subscription_table_body = require('../templates/subscription_table_body.hbs');
@@ -162,7 +163,7 @@ exports.update_stream_description = function (sub, description, rendered_descrip
// Update stream row
const sub_row = exports.row_for_stream_id(sub.stream_id);
sub_row.find(".description").html(sub.rendered_description);
sub_row.find(".description").html(util.clean_user_content_links(sub.rendered_description));
// Update stream settings
stream_edit.update_stream_description(sub);

View File

@@ -1,3 +1,5 @@
const util = require("./util");
// Below, we register Zulip-specific extensions to the handlebars API.
//
// IMPORTANT: When adding a new handlebars helper, update the
@@ -73,4 +75,9 @@ Handlebars.registerHelper('tr', function (context, options) {
return new Handlebars.SafeString(result);
});
Handlebars.registerHelper(
"rendered_markdown",
content => new Handlebars.SafeString(util.clean_user_content_links(content))
);
window.templates = exports;

View File

@@ -347,4 +347,44 @@ exports.convert_message_topic = function (message) {
}
};
exports.clean_user_content_links = function (html) {
const content = new DOMParser().parseFromString(html, "text/html").body;
for (const elt of content.getElementsByTagName("a")) {
// Ensure that all external links have target="_blank"
// rel="opener noreferrer". This ensures that external links
// never replace the Zulip webapp while also protecting
// against reverse tabnapping attacks, without relying on the
// correctness of how Zulip's markdown processor generates links.
//
// Fragment links, which we intend to only open within the
// Zulip webapp using our hashchange system, do not require
// these attributes.
let url;
try {
url = new URL(elt.getAttribute("href"), window.location.href);
} catch {
elt.removeAttribute("href");
continue;
}
if (
// eslint-disable-next-line no-script-url
["data:", "javascript:", "vbscript:"].indexOf(url.protocol) !== -1
) {
// Remove unsafe links completely.
elt.removeAttribute("href");
} else if (
// We detect URLs that are just fragments by comparing the URL
// against a new URL generated using only the hash.
url.hash === "" || url.href !== new URL(url.hash, window.location.href).href
) {
elt.setAttribute("target", "_blank");
elt.setAttribute("rel", "noopener noreferrer");
} else {
elt.removeAttribute("target");
}
}
return content.innerHTML;
};
window.util = exports;

View File

@@ -249,7 +249,9 @@ html {
margin: 0 auto 10px;
}
.register-account .terms-of-service .input-group {
.register-account .terms-of-service .input-group,
.register-account .default-stream-groups .input-group,
.register-account .default-stream-groups p {
width: 330px;
margin: 0 auto 10px;
}

View File

@@ -1303,12 +1303,16 @@ input.new-organization-button {
}
.error_page {
padding: 20px 0px;
min-height: calc(100vh - 290px);
height: 100%;
background-color: hsl(163, 42%, 85%);
font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif;
}
.error_page .container {
padding: 20px 0px;
}
.error_page .row-fluid {
margin-top: 60px;
}

View File

@@ -227,13 +227,6 @@
}
}
#feedback_section {
text-align: left;
padding-bottom: 15px;
border-bottom: 1px solid hsl(0, 0%, 88%);
margin-right: 10px;
}
#keyboard-icon {
position: fixed;
bottom: 8px; /* bottom padding of .compose-content */

View File

@@ -36,7 +36,7 @@
<i class="fa fa-trash-o fa-lg delete-draft" aria-hidden="true" data-toggle="tooltip" title="{{t 'Delete draft' }} (Backspace)"></i>
</div>
</div>
<div class="message_content rendered_markdown restore-draft" data-toggle="tooltip" title="{{t 'Restore draft' }}">{{{content}}}</div>
<div class="message_content rendered_markdown restore-draft" data-toggle="tooltip" title="{{t 'Restore draft' }}">{{rendered_markdown content}}</div>
</div>
</div>
</div>

View File

@@ -9,9 +9,7 @@
<i class="zulip-icon bot" aria-label="{{t 'Bot' }}"></i>
{{/if}}
<span class="status-message auto-select">
{{{ status_message }}}
</span>
<span class="rendered_markdown status-message auto-select">{{rendered_markdown status_message}}</span>
{{#if edited_status_msg}}
{{> edited_notice}}

View File

@@ -36,7 +36,7 @@
</div>
{{#unless status_message}}
<div class="message_content rendered_markdown">{{#if use_match_properties}}{{{msg/match_content}}}{{else}}{{{msg/content}}}{{/if}}</div>
<div class="message_content rendered_markdown">{{#if use_match_properties}}{{rendered_markdown msg/match_content}}{{else}}{{rendered_markdown msg/content}}{{/if}}</div>
{{/unless}}
{{#if edited_in_left_col}}

View File

@@ -24,7 +24,7 @@
<path fill="#777" d="M128 768h256v64H128v-64z m320-384H128v64h320v-64z m128 192V448L384 640l192 192V704h320V576H576z m-288-64H128v64h160v-64zM128 704h160v-64H128v64z m576 64h64v128c-1 18-7 33-19 45s-27 18-45 19H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h192C256 57 313 0 384 0s128 57 128 128h192c35 0 64 29 64 64v320h-64V320H64v576h640V768zM128 256h512c0-35-29-64-64-64h-64c-35 0-64-29-64-64s-29-64-64-64-64 29-64 64-29 64-64 64h-64c-35 0-64 29-64 64z" />
</svg>
</button>
<textarea class="message_edit_content rendered_markdown" maxlength="10000" id="message_edit_content_{{message_id}}">{{content}}</textarea>
<textarea class="message_edit_content" maxlength="10000" id="message_edit_content_{{message_id}}">{{content}}</textarea>
<div class="scrolling_list preview_message_area" id="preview_message_area_{{message_id}}" style="display:none;">
<div id="markdown_preview_spinner_{{message_id}}"></div>
<div id="preview_content_{{message_id}}" class="preview_content rendered_markdown"></div>

View File

@@ -10,7 +10,7 @@
<div class="message_content message_edit_history_content"><p>Topic: <span class="highlight_text_inserted">{{ new_topic }}</span> <span class="highlight_text_deleted">{{ prev_topic }}</span></p></div>
{{/if}}
{{#if body_to_render}}
<div class="message_content rendered_markdown message_edit_history_content">{{{ body_to_render }}}</div>
<div class="message_content rendered_markdown message_edit_history_content">{{rendered_markdown body_to_render}}</div>
{{/if}}
<div class="message_author"><div class="author_details">{{ posted_or_edited }} {{ edited_by }}</div></div>
</div>

View File

@@ -16,7 +16,7 @@
</div>
</div>
<div class="bottom-bar">
<div class="description rendered_markdown" data-no-description="{{t 'No description.'}}">{{{rendered_description}}}</div>
<div class="description rendered_markdown" data-no-description="{{t 'No description.'}}">{{rendered_markdown rendered_description}}</div>
{{#if is_old_stream}}
<div class="stream-message-count" data-toggle="tooltip" title="{{t 'Estimated messages per week' }}">
<i class="fa fa-bar-chart"></i>

View File

@@ -33,7 +33,7 @@
</div>
</div>
<div class="stream-description">
<span class="stream-description-editable editable-section description rendered_markdown" data-no-description="{{t 'No description.' }}">{{{rendered_description}}}</span>
<span class="stream-description-editable editable-section description rendered_markdown" data-no-description="{{t 'No description.' }}">{{rendered_markdown rendered_description}}</span>
{{#if can_change_name_description}}
<span class="editable" data-make-editable=".stream-description-editable"></span>
<span class="checkmark" data-finish-editing=".stream-description-editable">✓</span>

View File

@@ -1 +1 @@
<li data-email="{{this.email}}" class="typing_notification">{{this.full_name}} is typing...</li>
<li data-email="{{this.email}}" class="typing_notification">{{#tr this}}__full_name__ is typing...{{/tr}}</li>

View File

@@ -11,7 +11,7 @@
<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}}
</p>
{{#unless (and (not (eq realm_user_group_edit_policy USER_GROUP_EDIT_POLICY_MEMBERS) (not is_admin)))}}
{{#if (or is_admin (eq realm_user_group_edit_policy USER_GROUP_EDIT_POLICY_MEMBERS))}}
<form class="form-horizontal admin-user-group-form">
<div class="add-new-user-group-box grey-box">
<div class="new-user-group-form">
@@ -31,7 +31,7 @@
</div>
</div>
</form>
{{/unless}}
{{/if}}
{{/unless}}
<div id="user-groups" class="new-style"></div>

View File

@@ -55,7 +55,7 @@
<a href={{this.link}} target="_blank" class="value">{{this.value}}</a>
{{else}}
{{#if this.rendered_value}}
<div class="value rendered_markdown">{{{this.rendered_value}}}</div>
<div class="value rendered_markdown">{{rendered_markdown this.rendered_value}}</div>
{{else}}
<div class="value">{{this.value}}</div>
{{/if}}

View File

@@ -30,8 +30,7 @@
</ol>
<h2>You're done!</h2>
<p>If you have any questions, please contact us using the "Send feedback" button in Zulip or e-mail us at
<a href="mailto:{{ support_email }}">{{ support_email }}</a></p>
<p>If you have any questions, please e-mail us at <a href="mailto:{{ support_email }}">{{ support_email }}</a></p>
<h2>If you want to automatically transfer your existing Zephyr subscriptions</h2>

View File

@@ -50,6 +50,7 @@
{% include "zerver/app/settings_overlay.html" %}
</div>
{% include "zerver/app/navbar_alerts.html" %}
{% include "zerver/app/navbar.html" %}
<div class="fixed-app">

View File

@@ -1,32 +1,3 @@
<div id="panels">
<div data-process="notifications" class="alert alert-info">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
{% trans %}Zulip needs your permission to
<a class="request-desktop-notifications alert-link">enable desktop notifications.</a>
{% endtrans %}
</div>
<div data-step="2" style="display: none">
{{ _('We strongly recommend enabling desktop notifications. They help Zulip keep your team connected.') }}
<span class="buttons">
<a class="alert-link request-desktop-notifications">{{ _('Enable notifications') }}</a>
&bull;
<a class="alert-link exit">{{ _('Ask me later') }}</a>
&bull;
<a class="alert-link reject-notifications">{{ _('Never ask on this computer') }}</a>
</span>
</div>
</div>
<div data-process="email-server" class="alert alert-info red">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
{% trans %}Zulip needs to send email to confirm users' addresses and send notifications.{% endtrans %}
<a class="alert-link" href="https://zulip.readthedocs.io/en/latest/production/email.html" target="_blank">
{% trans %}See how to configure email.{% endtrans %}
</a>
</div>
</div>
</div>
<div class="header">
<nav class="header-main rightside-userlist" id="top_navbar">
<div class="column-left">
@@ -167,13 +138,6 @@
</li>
{% endif %}
<li class="divider" role="presentation"></li>
{% if enable_feedback %}
<li role="presentation">
<a href="#feedback" class="feedback" role="menuitem">
<i class="fa fa-comment" aria-hidden="true"></i> {{ _('Feedback') }}
</a>
</li>
{% endif %}
{% if show_invites %}
<li role="presentation">
<a href="#invite" role="menuitem">

View File

@@ -0,0 +1,40 @@
<div id="panels">
<div data-process="notifications" class="alert alert-info">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
{% trans %}Zulip needs your permission to
<a class="request-desktop-notifications alert-link">enable desktop notifications.</a>
{% endtrans %}
</div>
<div data-step="2" style="display: none">
{{ _('We strongly recommend enabling desktop notifications. They help Zulip keep your team connected.') }}
<span class="buttons">
<a class="alert-link request-desktop-notifications">{{ _('Enable notifications') }}</a>
&bull;
<a class="alert-link exit">{{ _('Ask me later') }}</a>
&bull;
<a class="alert-link reject-notifications">{{ _('Never ask on this computer') }}</a>
</span>
</div>
</div>
<div data-process="email-server" class="alert alert-info red">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
{% trans %}Zulip needs to send email to confirm users' addresses and send notifications.{% endtrans %}
<a class="alert-link" href="https://zulip.readthedocs.io/en/latest/production/email.html" target="_blank">
{% trans %}See how to configure email.{% endtrans %}
</a>
</div>
</div>
<div data-process="insecure-desktop-app" class="alert alert-info red">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<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">
Download the latest version.
</a>
{% endtrans %}
</div>
</div>
</div>

View File

@@ -1,12 +1,5 @@
<div class="right-sidebar" id="right-sidebar" role="navigation">
<div class="right-sidebar-items">
{% if enable_feedback %}
<div id="feedback_section" class="new-style">
<button type="button" class="button small rounded" id="feedback_button">
<i class="fa fa-comment" aria-hidden="true"></i>&nbsp;&nbsp;{{ _('Send feedback') }}
</button>
</div>
{% endif %}
<div id="user-list">
<div id="userlist-header">
<h4 class='sidebar-title' id='userlist-title' data-toggle="tooltip" title="{{ _('Filter users') }}">{{ _('USERS') }}</h4>

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/registration_confirmation.png" alt="{{ _('Turtle with envelope') }}"/>
<img src="{{ email_images_base_uri }}/registration_confirmation.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -299,6 +299,10 @@ a.button:hover {
font-weight: bold;
}
.content_disabled_help_link {
color: #15c;
}
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/day2_1.png" alt="{{ _('Octopus box with heart') }}"/>
<img src="{{ email_images_base_uri }}/day2_1.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/registration_confirmation.png" alt="{{ _('Turtle with envelope') }}"/>
<img src="{{ email_images_base_uri }}/registration_confirmation.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/invitation_reminder.png" alt="{{ _('Mailbox with envelope') }}"/>
<img src="{{ email_images_base_uri }}/invitation_reminder.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -11,6 +11,14 @@
</div>
{% endfor %}
{% endfor %}
{% else %}
<div class="missed_message">
{% if message_content_disabled_by_realm %}
This email does not include message content because your organization has disabled <a class="content_disabled_help_link" href="{{ realm_uri }}/help/hide-message-content-in-emails">message content appearing in email notifications</a>.
{% elif message_content_disabled_by_user %}
This email does not include message content because you have disabled <a class="content_disabled_help_link" href="{{ realm_uri }}/help/pm-mention-alert-notifications">message content appearing in email notifications</a>.
{% endif %}
</div>
{% endif %}
{% endblock %}
@@ -24,10 +32,12 @@
{% endif %}
{% if reply_to_zulip %}
Reply to this email directly, <a href="{{ narrow_url }}">view it in Zulip</a>, or <a href="{{ realm_uri }}/#settings/notifications">manage email preferences</a>.
{% elif not show_message_content %}
<a href="{{ narrow_url }}">View or reply in Zulip</a>, or <a href="{{ realm_uri }}/#settings/notifications">manage email preferences</a>.<br>
{% else %}
<a href="{{ narrow_url }}">Reply in Zulip</a>, or <a href="{{ realm_uri }}/#settings/notifications">manage email preferences</a>.<br>
<br>
Do not reply to this message. This Zulip server is not
Do not reply to this email. This Zulip server is not
configured to accept incoming emails (<a href="https://zulip.readthedocs.io/en/latest/production/email-gateway.html">help</a>).
{% endif %}

View File

@@ -6,6 +6,14 @@
{% endfor %}
{% endfor %}
{% endfor %}
{% else %}
{% if message_content_disabled_by_realm %}
This email does not include message content because your organization has disabled message content appearing in email notifications.
See {{ realm_uri }}/help/hide-message-content-in-emails for more details.
{% elif message_content_disabled_by_user %}
This email does not include message content because you have disabled message content appearing in email notifications.
See {{ realm_uri }}/help/pm-mention-alert-notifications for more details.
{% endif %}
{% endif %}
--
@@ -18,11 +26,14 @@ You are receiving this because you have email notifications enabled for this str
{% if reply_to_zulip %}
Reply to this email directly, or view it in Zulip:
{{ narrow_url }}
{% elif not show_message_content %}
View or reply in Zulip:
{{ narrow_url }}
{% else %}
Reply in Zulip:
{{ narrow_url }}
Do not reply to this message. This Zulip server is not configured to accept
Do not reply to this email. This Zulip server is not configured to accept
incoming emails. Help:
https://zulip.readthedocs.io/en/latest/production/email-gateway.html
{% endif %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -5,7 +5,7 @@
{% endblock %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

View File

@@ -1,7 +1,7 @@
{% extends "zerver/emails/compiled/email_base_default.html" %}
{% block illustration %}
<img src="{{ email_images_base_uri }}/email_logo.png" alt="{{ _('Zulip logo') }}"/>
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
{% endblock %}
{% block content %}

Some files were not shown because too many files have changed in this diff Show More