Advertisement
Guest User

Untitled

a guest
Nov 30th, 2016
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 159.99 KB | None | 0 0
  1. from __future__ import absolute_import
  2. from __future__ import print_function
  3. from typing import (
  4. AbstractSet, Any, AnyStr, Callable, Dict, Iterable, Mapping, MutableMapping,
  5. Optional, Sequence, Set, Tuple, TypeVar, Union
  6. )
  7.  
  8. from django.utils.translation import ugettext as _
  9. from django.conf import settings
  10. from django.core import validators
  11. from django.contrib.sessions.models import Session
  12. from zerver.lib.bugdown import (
  13. BugdownRenderingException,
  14. version as bugdown_version
  15. )
  16. from zerver.lib.cache import (
  17. to_dict_cache_key,
  18. to_dict_cache_key_id,
  19. )
  20. from zerver.lib.context_managers import lockfile
  21. from zerver.lib.message import (
  22. access_message,
  23. MessageDict,
  24. message_to_dict,
  25. render_markdown,
  26. )
  27. from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, RealmAlias, \
  28. Subscription, Recipient, Message, Attachment, UserMessage, valid_stream_name, \
  29. Client, DefaultStream, UserPresence, Referral, PushDeviceToken, MAX_SUBJECT_LENGTH, \
  30. MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \
  31. get_user_profile_by_id, PreregistrationUser, get_display_recipient, \
  32. get_realm, get_realm_by_string_id, bulk_get_recipients, \
  33. email_allowed_for_realm, email_to_username, display_recipient_cache_key, \
  34. get_user_profile_by_email, get_stream_cache_key, \
  35. UserActivityInterval, get_active_user_dicts_in_realm, get_active_streams, \
  36. realm_filters_for_domain, RealmFilter, receives_offline_notifications, \
  37. ScheduledJob, realm_filters_for_domain, get_owned_bot_dicts, \
  38. get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications
  39.  
  40. from zerver.lib.alert_words import alert_words_in_realm
  41. from zerver.lib.avatar import get_avatar_url, avatar_url
  42.  
  43. from django.db import transaction, IntegrityError, connection
  44. from django.db.models import F, Q
  45. from django.db.models.query import QuerySet
  46. from django.core.exceptions import ValidationError
  47. from importlib import import_module
  48. from django.core.mail import EmailMessage
  49. from django.utils.timezone import now
  50.  
  51. from confirmation.models import Confirmation
  52. import six
  53. from six import text_type
  54. from six.moves import filter
  55. from six.moves import map
  56. from six.moves import range
  57. from six import unichr
  58.  
  59. session_engine = import_module(settings.SESSION_ENGINE)
  60.  
  61. from zerver.lib.create_user import random_api_key
  62. from zerver.lib.timestamp import timestamp_to_datetime, datetime_to_timestamp
  63. from zerver.lib.queue import queue_json_publish
  64. from django.utils import timezone
  65. from zerver.lib.create_user import create_user
  66. from zerver.lib import bugdown
  67. from zerver.lib.cache import cache_with_key, cache_set, \
  68. user_profile_by_email_cache_key, cache_set_many, \
  69. cache_delete, cache_delete_many
  70. from zerver.decorator import statsd_increment
  71. from zerver.lib.event_queue import request_event_queue, get_user_events, send_event
  72. from zerver.lib.utils import log_statsd_event, statsd
  73. from zerver.lib.html_diff import highlight_html_differences
  74. from zerver.lib.alert_words import user_alert_words, add_user_alert_words, \
  75. remove_user_alert_words, set_user_alert_words
  76. from zerver.lib.push_notifications import num_push_devices_for_user, \
  77. send_apple_push_notification, send_android_push_notification
  78. from zerver.lib.notifications import clear_followup_emails_queue
  79. from zerver.lib.narrow import check_supported_events_narrow_filter
  80. from zerver.lib.request import JsonableError
  81. from zerver.lib.session_user import get_session_user
  82. from zerver.lib.upload import attachment_url_re, attachment_url_to_path_id, \
  83. claim_attachment, delete_message_image
  84. from zerver.lib.str_utils import NonBinaryStr, force_str
  85.  
  86. import DNS
  87. import ujson
  88. import time
  89. import traceback
  90. import re
  91. import datetime
  92. import os
  93. import platform
  94. import logging
  95. import itertools
  96. from collections import defaultdict
  97. import copy
  98.  
  99. # This will be used to type annotate parameters in a function if the function
  100. # works on both str and unicode in python 2 but in python 3 it only works on str.
  101. SizedTextIterable = Union[Sequence[text_type], AbstractSet[text_type]]
  102.  
  103. STREAM_ASSIGNMENT_COLORS = [
  104. "#76ce90", "#fae589", "#a6c7e5", "#e79ab5",
  105. "#bfd56f", "#f4ae55", "#b0a5fd", "#addfe5",
  106. "#f5ce6e", "#c2726a", "#94c849", "#bd86e5",
  107. "#ee7e4a", "#a6dcbf", "#95a5fd", "#53a063",
  108. "#9987e1", "#e4523d", "#c2c2c2", "#4f8de4",
  109. "#c6a8ad", "#e7cc4d", "#c8bebf", "#a47462"]
  110.  
  111. # Store an event in the log for re-importing messages
  112. def log_event(event):
  113. # type: (MutableMapping[str, Any]) -> None
  114. if settings.EVENT_LOG_DIR is None:
  115. return
  116.  
  117. if "timestamp" not in event:
  118. event["timestamp"] = time.time()
  119.  
  120. if not os.path.exists(settings.EVENT_LOG_DIR):
  121. os.mkdir(settings.EVENT_LOG_DIR)
  122.  
  123. template = os.path.join(settings.EVENT_LOG_DIR,
  124. '%s.' + platform.node()
  125. + datetime.datetime.now().strftime('.%Y-%m-%d'))
  126.  
  127. with lockfile(template % ('lock',)):
  128. with open(template % ('events',), 'a') as log:
  129. log.write(force_str(ujson.dumps(event) + u'\n'))
  130.  
  131. def active_user_ids(realm):
  132. # type: (Realm) -> List[int]
  133. return [userdict['id'] for userdict in get_active_user_dicts_in_realm(realm)]
  134.  
  135. def can_access_stream_user_ids(stream):
  136. # type: (Stream) -> Set[int]
  137.  
  138. # return user ids of users who can access the attributes of
  139. # a stream, such as its name/description
  140. if stream.is_public():
  141. return set(active_user_ids(stream.realm))
  142. else:
  143. return private_stream_user_ids(stream)
  144.  
  145. def private_stream_user_ids(stream):
  146. # type: (Stream) -> Set[int]
  147.  
  148. # TODO: Find similar queries elsewhere and de-duplicate this code.
  149. subscriptions = Subscription.objects.filter(
  150. recipient__type=Recipient.STREAM,
  151. recipient__type_id=stream.id,
  152. active=True)
  153.  
  154. return {sub['user_profile_id'] for sub in subscriptions.values('user_profile_id')}
  155.  
  156. def bot_owner_userids(user_profile):
  157. # type: (UserProfile) -> Sequence[int]
  158. is_private_bot = (
  159. user_profile.default_sending_stream and user_profile.default_sending_stream.invite_only or
  160. user_profile.default_events_register_stream and user_profile.default_events_register_stream.invite_only)
  161. if is_private_bot:
  162. return (user_profile.bot_owner_id,) # TODO: change this to list instead of tuple
  163. else:
  164. return active_user_ids(user_profile.realm)
  165.  
  166. def realm_user_count(realm):
  167. # type: (Realm) -> int
  168. return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False).count()
  169.  
  170. def get_topic_history_for_stream(user_profile, recipient):
  171. # type: (UserProfile, Recipient) -> List[Tuple[str, int]]
  172.  
  173. # We tested the below query on some large prod datasets, and we never
  174. # saw more than 50ms to execute it, so we think that's acceptable,
  175. # but we will monitor it, and we may later optimize it further.
  176. query = '''
  177. SELECT topic, read, count(*)
  178. FROM (
  179. SELECT
  180. ("zerver_usermessage"."flags" & 1) as read,
  181. "zerver_message"."subject" as topic,
  182. "zerver_message"."id" as message_id
  183. FROM "zerver_usermessage"
  184. INNER JOIN "zerver_message" ON (
  185. "zerver_usermessage"."message_id" = "zerver_message"."id"
  186. ) WHERE (
  187. "zerver_usermessage"."user_profile_id" = %s AND
  188. "zerver_message"."recipient_id" = %s
  189. ) ORDER BY "zerver_usermessage"."message_id" DESC
  190. ) messages_for_stream
  191. GROUP BY topic, read
  192. ORDER BY max(message_id) desc
  193. '''
  194. cursor = connection.cursor()
  195. cursor.execute(query, [user_profile.id, recipient.id])
  196. rows = cursor.fetchall()
  197. cursor.close()
  198.  
  199. topic_names = dict() # type: Dict[str, str]
  200. topic_counts = dict() # type: Dict[str, int]
  201. topics = []
  202. for row in rows:
  203. topic_name, read, count = row
  204. if topic_name.lower() not in topic_names:
  205. topic_names[topic_name.lower()] = topic_name
  206. topic_name = topic_names[topic_name.lower()]
  207. if topic_name not in topic_counts:
  208. topic_counts[topic_name] = 0
  209. topics.append(topic_name)
  210. if not read:
  211. topic_counts[topic_name] += count
  212.  
  213. history = [(topic, topic_counts[topic]) for topic in topics]
  214. return history
  215.  
  216. def send_signup_message(sender, signups_stream, user_profile,
  217. internal=False, realm=None):
  218. # type: (UserProfile, text_type, UserProfile, bool, Optional[Realm]) -> None
  219. if internal:
  220. # When this is done using manage.py vs. the web interface
  221. internal_blurb = " **INTERNAL SIGNUP** "
  222. else:
  223. internal_blurb = " "
  224.  
  225. user_count = realm_user_count(user_profile.realm)
  226. # Send notification to realm notifications stream if it exists
  227. # Don't send notification for the first user in a realm
  228. if user_profile.realm.notifications_stream is not None and user_count > 1:
  229. internal_send_message(sender, "stream",
  230. user_profile.realm.notifications_stream.name,
  231. "New users", "%s just signed up for Zulip. Say hello!" % \
  232. (user_profile.full_name,),
  233. realm=user_profile.realm)
  234.  
  235. internal_send_message(sender,
  236. "stream", signups_stream, user_profile.realm.domain,
  237. "%s <`%s`> just signed up for Zulip!%s(total: **%i**)" % (
  238. user_profile.full_name,
  239. user_profile.email,
  240. internal_blurb,
  241. user_count,
  242. )
  243. )
  244.  
  245. def notify_new_user(user_profile, internal=False):
  246. # type: (UserProfile, bool) -> None
  247. if settings.NEW_USER_BOT is not None:
  248. send_signup_message(settings.NEW_USER_BOT, "signups", user_profile, internal)
  249. statsd.gauge("users.signups.%s" % (user_profile.realm.domain.replace('.', '_')), 1, delta=True)
  250.  
  251. def add_new_user_history(user_profile, streams):
  252. # type: (UserProfile, Iterable[Stream]) -> None
  253. """Give you the last 100 messages on your public streams, so you have
  254. something to look at in your home view once you finish the
  255. tutorial."""
  256. one_week_ago = now() - datetime.timedelta(weeks=1)
  257. recipients = Recipient.objects.filter(type=Recipient.STREAM,
  258. type_id__in=[stream.id for stream in streams
  259. if not stream.invite_only])
  260. recent_messages = Message.objects.filter(recipient_id__in=recipients,
  261. pub_date__gt=one_week_ago).order_by("-id")
  262. message_ids_to_use = list(reversed(recent_messages.values_list('id', flat=True)[0:100]))
  263. if len(message_ids_to_use) == 0:
  264. return
  265.  
  266. # Handle the race condition where a message arrives between
  267. # bulk_add_subscriptions above and the Message query just above
  268. already_ids = set(UserMessage.objects.filter(message_id__in=message_ids_to_use,
  269. user_profile=user_profile).values_list("message_id", flat=True))
  270. ums_to_create = [UserMessage(user_profile=user_profile, message_id=message_id,
  271. flags=UserMessage.flags.read)
  272. for message_id in message_ids_to_use
  273. if message_id not in already_ids]
  274.  
  275. UserMessage.objects.bulk_create(ums_to_create)
  276.  
  277. # Does the processing for a new user account:
  278. # * Subscribes to default/invitation streams
  279. # * Fills in some recent historical messages
  280. # * Notifies other users in realm and Zulip about the signup
  281. # * Deactivates PreregistrationUser objects
  282. # * subscribe the user to newsletter if newsletter_data is specified
  283. def process_new_human_user(user_profile, prereg_user=None, newsletter_data=None):
  284. # type: (UserProfile, Optional[PreregistrationUser], Optional[Dict[str, str]]) -> None
  285. mit_beta_user = user_profile.realm.is_zephyr_mirror_realm
  286. try:
  287. streams = prereg_user.streams.all()
  288. except AttributeError:
  289. # This will catch both the case where prereg_user is None and where it
  290. # is a MitUser.
  291. streams = []
  292.  
  293. # If the user's invitation didn't explicitly list some streams, we
  294. # add the default streams
  295. if len(streams) == 0:
  296. streams = get_default_subs(user_profile)
  297. bulk_add_subscriptions(streams, [user_profile])
  298.  
  299. add_new_user_history(user_profile, streams)
  300.  
  301. # mit_beta_users don't have a referred_by field
  302. if not mit_beta_user and prereg_user is not None and prereg_user.referred_by is not None \
  303. and settings.NOTIFICATION_BOT is not None:
  304. # This is a cross-realm private message.
  305. internal_send_message(settings.NOTIFICATION_BOT,
  306. "private", prereg_user.referred_by.email, user_profile.realm.domain,
  307. "%s <`%s`> accepted your invitation to join Zulip!" % (
  308. user_profile.full_name,
  309. user_profile.email,
  310. )
  311. )
  312. # Mark any other PreregistrationUsers that are STATUS_ACTIVE as
  313. # inactive so we can keep track of the PreregistrationUser we
  314. # actually used for analytics
  315. if prereg_user is not None:
  316. PreregistrationUser.objects.filter(email__iexact=user_profile.email).exclude(
  317. id=prereg_user.id).update(status=0)
  318. else:
  319. PreregistrationUser.objects.filter(email__iexact=user_profile.email).update(status=0)
  320.  
  321. notify_new_user(user_profile)
  322.  
  323. if newsletter_data is not None:
  324. # If the user was created automatically via the API, we may
  325. # not want to register them for the newsletter
  326. queue_json_publish(
  327. "signups",
  328. {
  329. 'EMAIL': user_profile.email,
  330. 'merge_vars': {
  331. 'NAME': user_profile.full_name,
  332. 'REALM_ID': user_profile.realm.id,
  333. 'OPTIN_IP': newsletter_data["IP"],
  334. 'OPTIN_TIME': datetime.datetime.isoformat(now().replace(microsecond=0)),
  335. },
  336. },
  337. lambda event: None)
  338.  
  339. def notify_created_user(user_profile):
  340. # type: (UserProfile) -> None
  341. event = dict(type="realm_user", op="add",
  342. person=dict(email=user_profile.email,
  343. user_id=user_profile.id,
  344. is_admin=user_profile.is_realm_admin,
  345. full_name=user_profile.full_name,
  346. is_bot=user_profile.is_bot))
  347. send_event(event, active_user_ids(user_profile.realm))
  348.  
  349. def notify_created_bot(user_profile):
  350. # type: (UserProfile) -> None
  351.  
  352. def stream_name(stream):
  353. # type: (Stream) -> Optional[text_type]
  354. if not stream:
  355. return None
  356. return stream.name
  357.  
  358. default_sending_stream_name = stream_name(user_profile.default_sending_stream)
  359. default_events_register_stream_name = stream_name(user_profile.default_events_register_stream)
  360.  
  361. event = dict(type="realm_bot", op="add",
  362. bot=dict(email=user_profile.email,
  363. user_id=user_profile.id,
  364. full_name=user_profile.full_name,
  365. api_key=user_profile.api_key,
  366. default_sending_stream=default_sending_stream_name,
  367. default_events_register_stream=default_events_register_stream_name,
  368. default_all_public_streams=user_profile.default_all_public_streams,
  369. avatar_url=avatar_url(user_profile),
  370. owner=user_profile.bot_owner.email,
  371. ))
  372. send_event(event, bot_owner_userids(user_profile))
  373.  
  374. def do_create_user(email, password, realm, full_name, short_name,
  375. active=True, bot_type=None, bot_owner=None, tos_version=None,
  376. avatar_source=UserProfile.AVATAR_FROM_GRAVATAR,
  377. default_sending_stream=None, default_events_register_stream=None,
  378. default_all_public_streams=None, prereg_user=None,
  379. newsletter_data=None):
  380. # type: (text_type, text_type, Realm, text_type, text_type, bool, Optional[int], Optional[UserProfile], Optional[text_type], text_type, Optional[Stream], Optional[Stream], bool, Optional[PreregistrationUser], Optional[Dict[str, str]]) -> UserProfile
  381. event = {'type': 'user_created',
  382. 'timestamp': time.time(),
  383. 'full_name': full_name,
  384. 'short_name': short_name,
  385. 'user': email,
  386. 'domain': realm.domain,
  387. 'bot': bool(bot_type)}
  388. if bot_type:
  389. event['bot_owner'] = bot_owner.email
  390. log_event(event)
  391.  
  392. user_profile = create_user(email=email, password=password, realm=realm,
  393. full_name=full_name, short_name=short_name,
  394. active=active, bot_type=bot_type, bot_owner=bot_owner,
  395. tos_version=tos_version, avatar_source=avatar_source,
  396. default_sending_stream=default_sending_stream,
  397. default_events_register_stream=default_events_register_stream,
  398. default_all_public_streams=default_all_public_streams)
  399.  
  400. notify_created_user(user_profile)
  401. if bot_type:
  402. notify_created_bot(user_profile)
  403. else:
  404. process_new_human_user(user_profile, prereg_user=prereg_user,
  405. newsletter_data=newsletter_data)
  406. return user_profile
  407.  
  408. def user_sessions(user_profile):
  409. # type: (UserProfile) -> List[Session]
  410. return [s for s in Session.objects.all()
  411. if get_session_user(s) == user_profile.id]
  412.  
  413. def delete_session(session):
  414. # type: (Session) -> None
  415. session_engine.SessionStore(session.session_key).delete() # type: ignore # import_module
  416.  
  417. def delete_user_sessions(user_profile):
  418. # type: (UserProfile) -> None
  419. for session in Session.objects.all():
  420. if get_session_user(session) == user_profile.id:
  421. delete_session(session)
  422.  
  423. def delete_realm_user_sessions(realm):
  424. # type: (Realm) -> None
  425. realm_user_ids = [user_profile.id for user_profile in
  426. UserProfile.objects.filter(realm=realm)]
  427. for session in Session.objects.filter(expire_date__gte=datetime.datetime.now()):
  428. if get_session_user(session) in realm_user_ids:
  429. delete_session(session)
  430.  
  431. def delete_all_user_sessions():
  432. # type: () -> None
  433. for session in Session.objects.all():
  434. delete_session(session)
  435.  
  436. def delete_all_deactivated_user_sessions():
  437. # type: () -> None
  438. for session in Session.objects.all():
  439. user_profile_id = get_session_user(session)
  440. if user_profile_id is None:
  441. continue
  442. user_profile = get_user_profile_by_id(user_profile_id)
  443. if not user_profile.is_active or user_profile.realm.deactivated:
  444. logging.info("Deactivating session for deactivated user %s" % (user_profile.email,))
  445. delete_session(session)
  446.  
  447. def active_humans_in_realm(realm):
  448. # type: (Realm) -> Sequence[UserProfile]
  449. return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False)
  450.  
  451. def do_set_realm_name(realm, name):
  452. # type: (Realm, text_type) -> None
  453. realm.name = name
  454. realm.save(update_fields=['name'])
  455. event = dict(
  456. type="realm",
  457. op="update",
  458. property='name',
  459. value=name,
  460. )
  461. send_event(event, active_user_ids(realm))
  462.  
  463. def do_set_realm_restricted_to_domain(realm, restricted):
  464. # type: (Realm, bool) -> None
  465. realm.restricted_to_domain = restricted
  466. realm.save(update_fields=['restricted_to_domain'])
  467. event = dict(
  468. type="realm",
  469. op="update",
  470. property='restricted_to_domain',
  471. value=restricted,
  472. )
  473. send_event(event, active_user_ids(realm))
  474.  
  475. def do_set_realm_invite_required(realm, invite_required):
  476. # type: (Realm, bool) -> None
  477. realm.invite_required = invite_required
  478. realm.save(update_fields=['invite_required'])
  479. event = dict(
  480. type="realm",
  481. op="update",
  482. property='invite_required',
  483. value=invite_required,
  484. )
  485. send_event(event, active_user_ids(realm))
  486.  
  487. def do_set_realm_invite_by_admins_only(realm, invite_by_admins_only):
  488. # type: (Realm, bool) -> None
  489. realm.invite_by_admins_only = invite_by_admins_only
  490. realm.save(update_fields=['invite_by_admins_only'])
  491. event = dict(
  492. type="realm",
  493. op="update",
  494. property='invite_by_admins_only',
  495. value=invite_by_admins_only,
  496. )
  497. send_event(event, active_user_ids(realm))
  498.  
  499. def do_set_realm_authentication_methods(realm, authentication_methods):
  500. # type: (Realm, Dict[str, bool]) -> None
  501. for key, value in list(authentication_methods.items()):
  502. index = getattr(realm.authentication_methods, key).number
  503. realm.authentication_methods.set_bit(index, int(value))
  504. realm.save(update_fields=['authentication_methods'])
  505. event = dict(
  506. type="realm",
  507. op="update_dict",
  508. property='default',
  509. data=dict(authentication_methods=realm.authentication_methods_dict())
  510. )
  511. send_event(event, active_user_ids(realm))
  512.  
  513. def do_set_realm_create_stream_by_admins_only(realm, create_stream_by_admins_only):
  514. # type: (Realm, bool) -> None
  515. realm.create_stream_by_admins_only = create_stream_by_admins_only
  516. realm.save(update_fields=['create_stream_by_admins_only'])
  517. event = dict(
  518. type="realm",
  519. op="update",
  520. property='create_stream_by_admins_only',
  521. value=create_stream_by_admins_only,
  522. )
  523. send_event(event, active_user_ids(realm))
  524.  
  525. def do_set_realm_message_editing(realm, allow_message_editing, message_content_edit_limit_seconds):
  526. # type: (Realm, bool, int) -> None
  527. realm.allow_message_editing = allow_message_editing
  528. realm.message_content_edit_limit_seconds = message_content_edit_limit_seconds
  529. realm.save(update_fields=['allow_message_editing', 'message_content_edit_limit_seconds'])
  530. event = dict(
  531. type="realm",
  532. op="update_dict",
  533. property="default",
  534. data=dict(allow_message_editing=allow_message_editing,
  535. message_content_edit_limit_seconds=message_content_edit_limit_seconds),
  536. )
  537. send_event(event, active_user_ids(realm))
  538.  
  539. def do_set_realm_default_language(realm, default_language):
  540. # type: (Realm, text_type) -> None
  541.  
  542. if default_language == 'zh_CN':
  543. # NB: remove this once we upgrade to Django 1.9
  544. # zh-cn and zh-tw will be replaced by zh-hans and zh-hant in
  545. # Django 1.9
  546. default_language= 'zh_HANS'
  547.  
  548. realm.default_language = default_language
  549. realm.save(update_fields=['default_language'])
  550. event = dict(
  551. type="realm",
  552. op="update",
  553. property="default_language",
  554. value=default_language
  555. )
  556. send_event(event, active_user_ids(realm))
  557.  
  558. def do_deactivate_realm(realm):
  559. # type: (Realm) -> None
  560. """
  561. Deactivate this realm. Do NOT deactivate the users -- we need to be able to
  562. tell the difference between users that were intentionally deactivated,
  563. e.g. by a realm admin, and users who can't currently use Zulip because their
  564. realm has been deactivated.
  565. """
  566. if realm.deactivated:
  567. return
  568.  
  569. realm.deactivated = True
  570. realm.save(update_fields=["deactivated"])
  571.  
  572. for user in active_humans_in_realm(realm):
  573. # Don't deactivate the users, but do delete their sessions so they get
  574. # bumped to the login screen, where they'll get a realm deactivation
  575. # notice when they try to log in.
  576. delete_user_sessions(user)
  577.  
  578. def do_reactivate_realm(realm):
  579. # type: (Realm) -> None
  580. realm.deactivated = False
  581. realm.save(update_fields=["deactivated"])
  582.  
  583. def do_deactivate_user(user_profile, log=True, _cascade=True):
  584. # type: (UserProfile, bool, bool) -> None
  585. if not user_profile.is_active:
  586. return
  587.  
  588. user_profile.is_active = False
  589. user_profile.save(update_fields=["is_active"])
  590.  
  591. delete_user_sessions(user_profile)
  592.  
  593. if log:
  594. log_event({'type': 'user_deactivated',
  595. 'timestamp': time.time(),
  596. 'user': user_profile.email,
  597. 'domain': user_profile.realm.domain})
  598.  
  599. event = dict(type="realm_user", op="remove",
  600. person=dict(email=user_profile.email,
  601. user_id=user_profile.id,
  602. full_name=user_profile.full_name))
  603. send_event(event, active_user_ids(user_profile.realm))
  604.  
  605. if user_profile.is_bot:
  606. event = dict(type="realm_bot", op="remove",
  607. bot=dict(email=user_profile.email,
  608. user_id=user_profile.id,
  609. full_name=user_profile.full_name))
  610. send_event(event, bot_owner_userids(user_profile))
  611.  
  612. if _cascade:
  613. bot_profiles = UserProfile.objects.filter(is_bot=True, is_active=True,
  614. bot_owner=user_profile)
  615. for profile in bot_profiles:
  616. do_deactivate_user(profile, _cascade=False)
  617.  
  618. def do_deactivate_stream(stream, log=True):
  619. # type: (Stream, bool) -> None
  620. user_profiles = UserProfile.objects.filter(realm=stream.realm)
  621. for user_profile in user_profiles:
  622. bulk_remove_subscriptions([user_profile], [stream])
  623.  
  624. was_invite_only = stream.invite_only
  625. stream.deactivated = True
  626. stream.invite_only = True
  627. # Preserve as much as possible the original stream name while giving it a
  628. # special prefix that both indicates that the stream is deactivated and
  629. # frees up the original name for reuse.
  630. old_name = stream.name
  631. new_name = ("!DEACTIVATED:" + old_name)[:Stream.MAX_NAME_LENGTH]
  632. for i in range(20):
  633. existing_deactivated_stream = get_stream(new_name, stream.realm)
  634. if existing_deactivated_stream:
  635. # This stream has alrady been deactivated, keep prepending !s until
  636. # we have a unique stream name or you've hit a rename limit.
  637. new_name = ("!" + new_name)[:Stream.MAX_NAME_LENGTH]
  638. else:
  639. break
  640.  
  641. # If you don't have a unique name at this point, this will fail later in the
  642. # code path.
  643.  
  644. stream.name = new_name[:Stream.MAX_NAME_LENGTH]
  645. stream.save()
  646.  
  647. # Remove the old stream information from remote cache.
  648. old_cache_key = get_stream_cache_key(old_name, stream.realm)
  649. cache_delete(old_cache_key)
  650.  
  651. if not was_invite_only:
  652. stream_dict = stream.to_dict()
  653. stream_dict.update(dict(name=old_name, invite_only=was_invite_only))
  654. event = dict(type="stream", op="delete",
  655. streams=[stream_dict])
  656. send_event(event, active_user_ids(stream.realm))
  657.  
  658. def do_change_user_email(user_profile, new_email):
  659. # type: (UserProfile, text_type) -> None
  660. old_email = user_profile.email
  661. user_profile.email = new_email
  662. user_profile.save(update_fields=["email"])
  663.  
  664. log_event({'type': 'user_email_changed',
  665. 'old_email': old_email,
  666. 'new_email': new_email})
  667.  
  668. def compute_irc_user_fullname(email):
  669. # type: (NonBinaryStr) -> NonBinaryStr
  670. return email.split("@")[0] + " (IRC)"
  671.  
  672. def compute_jabber_user_fullname(email):
  673. # type: (NonBinaryStr) -> NonBinaryStr
  674. return email.split("@")[0] + " (XMPP)"
  675.  
  676. def compute_mit_user_fullname(email):
  677. # type: (NonBinaryStr) -> NonBinaryStr
  678. try:
  679. # Input is either e.g. username@mit.edu or user|CROSSREALM.INVALID@mit.edu
  680. match_user = re.match(r'^([a-zA-Z0-9_.-]+)(\|.+)?@mit\.edu$', email.lower())
  681. if match_user and match_user.group(2) is None:
  682. answer = DNS.dnslookup(
  683. "%s.passwd.ns.athena.mit.edu" % (match_user.group(1),),
  684. DNS.Type.TXT)
  685. hesiod_name = force_str(answer[0][0]).split(':')[4].split(',')[0].strip()
  686. if hesiod_name != "":
  687. return hesiod_name
  688. elif match_user:
  689. return match_user.group(1).lower() + "@" + match_user.group(2).upper()[1:]
  690. except DNS.Base.ServerError:
  691. pass
  692. except:
  693. print ("Error getting fullname for %s:" % (email,))
  694. traceback.print_exc()
  695. return email.lower()
  696.  
  697. @cache_with_key(lambda realm, email, f: user_profile_by_email_cache_key(email),
  698. timeout=3600*24*7)
  699. def create_mirror_user_if_needed(realm, email, email_to_fullname):
  700. # type: (Realm, text_type, Callable[[text_type], text_type]) -> UserProfile
  701. try:
  702. return get_user_profile_by_email(email)
  703. except UserProfile.DoesNotExist:
  704. try:
  705. # Forge a user for this person
  706. return create_user(email, None, realm,
  707. email_to_fullname(email), email_to_username(email),
  708. active=False, is_mirror_dummy=True)
  709. except IntegrityError:
  710. return get_user_profile_by_email(email)
  711.  
  712. def log_message(message):
  713. # type: (Message) -> None
  714. if not message.sending_client.name.startswith("test:"):
  715. log_event(message.to_log_dict())
  716.  
  717. # Helper function. Defaults here are overriden by those set in do_send_messages
  718. def do_send_message(message, rendered_content = None, no_log = False, stream = None, local_id = None):
  719. # type: (Union[int, Message], Optional[text_type], bool, Optional[Stream], Optional[int]) -> int
  720. return do_send_messages([{'message': message,
  721. 'rendered_content': rendered_content,
  722. 'no_log': no_log,
  723. 'stream': stream,
  724. 'local_id': local_id}])[0]
  725.  
  726. def render_incoming_message(message, content, message_users):
  727. # type: (Message, text_type, Set[UserProfile]) -> text_type
  728. realm_alert_words = alert_words_in_realm(message.get_realm())
  729. try:
  730. rendered_content = render_markdown(
  731. message=message,
  732. content=content,
  733. realm_alert_words=realm_alert_words,
  734. message_users=message_users,
  735. )
  736. except BugdownRenderingException:
  737. raise JsonableError(_('Unable to render message'))
  738. return rendered_content
  739.  
  740. def get_recipient_user_profiles(recipient, sender_id):
  741. # type: (Recipient, text_type) -> List[UserProfile]
  742. if recipient.type == Recipient.PERSONAL:
  743. recipients = list(set([get_user_profile_by_id(recipient.type_id),
  744. get_user_profile_by_id(sender_id)]))
  745. # For personals, you send out either 1 or 2 copies, for
  746. # personals to yourself or to someone else, respectively.
  747. assert((len(recipients) == 1) or (len(recipients) == 2))
  748. elif (recipient.type == Recipient.STREAM or recipient.type == Recipient.HUDDLE):
  749. # We use select_related()/only() here, while the PERSONAL case above uses
  750. # get_user_profile_by_id() to get UserProfile objects from cache. Streams will
  751. # typically have more recipients than PMs, so get_user_profile_by_id() would be
  752. # a bit more expensive here, given that we need to hit the DB anyway and only
  753. # care about the email from the user profile.
  754. fields = [
  755. 'user_profile__id',
  756. 'user_profile__email',
  757. 'user_profile__enable_online_push_notifications',
  758. 'user_profile__is_active',
  759. 'user_profile__realm__domain'
  760. ]
  761. query = Subscription.objects.select_related("user_profile", "user_profile__realm").only(*fields).filter(
  762. recipient=recipient, active=True)
  763. recipients = [s.user_profile for s in query]
  764. else:
  765. raise ValueError('Bad recipient type')
  766. return recipients
  767.  
  768. def do_send_messages(messages):
  769. # type: (Sequence[Optional[MutableMapping[str, Any]]]) -> List[int]
  770. # Filter out messages which didn't pass internal_prep_message properly
  771. messages = [message for message in messages if message is not None]
  772.  
  773. # Filter out zephyr mirror anomalies where the message was already sent
  774. already_sent_ids = [] # type: List[int]
  775. new_messages = [] # type: List[MutableMapping[str, Any]]
  776. for message in messages:
  777. if isinstance(message['message'], int):
  778. already_sent_ids.append(message['message'])
  779. else:
  780. new_messages.append(message)
  781. messages = new_messages
  782.  
  783. # For consistency, changes to the default values for these gets should also be applied
  784. # to the default args in do_send_message
  785. for message in messages:
  786. message['rendered_content'] = message.get('rendered_content', None)
  787. message['no_log'] = message.get('no_log', False)
  788. message['stream'] = message.get('stream', None)
  789. message['local_id'] = message.get('local_id', None)
  790. message['sender_queue_id'] = message.get('sender_queue_id', None)
  791.  
  792. # Log the message to our message log for populate_db to refill
  793. for message in messages:
  794. if not message['no_log']:
  795. log_message(message['message'])
  796.  
  797. for message in messages:
  798. message['recipients'] = get_recipient_user_profiles(message['message'].recipient,
  799. message['message'].sender_id)
  800. # Only deliver the message to active user recipients
  801. message['active_recipients'] = [user_profile for user_profile in message['recipients']
  802. if user_profile.is_active]
  803.  
  804. # Render our messages.
  805. for message in messages:
  806. assert message['message'].rendered_content is None
  807. rendered_content = render_incoming_message(
  808. message['message'],
  809. message['message'].content,
  810. message_users=message['active_recipients'])
  811. message['message'].rendered_content = rendered_content
  812. message['message'].rendered_content_version = bugdown_version
  813.  
  814. for message in messages:
  815. message['message'].update_calculated_fields()
  816.  
  817. # Save the message receipts in the database
  818. user_message_flags = defaultdict(dict) # type: Dict[int, Dict[int, List[str]]]
  819. with transaction.atomic():
  820. Message.objects.bulk_create([message['message'] for message in messages])
  821. ums = [] # type: List[UserMessage]
  822. for message in messages:
  823. ums_to_create = [UserMessage(user_profile=user_profile, message=message['message'])
  824. for user_profile in message['active_recipients']]
  825.  
  826. # These properties on the Message are set via
  827. # render_markdown by code in the bugdown inline patterns
  828. wildcard = message['message'].mentions_wildcard
  829. mentioned_ids = message['message'].mentions_user_ids
  830. ids_with_alert_words = message['message'].user_ids_with_alert_words
  831. is_me_message = message['message'].is_me_message
  832.  
  833. for um in ums_to_create:
  834. if um.user_profile.id == message['message'].sender.id and \
  835. message['message'].sent_by_human():
  836. um.flags |= UserMessage.flags.read
  837. if wildcard:
  838. um.flags |= UserMessage.flags.wildcard_mentioned
  839. if um.user_profile_id in mentioned_ids:
  840. um.flags |= UserMessage.flags.mentioned
  841. if um.user_profile_id in ids_with_alert_words:
  842. um.flags |= UserMessage.flags.has_alert_word
  843. if is_me_message:
  844. um.flags |= UserMessage.flags.is_me_message
  845. user_message_flags[message['message'].id][um.user_profile_id] = um.flags_list()
  846. ums.extend(ums_to_create)
  847. UserMessage.objects.bulk_create(ums)
  848.  
  849. # Claim attachments in message
  850. for message in messages:
  851. if Message.content_has_attachment(message['message'].content):
  852. do_claim_attachments(message['message'])
  853.  
  854. for message in messages:
  855. # Render Markdown etc. here and store (automatically) in
  856. # remote cache, so that the single-threaded Tornado server
  857. # doesn't have to.
  858. user_flags = user_message_flags.get(message['message'].id, {})
  859. sender = message['message'].sender
  860. user_presences = get_status_dict(sender)
  861. presences = {}
  862. for user_profile in message['active_recipients']:
  863. if user_profile.email in user_presences:
  864. presences[user_profile.id] = user_presences[user_profile.email]
  865.  
  866. event = dict(
  867. type = 'message',
  868. message = message['message'].id,
  869. message_dict_markdown = message_to_dict(message['message'], apply_markdown=True),
  870. message_dict_no_markdown = message_to_dict(message['message'], apply_markdown=False),
  871. presences = presences)
  872. users = [{'id': user.id,
  873. 'flags': user_flags.get(user.id, []),
  874. 'always_push_notify': user.enable_online_push_notifications}
  875. for user in message['active_recipients']]
  876. if message['message'].recipient.type == Recipient.STREAM:
  877. # Note: This is where authorization for single-stream
  878. # get_updates happens! We only attach stream data to the
  879. # notify new_message request if it's a public stream,
  880. # ensuring that in the tornado server, non-public stream
  881. # messages are only associated to their subscribed users.
  882. if message['stream'] is None:
  883. message['stream'] = Stream.objects.select_related("realm").get(id=message['message'].recipient.type_id)
  884. if message['stream'].is_public():
  885. event['realm_id'] = message['stream'].realm.id
  886. event['stream_name'] = message['stream'].name
  887. if message['stream'].invite_only:
  888. event['invite_only'] = True
  889. if message['local_id'] is not None:
  890. event['local_id'] = message['local_id']
  891. if message['sender_queue_id'] is not None:
  892. event['sender_queue_id'] = message['sender_queue_id']
  893. send_event(event, users)
  894. if (settings.ENABLE_FEEDBACK and
  895. message['message'].recipient.type == Recipient.PERSONAL and
  896. settings.FEEDBACK_BOT in [up.email for up in message['recipients']]):
  897. queue_json_publish(
  898. 'feedback_messages',
  899. message_to_dict(message['message'], apply_markdown=False),
  900. lambda x: None
  901. )
  902.  
  903. # Note that this does not preserve the order of message ids
  904. # returned. In practice, this shouldn't matter, as we only
  905. # mirror single zephyr messages at a time and don't otherwise
  906. # intermingle sending zephyr messages with other messages.
  907. return already_sent_ids + [message['message'].id for message in messages]
  908.  
  909. def do_send_typing_notification(notification):
  910. # type: (Dict[str, Any]) -> None
  911. recipient_user_profiles = get_recipient_user_profiles(notification['recipient'],
  912. notification['sender'].id)
  913. # Only deliver the notification to active user recipients
  914. user_ids_to_notify = [profile.id for profile in recipient_user_profiles if profile.is_active]
  915. sender_dict = {'user_id': notification['sender'].id, 'email': notification['sender'].email}
  916. # Include a list of recipients in the event body to help identify where the typing is happening
  917. recipient_dicts = [{'user_id': profile.id, 'email': profile.email} for profile in recipient_user_profiles]
  918. event = dict(
  919. type = 'typing',
  920. op = notification['op'],
  921. sender = sender_dict,
  922. recipients = recipient_dicts)
  923.  
  924. send_event(event, user_ids_to_notify)
  925.  
  926. # check_send_typing_notification:
  927. # Checks the typing notification and sends it
  928. def check_send_typing_notification(sender, notification_to, operator):
  929. # type: (UserProfile, Sequence[text_type], text_type) -> None
  930. typing_notification = check_typing_notification(sender, notification_to, operator)
  931. do_send_typing_notification(typing_notification)
  932.  
  933. # check_typing_notification:
  934. # Returns typing notification ready for sending with do_send_typing_notification on success
  935. # or the error message (string) on error.
  936. def check_typing_notification(sender, notification_to, operator):
  937. # type: (UserProfile, Sequence[text_type], text_type) -> Dict[str, Any]
  938. if len(notification_to) == 0:
  939. raise JsonableError(_('Missing parameter: \'to\' (recipient)'))
  940. elif operator not in ('start', 'stop'):
  941. raise JsonableError(_('Invalid \'op\' value (should be start or stop)'))
  942. else:
  943. try:
  944. recipient = recipient_for_emails(notification_to, False,
  945. sender, sender)
  946. except ValidationError as e:
  947. assert isinstance(e.messages[0], six.string_types)
  948. raise JsonableError(e.messages[0])
  949. if recipient.type == Recipient.STREAM:
  950. raise ValueError('Forbidden recipient type')
  951. return {'sender': sender, 'recipient': recipient, 'op': operator}
  952.  
  953. def do_create_stream(realm, stream_name):
  954. # type: (Realm, text_type) -> None
  955. # This is used by a management command now, mostly to facilitate testing. It
  956. # doesn't simulate every single aspect of creating a subscription; for example,
  957. # we don't send Zulips to users to tell them they have been subscribed.
  958. stream = Stream()
  959. stream.realm = realm
  960. stream.name = stream_name
  961. stream.save()
  962. Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
  963. subscribers = UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False)
  964. bulk_add_subscriptions([stream], subscribers)
  965.  
  966. def create_stream_if_needed(realm, stream_name, invite_only=False):
  967. # type: (Realm, text_type, bool) -> Tuple[Stream, bool]
  968. (stream, created) = Stream.objects.get_or_create(
  969. realm=realm, name__iexact=stream_name,
  970. defaults={'name': stream_name, 'invite_only': invite_only})
  971. if created:
  972. Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
  973. if not invite_only:
  974. event = dict(type="stream", op="create",
  975. streams=[stream.to_dict()])
  976. send_event(event, active_user_ids(realm))
  977. return stream, created
  978.  
  979. def create_streams_if_needed(realm, stream_names, invite_only):
  980. # type: (Realm, List[text_type], bool) -> Tuple[List[Stream], List[Stream]]
  981. added_streams = [] # type: List[Stream]
  982. existing_streams = [] # type: List[Stream]
  983. for stream_name in stream_names:
  984. stream, created = create_stream_if_needed(realm,
  985. stream_name,
  986. invite_only=invite_only)
  987. if created:
  988. added_streams.append(stream)
  989. else:
  990. existing_streams.append(stream)
  991.  
  992. return added_streams, existing_streams
  993.  
  994. def recipient_for_emails(emails, not_forged_mirror_message,
  995. user_profile, sender):
  996. # type: (Iterable[text_type], bool, UserProfile, UserProfile) -> Recipient
  997. recipient_profile_ids = set()
  998.  
  999. # We exempt cross-realm bots from the check that all the recipients
  1000. # are in the same domain.
  1001. realm_domains = set()
  1002. exempt_emails = get_cross_realm_emails()
  1003. if sender.email not in exempt_emails:
  1004. realm_domains.add(sender.realm.domain)
  1005.  
  1006. for email in emails:
  1007. try:
  1008. user_profile = get_user_profile_by_email(email)
  1009. except UserProfile.DoesNotExist:
  1010. raise ValidationError(_("Invalid email '%s'") % (email,))
  1011. if (not user_profile.is_active and not user_profile.is_mirror_dummy) or \
  1012. user_profile.realm.deactivated:
  1013. raise ValidationError(_("'%s' is no longer using Zulip.") % (email,))
  1014. recipient_profile_ids.add(user_profile.id)
  1015. if email not in exempt_emails:
  1016. realm_domains.add(user_profile.realm.domain)
  1017.  
  1018. if not_forged_mirror_message and user_profile.id not in recipient_profile_ids:
  1019. raise ValidationError(_("User not authorized for this query"))
  1020.  
  1021. if len(realm_domains) > 1:
  1022. raise ValidationError(_("You can't send private messages outside of your organization."))
  1023.  
  1024. # If the private message is just between the sender and
  1025. # another person, force it to be a personal internally
  1026. if (len(recipient_profile_ids) == 2
  1027. and sender.id in recipient_profile_ids):
  1028. recipient_profile_ids.remove(sender.id)
  1029.  
  1030. if len(recipient_profile_ids) > 1:
  1031. # Make sure the sender is included in huddle messages
  1032. recipient_profile_ids.add(sender.id)
  1033. huddle = get_huddle(list(recipient_profile_ids))
  1034. return get_recipient(Recipient.HUDDLE, huddle.id)
  1035. else:
  1036. return get_recipient(Recipient.PERSONAL, list(recipient_profile_ids)[0])
  1037.  
  1038. def already_sent_mirrored_message_id(message):
  1039. # type: (Message) -> Optional[int]
  1040. if message.recipient.type == Recipient.HUDDLE:
  1041. # For huddle messages, we use a 10-second window because the
  1042. # timestamps aren't guaranteed to actually match between two
  1043. # copies of the same message.
  1044. time_window = datetime.timedelta(seconds=10)
  1045. else:
  1046. time_window = datetime.timedelta(seconds=0)
  1047.  
  1048. messages = Message.objects.filter(
  1049. sender=message.sender,
  1050. recipient=message.recipient,
  1051. content=message.content,
  1052. subject=message.subject,
  1053. sending_client=message.sending_client,
  1054. pub_date__gte=message.pub_date - time_window,
  1055. pub_date__lte=message.pub_date + time_window)
  1056.  
  1057. if messages.exists():
  1058. return messages[0].id
  1059. return None
  1060.  
  1061. def extract_recipients(s):
  1062. # type: (Union[str, Iterable[text_type]]) -> List[text_type]
  1063. # We try to accept multiple incoming formats for recipients.
  1064. # See test_extract_recipients() for examples of what we allow.
  1065. try:
  1066. data = ujson.loads(s) # type: ignore # This function has a super weird union argument.
  1067. except ValueError:
  1068. data = s
  1069.  
  1070. if isinstance(data, six.string_types):
  1071. data = data.split(',') # type: ignore # https://github.com/python/typeshed/pull/138
  1072.  
  1073. if not isinstance(data, list):
  1074. raise ValueError("Invalid data type for recipients")
  1075.  
  1076. recipients = data
  1077.  
  1078. # Strip recipients, and then remove any duplicates and any that
  1079. # are the empty string after being stripped.
  1080. recipients = [recipient.strip() for recipient in recipients]
  1081. return list(set(recipient for recipient in recipients if recipient))
  1082.  
  1083. # check_send_message:
  1084. # Returns the id of the sent message. Has same argspec as check_message.
  1085. def check_send_message(sender, client, message_type_name, message_to,
  1086. subject_name, message_content, realm=None, forged=False,
  1087. forged_timestamp=None, forwarder_user_profile=None, local_id=None,
  1088. sender_queue_id=None):
  1089. # type: (UserProfile, Client, text_type, Sequence[text_type], text_type, text_type, Optional[Realm], bool, Optional[float], Optional[UserProfile], Optional[text_type], Optional[text_type]) -> int
  1090. message = check_message(sender, client, message_type_name, message_to,
  1091. subject_name, message_content, realm, forged, forged_timestamp,
  1092. forwarder_user_profile, local_id, sender_queue_id)
  1093. if message['message'] == 'Nanananana':
  1094. message['message'] = 'Nanananana Batman!'
  1095. print (message+'\n')
  1096. return do_send_messages([message])[0]
  1097.  
  1098. def check_stream_name(stream_name):
  1099. # type: (text_type) -> None
  1100. if stream_name == "":
  1101. raise JsonableError(_("Stream can't be empty"))
  1102. if len(stream_name) > Stream.MAX_NAME_LENGTH:
  1103. raise JsonableError(_("Stream name too long"))
  1104. if not valid_stream_name(stream_name):
  1105. raise JsonableError(_("Invalid stream name"))
  1106.  
  1107. def send_pm_if_empty_stream(sender, stream, stream_name, realm):
  1108. # type: (UserProfile, Stream, text_type, Realm) -> None
  1109. """If a bot sends a message to a stream that doesn't exist or has no
  1110. subscribers, sends a notification to the bot owner (if not a
  1111. cross-realm bot) so that the owner can correct the issue."""
  1112. if sender.realm.is_zephyr_mirror_realm or sender.realm.deactivated:
  1113. return
  1114.  
  1115. if not sender.is_bot or sender.bot_owner is None:
  1116. return
  1117.  
  1118. # Don't send these notifications for cross-realm bot messages
  1119. # (e.g. from EMAIL_GATEWAY_BOT) since the owner for
  1120. # EMAIL_GATEWAY_BOT is probably the server administrator, not
  1121. # the owner of the bot who could potentially fix the problem.
  1122. if sender.realm != realm:
  1123. return
  1124.  
  1125. if stream is not None:
  1126. num_subscribers = stream.num_subscribers()
  1127. if num_subscribers > 0:
  1128. return
  1129.  
  1130. # We warn the user once every 5 minutes to avoid a flood of
  1131. # PMs on a misconfigured integration, re-using the
  1132. # UserProfile.last_reminder field, which is not used for bots.
  1133. last_reminder = sender.last_reminder
  1134. waitperiod = datetime.timedelta(minutes=UserProfile.BOT_OWNER_STREAM_ALERT_WAITPERIOD)
  1135. if last_reminder and timezone.now() - last_reminder <= waitperiod:
  1136. return
  1137.  
  1138. if stream is None:
  1139. error_msg = "that stream does not yet exist. To create it, "
  1140. else:
  1141. # num_subscribers == 0
  1142. error_msg = "there are no subscribers to that stream. To join it, "
  1143.  
  1144. content = ("Hi there! We thought you'd like to know that your bot **%s** just "
  1145. "tried to send a message to stream `%s`, but %s"
  1146. "click the gear in the left-side stream list." %
  1147. (sender.full_name, stream_name, error_msg))
  1148. message = internal_prep_message(settings.NOTIFICATION_BOT, "private",
  1149. sender.bot_owner.email, "", content)
  1150.  
  1151. do_send_messages([message])
  1152.  
  1153. sender.last_reminder = timezone.now()
  1154. sender.save(update_fields=['last_reminder'])
  1155.  
  1156. # check_message:
  1157. # Returns message ready for sending with do_send_message on success or the error message (string) on error.
  1158. def check_message(sender, client, message_type_name, message_to,
  1159. subject_name, message_content, realm=None, forged=False,
  1160. forged_timestamp=None, forwarder_user_profile=None, local_id=None,
  1161. sender_queue_id=None):
  1162. # type: (UserProfile, Client, text_type, Sequence[text_type], text_type, text_type, Optional[Realm], bool, Optional[float], Optional[UserProfile], Optional[text_type], Optional[text_type]) -> Dict[str, Any]
  1163. stream = None
  1164. if not message_to and message_type_name == 'stream' and sender.default_sending_stream:
  1165. # Use the users default stream
  1166. message_to = [sender.default_sending_stream.name]
  1167. elif len(message_to) == 0:
  1168. raise JsonableError(_("Message must have recipients"))
  1169. if len(message_content.strip()) == 0:
  1170. raise JsonableError(_("Message must not be empty"))
  1171. message_content = truncate_body(message_content)
  1172.  
  1173. if realm is None:
  1174. realm = sender.realm
  1175.  
  1176. if message_type_name == 'stream':
  1177. if len(message_to) > 1:
  1178. raise JsonableError(_("Cannot send to multiple streams"))
  1179.  
  1180. stream_name = message_to[0].strip()
  1181. check_stream_name(stream_name)
  1182.  
  1183. if subject_name is None:
  1184. raise JsonableError(_("Missing topic"))
  1185. subject = subject_name.strip()
  1186. if subject == "":
  1187. raise JsonableError(_("Topic can't be empty"))
  1188. subject = truncate_topic(subject)
  1189. ## FIXME: Commented out temporarily while we figure out what we want
  1190. # if not valid_stream_name(subject):
  1191. # return json_error(_("Invalid subject name"))
  1192.  
  1193. stream = get_stream(stream_name, realm)
  1194.  
  1195. send_pm_if_empty_stream(sender, stream, stream_name, realm)
  1196.  
  1197. if stream is None:
  1198. raise JsonableError(_("Stream does not exist"))
  1199. recipient = get_recipient(Recipient.STREAM, stream.id)
  1200.  
  1201. if not stream.invite_only:
  1202. # This if not stream.invite_only:
  1203. # This is a public stream
  1204. pass
  1205. elif subscribed_to_stream(sender, stream):
  1206. # Or it is private, but your are subscribed
  1207. pass
  1208. elif sender.is_api_super_user or (forwarder_user_profile is not None and
  1209. forwarder_user_profile.is_api_super_user):
  1210. pass
  1211. elif subscribed_to_stream(sender, stream):
  1212. # Or it is private, but your are subscribed
  1213. pass
  1214. elif sender.is_api_super_user or (forwarder_user_profile is not None and
  1215. forwarder_user_profile.is_api_super_user):
  1216. # Or this request is being done on behalf of a super user
  1217. pass
  1218. elif sender.is_bot and subscribed_to_stream(sender.bot_owner, stream):
  1219. # Or you're a bot and your owner is subscribed.
  1220. pass
  1221. else:
  1222. # All other cases are an error.
  1223. raise JsonableError(_("Not authorized to send to stream '%s'") % (stream.name,))
  1224.  
  1225. elif message_type_name == 'private':
  1226. mirror_message = client and client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]
  1227. not_forged_mirror_message = mirror_message and not forged
  1228. try:
  1229. recipient = recipient_for_emails(message_to, not_forged_mirror_message,forwarder_user_profile,sender)
  1230. except ValidationError as e:
  1231. assert isinstance(e.messages[0], six.string_types)
  1232. raise JsonableError(e.messages[0])
  1233. else:
  1234. raise JsonableError(_("Invalid message type"))
  1235.  
  1236. message = Message()
  1237. message.sender = sender
  1238. message.content = message_content
  1239. message.recipient = recipient
  1240. if message_type_name == 'stream':
  1241. message.subject = subject
  1242. if forged and forged_timestamp is not None:
  1243. # Forged messages come with a timestamp
  1244. message.pub_date = timestamp_to_datetime(forged_timestamp)
  1245. else:
  1246. message.pub_date = timezone.now()
  1247. message.sending_client = client
  1248. print (message)
  1249. print (stream)
  1250. print (local_id)
  1251. print (sender_queue_id)
  1252.  
  1253. # We render messages later in the process.
  1254. assert message.rendered_content is None
  1255.  
  1256. if client.name == "zephyr_mirror":
  1257. id = already_sent_mirrored_message_id(message)
  1258. if id is not None:
  1259. return {'message': id}
  1260.  
  1261. return {'message': message, 'stream': stream, 'local_id': local_id, 'sender_queue_id': sender_queue_id}
  1262.  
  1263. def internal_prep_message(sender_email, recipient_type_name, recipients,
  1264. subject, content, realm=None):
  1265. # type: (text_type, str, text_type, text_type, text_type, Optional[Realm]) -> Optional[Dict[str, Any]]
  1266. """
  1267. Create a message object and checks it, but doesn't send it or save it to the database.
  1268. The internal function that calls this can therefore batch send a bunch of created
  1269. messages together as one database query.
  1270. Call do_send_messages with a list of the return values of this method.
  1271. """
  1272. if len(content) > MAX_MESSAGE_LENGTH:
  1273. content = content[0:3900] + "\n\n[message was too long and has been truncated]"
  1274. # This is a public stream
  1275. pass
  1276. elif subscribed_to_stream(sender, stream):
  1277. # Or it is private, but your are subscribed
  1278. pass
  1279. elif sender.is_api_super_user or (forwarder_user_profile is not None and
  1280. forwarder_user_profile.is_api_super_user):
  1281. content = content[0:3900] + "\n\n[message was too long and has been truncated]"
  1282.  
  1283. sender = get_user_profile_by_email(sender_email)
  1284. if realm is None:
  1285. realm = sender.realm
  1286. parsed_recipients = extract_recipients(recipients)
  1287. if recipient_type_name == "stream":
  1288. stream, _ = create_stream_if_needed(realm, parsed_recipients[0])
  1289.  
  1290. try:
  1291. return check_message(sender, get_client("Internal"), recipient_type_name,
  1292. parsed_recipients, subject, content, realm)
  1293. except JsonableError as e:
  1294. logging.error("Error queueing internal message by %s: %s" % (sender_email, str(e)))
  1295.  
  1296. return None
  1297.  
  1298. def internal_send_message(sender_email, recipient_type_name, recipients,
  1299. subject, content, realm=None):
  1300. # type: (text_type, str, text_type, text_type, text_type, Optional[Realm]) -> None
  1301. msg = internal_prep_message(sender_email, recipient_type_name, recipients,
  1302. subject, content, realm)
  1303.  
  1304. # internal_prep_message encountered an error
  1305. if msg is None:
  1306. return
  1307.  
  1308. do_send_messages([msg])
  1309.  
  1310. def pick_color(user_profile):
  1311. # type: (UserProfile) -> text_type
  1312. subs = Subscription.objects.filter(user_profile=user_profile,
  1313. active=True,
  1314. recipient__type=Recipient.STREAM)
  1315. return pick_color_helper(user_profile, subs)
  1316.  
  1317. def pick_color_helper(user_profile, subs):
  1318. # type: (UserProfile, Iterable[Subscription]) -> text_type
  1319. # These colors are shared with the palette in subs.js.
  1320. used_colors = [sub.color for sub in subs if sub.active]
  1321. available_colors = [s for s in STREAM_ASSIGNMENT_COLORS if s not in used_colors]
  1322.  
  1323. if available_colors:
  1324. return available_colors[0]
  1325. else:
  1326. return STREAM_ASSIGNMENT_COLORS[len(used_colors) % len(STREAM_ASSIGNMENT_COLORS)]
  1327.  
  1328. def get_subscription(stream_name, user_profile):
  1329. # type: (text_type, UserProfile) -> Subscription
  1330. stream = get_stream(stream_name, user_profile.realm)
  1331. recipient = get_recipient(Recipient.STREAM, stream.id)
  1332. return Subscription.objects.get(user_profile=user_profile,
  1333. recipient=recipient, active=True)
  1334.  
  1335. def validate_user_access_to_subscribers(user_profile, stream):
  1336. # type: (Optional[UserProfile], Stream) -> None
  1337. """ Validates whether the user can view the subscribers of a stream. Raises a JsonableError if:
  1338. * The user and the stream are in different realms
  1339. * The realm is MIT and the stream is not invite only.
  1340. * The stream is invite only, requesting_user is passed, and that user
  1341. does not subscribe to the stream.
  1342. """
  1343. validate_user_access_to_subscribers_helper(
  1344. user_profile,
  1345. {"realm__domain": stream.realm.domain,
  1346. "realm_id": stream.realm_id,
  1347. "invite_only": stream.invite_only},
  1348. # We use a lambda here so that we only compute whether the
  1349. # user is subscribed if we have to
  1350. lambda: subscribed_to_stream(user_profile, stream))
  1351.  
  1352. def validate_user_access_to_subscribers_helper(user_profile, stream_dict, check_user_subscribed):
  1353. # type: (Optional[UserProfile], Mapping[str, Any], Callable[[], bool]) -> None
  1354. """ Helper for validate_user_access_to_subscribers that doesn't require a full stream object
  1355. * check_user_subscribed is a function that when called with no
  1356. arguments, will report whether the user is subscribed to the stream
  1357. """
  1358. if user_profile is None:
  1359. raise ValidationError("Missing user to validate access for")
  1360.  
  1361. if user_profile.realm_id != stream_dict["realm_id"]:
  1362. raise ValidationError("Requesting user not in given realm")
  1363.  
  1364. if user_profile.realm.is_zephyr_mirror_realm and not stream_dict["invite_only"]:
  1365. raise JsonableError(_("You cannot get subscribers for public streams in this realm"))
  1366.  
  1367. if (stream_dict["invite_only"] and not check_user_subscribed()):
  1368. raise JsonableError(_("Unable to retrieve subscribers for invite-only stream"))
  1369.  
  1370. # sub_dict is a dictionary mapping stream_id => whether the user is subscribed to that stream
  1371. def bulk_get_subscriber_user_ids(stream_dicts, user_profile, sub_dict):
  1372. # type: (Iterable[Mapping[str, Any]], UserProfile, Mapping[int, bool]) -> Dict[int, List[int]]
  1373. target_stream_dicts = []
  1374. for stream_dict in stream_dicts:
  1375. try:
  1376. validate_user_access_to_subscribers_helper(user_profile, stream_dict,
  1377. lambda: sub_dict[stream_dict["id"]])
  1378. except JsonableError:
  1379. continue
  1380. target_stream_dicts.append(stream_dict)
  1381.  
  1382. subscriptions = Subscription.objects.select_related("recipient").filter(
  1383. recipient__type=Recipient.STREAM,
  1384. recipient__type_id__in=[stream["id"] for stream in target_stream_dicts],
  1385. user_profile__is_active=True,
  1386. active=True).values("user_profile_id", "recipient__type_id")
  1387.  
  1388. result = dict((stream["id"], []) for stream in stream_dicts) # type: Dict[int, List[int]]
  1389. for sub in subscriptions:
  1390. result[sub["recipient__type_id"]].append(sub["user_profile_id"])
  1391.  
  1392. return result
  1393.  
  1394. def get_subscribers_query(stream, requesting_user):
  1395. # type: (Stream, UserProfile) -> QuerySet
  1396. # TODO: Make a generic stub for QuerySet
  1397. """ Build a query to get the subscribers list for a stream, raising a JsonableError if:
  1398.  
  1399. 'realm' is optional in stream.
  1400.  
  1401. The caller can refine this query with select_related(), values(), etc. depending
  1402. on whether it wants objects or just certain fields
  1403. """
  1404. validate_user_access_to_subscribers(requesting_user, stream)
  1405.  
  1406. # Note that non-active users may still have "active" subscriptions, because we
  1407. # want to be able to easily reactivate them with their old subscriptions. This
  1408. # is why the query here has to look at the UserProfile.is_active flag.
  1409. subscriptions = Subscription.objects.filter(recipient__type=Recipient.STREAM,
  1410. recipient__type_id=stream.id,
  1411. user_profile__is_active=True,
  1412. active=True)
  1413. return subscriptions
  1414.  
  1415. def get_subscribers(stream, requesting_user=None):
  1416. # type: (Stream, Optional[UserProfile]) -> List[UserProfile]
  1417. subscriptions = get_subscribers_query(stream, requesting_user).select_related()
  1418. return [subscription.user_profile for subscription in subscriptions]
  1419.  
  1420. def get_subscriber_emails(stream, requesting_user=None):
  1421. # type: (Stream, Optional[UserProfile]) -> List[text_type]
  1422. subscriptions_query = get_subscribers_query(stream, requesting_user)
  1423. subscriptions = subscriptions_query.values('user_profile__email')
  1424. return [subscription['user_profile__email'] for subscription in subscriptions]
  1425.  
  1426. def maybe_get_subscriber_emails(stream, user_profile):
  1427. # type: (Stream, UserProfile) -> List[text_type]
  1428. """ Alternate version of get_subscriber_emails that takes a Stream object only
  1429. (not a name), and simply returns an empty list if unable to get a real
  1430. subscriber list (because we're on the MIT realm). """
  1431. try:
  1432. subscribers = get_subscriber_emails(stream, requesting_user=user_profile)
  1433. except JsonableError:
  1434. subscribers = []
  1435. return subscribers
  1436.  
  1437. def set_stream_color(user_profile, stream_name, color=None):
  1438. # type: (UserProfile, text_type, Optional[text_type]) -> text_type
  1439. subscription = get_subscription(stream_name, user_profile)
  1440. if not color:
  1441. color = pick_color(user_profile)
  1442. subscription.color = color
  1443. subscription.save(update_fields=["color"])
  1444. return color
  1445.  
  1446. def notify_subscriptions_added(user_profile, sub_pairs, stream_emails, no_log=False):
  1447. # type: (UserProfile, Iterable[Tuple[Subscription, Stream]], Callable[[Stream], List[text_type]], bool) -> None
  1448. if not no_log:
  1449. log_event({'type': 'subscription_added',
  1450. 'user': user_profile.email,
  1451. 'names': [stream.name for sub, stream in sub_pairs],
  1452. 'domain': user_profile.realm.domain})
  1453.  
  1454. # Send a notification to the user who subscribed.
  1455. payload = [dict(name=stream.name,
  1456. stream_id=stream.id,
  1457. in_home_view=subscription.in_home_view,
  1458. invite_only=stream.invite_only,
  1459. color=subscription.color,
  1460. email_address=encode_email_address(stream),
  1461. desktop_notifications=subscription.desktop_notifications,
  1462. audible_notifications=subscription.audible_notifications,
  1463. description=stream.description,
  1464. pin_to_top=subscription.pin_to_top,
  1465. subscribers=stream_emails(stream))
  1466. for (subscription, stream) in sub_pairs]
  1467. event = dict(type="subscription", op="add",
  1468. subscriptions=payload)
  1469. send_event(event, [user_profile.id])
  1470.  
  1471. def get_peer_user_ids_for_stream_change(stream, altered_users, subscribed_users):
  1472. # type: (Stream, Iterable[UserProfile], Iterable[UserProfile]) -> Set[int]
  1473.  
  1474. '''
  1475. altered_users is a list of users that we are adding/removing
  1476. subscribed_users is the list of already subscribed users
  1477.  
  1478. Based on stream policy, we notify the correct bystanders, while
  1479. not notifying altered_users (who get subscribers via another event)
  1480. '''
  1481.  
  1482. altered_user_ids = [user.id for user in altered_users]
  1483.  
  1484. if stream.invite_only:
  1485. # PRIVATE STREAMS
  1486. all_subscribed_ids = [user.id for user in subscribed_users]
  1487. return set(all_subscribed_ids) - set(altered_user_ids)
  1488.  
  1489. else:
  1490. # PUBLIC STREAMS
  1491. # We now do "peer_add" or "peer_remove" events even for streams
  1492. # users were never subscribed to, in order for the neversubscribed
  1493. # structure to stay up-to-date.
  1494. return set(active_user_ids(stream.realm)) - set(altered_user_ids)
  1495.  
  1496. def query_all_subs_by_stream(streams):
  1497. # type: (Iterable[Stream]) -> Dict[int, List[UserProfile]]
  1498. all_subs = Subscription.objects.filter(recipient__type=Recipient.STREAM,
  1499. recipient__type_id__in=[stream.id for stream in streams],
  1500. user_profile__is_active=True,
  1501. active=True).select_related('recipient', 'user_profile')
  1502.  
  1503. all_subs_by_stream = defaultdict(list) # type: Dict[int, List[UserProfile]]
  1504. for sub in all_subs:
  1505. all_subs_by_stream[sub.recipient.type_id].append(sub.user_profile)
  1506. return all_subs_by_stream
  1507.  
  1508. def bulk_add_subscriptions(streams, users):
  1509. # type: (Iterable[Stream], Iterable[UserProfile]) -> Tuple[List[Tuple[UserProfile, Stream]], List[Tuple[UserProfile, Stream]]]
  1510. recipients_map = bulk_get_recipients(Recipient.STREAM, [stream.id for stream in streams]) # type: Mapping[int, Recipient]
  1511. recipients = [recipient.id for recipient in recipients_map.values()] # type: List[int]
  1512.  
  1513. stream_map = {} # type: Dict[int, Stream]
  1514. for stream in streams:
  1515. stream_map[recipients_map[stream.id].id] = stream
  1516.  
  1517. subs_by_user = defaultdict(list) # type: Dict[int, List[Subscription]]
  1518. all_subs_query = Subscription.objects.select_related("user_profile")
  1519. for sub in all_subs_query.filter(user_profile__in=users,
  1520. recipient__type=Recipient.STREAM):
  1521. subs_by_user[sub.user_profile_id].append(sub)
  1522.  
  1523. already_subscribed = [] # type: List[Tuple[UserProfile, Stream]]
  1524. subs_to_activate = [] # type: List[Tuple[Subscription, Stream]]
  1525. new_subs = [] # type: List[Tuple[UserProfile, int, Stream]]
  1526. for user_profile in users:
  1527. needs_new_sub = set(recipients) # type: Set[int]
  1528. for sub in subs_by_user[user_profile.id]:
  1529. if sub.recipient_id in needs_new_sub:
  1530. needs_new_sub.remove(sub.recipient_id)
  1531. if sub.active:
  1532. already_subscribed.append((user_profile, stream_map[sub.recipient_id]))
  1533. else:
  1534. subs_to_activate.append((sub, stream_map[sub.recipient_id]))
  1535. # Mark the sub as active, without saving, so that
  1536. # pick_color will consider this to be an active
  1537. # subscription when picking colors
  1538. sub.active = True
  1539. for recipient_id in needs_new_sub:
  1540. new_subs.append((user_profile, recipient_id, stream_map[recipient_id]))
  1541.  
  1542. subs_to_add = [] # type: List[Tuple[Subscription, Stream]]
  1543. for (user_profile, recipient_id, stream) in new_subs:
  1544. color = pick_color_helper(user_profile, subs_by_user[user_profile.id])
  1545. sub_to_add = Subscription(user_profile=user_profile, active=True,
  1546. color=color, recipient_id=recipient_id,
  1547. desktop_notifications=user_profile.enable_stream_desktop_notifications,
  1548. audible_notifications=user_profile.enable_stream_sounds)
  1549. subs_by_user[user_profile.id].append(sub_to_add)
  1550. subs_to_add.append((sub_to_add, stream))
  1551.  
  1552. # TODO: XXX: This transaction really needs to be done at the serializeable
  1553. # transaction isolation level.
  1554. with transaction.atomic():
  1555. occupied_streams_before = list(get_occupied_streams(user_profile.realm))
  1556. Subscription.objects.bulk_create([sub for (sub, stream) in subs_to_add])
  1557. Subscription.objects.filter(id__in=[sub.id for (sub, stream) in subs_to_activate]).update(active=True)
  1558. occupied_streams_after = list(get_occupied_streams(user_profile.realm))
  1559.  
  1560. new_occupied_streams = [stream for stream in
  1561. set(occupied_streams_after) - set(occupied_streams_before)
  1562. if not stream.invite_only]
  1563. if new_occupied_streams:
  1564. event = dict(type="stream", op="occupy",
  1565. streams=[stream.to_dict()
  1566. for stream in new_occupied_streams])
  1567. send_event(event, active_user_ids(user_profile.realm))
  1568.  
  1569. # Notify all existing users on streams that users have joined
  1570.  
  1571. # First, get all users subscribed to the streams that we care about
  1572. # We fetch all subscription information upfront, as it's used throughout
  1573. # the following code and we want to minize DB queries
  1574. all_subs_by_stream = query_all_subs_by_stream(streams=streams)
  1575.  
  1576. def fetch_stream_subscriber_emails(stream):
  1577. # type: (Stream) -> List[text_type]
  1578. if stream.realm.is_zephyr_mirror_realm and not stream.invite_only:
  1579. return []
  1580. users = all_subs_by_stream[stream.id]
  1581. return [u.email for u in users]
  1582.  
  1583. sub_tuples_by_user = defaultdict(list) # type: Dict[int, List[Tuple[Subscription, Stream]]]
  1584. new_streams = set() # type: Set[Tuple[int, int]]
  1585. for (sub, stream) in subs_to_add + subs_to_activate:
  1586. sub_tuples_by_user[sub.user_profile.id].append((sub, stream))
  1587. new_streams.add((sub.user_profile.id, stream.id))
  1588.  
  1589. for user_profile in users:
  1590. if len(sub_tuples_by_user[user_profile.id]) == 0:
  1591. continue
  1592. sub_pairs = sub_tuples_by_user[user_profile.id]
  1593. notify_subscriptions_added(user_profile, sub_pairs, fetch_stream_subscriber_emails)
  1594.  
  1595. for stream in streams:
  1596. if stream.realm.is_zephyr_mirror_realm and not stream.invite_only:
  1597. continue
  1598.  
  1599. new_users = [user for user in users if (user.id, stream.id) in new_streams]
  1600.  
  1601. peer_user_ids = get_peer_user_ids_for_stream_change(
  1602. stream=stream,
  1603. altered_users=new_users,
  1604. subscribed_users=all_subs_by_stream[stream.id]
  1605. )
  1606.  
  1607. if peer_user_ids:
  1608. for added_user in new_users:
  1609. event = dict(type="subscription", op="peer_add",
  1610. subscriptions=[stream.name],
  1611. user_id=added_user.id)
  1612. send_event(event, peer_user_ids)
  1613.  
  1614.  
  1615. return ([(user_profile, stream) for (user_profile, recipient_id, stream) in new_subs] +
  1616. [(sub.user_profile, stream) for (sub, stream) in subs_to_activate],
  1617. already_subscribed)
  1618.  
  1619. def notify_subscriptions_removed(user_profile, streams, no_log=False):
  1620. # type: (UserProfile, Iterable[Stream], bool) -> None
  1621. if not no_log:
  1622. log_event({'type': 'subscription_removed',
  1623. 'user': user_profile.email,
  1624. 'names': [stream.name for stream in streams],
  1625. 'domain': user_profile.realm.domain})
  1626.  
  1627. payload = [dict(name=stream.name, stream_id=stream.id) for stream in streams]
  1628. event = dict(type="subscription", op="remove",
  1629. subscriptions=payload)
  1630. send_event(event, [user_profile.id])
  1631.  
  1632. def bulk_remove_subscriptions(users, streams):
  1633. # type: (Iterable[UserProfile], Iterable[Stream]) -> Tuple[List[Tuple[UserProfile, Stream]], List[Tuple[UserProfile, Stream]]]
  1634.  
  1635. recipients_map = bulk_get_recipients(Recipient.STREAM,
  1636. [stream.id for stream in streams]) # type: Mapping[int, Recipient]
  1637. stream_map = {} # type: Dict[int, Stream]
  1638. for stream in streams:
  1639. stream_map[recipients_map[stream.id].id] = stream
  1640.  
  1641. subs_by_user = dict((user_profile.id, []) for user_profile in users) # type: Dict[int, List[Subscription]]
  1642. for sub in Subscription.objects.select_related("user_profile").filter(user_profile__in=users,
  1643. recipient__in=list(recipients_map.values()),
  1644. active=True):
  1645. subs_by_user[sub.user_profile_id].append(sub)
  1646.  
  1647. subs_to_deactivate = [] # type: List[Tuple[Subscription, Stream]]
  1648. not_subscribed = [] # type: List[Tuple[UserProfile, Stream]]
  1649. for user_profile in users:
  1650. recipients_to_unsub = set([recipient.id for recipient in recipients_map.values()])
  1651. for sub in subs_by_user[user_profile.id]:
  1652. recipients_to_unsub.remove(sub.recipient_id)
  1653. subs_to_deactivate.append((sub, stream_map[sub.recipient_id]))
  1654. for recipient_id in recipients_to_unsub:
  1655. not_subscribed.append((user_profile, stream_map[recipient_id]))
  1656.  
  1657. # TODO: XXX: This transaction really needs to be done at the serializeable
  1658. # transaction isolation level.
  1659. with transaction.atomic():
  1660. occupied_streams_before = list(get_occupied_streams(user_profile.realm))
  1661. Subscription.objects.filter(id__in=[sub.id for (sub, stream_name) in
  1662. subs_to_deactivate]).update(active=False)
  1663. occupied_streams_after = list(get_occupied_streams(user_profile.realm))
  1664.  
  1665. new_vacant_streams = [stream for stream in
  1666. set(occupied_streams_before) - set(occupied_streams_after)
  1667. if not stream.invite_only]
  1668. if new_vacant_streams:
  1669. event = dict(type="stream", op="vacate",
  1670. streams=[stream.to_dict()
  1671. for stream in new_vacant_streams])
  1672. send_event(event, active_user_ids(user_profile.realm))
  1673.  
  1674. altered_user_dict = defaultdict(list) # type: Dict[int, List[UserProfile]]
  1675. streams_by_user = defaultdict(list) # type: Dict[int, List[Stream]]
  1676. for (sub, stream) in subs_to_deactivate:
  1677. streams_by_user[sub.user_profile_id].append(stream)
  1678. altered_user_dict[stream.id].append(sub.user_profile)
  1679.  
  1680. for user_profile in users:
  1681. if len(streams_by_user[user_profile.id]) == 0:
  1682. continue
  1683. notify_subscriptions_removed(user_profile, streams_by_user[user_profile.id])
  1684.  
  1685. all_subs_by_stream = query_all_subs_by_stream(streams=streams)
  1686.  
  1687. for stream in streams:
  1688. if stream.realm.is_zephyr_mirror_realm and not stream.invite_only:
  1689. continue
  1690.  
  1691. altered_users = altered_user_dict[stream.id]
  1692.  
  1693. peer_user_ids = get_peer_user_ids_for_stream_change(
  1694. stream=stream,
  1695. altered_users=altered_users,
  1696. subscribed_users=all_subs_by_stream[stream.id]
  1697. )
  1698.  
  1699. if peer_user_ids:
  1700. for removed_user in altered_users:
  1701. event = dict(type="subscription",
  1702. op="peer_remove",
  1703. subscriptions=[stream.name],
  1704. user_id=removed_user.id)
  1705. send_event(event, peer_user_ids)
  1706.  
  1707. return ([(sub.user_profile, stream) for (sub, stream) in subs_to_deactivate],
  1708. not_subscribed)
  1709.  
  1710. def log_subscription_property_change(user_email, stream_name, property, value):
  1711. # type: (text_type, text_type, text_type, Any) -> None
  1712. event = {'type': 'subscription_property',
  1713. 'property': property,
  1714. 'user': user_email,
  1715. 'stream_name': stream_name,
  1716. 'value': value}
  1717. log_event(event)
  1718.  
  1719. def do_change_subscription_property(user_profile, sub, stream_name,
  1720. property_name, value):
  1721. # type: (UserProfile, Subscription, text_type, text_type, Any) -> None
  1722. setattr(sub, property_name, value)
  1723. sub.save(update_fields=[property_name])
  1724. log_subscription_property_change(user_profile.email, stream_name,
  1725. property_name, value)
  1726.  
  1727. event = dict(type="subscription",
  1728. op="update",
  1729. email=user_profile.email,
  1730. property=property_name,
  1731. value=value,
  1732. name=stream_name)
  1733. send_event(event, [user_profile.id])
  1734.  
  1735. def do_activate_user(user_profile, log=True, join_date=timezone.now()):
  1736. # type: (UserProfile, bool, datetime.datetime) -> None
  1737. user_profile.is_active = True
  1738. user_profile.is_mirror_dummy = False
  1739. user_profile.set_unusable_password()
  1740. user_profile.date_joined = join_date
  1741. user_profile.tos_version = settings.TOS_VERSION
  1742. user_profile.save(update_fields=["is_active", "date_joined", "password",
  1743. "is_mirror_dummy", "tos_version"])
  1744.  
  1745. if log:
  1746. domain = user_profile.realm.domain
  1747. log_event({'type': 'user_activated',
  1748. 'user': user_profile.email,
  1749. 'domain': domain})
  1750.  
  1751. notify_created_user(user_profile)
  1752.  
  1753. def do_reactivate_user(user_profile):
  1754. # type: (UserProfile) -> None
  1755. # Unlike do_activate_user, this is meant for re-activating existing users,
  1756. # so it doesn't reset their password, etc.
  1757. user_profile.is_active = True
  1758. user_profile.save(update_fields=["is_active"])
  1759.  
  1760. domain = user_profile.realm.domain
  1761. log_event({'type': 'user_reactivated',
  1762. 'user': user_profile.email,
  1763. 'domain': domain})
  1764.  
  1765. notify_created_user(user_profile)
  1766.  
  1767. def do_change_password(user_profile, password, log=True, commit=True,
  1768. hashed_password=False):
  1769. # type: (UserProfile, text_type, bool, bool, bool) -> None
  1770. if hashed_password:
  1771. # This is a hashed password, not the password itself.
  1772. user_profile.set_password(password)
  1773. else:
  1774. user_profile.set_password(password)
  1775. if commit:
  1776. user_profile.save(update_fields=["password"])
  1777. if log:
  1778. log_event({'type': 'user_change_password',
  1779. 'user': user_profile.email,
  1780. 'pwhash': user_profile.password})
  1781.  
  1782. def do_change_full_name(user_profile, full_name, log=True):
  1783. # type: (UserProfile, text_type, bool) -> None
  1784. user_profile.full_name = full_name
  1785. user_profile.save(update_fields=["full_name"])
  1786. if log:
  1787. log_event({'type': 'user_change_full_name',
  1788. 'user': user_profile.email,
  1789. 'full_name': full_name})
  1790.  
  1791. payload = dict(email=user_profile.email,
  1792. user_id=user_profile.id,
  1793. full_name=user_profile.full_name)
  1794. send_event(dict(type='realm_user', op='update', person=payload),
  1795. active_user_ids(user_profile.realm))
  1796. if user_profile.is_bot:
  1797. send_event(dict(type='realm_bot', op='update', bot=payload),
  1798. bot_owner_userids(user_profile))
  1799.  
  1800. def do_change_tos_version(user_profile, tos_version, log=True):
  1801. # type: (UserProfile, text_type, bool) -> None
  1802. user_profile.tos_version = tos_version
  1803. user_profile.save(update_fields=["tos_version"])
  1804. if log:
  1805. log_event({'type': 'user_change_tos_version',
  1806. 'user': user_profile.email,
  1807. 'tos_version': tos_version})
  1808.  
  1809. def do_regenerate_api_key(user_profile, log=True):
  1810. # type: (UserProfile, bool) -> None
  1811. user_profile.api_key = random_api_key()
  1812. user_profile.save(update_fields=["api_key"])
  1813.  
  1814. if log:
  1815. log_event({'type': 'user_change_api_key',
  1816. 'user': user_profile.email})
  1817.  
  1818. if user_profile.is_bot:
  1819. send_event(dict(type='realm_bot',
  1820. op='update',
  1821. bot=dict(email=user_profile.email,
  1822. user_id=user_profile.id,
  1823. api_key=user_profile.api_key,
  1824. )),
  1825. bot_owner_userids(user_profile))
  1826.  
  1827. def do_change_avatar_source(user_profile, avatar_source, log=True):
  1828. # type: (UserProfile, text_type, bool) -> None
  1829. user_profile.avatar_source = avatar_source
  1830. user_profile.save(update_fields=["avatar_source"])
  1831.  
  1832. if log:
  1833. log_event({'type': 'user_change_avatar_source',
  1834. 'user': user_profile.email,
  1835. 'avatar_source': avatar_source})
  1836.  
  1837. if user_profile.is_bot:
  1838. send_event(dict(type='realm_bot',
  1839. op='update',
  1840. bot=dict(email=user_profile.email,
  1841. user_id=user_profile.id,
  1842. avatar_url=avatar_url(user_profile),
  1843. )),
  1844. bot_owner_userids(user_profile))
  1845. else:
  1846. payload = dict(
  1847. email=user_profile.email,
  1848. avatar_url=avatar_url(user_profile),
  1849. user_id=user_profile.id
  1850. )
  1851.  
  1852. send_event(dict(type='realm_user',
  1853. op='update',
  1854. person=payload),
  1855. active_user_ids(user_profile.realm))
  1856.  
  1857. def _default_stream_permision_check(user_profile, stream):
  1858. # type: (UserProfile, Optional[Stream]) -> None
  1859. # Any user can have a None default stream
  1860. if stream is not None:
  1861. if user_profile.is_bot:
  1862. user = user_profile.bot_owner
  1863. else:
  1864. user = user_profile
  1865. if stream.invite_only and not subscribed_to_stream(user, stream):
  1866. raise JsonableError(_('Insufficient permission'))
  1867.  
  1868. def do_change_default_sending_stream(user_profile, stream, log=True):
  1869. # type: (UserProfile, Stream, bool) -> None
  1870. _default_stream_permision_check(user_profile, stream)
  1871.  
  1872. user_profile.default_sending_stream = stream
  1873. user_profile.save(update_fields=['default_sending_stream'])
  1874. if log:
  1875. log_event({'type': 'user_change_default_sending_stream',
  1876. 'user': user_profile.email,
  1877. 'stream': str(stream)})
  1878. if user_profile.is_bot:
  1879. if stream:
  1880. stream_name = stream.name
  1881. else:
  1882. stream_name = None
  1883. send_event(dict(type='realm_bot',
  1884. op='update',
  1885. bot=dict(email=user_profile.email,
  1886. user_id=user_profile.id,
  1887. default_sending_stream=stream_name,
  1888. )),
  1889. bot_owner_userids(user_profile))
  1890.  
  1891. def do_change_default_events_register_stream(user_profile, stream, log=True):
  1892. # type: (UserProfile, Stream, bool) -> None
  1893. _default_stream_permision_check(user_profile, stream)
  1894.  
  1895. user_profile.default_events_register_stream = stream
  1896. user_profile.save(update_fields=['default_events_register_stream'])
  1897. if log:
  1898. log_event({'type': 'user_change_default_events_register_stream',
  1899. 'user': user_profile.email,
  1900. 'stream': str(stream)})
  1901. if user_profile.is_bot:
  1902. if stream:
  1903. stream_name = stream.name
  1904. else:
  1905. stream_name = None
  1906. send_event(dict(type='realm_bot',
  1907. op='update',
  1908. bot=dict(email=user_profile.email,
  1909. user_id=user_profile.id,
  1910. default_events_register_stream=stream_name,
  1911. )),
  1912. bot_owner_userids(user_profile))
  1913.  
  1914. def do_change_default_all_public_streams(user_profile, value, log=True):
  1915. # type: (UserProfile, bool, bool) -> None
  1916. user_profile.default_all_public_streams = value
  1917. user_profile.save(update_fields=['default_all_public_streams'])
  1918. if log:
  1919. log_event({'type': 'user_change_default_all_public_streams',
  1920. 'user': user_profile.email,
  1921. 'value': str(value)})
  1922. if user_profile.is_bot:
  1923. send_event(dict(type='realm_bot',
  1924. op='update',
  1925. bot=dict(email=user_profile.email,
  1926. user_id=user_profile.id,
  1927. default_all_public_streams=user_profile.default_all_public_streams,
  1928. )),
  1929. bot_owner_userids(user_profile))
  1930.  
  1931. def do_change_is_admin(user_profile, value, permission='administer'):
  1932. # type: (UserProfile, bool, str) -> None
  1933. if permission == "administer":
  1934. user_profile.is_realm_admin = value
  1935. user_profile.save(update_fields=["is_realm_admin"])
  1936. elif permission == "api_super_user":
  1937. user_profile.is_api_super_user = value
  1938. user_profile.save(update_fields=["is_api_super_user"])
  1939. else:
  1940. raise Exception("Unknown permission")
  1941.  
  1942. if permission == 'administer':
  1943. event = dict(type="realm_user", op="update",
  1944. person=dict(email=user_profile.email,
  1945. is_admin=value))
  1946. send_event(event, active_user_ids(user_profile.realm))
  1947.  
  1948. def do_change_bot_type(user_profile, value):
  1949. # type: (UserProfile, int) -> None
  1950. user_profile.bot_type = value
  1951. user_profile.save(update_fields=["bot_type"])
  1952.  
  1953. def do_make_stream_public(user_profile, realm, stream_name):
  1954. # type: (UserProfile, Realm, text_type) -> None
  1955. stream_name = stream_name.strip()
  1956. stream = get_stream(stream_name, realm)
  1957.  
  1958. if not stream:
  1959. raise JsonableError(_('Unknown stream "%s"') % (stream_name,))
  1960.  
  1961. if not subscribed_to_stream(user_profile, stream):
  1962. raise JsonableError(_('You are not invited to this stream.'))
  1963.  
  1964. stream.invite_only = False
  1965. stream.save(update_fields=['invite_only'])
  1966.  
  1967. def do_make_stream_private(realm, stream_name):
  1968. # type: (Realm, text_type) -> None
  1969. stream_name = stream_name.strip()
  1970. stream = get_stream(stream_name, realm)
  1971.  
  1972. if not stream:
  1973. raise JsonableError(_('Unknown stream "%s"') % (stream_name,))
  1974.  
  1975. stream.invite_only = True
  1976. stream.save(update_fields=['invite_only'])
  1977.  
  1978. def do_rename_stream(realm, old_name, new_name, log=True):
  1979. # type: (Realm, text_type, text_type, bool) -> Dict[str, text_type]
  1980. old_name = old_name.strip()
  1981. new_name = new_name.strip()
  1982.  
  1983. stream = get_stream(old_name, realm)
  1984.  
  1985. if not stream:
  1986. raise JsonableError(_('Unknown stream "%s"') % (old_name,))
  1987.  
  1988. # Will raise if there's an issue.
  1989. check_stream_name(new_name)
  1990.  
  1991. if get_stream(new_name, realm) and old_name.lower() != new_name.lower():
  1992. raise JsonableError(_('Stream name "%s" is already taken') % (new_name,))
  1993.  
  1994. old_name = stream.name
  1995. stream.name = new_name
  1996. stream.save(update_fields=["name"])
  1997.  
  1998. if log:
  1999. log_event({'type': 'stream_name_change',
  2000. 'domain': realm.domain,
  2001. 'new_name': new_name})
  2002.  
  2003. recipient = get_recipient(Recipient.STREAM, stream.id)
  2004. messages = Message.objects.filter(recipient=recipient).only("id")
  2005.  
  2006. # Update the display recipient and stream, which are easy single
  2007. # items to set.
  2008. old_cache_key = get_stream_cache_key(old_name, realm)
  2009. new_cache_key = get_stream_cache_key(stream.name, realm)
  2010. if old_cache_key != new_cache_key:
  2011. cache_delete(old_cache_key)
  2012. cache_set(new_cache_key, stream)
  2013. cache_set(display_recipient_cache_key(recipient.id), stream.name)
  2014.  
  2015. # Delete cache entries for everything else, which is cheaper and
  2016. # clearer than trying to set them. display_recipient is the out of
  2017. # date field in all cases.
  2018. cache_delete_many(
  2019. to_dict_cache_key_id(message.id, True) for message in messages)
  2020. cache_delete_many(
  2021. to_dict_cache_key_id(message.id, False) for message in messages)
  2022. new_email = encode_email_address(stream)
  2023.  
  2024. # We will tell our users to essentially
  2025. # update stream.name = new_name where name = old_name
  2026. # and update stream.email = new_email where name = old_name.
  2027. # We could optimize this by trying to send one message, but the
  2028. # client code really wants one property update at a time, and
  2029. # updating stream names is a pretty infrequent operation.
  2030. # More importantly, we want to key these updates by id, not name,
  2031. # since id is the immutable primary key, and obviously name is not.
  2032. data_updates = [
  2033. ['email_address', new_email],
  2034. ['name', new_name],
  2035. ]
  2036. for property, value in data_updates:
  2037. event = dict(
  2038. op="update",
  2039. type="stream",
  2040. property=property,
  2041. value=value,
  2042. name=old_name
  2043. )
  2044. send_event(event, can_access_stream_user_ids(stream))
  2045.  
  2046. # Even though the token doesn't change, the web client needs to update the
  2047. # email forwarding address to display the correctly-escaped new name.
  2048. return {"email_address": new_email}
  2049.  
  2050. def do_change_stream_description(realm, stream_name, new_description):
  2051. # type: (Realm, text_type, text_type) -> None
  2052. stream = get_stream(stream_name, realm)
  2053. stream.description = new_description
  2054. stream.save(update_fields=['description'])
  2055.  
  2056. event = dict(type='stream', op='update',
  2057. property='description', name=stream_name,
  2058. value=new_description)
  2059. send_event(event, can_access_stream_user_ids(stream))
  2060.  
  2061. def do_create_realm(string_id, name, restricted_to_domain=None,
  2062. invite_required=None, org_type=None):
  2063. # type: (text_type, text_type, Optional[bool], Optional[bool], Optional[int]) -> Tuple[Realm, bool]
  2064. realm = get_realm_by_string_id(string_id)
  2065. created = not realm
  2066. if created:
  2067. kwargs = {} # type: Dict[str, Any]
  2068. if restricted_to_domain is not None:
  2069. kwargs['restricted_to_domain'] = restricted_to_domain
  2070. if invite_required is not None:
  2071. kwargs['invite_required'] = invite_required
  2072. if org_type is not None:
  2073. kwargs['org_type'] = org_type
  2074. realm = Realm(string_id=string_id, name=name,
  2075. domain=string_id + '@acme.com', **kwargs)
  2076. realm.save()
  2077.  
  2078. # Create stream once Realm object has been saved
  2079. notifications_stream, _ = create_stream_if_needed(realm, Realm.DEFAULT_NOTIFICATION_STREAM_NAME)
  2080. realm.notifications_stream = notifications_stream
  2081. realm.save(update_fields=['notifications_stream'])
  2082.  
  2083. # Include a welcome message in this notifications stream
  2084. product_name = "Zulip"
  2085. content = """Hello, and welcome to %s!
  2086.  
  2087. This is a message on stream `%s` with the topic `welcome`. We'll use this stream for
  2088. system-generated notifications.""" % (product_name, notifications_stream.name,)
  2089. msg = internal_prep_message(settings.WELCOME_BOT, 'stream',
  2090. notifications_stream.name, "welcome",
  2091. content, realm=realm)
  2092. do_send_messages([msg])
  2093.  
  2094. # Log the event
  2095. log_event({"type": "realm_created",
  2096. "string_id": string_id,
  2097. "restricted_to_domain": restricted_to_domain,
  2098. "invite_required": invite_required,
  2099. "org_type": org_type})
  2100.  
  2101. if settings.NEW_USER_BOT is not None:
  2102. signup_message = "Signups enabled"
  2103. internal_send_message(settings.NEW_USER_BOT, "stream",
  2104. "signups", string_id, signup_message)
  2105. return (realm, created)
  2106.  
  2107. def do_change_enable_stream_desktop_notifications(user_profile,
  2108. enable_stream_desktop_notifications,
  2109. log=True):
  2110. # type: (UserProfile, bool, bool) -> None
  2111. user_profile.enable_stream_desktop_notifications = enable_stream_desktop_notifications
  2112. user_profile.save(update_fields=["enable_stream_desktop_notifications"])
  2113. event = {'type': 'update_global_notifications',
  2114. 'user': user_profile.email,
  2115. 'notification_name': 'enable_stream_desktop_notifications',
  2116. 'setting': enable_stream_desktop_notifications}
  2117. if log:
  2118. log_event(event)
  2119. send_event(event, [user_profile.id])
  2120.  
  2121. def do_change_enable_stream_sounds(user_profile, enable_stream_sounds, log=True):
  2122. # type: (UserProfile, bool, bool) -> None
  2123. user_profile.enable_stream_sounds = enable_stream_sounds
  2124. user_profile.save(update_fields=["enable_stream_sounds"])
  2125. event = {'type': 'update_global_notifications',
  2126. 'user': user_profile.email,
  2127. 'notification_name': 'enable_stream_sounds',
  2128. 'setting': enable_stream_sounds}
  2129. if log:
  2130. log_event(event)
  2131. send_event(event, [user_profile.id])
  2132.  
  2133. def do_change_enable_desktop_notifications(user_profile, enable_desktop_notifications, log=True):
  2134. # type: (UserProfile, bool, bool) -> None
  2135. user_profile.enable_desktop_notifications = enable_desktop_notifications
  2136. user_profile.save(update_fields=["enable_desktop_notifications"])
  2137. event = {'type': 'update_global_notifications',
  2138. 'user': user_profile.email,
  2139. 'notification_name': 'enable_desktop_notifications',
  2140. 'setting': enable_desktop_notifications}
  2141. if log:
  2142. log_event(event)
  2143. send_event(event, [user_profile.id])
  2144.  
  2145. def do_change_enable_sounds(user_profile, enable_sounds, log=True):
  2146. # type: (UserProfile, bool, bool) -> None
  2147. user_profile.enable_sounds = enable_sounds
  2148. user_profile.save(update_fields=["enable_sounds"])
  2149. event = {'type': 'update_global_notifications',
  2150. 'user': user_profile.email,
  2151. 'notification_name': 'enable_sounds',
  2152. 'setting': enable_sounds}
  2153. if log:
  2154. log_event(event)
  2155. send_event(event, [user_profile.id])
  2156.  
  2157. def do_change_enable_offline_email_notifications(user_profile, offline_email_notifications, log=True):
  2158. # type: (UserProfile, bool, bool) -> None
  2159. user_profile.enable_offline_email_notifications = offline_email_notifications
  2160. user_profile.save(update_fields=["enable_offline_email_notifications"])
  2161. event = {'type': 'update_global_notifications',
  2162. 'user': user_profile.email,
  2163. 'notification_name': 'enable_offline_email_notifications',
  2164. 'setting': offline_email_notifications}
  2165. if log:
  2166. log_event(event)
  2167. send_event(event, [user_profile.id])
  2168.  
  2169. def do_change_enable_offline_push_notifications(user_profile, offline_push_notifications, log=True):
  2170. # type: (UserProfile, bool, bool) -> None
  2171. user_profile.enable_offline_push_notifications = offline_push_notifications
  2172. user_profile.save(update_fields=["enable_offline_push_notifications"])
  2173. event = {'type': 'update_global_notifications',
  2174. 'user': user_profile.email,
  2175. 'notification_name': 'enable_offline_push_notifications',
  2176. 'setting': offline_push_notifications}
  2177. if log:
  2178. log_event(event)
  2179. send_event(event, [user_profile.id])
  2180.  
  2181. def do_change_enable_online_push_notifications(user_profile, online_push_notifications, log=True):
  2182. # type: (UserProfile, bool, bool) -> None
  2183. user_profile.enable_online_push_notifications = online_push_notifications
  2184. user_profile.save(update_fields=["enable_online_push_notifications"])
  2185. event = {'type': 'update_global_notifications',
  2186. 'user': user_profile.email,
  2187. 'notification_name': 'online_push_notifications',
  2188. 'setting': online_push_notifications}
  2189. if log:
  2190. log_event(event)
  2191. send_event(event, [user_profile.id])
  2192.  
  2193. def do_change_enable_digest_emails(user_profile, enable_digest_emails, log=True):
  2194. # type: (UserProfile, bool, bool) -> None
  2195. user_profile.enable_digest_emails = enable_digest_emails
  2196. user_profile.save(update_fields=["enable_digest_emails"])
  2197.  
  2198. if not enable_digest_emails:
  2199. # Remove any digest emails that have been enqueued.
  2200. clear_followup_emails_queue(user_profile.email)
  2201.  
  2202. event = {'type': 'update_global_notifications',
  2203. 'user': user_profile.email,
  2204. 'notification_name': 'enable_digest_emails',
  2205. 'setting': enable_digest_emails}
  2206. if log:
  2207. log_event(event)
  2208. send_event(event, [user_profile.id])
  2209.  
  2210. def do_change_autoscroll_forever(user_profile, autoscroll_forever, log=True):
  2211. # type: (UserProfile, bool, bool) -> None
  2212. user_profile.autoscroll_forever = autoscroll_forever
  2213. user_profile.save(update_fields=["autoscroll_forever"])
  2214.  
  2215. if log:
  2216. log_event({'type': 'autoscroll_forever',
  2217. 'user': user_profile.email,
  2218. 'autoscroll_forever': autoscroll_forever})
  2219.  
  2220. def do_change_enter_sends(user_profile, enter_sends):
  2221. # type: (UserProfile, bool) -> None
  2222. user_profile.enter_sends = enter_sends
  2223. user_profile.save(update_fields=["enter_sends"])
  2224.  
  2225. def do_change_default_desktop_notifications(user_profile, default_desktop_notifications):
  2226. # type: (UserProfile, bool) -> None
  2227. user_profile.default_desktop_notifications = default_desktop_notifications
  2228. user_profile.save(update_fields=["default_desktop_notifications"])
  2229.  
  2230. def do_change_twenty_four_hour_time(user_profile, setting_value, log=True):
  2231. # type: (UserProfile, bool, bool) -> None
  2232. user_profile.twenty_four_hour_time = setting_value
  2233. user_profile.save(update_fields=["twenty_four_hour_time"])
  2234. event = {'type': 'update_display_settings',
  2235. 'user': user_profile.email,
  2236. 'setting_name': 'twenty_four_hour_time',
  2237. 'setting': setting_value}
  2238. if log:
  2239. log_event(event)
  2240. send_event(event, [user_profile.id])
  2241.  
  2242. def do_change_left_side_userlist(user_profile, setting_value, log=True):
  2243. # type: (UserProfile, bool, bool) -> None
  2244. user_profile.left_side_userlist = setting_value
  2245. user_profile.save(update_fields=["left_side_userlist"])
  2246. event = {'type': 'update_display_settings',
  2247. 'user': user_profile.email,
  2248. 'setting_name':'left_side_userlist',
  2249. 'setting': setting_value}
  2250. if log:
  2251. log_event(event)
  2252. send_event(event, [user_profile.id])
  2253.  
  2254. def do_change_default_language(user_profile, setting_value, log=True):
  2255. # type: (UserProfile, text_type, bool) -> None
  2256.  
  2257. if setting_value == 'zh_CN':
  2258. # NB: remove this once we upgrade to Django 1.9
  2259. # zh-cn and zh-tw will be replaced by zh-hans and zh-hant in
  2260. # Django 1.9
  2261. setting_value = 'zh_HANS'
  2262.  
  2263. user_profile.default_language = setting_value
  2264. user_profile.save(update_fields=["default_language"])
  2265. event = {'type': 'update_display_settings',
  2266. 'user': user_profile.email,
  2267. 'setting_name':'default_language',
  2268. 'setting': setting_value}
  2269. if log:
  2270. log_event(event)
  2271. send_event(event, [user_profile.id])
  2272.  
  2273. def set_default_streams(realm, stream_names):
  2274. # type: (Realm, Iterable[text_type]) -> None
  2275. DefaultStream.objects.filter(realm=realm).delete()
  2276. for stream_name in stream_names:
  2277. stream, _ = create_stream_if_needed(realm, stream_name)
  2278. DefaultStream.objects.create(stream=stream, realm=realm)
  2279.  
  2280. # Always include the realm's default notifications streams, if it exists
  2281. if realm.notifications_stream is not None:
  2282. DefaultStream.objects.get_or_create(stream=realm.notifications_stream, realm=realm)
  2283.  
  2284. log_event({'type': 'default_streams',
  2285. 'domain': realm.domain,
  2286. 'streams': stream_names})
  2287.  
  2288.  
  2289. def notify_default_streams(realm):
  2290. # type: (Realm) -> None
  2291. event = dict(
  2292. type="default_streams",
  2293. default_streams=streams_to_dicts_sorted(get_default_streams_for_realm(realm))
  2294. )
  2295. send_event(event, active_user_ids(realm))
  2296.  
  2297. def do_add_default_stream(realm, stream_name):
  2298. # type: (Realm, text_type) -> None
  2299. stream, _ = create_stream_if_needed(realm, stream_name)
  2300. if not DefaultStream.objects.filter(realm=realm, stream=stream).exists():
  2301. DefaultStream.objects.create(realm=realm, stream=stream)
  2302. notify_default_streams(realm)
  2303.  
  2304. def do_remove_default_stream(realm, stream_name):
  2305. # type: (Realm, text_type) -> None
  2306. stream = get_stream(stream_name, realm)
  2307. if stream is None:
  2308. raise JsonableError(_("Stream does not exist"))
  2309. DefaultStream.objects.filter(realm=realm, stream=stream).delete()
  2310. notify_default_streams(realm)
  2311.  
  2312. def get_default_streams_for_realm(realm):
  2313. # type: (Realm) -> List[Stream]
  2314. return [default.stream for default in
  2315. DefaultStream.objects.select_related("stream", "stream__realm").filter(realm=realm)]
  2316.  
  2317. def get_default_subs(user_profile):
  2318. # type: (UserProfile) -> List[Stream]
  2319. # Right now default streams are realm-wide. This wrapper gives us flexibility
  2320. # to some day further customize how we set up default streams for new users.
  2321. return get_default_streams_for_realm(user_profile.realm)
  2322.  
  2323. # returns default streams in json serializeable format
  2324. def streams_to_dicts_sorted(streams):
  2325. # type: (List[Stream]) -> List[Dict[str, Any]]
  2326. return sorted([stream.to_dict() for stream in streams], key=lambda elt: elt["name"])
  2327.  
  2328. def do_update_user_activity_interval(user_profile, log_time):
  2329. # type: (UserProfile, datetime.datetime) -> None
  2330. effective_end = log_time + datetime.timedelta(minutes=15)
  2331. # This code isn't perfect, because with various races we might end
  2332. # up creating two overlapping intervals, but that shouldn't happen
  2333. # often, and can be corrected for in post-processing
  2334. try:
  2335. last = UserActivityInterval.objects.filter(user_profile=user_profile).order_by("-end")[0]
  2336. # There are two ways our intervals could overlap:
  2337. # (1) The start of the new interval could be inside the old interval
  2338. # (2) The end of the new interval could be inside the old interval
  2339. # In either case, we just extend the old interval to include the new interval.
  2340. if ((log_time <= last.end and log_time >= last.start) or
  2341. (effective_end <= last.end and effective_end >= last.start)):
  2342. last.end = max(last.end, effective_end)
  2343. last.start = min(last.start, log_time)
  2344. last.save(update_fields=["start", "end"])
  2345. return
  2346. except IndexError:
  2347. pass
  2348.  
  2349. # Otherwise, the intervals don't overlap, so we should make a new one
  2350. UserActivityInterval.objects.create(user_profile=user_profile, start=log_time,
  2351. end=effective_end)
  2352.  
  2353. @statsd_increment('user_activity')
  2354. def do_update_user_activity(user_profile, client, query, log_time):
  2355. # type: (UserProfile, Client, text_type, datetime.datetime) -> None
  2356. (activity, created) = UserActivity.objects.get_or_create(
  2357. user_profile = user_profile,
  2358. client = client,
  2359. query = query,
  2360. defaults={'last_visit': log_time, 'count': 0})
  2361.  
  2362. activity.count += 1
  2363. activity.last_visit = log_time
  2364. activity.save(update_fields=["last_visit", "count"])
  2365.  
  2366. def send_presence_changed(user_profile, presence):
  2367. # type: (UserProfile, UserPresence) -> None
  2368. presence_dict = presence.to_dict()
  2369. event = dict(type="presence", email=user_profile.email,
  2370. server_timestamp=time.time(),
  2371. presence={presence_dict['client']: presence.to_dict()})
  2372. send_event(event, active_user_ids(user_profile.realm))
  2373.  
  2374. def consolidate_client(client):
  2375. # type: (Client) -> Client
  2376. # The web app reports a client as 'website'
  2377. # The desktop app reports a client as ZulipDesktop
  2378. # due to it setting a custom user agent. We want both
  2379. # to count as web users
  2380.  
  2381. # Alias ZulipDesktop to website
  2382. if client.name in ['ZulipDesktop']:
  2383. return get_client('website')
  2384. else:
  2385. return client
  2386.  
  2387. @statsd_increment('user_presence')
  2388. def do_update_user_presence(user_profile, client, log_time, status):
  2389. # type: (UserProfile, Client, datetime.datetime, int) -> None
  2390. client = consolidate_client(client)
  2391. (presence, created) = UserPresence.objects.get_or_create(
  2392. user_profile = user_profile,
  2393. client = client,
  2394. defaults = {'timestamp': log_time,
  2395. 'status': status})
  2396.  
  2397. stale_status = (log_time - presence.timestamp) > datetime.timedelta(minutes=1, seconds=10)
  2398. was_idle = presence.status == UserPresence.IDLE
  2399. became_online = (status == UserPresence.ACTIVE) and (stale_status or was_idle)
  2400.  
  2401. # If an object was created, it has already been saved.
  2402. #
  2403. # We suppress changes from ACTIVE to IDLE before stale_status is reached;
  2404. # this protects us from the user having two clients open: one active, the
  2405. # other idle. Without this check, we would constantly toggle their status
  2406. # between the two states.
  2407. if not created and stale_status or was_idle or status == presence.status:
  2408. # The following block attempts to only update the "status"
  2409. # field in the event that it actually changed. This is
  2410. # important to avoid flushing the UserPresence cache when the
  2411. # data it would return to a client hasn't actually changed
  2412. # (see the UserPresence post_save hook for details).
  2413. presence.timestamp = log_time
  2414. update_fields = ["timestamp"]
  2415. if presence.status != status:
  2416. presence.status = status
  2417. update_fields.append("status")
  2418. presence.save(update_fields=update_fields)
  2419.  
  2420. if not user_profile.realm.is_zephyr_mirror_realm and (created or became_online):
  2421. # Push event to all users in the realm so they see the new user
  2422. # appear in the presence list immediately, or the newly online
  2423. # user without delay. Note that we won't send an update here for a
  2424. # timestamp update, because we rely on the browser to ping us every 50
  2425. # seconds for realm-wide status updates, and those updates should have
  2426. # recent timestamps, which means the browser won't think active users
  2427. # have gone idle. If we were more aggressive in this function about
  2428. # sending timestamp updates, we could eliminate the ping responses, but
  2429. # that's not a high priority for now, considering that most of our non-MIT
  2430. # realms are pretty small.
  2431. send_presence_changed(user_profile, presence)
  2432.  
  2433. def update_user_activity_interval(user_profile, log_time):
  2434. # type: (UserProfile, datetime.datetime) -> None
  2435. event={'user_profile_id': user_profile.id,
  2436. 'time': datetime_to_timestamp(log_time)}
  2437. queue_json_publish("user_activity_interval", event,
  2438. lambda e: do_update_user_activity_interval(user_profile, log_time))
  2439.  
  2440. def update_user_presence(user_profile, client, log_time, status,
  2441. new_user_input):
  2442. # type: (UserProfile, Client, datetime.datetime, int, bool) -> None
  2443. event={'user_profile_id': user_profile.id,
  2444. 'status': status,
  2445. 'time': datetime_to_timestamp(log_time),
  2446. 'client': client.name}
  2447.  
  2448. queue_json_publish("user_presence", event,
  2449. lambda e: do_update_user_presence(user_profile, client,
  2450. log_time, status))
  2451.  
  2452. if new_user_input:
  2453. update_user_activity_interval(user_profile, log_time)
  2454.  
  2455. def do_update_pointer(user_profile, pointer, update_flags=False):
  2456. # type: (UserProfile, int, bool) -> None
  2457. prev_pointer = user_profile.pointer
  2458. user_profile.pointer = pointer
  2459. user_profile.save(update_fields=["pointer"])
  2460.  
  2461. if update_flags:
  2462. # Until we handle the new read counts in the Android app
  2463. # natively, this is a shim that will mark as read any messages
  2464. # up until the pointer move
  2465. UserMessage.objects.filter(user_profile=user_profile,
  2466. message__id__gt=prev_pointer,
  2467. message__id__lte=pointer,
  2468. flags=~UserMessage.flags.read) \
  2469. .update(flags=F('flags').bitor(UserMessage.flags.read))
  2470.  
  2471. event = dict(type='pointer', pointer=pointer)
  2472. send_event(event, [user_profile.id])
  2473.  
  2474. def do_update_message_flags(user_profile, operation, flag, messages, all, stream_obj, topic_name):
  2475. # type: (UserProfile, text_type, text_type, Sequence[int], bool, Optional[Stream], Optional[text_type]) -> int
  2476. flagattr = getattr(UserMessage.flags, flag)
  2477.  
  2478. if all:
  2479. log_statsd_event('bankruptcy')
  2480. msgs = UserMessage.objects.filter(user_profile=user_profile)
  2481. elif stream_obj is not None:
  2482. recipient = get_recipient(Recipient.STREAM, stream_obj.id)
  2483. if topic_name:
  2484. msgs = UserMessage.objects.filter(message__recipient=recipient,
  2485. user_profile=user_profile,
  2486. message__subject__iexact=topic_name)
  2487. else:
  2488. msgs = UserMessage.objects.filter(message__recipient=recipient, user_profile=user_profile)
  2489. else:
  2490. msgs = UserMessage.objects.filter(user_profile=user_profile,
  2491. message__id__in=messages)
  2492. # Hack to let you star any message
  2493. if msgs.count() == 0:
  2494. if not len(messages) == 1:
  2495. raise JsonableError(_("Invalid message(s)"))
  2496. if flag != "starred":
  2497. raise JsonableError(_("Invalid message(s)"))
  2498. # Validate that the user could have read the relevant message
  2499. message = access_message(user_profile, messages[0])[0]
  2500.  
  2501. # OK, this is a message that you legitimately have access
  2502. # to via narrowing to the stream it is on, even though you
  2503. # didn't actually receive it. So we create a historical,
  2504. # read UserMessage message row for you to star.
  2505. UserMessage.objects.create(user_profile=user_profile,
  2506. message=message,
  2507. flags=UserMessage.flags.historical | UserMessage.flags.read)
  2508.  
  2509. # The filter() statements below prevent postgres from doing a lot of
  2510. # unnecessary work, which is a big deal for users updating lots of
  2511. # flags (e.g. bankruptcy). This patch arose from seeing slow calls
  2512. # to POST /json/messages/flags in the logs. The filter() statements
  2513. # are kind of magical; they are actually just testing the one bit.
  2514. if operation == 'add':
  2515. msgs = msgs.filter(flags=~flagattr)
  2516. if stream_obj:
  2517. messages = list(msgs.values_list('message__id', flat=True))
  2518. count = msgs.update(flags=F('flags').bitor(flagattr))
  2519. elif operation == 'remove':
  2520. msgs = msgs.filter(flags=flagattr)
  2521. if stream_obj:
  2522. messages = list(msgs.values_list('message__id', flat=True))
  2523. count = msgs.update(flags=F('flags').bitand(~flagattr))
  2524.  
  2525. event = {'type': 'update_message_flags',
  2526. 'operation': operation,
  2527. 'flag': flag,
  2528. 'messages': messages,
  2529. 'all': all}
  2530. log_event(event)
  2531. send_event(event, [user_profile.id])
  2532.  
  2533. statsd.incr("flags.%s.%s" % (flag, operation), count)
  2534. return count
  2535.  
  2536. def subscribed_to_stream(user_profile, stream):
  2537. # type: (UserProfile, Stream) -> bool
  2538. try:
  2539. if Subscription.objects.get(user_profile=user_profile,
  2540. active=True,
  2541. recipient__type=Recipient.STREAM,
  2542. recipient__type_id=stream.id):
  2543. return True
  2544. return False
  2545. except Subscription.DoesNotExist:
  2546. return False
  2547.  
  2548. def truncate_content(content, max_length, truncation_message):
  2549. # type: (text_type, int, text_type) -> text_type
  2550. if len(content) > max_length:
  2551. content = content[:max_length - len(truncation_message)] + truncation_message
  2552. return content
  2553.  
  2554. def truncate_body(body):
  2555. # type: (text_type) -> text_type
  2556. return truncate_content(body, MAX_MESSAGE_LENGTH, "...")
  2557.  
  2558. def truncate_topic(topic):
  2559. # type: (text_type) -> text_type
  2560. return truncate_content(topic, MAX_SUBJECT_LENGTH, "...")
  2561.  
  2562.  
  2563. def update_user_message_flags(message, ums):
  2564. # type: (Message, Iterable[UserMessage]) -> None
  2565. wildcard = message.mentions_wildcard
  2566. mentioned_ids = message.mentions_user_ids
  2567. ids_with_alert_words = message.user_ids_with_alert_words
  2568. changed_ums = set() # type: Set[UserMessage]
  2569.  
  2570. def update_flag(um, should_set, flag):
  2571. # type: (UserMessage, bool, int) -> None
  2572. if should_set:
  2573. if not (um.flags & flag):
  2574. um.flags |= flag
  2575. changed_ums.add(um)
  2576. else:
  2577. if (um.flags & flag):
  2578. um.flags &= ~flag
  2579. changed_ums.add(um)
  2580.  
  2581. for um in ums:
  2582. has_alert_word = um.user_profile_id in ids_with_alert_words
  2583. update_flag(um, has_alert_word, UserMessage.flags.has_alert_word)
  2584.  
  2585. mentioned = um.user_profile_id in mentioned_ids
  2586. update_flag(um, mentioned, UserMessage.flags.mentioned)
  2587.  
  2588. update_flag(um, wildcard, UserMessage.flags.wildcard_mentioned)
  2589.  
  2590. is_me_message = getattr(message, 'is_me_message', False)
  2591. update_flag(um, is_me_message, UserMessage.flags.is_me_message)
  2592.  
  2593. for um in changed_ums:
  2594. um.save(update_fields=['flags'])
  2595.  
  2596. # We use transaction.atomic to support select_for_update in the attachment codepath.
  2597. @transaction.atomic
  2598. def do_update_message(user_profile, message, subject, propagate_mode, content, rendered_content):
  2599. # type: (UserProfile, Message, Optional[text_type], str, Optional[text_type], Optional[text_type]) -> None
  2600. event = {'type': 'update_message',
  2601. 'sender': user_profile.email,
  2602. 'message_id': message.id} # type: Dict[str, Any]
  2603. edit_history_event = {} # type: Dict[str, Any]
  2604. changed_messages = [message]
  2605.  
  2606. # Set first_rendered_content to be the oldest version of the
  2607. # rendered content recorded; which is the current version if the
  2608. # content hasn't been edited before. Note that because one could
  2609. # have edited just the subject, not every edit history event
  2610. # contains a prev_rendered_content element.
  2611. first_rendered_content = message.rendered_content
  2612. if message.edit_history is not None:
  2613. edit_history = ujson.loads(message.edit_history)
  2614. for old_edit_history_event in edit_history:
  2615. if 'prev_rendered_content' in old_edit_history_event:
  2616. first_rendered_content = old_edit_history_event['prev_rendered_content']
  2617.  
  2618. ums = UserMessage.objects.filter(message=message.id)
  2619.  
  2620. if content is not None:
  2621. update_user_message_flags(message, ums)
  2622.  
  2623. # We are turning off diff highlighting everywhere until ticket #1532 is addressed.
  2624. if False:
  2625. # Don't highlight message edit diffs on prod
  2626. rendered_content = highlight_html_differences(first_rendered_content, rendered_content)
  2627.  
  2628. event['orig_content'] = message.content
  2629. event['orig_rendered_content'] = message.rendered_content
  2630. edit_history_event["prev_content"] = message.content
  2631. edit_history_event["prev_rendered_content"] = message.rendered_content
  2632. edit_history_event["prev_rendered_content_version"] = message.rendered_content_version
  2633. message.content = content
  2634. message.rendered_content = rendered_content
  2635. message.rendered_content_version = bugdown_version
  2636. event["content"] = content
  2637. event["rendered_content"] = rendered_content
  2638.  
  2639. prev_content = edit_history_event['prev_content']
  2640. if Message.content_has_attachment(prev_content) or Message.content_has_attachment(message.content):
  2641. check_attachment_reference_change(prev_content, message)
  2642.  
  2643. if subject is not None:
  2644. orig_subject = message.topic_name()
  2645. subject = truncate_topic(subject)
  2646. event["orig_subject"] = orig_subject
  2647. event["propagate_mode"] = propagate_mode
  2648. message.subject = subject
  2649. event["stream_id"] = message.recipient.type_id
  2650. event["subject"] = subject
  2651. event['subject_links'] = bugdown.subject_links(message.sender.realm.domain.lower(), subject)
  2652. edit_history_event["prev_subject"] = orig_subject
  2653.  
  2654.  
  2655. if propagate_mode in ["change_later", "change_all"]:
  2656. propagate_query = Q(recipient = message.recipient, subject = orig_subject)
  2657. # We only change messages up to 2 days in the past, to avoid hammering our
  2658. # DB by changing an unbounded amount of messages
  2659. if propagate_mode == 'change_all':
  2660. before_bound = now() - datetime.timedelta(days=2)
  2661.  
  2662. propagate_query = propagate_query & ~Q(id = message.id) & \
  2663. Q(pub_date__range=(before_bound, now()))
  2664. if propagate_mode == 'change_later':
  2665. propagate_query = propagate_query & Q(id__gt = message.id)
  2666.  
  2667. messages = Message.objects.filter(propagate_query).select_related()
  2668.  
  2669. # Evaluate the query before running the update
  2670. messages_list = list(messages)
  2671. messages.update(subject=subject)
  2672.  
  2673. for m in messages_list:
  2674. # The cached ORM object is not changed by messages.update()
  2675. # and the remote cache update requires the new value
  2676. m.subject = subject
  2677.  
  2678. changed_messages += messages_list
  2679.  
  2680. message.last_edit_time = timezone.now()
  2681. event['edit_timestamp'] = datetime_to_timestamp(message.last_edit_time)
  2682. edit_history_event['timestamp'] = event['edit_timestamp']
  2683. if message.edit_history is not None:
  2684. edit_history.insert(0, edit_history_event)
  2685. else:
  2686. edit_history = [edit_history_event]
  2687. message.edit_history = ujson.dumps(edit_history)
  2688.  
  2689. log_event(event)
  2690. message.save(update_fields=["subject", "content", "rendered_content",
  2691. "rendered_content_version", "last_edit_time",
  2692. "edit_history"])
  2693.  
  2694. # Update the message as stored in the (deprecated) message
  2695. # cache (for shunting the message over to Tornado in the old
  2696. # get_messages API) and also the to_dict caches.
  2697. items_for_remote_cache = {}
  2698. event['message_ids'] = []
  2699. for changed_message in changed_messages:
  2700. event['message_ids'].append(changed_message.id)
  2701. items_for_remote_cache[to_dict_cache_key(changed_message, True)] = \
  2702. (MessageDict.to_dict_uncached(changed_message, apply_markdown=True),)
  2703. items_for_remote_cache[to_dict_cache_key(changed_message, False)] = \
  2704. (MessageDict.to_dict_uncached(changed_message, apply_markdown=False),)
  2705. cache_set_many(items_for_remote_cache)
  2706.  
  2707. def user_info(um):
  2708. # type: (UserMessage) -> Dict[str, Any]
  2709. return {
  2710. 'id': um.user_profile_id,
  2711. 'flags': um.flags_list()
  2712. }
  2713. send_event(event, list(map(user_info, ums)))
  2714.  
  2715. def encode_email_address(stream):
  2716. # type: (Stream) -> text_type
  2717. return encode_email_address_helper(stream.name, stream.email_token)
  2718.  
  2719. def encode_email_address_helper(name, email_token):
  2720. # type: (text_type, text_type) -> text_type
  2721. # Some deployments may not use the email gateway
  2722. if settings.EMAIL_GATEWAY_PATTERN == '':
  2723. return ''
  2724.  
  2725. # Given the fact that we have almost no restrictions on stream names and
  2726. # that what characters are allowed in e-mail addresses is complicated and
  2727. # dependent on context in the address, we opt for a very simple scheme:
  2728. #
  2729. # Only encode the stream name (leave the + and token alone). Encode
  2730. # everything that isn't alphanumeric plus _ as the percent-prefixed integer
  2731. # ordinal of that character, padded with zeroes to the maximum number of
  2732. # bytes of a UTF-8 encoded Unicode character.
  2733. encoded_name = re.sub("\W", lambda x: "%" + str(ord(x.group(0))).zfill(4), name)
  2734. encoded_token = "%s+%s" % (encoded_name, email_token)
  2735. return settings.EMAIL_GATEWAY_PATTERN % (encoded_token,)
  2736.  
  2737. def get_email_gateway_message_string_from_address(address):
  2738. # type: (text_type) -> Optional[text_type]
  2739. pattern_parts = [re.escape(part) for part in settings.EMAIL_GATEWAY_PATTERN.split('%s')]
  2740. if settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK:
  2741. # Accept mails delivered to any Zulip server
  2742. pattern_parts[-1] = settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK
  2743. match_email_re = re.compile("(.*?)".join(pattern_parts))
  2744. match = match_email_re.match(address)
  2745.  
  2746. if not match:
  2747. return None
  2748.  
  2749. msg_string = match.group(1)
  2750.  
  2751. return msg_string
  2752.  
  2753. def decode_email_address(email):
  2754. # type: (text_type) -> Tuple[text_type, text_type]
  2755. # Perform the reverse of encode_email_address. Returns a tuple of (streamname, email_token)
  2756. msg_string = get_email_gateway_message_string_from_address(email)
  2757.  
  2758. if '.' in msg_string:
  2759. # Workaround for Google Groups and other programs that don't accept emails
  2760. # that have + signs in them (see Trac #2102)
  2761. encoded_stream_name, token = msg_string.split('.')
  2762. else:
  2763. encoded_stream_name, token = msg_string.split('+')
  2764. stream_name = re.sub("%\d{4}", lambda x: unichr(int(x.group(0)[1:])), encoded_stream_name)
  2765. return stream_name, token
  2766.  
  2767. # In general, it's better to avoid using .values() because it makes
  2768. # the code pretty ugly, but in this case, it has significant
  2769. # performance impact for loading / for users with large numbers of
  2770. # subscriptions, so it's worth optimizing.
  2771. def gather_subscriptions_helper(user_profile):
  2772. # type: (UserProfile) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]
  2773. sub_dicts = Subscription.objects.select_related("recipient").filter(
  2774. user_profile = user_profile,
  2775. recipient__type = Recipient.STREAM).values(
  2776. "recipient__type_id", "in_home_view", "color", "desktop_notifications",
  2777. "audible_notifications", "active", "pin_to_top")
  2778.  
  2779. stream_ids = set([sub["recipient__type_id"] for sub in sub_dicts])
  2780. all_streams = get_active_streams(user_profile.realm).select_related(
  2781. "realm").values("id", "name", "invite_only", "realm_id", \
  2782. "realm__domain", "email_token", "description")
  2783.  
  2784. stream_dicts = [stream for stream in all_streams if stream['id'] in stream_ids]
  2785. stream_hash = {}
  2786. for stream in stream_dicts:
  2787. stream_hash[stream["id"]] = stream
  2788.  
  2789. all_streams_id = [stream["id"] for stream in all_streams]
  2790.  
  2791. subscribed = []
  2792. unsubscribed = []
  2793. never_subscribed = []
  2794.  
  2795. # Deactivated streams aren't in stream_hash.
  2796. streams = [stream_hash[sub["recipient__type_id"]] for sub in sub_dicts \
  2797. if sub["recipient__type_id"] in stream_hash]
  2798. streams_subscribed_map = dict((sub["recipient__type_id"], sub["active"]) for sub in sub_dicts)
  2799.  
  2800. # Add never subscribed streams to streams_subscribed_map
  2801. streams_subscribed_map.update({stream['id']: False for stream in all_streams if stream not in streams})
  2802.  
  2803. subscriber_map = bulk_get_subscriber_user_ids(all_streams, user_profile, streams_subscribed_map)
  2804.  
  2805. sub_unsub_stream_ids = set()
  2806. for sub in sub_dicts:
  2807. sub_unsub_stream_ids.add(sub["recipient__type_id"])
  2808. stream = stream_hash.get(sub["recipient__type_id"])
  2809. if not stream:
  2810. # This stream has been deactivated, don't include it.
  2811. continue
  2812.  
  2813. subscribers = subscriber_map[stream["id"]]
  2814.  
  2815. # Important: don't show the subscribers if the stream is invite only
  2816. # and this user isn't on it anymore.
  2817. if stream["invite_only"] and not sub["active"]:
  2818. subscribers = None
  2819.  
  2820. stream_dict = {'name': stream["name"],
  2821. 'in_home_view': sub["in_home_view"],
  2822. 'invite_only': stream["invite_only"],
  2823. 'color': sub["color"],
  2824. 'desktop_notifications': sub["desktop_notifications"],
  2825. 'audible_notifications': sub["audible_notifications"],
  2826. 'pin_to_top': sub["pin_to_top"],
  2827. 'stream_id': stream["id"],
  2828. 'description': stream["description"],
  2829. 'email_address': encode_email_address_helper(stream["name"], stream["email_token"])}
  2830. if subscribers is not None:
  2831. stream_dict['subscribers'] = subscribers
  2832. if sub["active"]:
  2833. subscribed.append(stream_dict)
  2834. else:
  2835. unsubscribed.append(stream_dict)
  2836.  
  2837. all_streams_id_set = set(all_streams_id)
  2838. # Listing public streams are disabled for Zephyr mirroring realms.
  2839. if user_profile.realm.is_zephyr_mirror_realm:
  2840. never_subscribed_stream_ids = set() # type: Set[int]
  2841. else:
  2842. never_subscribed_stream_ids = all_streams_id_set - sub_unsub_stream_ids
  2843. never_subscribed_streams = [ns_stream_dict for ns_stream_dict in all_streams
  2844. if ns_stream_dict['id'] in never_subscribed_stream_ids]
  2845.  
  2846. for stream in never_subscribed_streams:
  2847. if not stream['invite_only']:
  2848. stream_dict = {'name': stream['name'],
  2849. 'invite_only': stream['invite_only'],
  2850. 'stream_id': stream['id'],
  2851. 'description': stream['description']}
  2852. subscribers = subscriber_map[stream["id"]]
  2853. if subscribers is not None:
  2854. stream_dict['subscribers'] = subscribers
  2855. never_subscribed.append(stream_dict)
  2856.  
  2857. return (sorted(subscribed, key=lambda x: x['name']),
  2858. sorted(unsubscribed, key=lambda x: x['name']),
  2859. sorted(never_subscribed, key=lambda x: x['name']))
  2860.  
  2861. def gather_subscriptions(user_profile):
  2862. # type: (UserProfile) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]
  2863. subscribed, unsubscribed, never_subscribed = gather_subscriptions_helper(user_profile)
  2864. user_ids = set()
  2865. for subs in [subscribed, unsubscribed, never_subscribed]:
  2866. for sub in subs:
  2867. if 'subscribers' in sub:
  2868. for subscriber in sub['subscribers']:
  2869. user_ids.add(subscriber)
  2870. email_dict = get_emails_from_user_ids(list(user_ids))
  2871.  
  2872. for subs in [subscribed, unsubscribed]:
  2873. for sub in subs:
  2874. if 'subscribers' in sub:
  2875. sub['subscribers'] = [email_dict[user_id] for user_id in sub['subscribers']]
  2876.  
  2877. return (subscribed, unsubscribed)
  2878.  
  2879. def get_status_dict(requesting_user_profile):
  2880. # type: (UserProfile) -> Dict[text_type, Dict[text_type, Dict[str, Any]]]
  2881. if requesting_user_profile.realm.presence_disabled:
  2882. # Return an empty dict if presence is disabled in this realm
  2883. return defaultdict(dict)
  2884.  
  2885. return UserPresence.get_status_dict_by_realm(requesting_user_profile.realm_id)
  2886.  
  2887.  
  2888. def get_realm_user_dicts(user_profile):
  2889. # type: (UserProfile) -> List[Dict[str, text_type]]
  2890. return [{'email' : userdict['email'],
  2891. 'user_id' : userdict['id'],
  2892. 'is_admin' : userdict['is_realm_admin'],
  2893. 'is_bot' : userdict['is_bot'],
  2894. 'full_name' : userdict['full_name']}
  2895. for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
  2896.  
  2897. def get_cross_realm_dicts():
  2898. # type: () -> List[Dict[str, Any]]
  2899. users = [get_user_profile_by_email(email) for email in get_cross_realm_emails()]
  2900. return [{'email' : user.email,
  2901. 'user_id' : user.id,
  2902. 'is_admin' : user.is_realm_admin,
  2903. 'is_bot' : user.is_bot,
  2904. 'full_name' : user.full_name}
  2905. for user in users]
  2906.  
  2907. # Fetch initial data. When event_types is not specified, clients want
  2908. # all event types. Whenever you add new code to this function, you
  2909. # should also add corresponding events for changes in the data
  2910. # structures and new code to apply_events (and add a test in EventsRegisterTest).
  2911. def fetch_initial_state_data(user_profile, event_types, queue_id):
  2912. # type: (UserProfile, Optional[Iterable[str]], str) -> Dict[str, Any]
  2913. state = {'queue_id': queue_id} # type: Dict[str, Any]
  2914.  
  2915. if event_types is None:
  2916. want = lambda msg_type: True
  2917. else:
  2918. want = set(event_types).__contains__
  2919.  
  2920. if want('alert_words'):
  2921. state['alert_words'] = user_alert_words(user_profile)
  2922.  
  2923. if want('message'):
  2924. # The client should use get_old_messages() to fetch messages
  2925. # starting with the max_message_id. They will get messages
  2926. # newer than that ID via get_events()
  2927. messages = Message.objects.filter(usermessage__user_profile=user_profile).order_by('-id')[:1]
  2928. if messages:
  2929. state['max_message_id'] = messages[0].id
  2930. else:
  2931. state['max_message_id'] = -1
  2932.  
  2933. if want('muted_topics'):
  2934. state['muted_topics'] = ujson.loads(user_profile.muted_topics)
  2935.  
  2936. if want('pointer'):
  2937. state['pointer'] = user_profile.pointer
  2938.  
  2939. if want('presence'):
  2940. state['presences'] = get_status_dict(user_profile)
  2941.  
  2942. if want('realm'):
  2943. state['realm_name'] = user_profile.realm.name
  2944. state['realm_restricted_to_domain'] = user_profile.realm.restricted_to_domain
  2945. state['realm_invite_required'] = user_profile.realm.invite_required
  2946. state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only
  2947. state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
  2948. state['realm_create_stream_by_admins_only'] = user_profile.realm.create_stream_by_admins_only
  2949. state['realm_allow_message_editing'] = user_profile.realm.allow_message_editing
  2950. state['realm_message_content_edit_limit_seconds'] = user_profile.realm.message_content_edit_limit_seconds
  2951. state['realm_default_language'] = user_profile.realm.default_language
  2952.  
  2953. if want('realm_domain'):
  2954. state['realm_domain'] = user_profile.realm.domain
  2955.  
  2956. if want('realm_emoji'):
  2957. state['realm_emoji'] = user_profile.realm.get_emoji()
  2958.  
  2959. if want('realm_filters'):
  2960. state['realm_filters'] = realm_filters_for_domain(user_profile.realm.domain)
  2961.  
  2962. if want('realm_user'):
  2963. state['realm_users'] = get_realm_user_dicts(user_profile)
  2964.  
  2965. if want('realm_bot'):
  2966. state['realm_bots'] = get_owned_bot_dicts(user_profile)
  2967.  
  2968. if want('referral'):
  2969. state['referrals'] = {'granted': user_profile.invites_granted,
  2970. 'used': user_profile.invites_used}
  2971.  
  2972. if want('subscription'):
  2973. subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper(user_profile)
  2974. state['subscriptions'] = subscriptions
  2975. state['unsubscribed'] = unsubscribed
  2976. state['never_subscribed'] = never_subscribed
  2977.  
  2978. if want('update_message_flags'):
  2979. # There's no initial data for message flag updates, client will
  2980. # get any updates during a session from get_events()
  2981. pass
  2982.  
  2983. if want('stream'):
  2984. state['streams'] = do_get_streams(user_profile)
  2985. if want('default_streams'):
  2986. state['realm_default_streams'] = streams_to_dicts_sorted(get_default_streams_for_realm(user_profile.realm))
  2987.  
  2988. if want('update_display_settings'):
  2989. state['twenty_four_hour_time'] = user_profile.twenty_four_hour_time
  2990. state['left_side_userlist'] = user_profile.left_side_userlist
  2991.  
  2992. default_language = user_profile.default_language
  2993. if user_profile.default_language == 'zh_HANS':
  2994. # NB: remove this once we upgrade to Django 1.9
  2995. # zh-cn and zh-tw will be replaced by zh-hans and zh-hant in
  2996. # Django 1.9
  2997. default_language = 'zh_CN'
  2998.  
  2999. state['default_language'] = default_language
  3000.  
  3001. return state
  3002.  
  3003. def apply_events(state, events, user_profile):
  3004. # type: (Dict[str, Any], Iterable[Dict[str, Any]], UserProfile) -> None
  3005. for event in events:
  3006. if event['type'] == "message":
  3007. state['max_message_id'] = max(state['max_message_id'], event['message']['id'])
  3008. elif event['type'] == "pointer":
  3009. state['pointer'] = max(state['pointer'], event['pointer'])
  3010. elif event['type'] == "realm_user":
  3011. person = event['person']
  3012.  
  3013. def our_person(p):
  3014. # type: (Dict[str, Any]) -> bool
  3015. return p['email'] == person['email']
  3016.  
  3017. if event['op'] == "add":
  3018. state['realm_users'].append(person)
  3019. elif event['op'] == "remove":
  3020. state['realm_users'] = [user for user in state['realm_users'] if not our_person(user)]
  3021. elif event['op'] == 'update':
  3022. for p in state['realm_users']:
  3023. if our_person(p):
  3024. # In the unlikely event that the current user
  3025. # just changed to/from being an admin, we need
  3026. # to add/remove the data on all bots in the
  3027. # realm. This is ugly and probably better
  3028. # solved by removing the all-realm-bots data
  3029. # given to admin users from this flow.
  3030. if ('is_admin' in person and 'realm_bots' in state and
  3031. user_profile.email == person['email']):
  3032. if p['is_admin'] and not person['is_admin']:
  3033. state['realm_bots'] = []
  3034. if not p['is_admin'] and person['is_admin']:
  3035. state['realm_bots'] = get_owned_bot_dicts(user_profile)
  3036. # Now update the person
  3037. p.update(person)
  3038. elif event['type'] == 'realm_bot':
  3039. if event['op'] == 'add':
  3040. state['realm_bots'].append(event['bot'])
  3041.  
  3042. if event['op'] == 'remove':
  3043. email = event['bot']['email']
  3044. state['realm_bots'] = [b for b in state['realm_bots'] if b['email'] != email]
  3045.  
  3046. if event['op'] == 'update':
  3047. for bot in state['realm_bots']:
  3048. if bot['email'] == event['bot']['email']:
  3049. bot.update(event['bot'])
  3050.  
  3051. elif event['type'] == 'stream':
  3052. if event['op'] == 'create':
  3053. for stream in event['streams']:
  3054. if not stream['invite_only']:
  3055. stream_data = copy.deepcopy(stream)
  3056. stream_data['subscribers'] = []
  3057. # Add stream to never_subscribed (if not invite_only)
  3058. state['never_subscribed'].append(stream_data)
  3059.  
  3060. if event['op'] == 'delete':
  3061. deleted_stream_ids = {stream['stream_id'] for stream in event['streams']}
  3062. state['streams'] = [s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids]
  3063. state['never_subscribed'] = [stream for stream in state['never_subscribed'] if
  3064. stream['stream_id'] not in deleted_stream_ids]
  3065.  
  3066. if event['op'] == 'update':
  3067. # For legacy reasons, we call stream data 'subscriptions' in
  3068. # the state var here, for the benefit of the JS code.
  3069. for obj in state['subscriptions']:
  3070. if obj['name'].lower() == event['name'].lower():
  3071. obj[event['property']] = event['value']
  3072. # Also update the pure streams data
  3073. for stream in state['streams']:
  3074. if stream['name'].lower() == event['name'].lower():
  3075. prop = event['property']
  3076. if prop in stream:
  3077. stream[prop] = event['value']
  3078. elif event['op'] == "occupy":
  3079. state['streams'] += event['streams']
  3080. elif event['op'] == "vacate":
  3081. stream_ids = [s["stream_id"] for s in event['streams']]
  3082. state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids]
  3083. elif event['type'] == 'default_streams':
  3084. state['realm_default_streams'] = event['default_streams']
  3085. elif event['type'] == 'realm':
  3086. if event['op'] == "update":
  3087. field = 'realm_' + event['property']
  3088. state[field] = event['value']
  3089. elif event['op'] == "update_dict":
  3090. for key, value in event['data'].items():
  3091. state['realm_' + key] = value
  3092. elif event['type'] == "subscription":
  3093. if event['op'] in ["add"]:
  3094. # Convert the user_profile IDs to emails since that's what register() returns
  3095. # TODO: Clean up this situation
  3096. for item in event["subscriptions"]:
  3097. item["subscribers"] = [get_user_profile_by_email(email).id for email in item["subscribers"]]
  3098.  
  3099. def name(sub):
  3100. # type: (Dict[str, Any]) -> text_type
  3101. return sub['name'].lower()
  3102.  
  3103. if event['op'] == "add":
  3104. added_names = set(map(name, event["subscriptions"]))
  3105. was_added = lambda s: name(s) in added_names
  3106.  
  3107. # add the new subscriptions
  3108. state['subscriptions'] += event['subscriptions']
  3109.  
  3110. # remove them from unsubscribed if they had been there
  3111. state['unsubscribed'] = [s for s in state['unsubscribed'] if not was_added(s)]
  3112.  
  3113. # remove them from never_subscribed if they had been there
  3114. state['never_subscribed'] = [s for s in state['never_subscribed'] if not was_added(s)]
  3115.  
  3116. elif event['op'] == "remove":
  3117. removed_names = set(map(name, event["subscriptions"]))
  3118. was_removed = lambda s: name(s) in removed_names
  3119.  
  3120. # Find the subs we are affecting.
  3121. removed_subs = list(filter(was_removed, state['subscriptions']))
  3122.  
  3123. # Remove our user from the subscribers of the removed subscriptions.
  3124. for sub in removed_subs:
  3125. sub['subscribers'] = [id for id in sub['subscribers'] if id != user_profile.id]
  3126.  
  3127. # We must effectively copy the removed subscriptions from subscriptions to
  3128. # unsubscribe, since we only have the name in our data structure.
  3129. state['unsubscribed'] += removed_subs
  3130.  
  3131. # Now filter out the removed subscriptions from subscriptions.
  3132. state['subscriptions'] = [s for s in state['subscriptions'] if not was_removed(s)]
  3133.  
  3134. elif event['op'] == 'update':
  3135. for sub in state['subscriptions']:
  3136. if sub['name'].lower() == event['name'].lower():
  3137. sub[event['property']] = event['value']
  3138. elif event['op'] == 'peer_add':
  3139. user_id = event['user_id']
  3140. for sub in state['subscriptions']:
  3141. if (sub['name'] in event['subscriptions'] and
  3142. user_id not in sub['subscribers']):
  3143. sub['subscribers'].append(user_id)
  3144. for sub in state['never_subscribed']:
  3145. if (sub['name'] in event['subscriptions'] and
  3146. user_id not in sub['subscribers']):
  3147. sub['subscribers'].append(user_id)
  3148. elif event['op'] == 'peer_remove':
  3149. user_id = event['user_id']
  3150. for sub in state['subscriptions']:
  3151. if (sub['name'] in event['subscriptions'] and
  3152. user_id in sub['subscribers']):
  3153. sub['subscribers'].remove(user_id)
  3154. elif event['type'] == "presence":
  3155. state['presences'][event['email']] = event['presence']
  3156. elif event['type'] == "update_message":
  3157. # The client will get the updated message directly
  3158. pass
  3159. elif event['type'] == "referral":
  3160. state['referrals'] = event['referrals']
  3161. elif event['type'] == "update_message_flags":
  3162. # The client will get the message with the updated flags directly
  3163. pass
  3164. elif event['type'] == "realm_emoji":
  3165. state['realm_emoji'] = event['realm_emoji']
  3166. elif event['type'] == "alert_words":
  3167. state['alert_words'] = event['alert_words']
  3168. elif event['type'] == "muted_topics":
  3169. state['muted_topics'] = event["muted_topics"]
  3170. elif event['type'] == "realm_filters":
  3171. state['realm_filters'] = event["realm_filters"]
  3172. elif event['type'] == "update_display_settings":
  3173. if event['setting_name'] == "twenty_four_hour_time":
  3174. state['twenty_four_hour_time'] = event["setting"]
  3175. if event['setting_name'] == 'left_side_userlist':
  3176. state['left_side_userlist'] = event["setting"]
  3177. else:
  3178. raise ValueError("Unexpected event type %s" % (event['type'],))
  3179.  
  3180. def do_events_register(user_profile, user_client, apply_markdown=True,
  3181. event_types=None, queue_lifespan_secs=0, all_public_streams=False,
  3182. narrow=[]):
  3183. # type: (UserProfile, Client, bool, Optional[Iterable[str]], int, bool, Iterable[Sequence[text_type]]) -> Dict[str, Any]
  3184. # Technically we don't need to check this here because
  3185. # build_narrow_filter will check it, but it's nicer from an error
  3186. # handling perspective to do it before contacting Tornado
  3187. check_supported_events_narrow_filter(narrow)
  3188. queue_id = request_event_queue(user_profile, user_client, apply_markdown,
  3189. queue_lifespan_secs, event_types, all_public_streams,
  3190. narrow=narrow)
  3191.  
  3192. if queue_id is None:
  3193. raise JsonableError(_("Could not allocate event queue"))
  3194. if event_types is not None:
  3195. event_types_set = set(event_types) # type: Optional[Set[str]]
  3196. else:
  3197. event_types_set = None
  3198.  
  3199. ret = fetch_initial_state_data(user_profile, event_types_set, queue_id)
  3200.  
  3201. # Apply events that came in while we were fetching initial data
  3202. events = get_user_events(user_profile, queue_id, -1)
  3203. apply_events(ret, events, user_profile)
  3204. if events:
  3205. ret['last_event_id'] = events[-1]['id']
  3206. else:
  3207. ret['last_event_id'] = -1
  3208. return ret
  3209.  
  3210. def do_send_confirmation_email(invitee, referrer):
  3211. # type: (PreregistrationUser, UserProfile) -> None
  3212. """
  3213. Send the confirmation/welcome e-mail to an invited user.
  3214.  
  3215. `invitee` is a PreregistrationUser.
  3216. `referrer` is a UserProfile.
  3217. """
  3218. subject_template_path = 'confirmation/invite_email_subject.txt'
  3219. body_template_path = 'confirmation/invite_email_body.txt'
  3220. context = {'referrer': referrer,
  3221. 'support_email': settings.ZULIP_ADMINISTRATOR,
  3222. 'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS}
  3223.  
  3224. if referrer.realm.is_zephyr_mirror_realm:
  3225. subject_template_path = 'confirmation/mituser_invite_email_subject.txt'
  3226. body_template_path = 'confirmation/mituser_invite_email_body.txt'
  3227.  
  3228. Confirmation.objects.send_confirmation(
  3229. invitee, invitee.email, additional_context=context,
  3230. subject_template_path=subject_template_path,
  3231. body_template_path=body_template_path, host=referrer.realm.host)
  3232.  
  3233. @statsd_increment("push_notifications")
  3234. def handle_push_notification(user_profile_id, missed_message):
  3235. # type: (int, Dict[str, Any]) -> None
  3236. try:
  3237. user_profile = get_user_profile_by_id(user_profile_id)
  3238. if not (receives_offline_notifications(user_profile) or receives_online_notifications(user_profile)):
  3239. return
  3240.  
  3241. umessage = UserMessage.objects.get(user_profile=user_profile,
  3242. message__id=missed_message['message_id'])
  3243. message = umessage.message
  3244. if umessage.flags.read:
  3245. return
  3246. sender_str = message.sender.full_name
  3247.  
  3248. apple = num_push_devices_for_user(user_profile, kind=PushDeviceToken.APNS)
  3249. android = num_push_devices_for_user(user_profile, kind=PushDeviceToken.GCM)
  3250.  
  3251. if apple or android:
  3252. # TODO: set badge count in a better way
  3253. # Determine what alert string to display based on the missed messages
  3254. if message.recipient.type == Recipient.HUDDLE:
  3255. alert = "New private group message from %s" % (sender_str,)
  3256. elif message.recipient.type == Recipient.PERSONAL:
  3257. alert = "New private message from %s" % (sender_str,)
  3258. elif message.recipient.type == Recipient.STREAM:
  3259. alert = "New mention from %s" % (sender_str,)
  3260. else:
  3261. alert = "New Zulip mentions and private messages from %s" % (sender_str,)
  3262.  
  3263. if apple:
  3264. apple_extra_data = {'message_ids': [message.id]}
  3265. send_apple_push_notification(user_profile, alert, badge=1, zulip=apple_extra_data)
  3266.  
  3267. if android:
  3268. content = message.content
  3269. content_truncated = (len(content) > 200)
  3270. if content_truncated:
  3271. content = content[:200] + "..."
  3272.  
  3273. android_data = {
  3274. 'user': user_profile.email,
  3275. 'event': 'message',
  3276. 'alert': alert,
  3277. 'zulip_message_id': message.id, # message_id is reserved for CCS
  3278. 'time': datetime_to_timestamp(message.pub_date),
  3279. 'content': content,
  3280. 'content_truncated': content_truncated,
  3281. 'sender_email': message.sender.email,
  3282. 'sender_full_name': message.sender.full_name,
  3283. 'sender_avatar_url': get_avatar_url(message.sender.avatar_source, message.sender.email),
  3284. }
  3285.  
  3286. if message.recipient.type == Recipient.STREAM:
  3287. android_data['recipient_type'] = "stream"
  3288. android_data['stream'] = get_display_recipient(message.recipient)
  3289. android_data['topic'] = message.subject
  3290. elif message.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL):
  3291. android_data['recipient_type'] = "private"
  3292.  
  3293. send_android_push_notification(user_profile, android_data)
  3294.  
  3295. except UserMessage.DoesNotExist:
  3296. logging.error("Could not find UserMessage with message_id %s" %(missed_message['message_id'],))
  3297.  
  3298. def is_inactive(email):
  3299. # type: (text_type) -> None
  3300. try:
  3301. if get_user_profile_by_email(email).is_active:
  3302. raise ValidationError(u'%s is already active' % (email,))
  3303. except UserProfile.DoesNotExist:
  3304. pass
  3305.  
  3306. def user_email_is_unique(email):
  3307. # type: (text_type) -> None
  3308. try:
  3309. get_user_profile_by_email(email)
  3310. raise ValidationError(u'%s is already registered' % (email,))
  3311. except UserProfile.DoesNotExist:
  3312. pass
  3313.  
  3314. def do_invite_users(user_profile, invitee_emails, streams):
  3315. # type: (UserProfile, SizedTextIterable, Iterable[Stream]) -> Tuple[Optional[str], Dict[str, List[Tuple[text_type, str]]]]
  3316. new_prereg_users = [] # type: List[PreregistrationUser]
  3317. errors = [] # type: List[Tuple[text_type, str]]
  3318. skipped = [] # type: List[Tuple[text_type, str]]
  3319.  
  3320. ret_error = None # type: Optional[str]
  3321. ret_error_data = {} # type: Dict[str, List[Tuple[text_type, str]]]
  3322.  
  3323. for email in invitee_emails:
  3324. if email == '':
  3325. continue
  3326.  
  3327. try:
  3328. validators.validate_email(email)
  3329. except ValidationError:
  3330. errors.append((email, _("Invalid address.")))
  3331. continue
  3332.  
  3333. if not email_allowed_for_realm(email, user_profile.realm):
  3334. errors.append((email, _("Outside your domain.")))
  3335. continue
  3336.  
  3337. try:
  3338. existing_user_profile = get_user_profile_by_email(email)
  3339. except UserProfile.DoesNotExist:
  3340. existing_user_profile = None
  3341. try:
  3342. if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
  3343. # Mirror dummy users to be activated must be inactive
  3344. is_inactive(email)
  3345. else:
  3346. # Other users should not already exist at all.
  3347. user_email_is_unique(email)
  3348. except ValidationError:
  3349. skipped.append((email, _("Already has an account.")))
  3350. continue
  3351.  
  3352. # The logged in user is the referrer.
  3353. prereg_user = PreregistrationUser(email=email, referred_by=user_profile)
  3354.  
  3355. # We save twice because you cannot associate a ManyToMany field
  3356. # on an unsaved object.
  3357. prereg_user.save()
  3358. prereg_user.streams = streams
  3359. prereg_user.save()
  3360.  
  3361. new_prereg_users.append(prereg_user)
  3362.  
  3363. if errors:
  3364. ret_error = _("Some emails did not validate, so we didn't send any invitations.")
  3365. ret_error_data = {'errors': errors}
  3366.  
  3367. if skipped and len(skipped) == len(invitee_emails):
  3368. # All e-mails were skipped, so we didn't actually invite anyone.
  3369. ret_error = _("We weren't able to invite anyone.")
  3370. ret_error_data = {'errors': skipped}
  3371. return ret_error, ret_error_data
  3372.  
  3373. # If we encounter an exception at any point before now, there are no unwanted side-effects,
  3374. # since it is totally fine to have duplicate PreregistrationUsers
  3375. for user in new_prereg_users:
  3376. event = {"email": user.email, "referrer_email": user_profile.email}
  3377. queue_json_publish("invites", event,
  3378. lambda event: do_send_confirmation_email(user, user_profile))
  3379.  
  3380. if skipped:
  3381. ret_error = _("Some of those addresses are already using Zulip, "
  3382. "so we didn't send them an invitation. We did send "
  3383. "invitations to everyone else!")
  3384. ret_error_data = {'errors': skipped}
  3385.  
  3386. return ret_error, ret_error_data
  3387.  
  3388. def send_referral_event(user_profile):
  3389. # type: (UserProfile) -> None
  3390. event = dict(type="referral",
  3391. referrals=dict(granted=user_profile.invites_granted,
  3392. used=user_profile.invites_used))
  3393. send_event(event, [user_profile.id])
  3394.  
  3395. def do_refer_friend(user_profile, email):
  3396. # type: (UserProfile, text_type) -> None
  3397. content = ('Referrer: "%s" <%s>\n'
  3398. 'Realm: %s\n'
  3399. 'Referred: %s') % (user_profile.full_name, user_profile.email,
  3400. user_profile.realm.domain, email)
  3401. subject = "Zulip referral: %s" % (email,)
  3402. from_email = '"%s" <%s>' % (user_profile.full_name, 'referrals@zulip.com')
  3403. to_email = '"Zulip Referrals" <zulip+referrals@zulip.com>'
  3404. headers = {'Reply-To' : '"%s" <%s>' % (user_profile.full_name, user_profile.email,)}
  3405. msg = EmailMessage(subject, content, from_email, [to_email], headers=headers)
  3406. msg.send()
  3407.  
  3408. referral = Referral(user_profile=user_profile, email=email)
  3409. referral.save()
  3410. user_profile.invites_used += 1
  3411. user_profile.save(update_fields=['invites_used'])
  3412.  
  3413. send_referral_event(user_profile)
  3414.  
  3415. def notify_realm_emoji(realm):
  3416. # type: (Realm) -> None
  3417. event = dict(type="realm_emoji", op="update",
  3418. realm_emoji=realm.get_emoji())
  3419. user_ids = [userdict['id'] for userdict in get_active_user_dicts_in_realm(realm)]
  3420. send_event(event, user_ids)
  3421.  
  3422. def check_add_realm_emoji(realm, name, img_url):
  3423. # type: (Realm, text_type, text_type) -> None
  3424. emoji = RealmEmoji(realm=realm, name=name, img_url=img_url)
  3425. emoji.full_clean()
  3426. emoji.save()
  3427. notify_realm_emoji(realm)
  3428.  
  3429. def do_remove_realm_emoji(realm, name):
  3430. # type: (Realm, text_type) -> None
  3431. RealmEmoji.objects.get(realm=realm, name=name).delete()
  3432. notify_realm_emoji(realm)
  3433.  
  3434. def notify_alert_words(user_profile, words):
  3435. # type: (UserProfile, Iterable[text_type]) -> None
  3436. event = dict(type="alert_words", alert_words=words)
  3437. send_event(event, [user_profile.id])
  3438.  
  3439. def do_add_alert_words(user_profile, alert_words):
  3440. # type: (UserProfile, Iterable[text_type]) -> None
  3441. words = add_user_alert_words(user_profile, alert_words)
  3442. notify_alert_words(user_profile, words)
  3443.  
  3444. def do_remove_alert_words(user_profile, alert_words):
  3445. # type: (UserProfile, Iterable[text_type]) -> None
  3446. words = remove_user_alert_words(user_profile, alert_words)
  3447. notify_alert_words(user_profile, words)
  3448.  
  3449. def do_set_alert_words(user_profile, alert_words):
  3450. # type: (UserProfile, List[text_type]) -> None
  3451. set_user_alert_words(user_profile, alert_words)
  3452. notify_alert_words(user_profile, alert_words)
  3453.  
  3454. def do_set_muted_topics(user_profile, muted_topics):
  3455. # type: (UserProfile, Union[List[List[text_type]], List[Tuple[text_type, text_type]]]) -> None
  3456. user_profile.muted_topics = ujson.dumps(muted_topics)
  3457. user_profile.save(update_fields=['muted_topics'])
  3458. event = dict(type="muted_topics", muted_topics=muted_topics)
  3459. send_event(event, [user_profile.id])
  3460.  
  3461. def notify_realm_filters(realm):
  3462. # type: (Realm) -> None
  3463. realm_filters = realm_filters_for_domain(realm.domain)
  3464. user_ids = [userdict['id'] for userdict in get_active_user_dicts_in_realm(realm)]
  3465. event = dict(type="realm_filters", realm_filters=realm_filters)
  3466. send_event(event, user_ids)
  3467.  
  3468. # NOTE: Regexes must be simple enough that they can be easily translated to JavaScript
  3469. # RegExp syntax. In addition to JS-compatible syntax, the following features are available:
  3470. # * Named groups will be converted to numbered groups automatically
  3471. # * Inline-regex flags will be stripped, and where possible translated to RegExp-wide flags
  3472. def do_add_realm_filter(realm, pattern, url_format_string):
  3473. # type: (Realm, text_type, text_type) -> int
  3474. pattern = pattern.strip()
  3475. url_format_string = url_format_string.strip()
  3476. realm_filter = RealmFilter(
  3477. realm=realm, pattern=pattern,
  3478. url_format_string=url_format_string)
  3479. realm_filter.full_clean()
  3480. realm_filter.save()
  3481. notify_realm_filters(realm)
  3482.  
  3483. return realm_filter.id
  3484.  
  3485. def do_remove_realm_filter(realm, pattern=None, id=None):
  3486. # type: (Realm, Optional[text_type], Optional[int]) -> None
  3487. if pattern is not None:
  3488. RealmFilter.objects.get(realm=realm, pattern=pattern).delete()
  3489. else:
  3490. RealmFilter.objects.get(realm=realm, pk=id).delete()
  3491. notify_realm_filters(realm)
  3492.  
  3493. def get_emails_from_user_ids(user_ids):
  3494. # type: (Sequence[int]) -> Dict[int, text_type]
  3495. # We may eventually use memcached to speed this up, but the DB is fast.
  3496. return UserProfile.emails_from_ids(user_ids)
  3497.  
  3498. def realm_aliases(realm):
  3499. # type: (Realm) -> List[text_type]
  3500. return [alias.domain for alias in realm.realmalias_set.all()]
  3501.  
  3502. def get_occupied_streams(realm):
  3503. # type: (Realm) -> QuerySet
  3504. # TODO: Make a generic stub for QuerySet
  3505. """ Get streams with subscribers """
  3506. subs_filter = Subscription.objects.filter(active=True, user_profile__realm=realm,
  3507. user_profile__is_active=True).values('recipient_id')
  3508. stream_ids = Recipient.objects.filter(
  3509. type=Recipient.STREAM, id__in=subs_filter).values('type_id')
  3510.  
  3511. return Stream.objects.filter(id__in=stream_ids, realm=realm, deactivated=False)
  3512.  
  3513. def do_get_streams(user_profile, include_public=True, include_subscribed=True,
  3514. include_all_active=False, include_default=False):
  3515. # type: (UserProfile, bool, bool, bool, bool) -> List[Dict[str, Any]]
  3516. if include_all_active and not user_profile.is_api_super_user:
  3517. raise JsonableError(_("User not authorized for this query"))
  3518.  
  3519. # Listing public streams are disabled for Zephyr mirroring realms.
  3520. include_public = include_public and not user_profile.realm.is_zephyr_mirror_realm
  3521. # Start out with all streams in the realm with subscribers
  3522. query = get_occupied_streams(user_profile.realm)
  3523.  
  3524. if not include_all_active:
  3525. user_subs = Subscription.objects.select_related("recipient").filter(
  3526. active=True, user_profile=user_profile,
  3527. recipient__type=Recipient.STREAM)
  3528.  
  3529. if include_subscribed:
  3530. recipient_check = Q(id__in=[sub.recipient.type_id for sub in user_subs])
  3531. if include_public:
  3532. invite_only_check = Q(invite_only=False)
  3533.  
  3534. if include_subscribed and include_public:
  3535. query = query.filter(recipient_check | invite_only_check)
  3536. elif include_public:
  3537. query = query.filter(invite_only_check)
  3538. elif include_subscribed:
  3539. query = query.filter(recipient_check)
  3540. else:
  3541. # We're including nothing, so don't bother hitting the DB.
  3542. query = []
  3543.  
  3544. streams = [(row.to_dict()) for row in query]
  3545. streams.sort(key=lambda elt: elt["name"])
  3546. if include_default:
  3547. is_default = {}
  3548. default_streams = get_default_streams_for_realm(user_profile.realm)
  3549. for default_stream in default_streams:
  3550. is_default[default_stream.id] = True
  3551. for stream in streams:
  3552. stream['is_default'] = is_default.get(stream["stream_id"], False)
  3553.  
  3554. return streams
  3555.  
  3556. def do_claim_attachments(message):
  3557. # type: (Message) -> List[Tuple[text_type, bool]]
  3558. attachment_url_list = attachment_url_re.findall(message.content)
  3559.  
  3560. results = []
  3561. for url in attachment_url_list:
  3562. path_id = attachment_url_to_path_id(url)
  3563. user_profile = message.sender
  3564. is_message_realm_public = False
  3565. if message.recipient.type == Recipient.STREAM:
  3566. is_message_realm_public = Stream.objects.get(id=message.recipient.type_id).is_public()
  3567.  
  3568. if path_id is not None:
  3569. is_claimed = claim_attachment(user_profile, path_id, message,
  3570. is_message_realm_public)
  3571. results.append((path_id, is_claimed))
  3572.  
  3573. return results
  3574.  
  3575. def do_delete_old_unclaimed_attachments(weeks_ago):
  3576. # type: (int) -> None
  3577. old_unclaimed_attachments = get_old_unclaimed_attachments(weeks_ago)
  3578.  
  3579. for attachment in old_unclaimed_attachments:
  3580. delete_message_image(attachment.path_id)
  3581. attachment.delete()
  3582.  
  3583. def check_attachment_reference_change(prev_content, message):
  3584. # type: (text_type, Message) -> None
  3585. new_content = message.content
  3586. prev_attachments = set(attachment_url_re.findall(prev_content))
  3587. new_attachments = set(attachment_url_re.findall(new_content))
  3588.  
  3589. to_remove = list(prev_attachments - new_attachments)
  3590. path_ids = []
  3591. for url in to_remove:
  3592. path_id = attachment_url_to_path_id(url)
  3593. path_ids.append(path_id)
  3594.  
  3595. attachments_to_update = Attachment.objects.filter(path_id__in=path_ids).select_for_update()
  3596. message.attachment_set.remove(*attachments_to_update)
  3597.  
  3598. to_add = list(new_attachments - prev_attachments)
  3599. if len(to_add) > 0:
  3600. do_claim_attachments(message)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement