def list_to_streams(streams_raw: Iterable[Mapping[str, Any]], user_profile: UserProfile, autocreate: bool=False) -> Tuple[List[Stream], List[Stream]]: """Converts list of dicts to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name using check_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream dictionaries to process; names should already be stripped of whitespace by the caller. @param user_profile The user for whom we are retreiving the streams @param autocreate Whether we should create streams if they don't already exist """ # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = set(stream_dict["name"] for stream_dict in streams_raw) for stream_name in stream_set: # Stream names should already have been stripped by the # caller, but it makes sense to verify anyway. assert stream_name == stream_name.strip() check_stream_name(stream_name) existing_streams = [] # type: List[Stream] missing_stream_dicts = [] # type: List[Mapping[str, Any]] existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) for stream_dict in streams_raw: stream_name = stream_dict["name"] stream = existing_stream_map.get(stream_name.lower()) if stream is None: missing_stream_dicts.append(stream_dict) else: existing_streams.append(stream) if len(missing_stream_dicts) == 0: # This is the happy path for callers who expected all of these # streams to exist already. created_streams = [] # type: List[Stream] else: # autocreate=True path starts here if not user_profile.can_create_streams(): raise JsonableError(_('User cannot create streams.')) elif not autocreate: raise JsonableError(_("Stream(s) (%s) do not exist") % ", ".join( stream_dict["name"] for stream_dict in missing_stream_dicts)) # We already filtered out existing streams, so dup_streams # will normally be an empty list below, but we protect against somebody # else racing to create the same stream. (This is not an entirely # paranoid approach, since often on Zulip two people will discuss # creating a new stream, and both people eagerly do it.) created_streams, dup_streams = create_streams_if_needed(realm=user_profile.realm, stream_dicts=missing_stream_dicts) existing_streams += dup_streams return existing_streams, created_streams
def apply_event(state: Dict[str, Any], event: Dict[str, Any], user_profile: UserProfile, client_gravatar: bool, include_subscribers: bool) -> None: if event['type'] == "message": state['max_message_id'] = max(state['max_message_id'], event['message']['id']) if 'raw_unread_msgs' in state: apply_unread_message_event( user_profile, state['raw_unread_msgs'], event['message'], event['flags'], ) elif event['type'] == "hotspots": state['hotspots'] = event['hotspots'] elif event['type'] == "custom_profile_fields": state['custom_profile_fields'] = event['fields'] elif event['type'] == "pointer": state['pointer'] = max(state['pointer'], event['pointer']) elif event['type'] == "realm_user": person = event['person'] person_user_id = person['user_id'] if event['op'] == "add": person = copy.deepcopy(person) if client_gravatar: if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['is_active'] = True if not person['is_bot']: person['profile_data'] = {} state['raw_users'][person_user_id] = person elif event['op'] == "remove": state['raw_users'][person_user_id]['is_active'] = False elif event['op'] == 'update': is_me = (person_user_id == user_profile.id) if is_me: if ('avatar_url' in person and 'avatar_url' in state): state['avatar_source'] = person['avatar_source'] state['avatar_url'] = person['avatar_url'] state['avatar_url_medium'] = person['avatar_url_medium'] for field in ['is_admin', 'email', 'full_name']: if field in person and field in state: state[field] = person[field] # In the unlikely event that the current user # just changed to/from being an admin, we need # to add/remove the data on all bots in the # realm. This is ugly and probably better # solved by removing the all-realm-bots data # given to admin users from this flow. if ('is_admin' in person and 'realm_bots' in state): prev_state = state['raw_users'][user_profile.id] was_admin = prev_state['is_admin'] now_admin = person['is_admin'] if was_admin and not now_admin: state['realm_bots'] = [] if not was_admin and now_admin: state['realm_bots'] = get_owned_bot_dicts(user_profile) if client_gravatar and 'avatar_url' in person: # Respect the client_gravatar setting in the `users` data. if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['avatar_url_medium'] = None if person_user_id in state['raw_users']: p = state['raw_users'][person_user_id] for field in p: if field in person: p[field] = person[field] if 'custom_profile_field' in person: custom_field_id = person['custom_profile_field']['id'] custom_field_new_value = person[ 'custom_profile_field']['value'] p['profile_data'][ custom_field_id] = custom_field_new_value elif event['type'] == 'realm_bot': if event['op'] == 'add': state['realm_bots'].append(event['bot']) if event['op'] == 'remove': email = event['bot']['email'] for bot in state['realm_bots']: if bot['email'] == email: bot['is_active'] = False if event['op'] == 'delete': state['realm_bots'] = [ item for item in state['realm_bots'] if item['email'] != event['bot']['email'] ] if event['op'] == 'update': for bot in state['realm_bots']: if bot['email'] == event['bot']['email']: if 'owner_id' in event['bot']: bot['owner'] = get_user_profile_by_id( event['bot']['owner_id']).email else: bot.update(event['bot']) elif event['type'] == 'stream': if event['op'] == 'create': for stream in event['streams']: if not stream['invite_only']: stream_data = copy.deepcopy(stream) if include_subscribers: stream_data['subscribers'] = [] stream_data['stream_weekly_traffic'] = None stream_data['is_old_stream'] = False stream_data['is_announcement_only'] = False # Add stream to never_subscribed (if not invite_only) state['never_subscribed'].append(stream_data) state['streams'].append(stream) state['streams'].sort(key=lambda elt: elt["name"]) if event['op'] == 'delete': deleted_stream_ids = { stream['stream_id'] for stream in event['streams'] } state['streams'] = [ s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids ] state['never_subscribed'] = [ stream for stream in state['never_subscribed'] if stream['stream_id'] not in deleted_stream_ids ] if event['op'] == 'update': # For legacy reasons, we call stream data 'subscriptions' in # the state var here, for the benefit of the JS code. for obj in state['subscriptions']: if obj['name'].lower() == event['name'].lower(): obj[event['property']] = event['value'] # Also update the pure streams data for stream in state['streams']: if stream['name'].lower() == event['name'].lower(): prop = event['property'] if prop in stream: stream[prop] = event['value'] elif event['op'] == "occupy": state['streams'] += event['streams'] elif event['op'] == "vacate": stream_ids = [s["stream_id"] for s in event['streams']] state['streams'] = [ s for s in state['streams'] if s["stream_id"] not in stream_ids ] elif event['type'] == 'default_streams': state['realm_default_streams'] = event['default_streams'] elif event['type'] == 'default_stream_groups': state['realm_default_stream_groups'] = event['default_stream_groups'] elif event['type'] == 'realm': if event['op'] == "update": field = 'realm_' + event['property'] state[field] = event['value'] # Tricky interaction: Whether we can create streams can get changed here. if (field in [ 'realm_create_stream_by_admins_only', 'realm_waiting_period_threshold' ]) and 'can_create_streams' in state: state['can_create_streams'] = user_profile.can_create_streams() elif event['op'] == "update_dict": for key, value in event['data'].items(): state['realm_' + key] = value # It's a bit messy, but this is where we need to # update the state for whether password authentication # is enabled on this server. if key == 'authentication_methods': state['realm_password_auth_enabled'] = (value['Email'] or value['LDAP']) state['realm_email_auth_enabled'] = value['Email'] elif event['type'] == "subscription": if not include_subscribers and event['op'] in [ 'peer_add', 'peer_remove' ]: return if event['op'] in ["add"]: if not include_subscribers: # Avoid letting 'subscribers' entries end up in the list for i, sub in enumerate(event['subscriptions']): event['subscriptions'][i] = copy.deepcopy( event['subscriptions'][i]) del event['subscriptions'][i]['subscribers'] def name(sub: Dict[str, Any]) -> str: return sub['name'].lower() if event['op'] == "add": added_names = set(map(name, event["subscriptions"])) was_added = lambda s: name(s) in added_names # add the new subscriptions state['subscriptions'] += event['subscriptions'] # remove them from unsubscribed if they had been there state['unsubscribed'] = [ s for s in state['unsubscribed'] if not was_added(s) ] # remove them from never_subscribed if they had been there state['never_subscribed'] = [ s for s in state['never_subscribed'] if not was_added(s) ] elif event['op'] == "remove": removed_names = set(map(name, event["subscriptions"])) was_removed = lambda s: name(s) in removed_names # Find the subs we are affecting. removed_subs = list(filter(was_removed, state['subscriptions'])) # Remove our user from the subscribers of the removed subscriptions. if include_subscribers: for sub in removed_subs: sub['subscribers'] = [ id for id in sub['subscribers'] if id != user_profile.id ] # We must effectively copy the removed subscriptions from subscriptions to # unsubscribe, since we only have the name in our data structure. state['unsubscribed'] += removed_subs # Now filter out the removed subscriptions from subscriptions. state['subscriptions'] = [ s for s in state['subscriptions'] if not was_removed(s) ] elif event['op'] == 'update': for sub in state['subscriptions']: if sub['name'].lower() == event['name'].lower(): sub[event['property']] = event['value'] elif event['op'] == 'peer_add': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) for sub in state['never_subscribed']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) elif event['op'] == 'peer_remove': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id in sub['subscribers']): sub['subscribers'].remove(user_id) elif event['type'] == "presence": # TODO: Add user_id to presence update events / state format! presence_user_profile = get_user(event['email'], user_profile.realm) state['presences'][ event['email']] = UserPresence.get_status_dict_by_user( presence_user_profile)[event['email']] elif event['type'] == "update_message": # We don't return messages in /register, so we don't need to # do anything for content updates, but we may need to update # the unread_msgs data if the topic of an unread message changed. if 'subject' in event: stream_dict = state['raw_unread_msgs']['stream_dict'] topic = event['subject'] for message_id in event['message_ids']: if message_id in stream_dict: stream_dict[message_id]['topic'] = topic elif event['type'] == "delete_message": max_message = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id').first() if max_message: state['max_message_id'] = max_message.id else: state['max_message_id'] = -1 remove_id = event['message_id'] remove_message_id_from_unread_mgs(state, remove_id) elif event['type'] == "reaction": # The client will get the message with the reactions directly pass elif event['type'] == "submessage": # The client will get submessages with their messages pass elif event['type'] == 'typing': # Typing notification events are transient and thus ignored pass elif event['type'] == "attachment": # Attachment events are just for updating the "uploads" UI; # they are not sent directly. pass elif event['type'] == "update_message_flags": # We don't return messages in `/register`, so most flags we # can ignore, but we do need to update the unread_msgs data if # unread state is changed. if event['flag'] == 'read' and event['operation'] == 'add': for remove_id in event['messages']: remove_message_id_from_unread_mgs(state, remove_id) elif event['type'] == "realm_domains": if event['op'] == 'add': state['realm_domains'].append(event['realm_domain']) elif event['op'] == 'change': for realm_domain in state['realm_domains']: if realm_domain['domain'] == event['realm_domain']['domain']: realm_domain['allow_subdomains'] = event['realm_domain'][ 'allow_subdomains'] elif event['op'] == 'remove': state['realm_domains'] = [ realm_domain for realm_domain in state['realm_domains'] if realm_domain['domain'] != event['domain'] ] elif event['type'] == "realm_emoji": state['realm_emoji'] = event['realm_emoji'] elif event['type'] == "alert_words": state['alert_words'] = event['alert_words'] elif event['type'] == "muted_topics": state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] elif event['type'] == "update_display_settings": assert event['setting_name'] in UserProfile.property_types state[event['setting_name']] = event['setting'] elif event['type'] == "update_global_notifications": assert event[ 'notification_name'] in UserProfile.notification_setting_types state[event['notification_name']] = event['setting'] elif event['type'] == "invites_changed": pass elif event['type'] == "user_group": if event['op'] == 'add': state['realm_user_groups'].append(event['group']) state['realm_user_groups'].sort(key=lambda group: group['id']) elif event['op'] == 'update': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group.update(event['data']) elif event['op'] == 'add_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group['members'].extend(event['user_ids']) user_group['members'].sort() elif event['op'] == 'remove_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: members = set(user_group['members']) user_group['members'] = list(members - set(event['user_ids'])) user_group['members'].sort() elif event['op'] == 'remove': state['realm_user_groups'] = [ ug for ug in state['realm_user_groups'] if ug['id'] != event['group_id'] ] else: raise AssertionError("Unexpected event type %s" % (event['type'], ))
def fetch_initial_state_data( user_profile: UserProfile, event_types: Optional[Iterable[str]], queue_id: str, client_gravatar: bool, include_subscribers: bool = True) -> Dict[str, Any]: state = {'queue_id': queue_id} # type: Dict[str, Any] realm = user_profile.realm if event_types is None: # return True always want = always_want # type: Callable[[str], bool] else: want = set(event_types).__contains__ if want('alert_words'): state['alert_words'] = user_alert_words(user_profile) if want('custom_profile_fields'): fields = custom_profile_fields_for_realm(realm.id) state['custom_profile_fields'] = [f.as_dict() for f in fields] state[ 'custom_profile_field_types'] = CustomProfileField.FIELD_TYPE_CHOICES if want('hotspots'): state['hotspots'] = get_next_hotspots(user_profile) if want('message'): # The client should use get_messages() to fetch messages # starting with the max_message_id. They will get messages # newer than that ID via get_events() messages = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id')[:1] if messages: state['max_message_id'] = messages[0].id else: state['max_message_id'] = -1 if want('muted_topics'): state['muted_topics'] = get_topic_mutes(user_profile) if want('pointer'): state['pointer'] = user_profile.pointer if want('presence'): state['presences'] = get_status_dict(user_profile) if want('realm'): for property_name in Realm.property_types: state['realm_' + property_name] = getattr(realm, property_name) # Most state is handled via the property_types framework; # these manual entries are for those realm settings that don't # fit into that framework. state[ 'realm_authentication_methods'] = realm.authentication_methods_dict( ) state['realm_allow_message_editing'] = realm.allow_message_editing state[ 'realm_allow_community_topic_editing'] = realm.allow_community_topic_editing state['realm_allow_message_deleting'] = realm.allow_message_deleting state[ 'realm_message_content_edit_limit_seconds'] = realm.message_content_edit_limit_seconds state[ 'realm_message_content_delete_limit_seconds'] = realm.message_content_delete_limit_seconds state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_source'] = realm.icon_source state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE state['realm_bot_domain'] = realm.get_bot_domain() state['realm_uri'] = realm.uri state[ 'realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS state['realm_presence_disabled'] = realm.presence_disabled state[ 'realm_digest_emails_enabled'] = realm.digest_emails_enabled and settings.SEND_DIGEST_EMAILS state['realm_is_zephyr_mirror_realm'] = realm.is_zephyr_mirror_realm state['realm_email_auth_enabled'] = email_auth_enabled(realm) state['realm_password_auth_enabled'] = password_auth_enabled(realm) state['realm_push_notifications_enabled'] = push_notifications_enabled( ) if realm.notifications_stream and not realm.notifications_stream.deactivated: notifications_stream = realm.notifications_stream state['realm_notifications_stream_id'] = notifications_stream.id else: state['realm_notifications_stream_id'] = -1 signup_notifications_stream = realm.get_signup_notifications_stream() if signup_notifications_stream: state[ 'realm_signup_notifications_stream_id'] = signup_notifications_stream.id else: state['realm_signup_notifications_stream_id'] = -1 if want('realm_domains'): state['realm_domains'] = get_realm_domains(realm) if want('realm_emoji'): state['realm_emoji'] = realm.get_emoji() if want('realm_filters'): state['realm_filters'] = realm_filters_for_realm(realm.id) if want('realm_user_groups'): state['realm_user_groups'] = user_groups_in_realm_serialized(realm) if want('realm_user'): state['raw_users'] = get_raw_user_data( realm_id=realm.id, client_gravatar=client_gravatar, ) # For the user's own avatar URL, we force # client_gravatar=False, since that saves some unnecessary # client-side code for handing medium-size avatars. See #8253 # for details. state['avatar_source'] = user_profile.avatar_source state['avatar_url_medium'] = avatar_url( user_profile, medium=True, client_gravatar=False, ) state['avatar_url'] = avatar_url( user_profile, medium=False, client_gravatar=False, ) state['can_create_streams'] = user_profile.can_create_streams() state['cross_realm_bots'] = list(get_cross_realm_dicts()) state['is_admin'] = user_profile.is_realm_admin state['is_guest'] = user_profile.is_guest state['user_id'] = user_profile.id state['enter_sends'] = user_profile.enter_sends state['email'] = user_profile.email state['delivery_email'] = user_profile.delivery_email state['full_name'] = user_profile.full_name if want('realm_bot'): state['realm_bots'] = get_owned_bot_dicts(user_profile) # This does not yet have an apply_event counterpart, since currently, # new entries for EMBEDDED_BOTS can only be added directly in the codebase. if want('realm_embedded_bots'): realm_embedded_bots = [] for bot in EMBEDDED_BOTS: realm_embedded_bots.append({ 'name': bot.name, 'config': load_bot_config_template(bot.name) }) state['realm_embedded_bots'] = realm_embedded_bots if want('subscription'): subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers) state['subscriptions'] = subscriptions state['unsubscribed'] = unsubscribed state['never_subscribed'] = never_subscribed if want('update_message_flags') and want('message'): # Keeping unread_msgs updated requires both message flag updates and # message updates. This is due to the fact that new messages will not # generate a flag update so we need to use the flags field in the # message event. state['raw_unread_msgs'] = get_raw_unread_data(user_profile) if want('stream'): state['streams'] = do_get_streams(user_profile) state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH if want('default_streams'): state['realm_default_streams'] = streams_to_dicts_sorted( get_default_streams_for_realm(realm.id)) if want('default_stream_groups'): state[ 'realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted( get_default_stream_groups(realm)) if want('update_display_settings'): for prop in UserProfile.property_types: state[prop] = getattr(user_profile, prop) state['emojiset_choices'] = user_profile.emojiset_choices() if want('update_global_notifications'): for notification in UserProfile.notification_setting_types: state[notification] = getattr(user_profile, notification) if want('zulip_version'): state['zulip_version'] = ZULIP_VERSION return state
def fetch_initial_state_data( user_profile: UserProfile, event_types: Optional[Iterable[str]], queue_id: str, client_gravatar: bool, user_avatar_url_field_optional: bool, slim_presence: bool = False, include_subscribers: bool = True) -> Dict[str, Any]: state: Dict[str, Any] = {'queue_id': queue_id} realm = user_profile.realm if event_types is None: # return True always want: Callable[[str], bool] = always_want else: want = set(event_types).__contains__ # Show the version info unconditionally. state['zulip_version'] = ZULIP_VERSION state['zulip_feature_level'] = API_FEATURE_LEVEL if want('alert_words'): state['alert_words'] = user_alert_words(user_profile) if want('custom_profile_fields'): fields = custom_profile_fields_for_realm(realm.id) state['custom_profile_fields'] = [f.as_dict() for f in fields] state[ 'custom_profile_field_types'] = CustomProfileField.FIELD_TYPE_CHOICES_DICT if want('hotspots'): state['hotspots'] = get_next_hotspots(user_profile) if want('message'): # The client should use get_messages() to fetch messages # starting with the max_message_id. They will get messages # newer than that ID via get_events() user_messages = UserMessage.objects \ .filter(user_profile=user_profile) \ .order_by('-message_id') \ .values('message_id')[:1] if user_messages: state['max_message_id'] = user_messages[0]['message_id'] else: state['max_message_id'] = -1 if want('muted_topics'): state['muted_topics'] = get_topic_mutes(user_profile) if want('presence'): state['presences'] = get_presences_for_realm(realm, user_profile, slim_presence) if want('realm'): for property_name in Realm.property_types: state['realm_' + property_name] = getattr(realm, property_name) # Most state is handled via the property_types framework; # these manual entries are for those realm settings that don't # fit into that framework. state[ 'realm_authentication_methods'] = realm.authentication_methods_dict( ) state['realm_allow_message_editing'] = realm.allow_message_editing state[ 'realm_allow_community_topic_editing'] = realm.allow_community_topic_editing state['realm_allow_message_deleting'] = realm.allow_message_deleting state[ 'realm_message_content_edit_limit_seconds'] = realm.message_content_edit_limit_seconds state[ 'realm_message_content_delete_limit_seconds'] = realm.message_content_delete_limit_seconds state['realm_community_topic_editing_limit_seconds'] = \ Realm.DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_source'] = realm.icon_source state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE add_realm_logo_fields(state, realm) state['realm_bot_domain'] = realm.get_bot_domain() state['realm_uri'] = realm.uri state[ 'realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS state['realm_presence_disabled'] = realm.presence_disabled state['settings_send_digest_emails'] = settings.SEND_DIGEST_EMAILS state[ 'realm_digest_emails_enabled'] = realm.digest_emails_enabled and settings.SEND_DIGEST_EMAILS state['realm_is_zephyr_mirror_realm'] = realm.is_zephyr_mirror_realm state['realm_email_auth_enabled'] = email_auth_enabled(realm) state['realm_password_auth_enabled'] = password_auth_enabled(realm) state['realm_push_notifications_enabled'] = push_notifications_enabled( ) state['realm_upload_quota'] = realm.upload_quota_bytes() state['realm_plan_type'] = realm.plan_type state['zulip_plan_is_not_limited'] = realm.plan_type != Realm.LIMITED state['upgrade_text_for_wide_organization_logo'] = str( Realm.UPGRADE_TEXT_STANDARD) state['realm_default_external_accounts'] = DEFAULT_EXTERNAL_ACCOUNTS state['jitsi_server_url'] = settings.JITSI_SERVER_URL state['development_environment'] = settings.DEVELOPMENT state['server_generation'] = settings.SERVER_GENERATION state['password_min_length'] = settings.PASSWORD_MIN_LENGTH state['password_min_guesses'] = settings.PASSWORD_MIN_GUESSES state['max_file_upload_size_mib'] = settings.MAX_FILE_UPLOAD_SIZE state['max_avatar_file_size_mib'] = settings.MAX_AVATAR_FILE_SIZE state['server_inline_image_preview'] = settings.INLINE_IMAGE_PREVIEW state[ 'server_inline_url_embed_preview'] = settings.INLINE_URL_EMBED_PREVIEW state[ 'server_avatar_changes_disabled'] = settings.AVATAR_CHANGES_DISABLED state['server_name_changes_disabled'] = settings.NAME_CHANGES_DISABLED if realm.notifications_stream and not realm.notifications_stream.deactivated: notifications_stream = realm.notifications_stream state['realm_notifications_stream_id'] = notifications_stream.id else: state['realm_notifications_stream_id'] = -1 signup_notifications_stream = realm.get_signup_notifications_stream() if signup_notifications_stream: state[ 'realm_signup_notifications_stream_id'] = signup_notifications_stream.id else: state['realm_signup_notifications_stream_id'] = -1 if want('realm_domains'): state['realm_domains'] = get_realm_domains(realm) if want('realm_emoji'): state['realm_emoji'] = realm.get_emoji() if want('realm_filters'): state['realm_filters'] = realm_filters_for_realm(realm.id) if want('realm_user_groups'): state['realm_user_groups'] = user_groups_in_realm_serialized(realm) if want('realm_filtered_user'): state['filtered_users'] = get_filtered_user_data( realm, user_profile, client_gravatar=client_gravatar, user_avatar_url_field_optional=user_avatar_url_field_optional) # For the user's own avatar URL, we force # client_gravatar=False, since that saves some unnecessary # client-side code for handing medium-size avatars. See #8253 # for details. state['avatar_source'] = user_profile.avatar_source state['avatar_url_medium'] = avatar_url( user_profile, medium=True, client_gravatar=False, ) state['avatar_url'] = avatar_url( user_profile, medium=False, client_gravatar=False, ) state['can_create_streams'] = user_profile.can_create_streams() state[ 'can_subscribe_other_users'] = user_profile.can_subscribe_other_users( ) state['cross_realm_bots'] = list(get_cross_realm_dicts()) state['is_admin'] = user_profile.is_realm_admin state['is_owner'] = user_profile.is_realm_owner state['is_guest'] = user_profile.is_guest state['user_id'] = user_profile.id state['enter_sends'] = user_profile.enter_sends state['email'] = user_profile.email state['delivery_email'] = user_profile.delivery_email state['full_name'] = user_profile.full_name if want('realm_user'): state['raw_users'] = get_raw_user_data( realm, user_profile, client_gravatar=client_gravatar, user_avatar_url_field_optional=user_avatar_url_field_optional) # For the user's own avatar URL, we force # client_gravatar=False, since that saves some unnecessary # client-side code for handing medium-size avatars. See #8253 # for details. state['avatar_source'] = user_profile.avatar_source state['avatar_url_medium'] = avatar_url( user_profile, medium=True, client_gravatar=False, ) state['avatar_url'] = avatar_url( user_profile, medium=False, client_gravatar=False, ) state['can_create_streams'] = user_profile.can_create_streams() state[ 'can_subscribe_other_users'] = user_profile.can_subscribe_other_users( ) state['cross_realm_bots'] = list(get_cross_realm_dicts()) state['is_admin'] = user_profile.is_realm_admin state['is_owner'] = user_profile.is_realm_owner state['is_guest'] = user_profile.is_guest state['user_id'] = user_profile.id state['enter_sends'] = user_profile.enter_sends state['email'] = user_profile.email state['delivery_email'] = user_profile.delivery_email state['full_name'] = user_profile.full_name if want('realm_bot'): state['realm_bots'] = get_owned_bot_dicts(user_profile) # This does not yet have an apply_event counterpart, since currently, # new entries for EMBEDDED_BOTS can only be added directly in the codebase. if want('realm_embedded_bots'): realm_embedded_bots = [] for bot in EMBEDDED_BOTS: realm_embedded_bots.append({ 'name': bot.name, 'config': load_bot_config_template(bot.name) }) state['realm_embedded_bots'] = realm_embedded_bots # This does not have an apply_events counterpart either since # this data is mostly static. if want('realm_incoming_webhook_bots'): realm_incoming_webhook_bots = [] for integration in WEBHOOK_INTEGRATIONS: realm_incoming_webhook_bots.append({ 'name': integration.name, 'config': {c[1]: c[0] for c in integration.config_options}, }) state['realm_incoming_webhook_bots'] = realm_incoming_webhook_bots if want('recent_private_conversations'): # A data structure containing records of this form: # # [{'max_message_id': 700175, 'user_ids': [801]}] # # for all recent private message conversations, ordered by the # highest message ID in the conversation. The user_ids list # is the list of users other than the current user in the # private message conversation (so it is [] for PMs to self). # Note that raw_recent_private_conversations is an # intermediate form as a dictionary keyed by recipient_id, # which is more efficient to update, and is rewritten to the # final format in post_process_state. state[ 'raw_recent_private_conversations'] = get_recent_private_conversations( user_profile) if want('subscription'): subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers) state['subscriptions'] = subscriptions state['unsubscribed'] = unsubscribed state['never_subscribed'] = never_subscribed if want('update_message_flags') and want('message'): # Keeping unread_msgs updated requires both message flag updates and # message updates. This is due to the fact that new messages will not # generate a flag update so we need to use the flags field in the # message event. state['raw_unread_msgs'] = get_raw_unread_data(user_profile) if want('starred_messages'): state['starred_messages'] = get_starred_message_ids(user_profile) if want('stream'): state['streams'] = do_get_streams(user_profile) state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH if want('default_streams'): if user_profile.is_guest: state['realm_default_streams'] = [] else: state['realm_default_streams'] = streams_to_dicts_sorted( get_default_streams_for_realm(realm.id)) if want('default_stream_groups'): if user_profile.is_guest: state['realm_default_stream_groups'] = [] else: state[ 'realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted( get_default_stream_groups(realm)) if want('stop_words'): state['stop_words'] = read_stop_words() if want('update_display_settings'): for prop in UserProfile.property_types: state[prop] = getattr(user_profile, prop) state['emojiset_choices'] = user_profile.emojiset_choices() if want('update_global_notifications'): for notification in UserProfile.notification_setting_types: state[notification] = getattr(user_profile, notification) state[ 'available_notification_sounds'] = get_available_notification_sounds( ) if want('user_status'): state['user_status'] = get_user_info_dict(realm_id=realm.id) if want('video_calls'): state['has_zoom_token'] = user_profile.zoom_token is not None return state
def apply_event(state: Dict[str, Any], event: Dict[str, Any], user_profile: UserProfile, client_gravatar: bool, slim_presence: bool, include_subscribers: bool) -> None: if event['type'] == "message": state['max_message_id'] = max(state['max_message_id'], event['message']['id']) if 'raw_unread_msgs' in state: apply_unread_message_event( user_profile, state['raw_unread_msgs'], event['message'], event['flags'], ) if event['message']['type'] != "stream": if 'raw_recent_private_conversations' in state: # Handle maintaining the recent_private_conversations data structure. conversations = state['raw_recent_private_conversations'] recipient_id = get_recent_conversations_recipient_id( user_profile, event['message']['recipient_id'], event['message']["sender_id"]) if recipient_id not in conversations: conversations[recipient_id] = dict(user_ids=sorted([ user_dict['id'] for user_dict in event['message']['display_recipient'] if user_dict['id'] != user_profile.id ]), ) conversations[recipient_id]['max_message_id'] = event[ 'message']['id'] return # Below, we handle maintaining first_message_id. for sub_dict in state.get('subscriptions', []): if event['message']['stream_id'] == sub_dict['stream_id']: if sub_dict['first_message_id'] is None: sub_dict['first_message_id'] = event['message']['id'] for stream_dict in state.get('streams', []): if event['message']['stream_id'] == stream_dict['stream_id']: if stream_dict['first_message_id'] is None: stream_dict['first_message_id'] = event['message']['id'] elif event['type'] == "hotspots": state['hotspots'] = event['hotspots'] elif event['type'] == "custom_profile_fields": state['custom_profile_fields'] = event['fields'] elif event['type'] == "realm_user": person = event['person'] person_user_id = person['user_id'] if event['op'] == "add": person = copy.deepcopy(person) if client_gravatar: if person['avatar_url'].startswith( "https://secure.gravatar.com"): person['avatar_url'] = None person['is_active'] = True if not person['is_bot']: person['profile_data'] = {} state['raw_users'][person_user_id] = person elif event['op'] == "remove": state['raw_users'][person_user_id]['is_active'] = False elif event['op'] == 'update': is_me = (person_user_id == user_profile.id) if is_me: if ('avatar_url' in person and 'avatar_url' in state): state['avatar_source'] = person['avatar_source'] state['avatar_url'] = person['avatar_url'] state['avatar_url_medium'] = person['avatar_url_medium'] if 'role' in person: state['is_admin'] = is_administrator_role(person['role']) state['is_owner'] = person[ 'role'] == UserProfile.ROLE_REALM_OWNER state['is_guest'] = person[ 'role'] == UserProfile.ROLE_GUEST # Recompute properties based on is_admin/is_guest state[ 'can_create_streams'] = user_profile.can_create_streams( ) state[ 'can_subscribe_other_users'] = user_profile.can_subscribe_other_users( ) # TODO: Probably rather than writing the perfect # live-update code for the case of racing with the # current user changing roles, we should just do a # full refetch. if 'never_subscribed' in state: subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers) state['subscriptions'] = subscriptions state['unsubscribed'] = unsubscribed state['never_subscribed'] = never_subscribed if 'streams' in state: state['streams'] = do_get_streams(user_profile) for field in ['delivery_email', 'email', 'full_name']: if field in person and field in state: state[field] = person[field] # In the unlikely event that the current user # just changed to/from being an admin, we need # to add/remove the data on all bots in the # realm. This is ugly and probably better # solved by removing the all-realm-bots data # given to admin users from this flow. if ('role' in person and 'realm_bots' in state): prev_state = state['raw_users'][user_profile.id] was_admin = prev_state['is_admin'] now_admin = is_administrator_role(person['role']) if was_admin and not now_admin: state['realm_bots'] = [] if not was_admin and now_admin: state['realm_bots'] = get_owned_bot_dicts(user_profile) if client_gravatar and 'avatar_url' in person: # Respect the client_gravatar setting in the `users` data. if person['avatar_url'].startswith( "https://secure.gravatar.com"): person['avatar_url'] = None person['avatar_url_medium'] = None if person_user_id in state['raw_users']: p = state['raw_users'][person_user_id] for field in p: if field in person: p[field] = person[field] if 'role' in person: p['is_admin'] = is_administrator_role(person['role']) p['is_owner'] = person[ 'role'] == UserProfile.ROLE_REALM_OWNER p['is_guest'] = person[ 'role'] == UserProfile.ROLE_GUEST if 'custom_profile_field' in person: custom_field_id = person['custom_profile_field']['id'] custom_field_new_value = person[ 'custom_profile_field']['value'] if 'rendered_value' in person['custom_profile_field']: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value, 'rendered_value': person['custom_profile_field'] ['rendered_value'], } else: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value, } elif event['type'] == 'realm_bot': if event['op'] == 'add': state['realm_bots'].append(event['bot']) if event['op'] == 'remove': user_id = event['bot']['user_id'] for bot in state['realm_bots']: if bot['user_id'] == user_id: bot['is_active'] = False if event['op'] == 'delete': state['realm_bots'] = [ item for item in state['realm_bots'] if item['user_id'] != event['bot']['user_id'] ] if event['op'] == 'update': for bot in state['realm_bots']: if bot['user_id'] == event['bot']['user_id']: if 'owner_id' in event['bot']: bot_owner_id = event['bot']['owner_id'] bot['owner_id'] = bot_owner_id else: bot.update(event['bot']) elif event['type'] == 'stream': if event['op'] == 'create': for stream in event['streams']: if not stream['invite_only']: stream_data = copy.deepcopy(stream) if include_subscribers: stream_data['subscribers'] = [] # We know the stream has no traffic, and this # field is not present in the event. # # TODO: Probably this should just be added to the event. stream_data['stream_weekly_traffic'] = None # Add stream to never_subscribed (if not invite_only) state['never_subscribed'].append(stream_data) state['streams'].append(stream) state['streams'].sort(key=lambda elt: elt["name"]) if event['op'] == 'delete': deleted_stream_ids = { stream['stream_id'] for stream in event['streams'] } state['streams'] = [ s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids ] state['never_subscribed'] = [ stream for stream in state['never_subscribed'] if stream['stream_id'] not in deleted_stream_ids ] if event['op'] == 'update': # For legacy reasons, we call stream data 'subscriptions' in # the state var here, for the benefit of the JS code. for obj in state['subscriptions']: if obj['name'].lower() == event['name'].lower(): obj[event['property']] = event['value'] if event['property'] == "description": obj['rendered_description'] = event[ 'rendered_description'] # Also update the pure streams data for stream in state['streams']: if stream['name'].lower() == event['name'].lower(): prop = event['property'] if prop in stream: stream[prop] = event['value'] if prop == 'description': stream['rendered_description'] = event[ 'rendered_description'] elif event['op'] == "occupy": state['streams'] += event['streams'] elif event['op'] == "vacate": stream_ids = [s["stream_id"] for s in event['streams']] state['streams'] = [ s for s in state['streams'] if s["stream_id"] not in stream_ids ] elif event['type'] == 'default_streams': state['realm_default_streams'] = event['default_streams'] elif event['type'] == 'default_stream_groups': state['realm_default_stream_groups'] = event['default_stream_groups'] elif event['type'] == 'realm': if event['op'] == "update": field = 'realm_' + event['property'] state[field] = event['value'] if event['property'] == 'plan_type': # Then there are some extra fields that also need to be set. state['zulip_plan_is_not_limited'] = event[ 'value'] != Realm.LIMITED state['realm_upload_quota'] = event['extra_data'][ 'upload_quota'] policy_permission_dict = { 'create_stream_policy': 'can_create_streams', 'invite_to_stream_policy': 'can_subscribe_other_users' } # Tricky interaction: Whether we can create streams and can subscribe other users # can get changed here. if field == 'realm_waiting_period_threshold': for policy, permission in policy_permission_dict.items(): if permission in state: state[permission] = user_profile.has_permission(policy) if event['property'] in policy_permission_dict.keys(): if policy_permission_dict[event['property']] in state: state[policy_permission_dict[ event['property']]] = user_profile.has_permission( event['property']) elif event['op'] == "update_dict": for key, value in event['data'].items(): state['realm_' + key] = value # It's a bit messy, but this is where we need to # update the state for whether password authentication # is enabled on this server. if key == 'authentication_methods': state['realm_password_auth_enabled'] = (value['Email'] or value['LDAP']) state['realm_email_auth_enabled'] = value['Email'] elif event['type'] == "subscription": if not include_subscribers and event['op'] in [ 'peer_add', 'peer_remove' ]: return if event['op'] in ["add"]: if not include_subscribers: # Avoid letting 'subscribers' entries end up in the list for i, sub in enumerate(event['subscriptions']): event['subscriptions'][i] = copy.deepcopy( event['subscriptions'][i]) del event['subscriptions'][i]['subscribers'] def name(sub: Dict[str, Any]) -> str: return sub['name'].lower() if event['op'] == "add": added_names = set(map(name, event["subscriptions"])) was_added = lambda s: name(s) in added_names # add the new subscriptions state['subscriptions'] += event['subscriptions'] # remove them from unsubscribed if they had been there state['unsubscribed'] = [ s for s in state['unsubscribed'] if not was_added(s) ] # remove them from never_subscribed if they had been there state['never_subscribed'] = [ s for s in state['never_subscribed'] if not was_added(s) ] elif event['op'] == "remove": removed_names = set(map(name, event["subscriptions"])) was_removed = lambda s: name(s) in removed_names # Find the subs we are affecting. removed_subs = list(filter(was_removed, state['subscriptions'])) # Remove our user from the subscribers of the removed subscriptions. if include_subscribers: for sub in removed_subs: sub['subscribers'].remove(user_profile.id) # We must effectively copy the removed subscriptions from subscriptions to # unsubscribe, since we only have the name in our data structure. state['unsubscribed'] += removed_subs # Now filter out the removed subscriptions from subscriptions. state['subscriptions'] = [ s for s in state['subscriptions'] if not was_removed(s) ] elif event['op'] == 'update': for sub in state['subscriptions']: if sub['name'].lower() == event['name'].lower(): sub[event['property']] = event['value'] elif event['op'] == 'peer_add': stream_id = event['stream_id'] user_id = event['user_id'] for sub in state['subscriptions']: if (sub['stream_id'] == stream_id and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) for sub in state['never_subscribed']: if (sub['stream_id'] == stream_id and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) elif event['op'] == 'peer_remove': stream_id = event['stream_id'] user_id = event['user_id'] for sub in state['subscriptions']: if (sub['stream_id'] == stream_id and user_id in sub['subscribers']): sub['subscribers'].remove(user_id) elif event['type'] == "presence": if slim_presence: user_key = str(event['user_id']) else: user_key = event['email'] state['presences'][user_key] = get_presence_for_user( event['user_id'], slim_presence)[user_key] elif event['type'] == "update_message": # We don't return messages in /register, so we don't need to # do anything for content updates, but we may need to update # the unread_msgs data if the topic of an unread message changed. if 'new_stream_id' in event: stream_dict = state['raw_unread_msgs']['stream_dict'] stream_id = event['new_stream_id'] for message_id in event['message_ids']: if message_id in stream_dict: stream_dict[message_id]['stream_id'] = stream_id if TOPIC_NAME in event: stream_dict = state['raw_unread_msgs']['stream_dict'] topic = event[TOPIC_NAME] for message_id in event['message_ids']: if message_id in stream_dict: stream_dict[message_id]['topic'] = topic elif event['type'] == "delete_message": if 'message_id' in event: message_ids = [event['message_id']] else: message_ids = event['message_ids'] # nocoverage max_message = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id').first() if max_message: state['max_message_id'] = max_message.id else: state['max_message_id'] = -1 if 'raw_unread_msgs' in state: for remove_id in message_ids: remove_message_id_from_unread_mgs(state['raw_unread_msgs'], remove_id) # The remainder of this block is about maintaining recent_private_conversations if 'raw_recent_private_conversations' not in state or event[ 'message_type'] != 'private': return recipient_id = get_recent_conversations_recipient_id( user_profile, event['recipient_id'], event['sender_id']) # Ideally, we'd have test coverage for these two blocks. To # do that, we'll need a test where we delete not-the-latest # messages or delete a private message not in # recent_private_conversations. if recipient_id not in state[ 'raw_recent_private_conversations']: # nocoverage return old_max_message_id = state['raw_recent_private_conversations'][ recipient_id]['max_message_id'] if old_max_message_id not in message_ids: # nocoverage return # OK, we just deleted what had been the max_message_id for # this recent conversation; we need to recompute that value # from scratch. Definitely don't need to re-query everything, # but this case is likely rare enough that it's reasonable to do so. state['raw_recent_private_conversations'] = \ get_recent_private_conversations(user_profile) elif event['type'] == "reaction": # The client will get the message with the reactions directly pass elif event['type'] == "submessage": # The client will get submessages with their messages pass elif event['type'] == 'typing': # Typing notification events are transient and thus ignored pass elif event['type'] == "attachment": # Attachment events are just for updating the "uploads" UI; # they are not sent directly. pass elif event['type'] == "update_message_flags": # We don't return messages in `/register`, so most flags we # can ignore, but we do need to update the unread_msgs data if # unread state is changed. if 'raw_unread_msgs' in state and event['flag'] == 'read' and event[ 'operation'] == 'add': for remove_id in event['messages']: remove_message_id_from_unread_mgs(state['raw_unread_msgs'], remove_id) if event['flag'] == 'starred' and 'starred_messages' in state: if event['operation'] == 'add': state['starred_messages'] += event['messages'] if event['operation'] == 'remove': state['starred_messages'] = [ message for message in state['starred_messages'] if not (message in event['messages']) ] elif event['type'] == "realm_domains": if event['op'] == 'add': state['realm_domains'].append(event['realm_domain']) elif event['op'] == 'change': for realm_domain in state['realm_domains']: if realm_domain['domain'] == event['realm_domain']['domain']: realm_domain['allow_subdomains'] = event['realm_domain'][ 'allow_subdomains'] elif event['op'] == 'remove': state['realm_domains'] = [ realm_domain for realm_domain in state['realm_domains'] if realm_domain['domain'] != event['domain'] ] elif event['type'] == "realm_emoji": state['realm_emoji'] = event['realm_emoji'] elif event['type'] == 'realm_export': # These realm export events are only available to # administrators, and aren't included in page_params. pass elif event['type'] == "alert_words": state['alert_words'] = event['alert_words'] elif event['type'] == "muted_topics": state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] elif event['type'] == "update_display_settings": assert event['setting_name'] in UserProfile.property_types state[event['setting_name']] = event['setting'] elif event['type'] == "update_global_notifications": assert event[ 'notification_name'] in UserProfile.notification_setting_types state[event['notification_name']] = event['setting'] elif event['type'] == "invites_changed": pass elif event['type'] == "user_group": if event['op'] == 'add': state['realm_user_groups'].append(event['group']) state['realm_user_groups'].sort(key=lambda group: group['id']) elif event['op'] == 'update': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group.update(event['data']) elif event['op'] == 'add_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group['members'].extend(event['user_ids']) user_group['members'].sort() elif event['op'] == 'remove_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: members = set(user_group['members']) user_group['members'] = list(members - set(event['user_ids'])) user_group['members'].sort() elif event['op'] == 'remove': state['realm_user_groups'] = [ ug for ug in state['realm_user_groups'] if ug['id'] != event['group_id'] ] elif event['type'] == 'user_status': user_id = event['user_id'] user_status = state['user_status'] away = event.get('away') status_text = event.get('status_text') if user_id not in user_status: user_status[user_id] = dict() if away is not None: if away: user_status[user_id]['away'] = True else: user_status[user_id].pop('away', None) if status_text is not None: if status_text == '': user_status[user_id].pop('status_text', None) else: user_status[user_id]['status_text'] = status_text if not user_status[user_id]: user_status.pop(user_id, None) state['user_status'] = user_status elif event['type'] == 'has_zoom_token': state['has_zoom_token'] = event['value'] else: raise AssertionError("Unexpected event type {}".format(event['type']))
def apply_event(state: Dict[str, Any], event: Dict[str, Any], user_profile: UserProfile, client_gravatar: bool, include_subscribers: bool) -> None: if event['type'] == "message": state['max_message_id'] = max(state['max_message_id'], event['message']['id']) if 'raw_unread_msgs' in state: apply_unread_message_event( user_profile, state['raw_unread_msgs'], event['message'], event['flags'], ) elif event['type'] == "hotspots": state['hotspots'] = event['hotspots'] elif event['type'] == "custom_profile_fields": state['custom_profile_fields'] = event['fields'] elif event['type'] == "pointer": state['pointer'] = max(state['pointer'], event['pointer']) elif event['type'] == "realm_user": person = event['person'] person_user_id = person['user_id'] if event['op'] == "add": person = copy.deepcopy(person) if client_gravatar: if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['is_active'] = True if not person['is_bot']: person['profile_data'] = {} state['raw_users'][person_user_id] = person elif event['op'] == "remove": state['raw_users'][person_user_id]['is_active'] = False elif event['op'] == 'update': is_me = (person_user_id == user_profile.id) if is_me: if ('avatar_url' in person and 'avatar_url' in state): state['avatar_source'] = person['avatar_source'] state['avatar_url'] = person['avatar_url'] state['avatar_url_medium'] = person['avatar_url_medium'] for field in ['is_admin', 'delivery_email', 'email', 'full_name']: if field in person and field in state: state[field] = person[field] # In the unlikely event that the current user # just changed to/from being an admin, we need # to add/remove the data on all bots in the # realm. This is ugly and probably better # solved by removing the all-realm-bots data # given to admin users from this flow. if ('is_admin' in person and 'realm_bots' in state): prev_state = state['raw_users'][user_profile.id] was_admin = prev_state['is_admin'] now_admin = person['is_admin'] if was_admin and not now_admin: state['realm_bots'] = [] if not was_admin and now_admin: state['realm_bots'] = get_owned_bot_dicts(user_profile) if client_gravatar and 'avatar_url' in person: # Respect the client_gravatar setting in the `users` data. if 'gravatar.com' in person['avatar_url']: person['avatar_url'] = None person['avatar_url_medium'] = None if person_user_id in state['raw_users']: p = state['raw_users'][person_user_id] for field in p: if field in person: p[field] = person[field] if 'custom_profile_field' in person: custom_field_id = person['custom_profile_field']['id'] custom_field_new_value = person['custom_profile_field']['value'] if 'rendered_value' in person['custom_profile_field']: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value, 'rendered_value': person['custom_profile_field']['rendered_value'] } else: p['profile_data'][custom_field_id] = { 'value': custom_field_new_value } elif event['type'] == 'realm_bot': if event['op'] == 'add': state['realm_bots'].append(event['bot']) if event['op'] == 'remove': email = event['bot']['email'] for bot in state['realm_bots']: if bot['email'] == email: bot['is_active'] = False if event['op'] == 'delete': state['realm_bots'] = [item for item in state['realm_bots'] if item['email'] != event['bot']['email']] if event['op'] == 'update': for bot in state['realm_bots']: if bot['email'] == event['bot']['email']: if 'owner_id' in event['bot']: bot['owner'] = get_user_profile_by_id(event['bot']['owner_id']).email else: bot.update(event['bot']) elif event['type'] == 'stream': if event['op'] == 'create': for stream in event['streams']: if not stream['invite_only']: stream_data = copy.deepcopy(stream) if include_subscribers: stream_data['subscribers'] = [] stream_data['stream_weekly_traffic'] = None stream_data['is_old_stream'] = False stream_data['is_announcement_only'] = False # Add stream to never_subscribed (if not invite_only) state['never_subscribed'].append(stream_data) state['streams'].append(stream) state['streams'].sort(key=lambda elt: elt["name"]) if event['op'] == 'delete': deleted_stream_ids = {stream['stream_id'] for stream in event['streams']} state['streams'] = [s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids] state['never_subscribed'] = [stream for stream in state['never_subscribed'] if stream['stream_id'] not in deleted_stream_ids] if event['op'] == 'update': # For legacy reasons, we call stream data 'subscriptions' in # the state var here, for the benefit of the JS code. for obj in state['subscriptions']: if obj['name'].lower() == event['name'].lower(): obj[event['property']] = event['value'] # Also update the pure streams data for stream in state['streams']: if stream['name'].lower() == event['name'].lower(): prop = event['property'] if prop in stream: stream[prop] = event['value'] elif event['op'] == "occupy": state['streams'] += event['streams'] elif event['op'] == "vacate": stream_ids = [s["stream_id"] for s in event['streams']] state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids] elif event['type'] == 'default_streams': state['realm_default_streams'] = event['default_streams'] elif event['type'] == 'default_stream_groups': state['realm_default_stream_groups'] = event['default_stream_groups'] elif event['type'] == 'realm': if event['op'] == "update": field = 'realm_' + event['property'] state[field] = event['value'] # Tricky interaction: Whether we can create streams can get changed here. if (field in ['realm_create_stream_by_admins_only', 'realm_waiting_period_threshold']) and 'can_create_streams' in state: state['can_create_streams'] = user_profile.can_create_streams() state['can_subscribe_other_users'] = user_profile.can_subscribe_other_users() elif event['op'] == "update_dict": for key, value in event['data'].items(): state['realm_' + key] = value # It's a bit messy, but this is where we need to # update the state for whether password authentication # is enabled on this server. if key == 'authentication_methods': state['realm_password_auth_enabled'] = (value['Email'] or value['LDAP']) state['realm_email_auth_enabled'] = value['Email'] elif event['type'] == "subscription": if not include_subscribers and event['op'] in ['peer_add', 'peer_remove']: return if event['op'] in ["add"]: if not include_subscribers: # Avoid letting 'subscribers' entries end up in the list for i, sub in enumerate(event['subscriptions']): event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i]) del event['subscriptions'][i]['subscribers'] def name(sub: Dict[str, Any]) -> str: return sub['name'].lower() if event['op'] == "add": added_names = set(map(name, event["subscriptions"])) was_added = lambda s: name(s) in added_names # add the new subscriptions state['subscriptions'] += event['subscriptions'] # remove them from unsubscribed if they had been there state['unsubscribed'] = [s for s in state['unsubscribed'] if not was_added(s)] # remove them from never_subscribed if they had been there state['never_subscribed'] = [s for s in state['never_subscribed'] if not was_added(s)] elif event['op'] == "remove": removed_names = set(map(name, event["subscriptions"])) was_removed = lambda s: name(s) in removed_names # Find the subs we are affecting. removed_subs = list(filter(was_removed, state['subscriptions'])) # Remove our user from the subscribers of the removed subscriptions. if include_subscribers: for sub in removed_subs: sub['subscribers'] = [id for id in sub['subscribers'] if id != user_profile.id] # We must effectively copy the removed subscriptions from subscriptions to # unsubscribe, since we only have the name in our data structure. state['unsubscribed'] += removed_subs # Now filter out the removed subscriptions from subscriptions. state['subscriptions'] = [s for s in state['subscriptions'] if not was_removed(s)] elif event['op'] == 'update': for sub in state['subscriptions']: if sub['name'].lower() == event['name'].lower(): sub[event['property']] = event['value'] elif event['op'] == 'peer_add': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) for sub in state['never_subscribed']: if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']): sub['subscribers'].append(user_id) elif event['op'] == 'peer_remove': user_id = event['user_id'] for sub in state['subscriptions']: if (sub['name'] in event['subscriptions'] and user_id in sub['subscribers']): sub['subscribers'].remove(user_id) elif event['type'] == "presence": # TODO: Add user_id to presence update events / state format! presence_user_profile = get_user(event['email'], user_profile.realm) state['presences'][event['email']] = UserPresence.get_status_dict_by_user( presence_user_profile)[event['email']] elif event['type'] == "update_message": # We don't return messages in /register, so we don't need to # do anything for content updates, but we may need to update # the unread_msgs data if the topic of an unread message changed. if TOPIC_NAME in event: stream_dict = state['raw_unread_msgs']['stream_dict'] topic = event[TOPIC_NAME] for message_id in event['message_ids']: if message_id in stream_dict: stream_dict[message_id]['topic'] = topic elif event['type'] == "delete_message": max_message = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id').first() if max_message: state['max_message_id'] = max_message.id else: state['max_message_id'] = -1 remove_id = event['message_id'] remove_message_id_from_unread_mgs(state, remove_id) elif event['type'] == "reaction": # The client will get the message with the reactions directly pass elif event['type'] == "submessage": # The client will get submessages with their messages pass elif event['type'] == 'typing': # Typing notification events are transient and thus ignored pass elif event['type'] == "attachment": # Attachment events are just for updating the "uploads" UI; # they are not sent directly. pass elif event['type'] == "update_message_flags": # We don't return messages in `/register`, so most flags we # can ignore, but we do need to update the unread_msgs data if # unread state is changed. if event['flag'] == 'read' and event['operation'] == 'add': for remove_id in event['messages']: remove_message_id_from_unread_mgs(state, remove_id) if event['flag'] == 'starred' and event['operation'] == 'add': state['starred_messages'] += event['messages'] if event['flag'] == 'starred' and event['operation'] == 'remove': state['starred_messages'] = [message for message in state['starred_messages'] if not (message in event['messages'])] elif event['type'] == "realm_domains": if event['op'] == 'add': state['realm_domains'].append(event['realm_domain']) elif event['op'] == 'change': for realm_domain in state['realm_domains']: if realm_domain['domain'] == event['realm_domain']['domain']: realm_domain['allow_subdomains'] = event['realm_domain']['allow_subdomains'] elif event['op'] == 'remove': state['realm_domains'] = [realm_domain for realm_domain in state['realm_domains'] if realm_domain['domain'] != event['domain']] elif event['type'] == "realm_emoji": state['realm_emoji'] = event['realm_emoji'] elif event['type'] == "alert_words": state['alert_words'] = event['alert_words'] elif event['type'] == "muted_topics": state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] elif event['type'] == "update_display_settings": assert event['setting_name'] in UserProfile.property_types state[event['setting_name']] = event['setting'] elif event['type'] == "update_global_notifications": assert event['notification_name'] in UserProfile.notification_setting_types state[event['notification_name']] = event['setting'] elif event['type'] == "invites_changed": pass elif event['type'] == "user_group": if event['op'] == 'add': state['realm_user_groups'].append(event['group']) state['realm_user_groups'].sort(key=lambda group: group['id']) elif event['op'] == 'update': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group.update(event['data']) elif event['op'] == 'add_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: user_group['members'].extend(event['user_ids']) user_group['members'].sort() elif event['op'] == 'remove_members': for user_group in state['realm_user_groups']: if user_group['id'] == event['group_id']: members = set(user_group['members']) user_group['members'] = list(members - set(event['user_ids'])) user_group['members'].sort() elif event['op'] == 'remove': state['realm_user_groups'] = [ug for ug in state['realm_user_groups'] if ug['id'] != event['group_id']] elif event['type'] == 'user_status': away_user_ids = set(state['away_user_ids']) user_id = event['user_id'] if event['away']: away_user_ids.add(user_id) else: away_user_ids.discard(user_id) state['away_user_ids'] = sorted(list(away_user_ids)) else: raise AssertionError("Unexpected event type %s" % (event['type'],))
def fetch_initial_state_data(user_profile: UserProfile, event_types: Optional[Iterable[str]], queue_id: str, client_gravatar: bool, include_subscribers: bool = True) -> Dict[str, Any]: state = {'queue_id': queue_id} # type: Dict[str, Any] realm = user_profile.realm if event_types is None: # return True always want = always_want # type: Callable[[str], bool] else: want = set(event_types).__contains__ if want('alert_words'): state['alert_words'] = user_alert_words(user_profile) if want('custom_profile_fields'): fields = custom_profile_fields_for_realm(realm.id) state['custom_profile_fields'] = [f.as_dict() for f in fields] state['custom_profile_field_types'] = CustomProfileField.FIELD_TYPE_CHOICES_DICT if want('hotspots'): state['hotspots'] = get_next_hotspots(user_profile) if want('message'): # The client should use get_messages() to fetch messages # starting with the max_message_id. They will get messages # newer than that ID via get_events() messages = Message.objects.filter(usermessage__user_profile=user_profile).order_by('-id')[:1] if messages: state['max_message_id'] = messages[0].id else: state['max_message_id'] = -1 if want('muted_topics'): state['muted_topics'] = get_topic_mutes(user_profile) if want('pointer'): state['pointer'] = user_profile.pointer if want('presence'): state['presences'] = get_status_dict(user_profile) if want('realm'): for property_name in Realm.property_types: state['realm_' + property_name] = getattr(realm, property_name) # Most state is handled via the property_types framework; # these manual entries are for those realm settings that don't # fit into that framework. state['realm_authentication_methods'] = realm.authentication_methods_dict() state['realm_allow_message_editing'] = realm.allow_message_editing state['realm_allow_community_topic_editing'] = realm.allow_community_topic_editing state['realm_allow_message_deleting'] = realm.allow_message_deleting state['realm_message_content_edit_limit_seconds'] = realm.message_content_edit_limit_seconds state['realm_message_content_delete_limit_seconds'] = realm.message_content_delete_limit_seconds state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_source'] = realm.icon_source state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE state['realm_logo_url'] = realm_logo_url(realm) state['realm_logo_source'] = realm.logo_source state['max_logo_file_size'] = settings.MAX_LOGO_FILE_SIZE state['realm_bot_domain'] = realm.get_bot_domain() state['realm_uri'] = realm.uri state['realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS state['realm_presence_disabled'] = realm.presence_disabled state['realm_digest_emails_enabled'] = realm.digest_emails_enabled and settings.SEND_DIGEST_EMAILS state['realm_is_zephyr_mirror_realm'] = realm.is_zephyr_mirror_realm state['realm_email_auth_enabled'] = email_auth_enabled(realm) state['realm_password_auth_enabled'] = password_auth_enabled(realm) state['realm_push_notifications_enabled'] = push_notifications_enabled() if realm.notifications_stream and not realm.notifications_stream.deactivated: notifications_stream = realm.notifications_stream state['realm_notifications_stream_id'] = notifications_stream.id else: state['realm_notifications_stream_id'] = -1 signup_notifications_stream = realm.get_signup_notifications_stream() if signup_notifications_stream: state['realm_signup_notifications_stream_id'] = signup_notifications_stream.id else: state['realm_signup_notifications_stream_id'] = -1 if want('realm_domains'): state['realm_domains'] = get_realm_domains(realm) if want('realm_emoji'): state['realm_emoji'] = realm.get_emoji() if want('realm_filters'): state['realm_filters'] = realm_filters_for_realm(realm.id) if want('realm_user_groups'): state['realm_user_groups'] = user_groups_in_realm_serialized(realm) if want('realm_user'): state['raw_users'] = get_raw_user_data( realm_id=realm.id, client_gravatar=client_gravatar, ) # For the user's own avatar URL, we force # client_gravatar=False, since that saves some unnecessary # client-side code for handing medium-size avatars. See #8253 # for details. state['avatar_source'] = user_profile.avatar_source state['avatar_url_medium'] = avatar_url( user_profile, medium=True, client_gravatar=False, ) state['avatar_url'] = avatar_url( user_profile, medium=False, client_gravatar=False, ) state['can_create_streams'] = user_profile.can_create_streams() state['can_subscribe_other_users'] = user_profile.can_subscribe_other_users() state['cross_realm_bots'] = list(get_cross_realm_dicts()) state['is_admin'] = user_profile.is_realm_admin state['is_guest'] = user_profile.is_guest state['user_id'] = user_profile.id state['enter_sends'] = user_profile.enter_sends state['email'] = user_profile.email state['delivery_email'] = user_profile.delivery_email state['full_name'] = user_profile.full_name if want('realm_bot'): state['realm_bots'] = get_owned_bot_dicts(user_profile) # This does not yet have an apply_event counterpart, since currently, # new entries for EMBEDDED_BOTS can only be added directly in the codebase. if want('realm_embedded_bots'): realm_embedded_bots = [] for bot in EMBEDDED_BOTS: realm_embedded_bots.append({'name': bot.name, 'config': load_bot_config_template(bot.name)}) state['realm_embedded_bots'] = realm_embedded_bots if want('subscription'): subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers) state['subscriptions'] = subscriptions state['unsubscribed'] = unsubscribed state['never_subscribed'] = never_subscribed if want('update_message_flags') and want('message'): # Keeping unread_msgs updated requires both message flag updates and # message updates. This is due to the fact that new messages will not # generate a flag update so we need to use the flags field in the # message event. state['raw_unread_msgs'] = get_raw_unread_data(user_profile) if want('starred_messages'): state['starred_messages'] = get_starred_message_ids(user_profile) if want('stream'): state['streams'] = do_get_streams(user_profile) state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH if want('default_streams'): state['realm_default_streams'] = streams_to_dicts_sorted( get_default_streams_for_realm(realm.id)) if want('default_stream_groups'): state['realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted( get_default_stream_groups(realm)) if want('update_display_settings'): for prop in UserProfile.property_types: state[prop] = getattr(user_profile, prop) state['emojiset_choices'] = user_profile.emojiset_choices() if want('update_global_notifications'): for notification in UserProfile.notification_setting_types: state[notification] = getattr(user_profile, notification) state['available_notification_sounds'] = get_available_notification_sounds() if want('user_status'): state['away_user_ids'] = sorted(list(get_away_user_ids(realm_id=realm.id))) if want('zulip_version'): state['zulip_version'] = ZULIP_VERSION return state
def list_to_streams( streams_raw: Iterable[StreamDict], user_profile: UserProfile, autocreate: bool = False, admin_access_required: bool = False, ) -> Tuple[List[Stream], List[Stream]]: """Converts list of dicts to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name using check_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream dictionaries to process; names should already be stripped of whitespace by the caller. @param user_profile The user for whom we are retrieving the streams @param autocreate Whether we should create streams if they don't already exist """ # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = {stream_dict["name"] for stream_dict in streams_raw} for stream_name in stream_set: # Stream names should already have been stripped by the # caller, but it makes sense to verify anyway. assert stream_name == stream_name.strip() check_stream_name(stream_name) existing_streams: List[Stream] = [] missing_stream_dicts: List[StreamDict] = [] existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) if admin_access_required: existing_recipient_ids = [ stream.recipient_id for stream in existing_stream_map.values() ] subs = Subscription.objects.filter( user_profile=user_profile, recipient_id__in=existing_recipient_ids, active=True) sub_map = {sub.recipient_id: sub for sub in subs} for stream in existing_stream_map.values(): sub = sub_map.get(stream.recipient_id, None) check_stream_access_for_delete_or_update(user_profile, stream, sub) message_retention_days_not_none = False for stream_dict in streams_raw: stream_name = stream_dict["name"] stream = existing_stream_map.get(stream_name.lower()) if stream is None: if stream_dict.get("message_retention_days", None) is not None: message_retention_days_not_none = True missing_stream_dicts.append(stream_dict) else: existing_streams.append(stream) if len(missing_stream_dicts) == 0: # This is the happy path for callers who expected all of these # streams to exist already. created_streams: List[Stream] = [] else: # autocreate=True path starts here if not user_profile.can_create_streams(): raise JsonableError(_("User cannot create streams.")) elif not autocreate: raise JsonableError( _("Stream(s) ({}) do not exist").format( ", ".join(stream_dict["name"] for stream_dict in missing_stream_dicts), )) elif message_retention_days_not_none: if not user_profile.is_realm_owner: raise JsonableError( _("User cannot create stream with this settings.")) user_profile.realm.ensure_not_on_limited_plan() # We already filtered out existing streams, so dup_streams # will normally be an empty list below, but we protect against somebody # else racing to create the same stream. (This is not an entirely # paranoid approach, since often on Zulip two people will discuss # creating a new stream, and both people eagerly do it.) created_streams, dup_streams = create_streams_if_needed( realm=user_profile.realm, stream_dicts=missing_stream_dicts, acting_user=user_profile) existing_streams += dup_streams return existing_streams, created_streams
def fetch_initial_state_data( user_profile: Optional[UserProfile], *, realm: Optional[Realm] = None, event_types: Optional[Iterable[str]] = None, queue_id: Optional[str] = "", client_gravatar: bool = False, user_avatar_url_field_optional: bool = False, slim_presence: bool = False, include_subscribers: bool = True, include_streams: bool = True, ) -> Dict[str, Any]: """When `event_types` is None, fetches the core data powering the webapp's `page_params` and `/api/v1/register` (for mobile/terminal apps). Can also fetch a subset as determined by `event_types`. The user_profile=None code path is used for logged-out public access to streams with is_web_public=True. Whenever you add new code to this function, you should also add corresponding events for changes in the data structures and new code to apply_events (and add a test in test_events.py). """ if realm is None: assert user_profile is not None realm = user_profile.realm state: Dict[str, Any] = {'queue_id': queue_id} if event_types is None: # return True always want: Callable[[str], bool] = always_want else: want = set(event_types).__contains__ # Show the version info unconditionally. state['zulip_version'] = ZULIP_VERSION state['zulip_feature_level'] = API_FEATURE_LEVEL if want('alert_words'): state['alert_words'] = [] if user_profile is None else user_alert_words(user_profile) if want('custom_profile_fields'): fields = custom_profile_fields_for_realm(realm.id) state['custom_profile_fields'] = [f.as_dict() for f in fields] state['custom_profile_field_types'] = { item[4]: {"id": item[0], "name": str(item[1])} for item in CustomProfileField.ALL_FIELD_TYPES } if want('hotspots'): # Even if we offered special hotspots for guests without an # account, we'd maybe need to store their state using cookies # or local storage, rather than in the database. state['hotspots'] = [] if user_profile is None else get_next_hotspots(user_profile) if want('message'): # Since the introduction of `anchor="latest"` in the API, # `max_message_id` is primarily used for generating `local_id` # values that are higher than this. We likely can eventually # remove this parameter from the API. user_messages = [] if user_profile is not None: user_messages = UserMessage.objects \ .filter(user_profile=user_profile) \ .order_by('-message_id') \ .values('message_id')[:1] if user_messages: state['max_message_id'] = user_messages[0]['message_id'] else: state['max_message_id'] = -1 if want('muted_topics'): state['muted_topics'] = [] if user_profile is None else get_topic_mutes(user_profile) if want('presence'): state['presences'] = {} if user_profile is None else get_presences_for_realm(realm, slim_presence) if want('realm'): for property_name in Realm.property_types: state['realm_' + property_name] = getattr(realm, property_name) # Most state is handled via the property_types framework; # these manual entries are for those realm settings that don't # fit into that framework. state['realm_authentication_methods'] = realm.authentication_methods_dict() # We pretend these features are disabled because guests can't # access them. In the future, we may want to move this logic # to the frontends, so that we can correctly display what # these fields are in the settings. state['realm_allow_message_editing'] = False if user_profile is None else realm.allow_message_editing state['realm_allow_community_topic_editing'] = False if user_profile is None else realm.allow_community_topic_editing state['realm_allow_message_deleting'] = False if user_profile is None else realm.allow_message_deleting state['realm_message_content_edit_limit_seconds'] = realm.message_content_edit_limit_seconds state['realm_message_content_delete_limit_seconds'] = realm.message_content_delete_limit_seconds state['realm_community_topic_editing_limit_seconds'] = \ Realm.DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS # This setting determines whether to send presence and also # whether to display of users list in the right sidebar; we # want both behaviors for logged-out users. We may in the # future choose to move this logic to the frontend. state['realm_presence_disabled'] = True if user_profile is None else realm.presence_disabled state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_source'] = realm.icon_source state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE add_realm_logo_fields(state, realm) state['realm_bot_domain'] = realm.get_bot_domain() state['realm_uri'] = realm.uri state['realm_available_video_chat_providers'] = realm.VIDEO_CHAT_PROVIDERS state['settings_send_digest_emails'] = settings.SEND_DIGEST_EMAILS state['realm_digest_emails_enabled'] = realm.digest_emails_enabled and settings.SEND_DIGEST_EMAILS state['realm_is_zephyr_mirror_realm'] = realm.is_zephyr_mirror_realm state['realm_email_auth_enabled'] = email_auth_enabled(realm) state['realm_password_auth_enabled'] = password_auth_enabled(realm) state['realm_push_notifications_enabled'] = push_notifications_enabled() state['realm_upload_quota'] = realm.upload_quota_bytes() state['realm_plan_type'] = realm.plan_type state['zulip_plan_is_not_limited'] = realm.plan_type != Realm.LIMITED state['upgrade_text_for_wide_organization_logo'] = str(Realm.UPGRADE_TEXT_STANDARD) state['realm_default_external_accounts'] = DEFAULT_EXTERNAL_ACCOUNTS state['jitsi_server_url'] = settings.JITSI_SERVER_URL.rstrip('/') state['development_environment'] = settings.DEVELOPMENT state['server_generation'] = settings.SERVER_GENERATION state['password_min_length'] = settings.PASSWORD_MIN_LENGTH state['password_min_guesses'] = settings.PASSWORD_MIN_GUESSES state['max_file_upload_size_mib'] = settings.MAX_FILE_UPLOAD_SIZE state['max_avatar_file_size_mib'] = settings.MAX_AVATAR_FILE_SIZE state['server_inline_image_preview'] = settings.INLINE_IMAGE_PREVIEW state['server_inline_url_embed_preview'] = settings.INLINE_URL_EMBED_PREVIEW state['server_avatar_changes_disabled'] = settings.AVATAR_CHANGES_DISABLED state['server_name_changes_disabled'] = settings.NAME_CHANGES_DISABLED if realm.notifications_stream and not realm.notifications_stream.deactivated: notifications_stream = realm.notifications_stream state['realm_notifications_stream_id'] = notifications_stream.id else: state['realm_notifications_stream_id'] = -1 signup_notifications_stream = realm.get_signup_notifications_stream() if signup_notifications_stream: state['realm_signup_notifications_stream_id'] = signup_notifications_stream.id else: state['realm_signup_notifications_stream_id'] = -1 if want('realm_domains'): state['realm_domains'] = get_realm_domains(realm) if want('realm_emoji'): state['realm_emoji'] = realm.get_emoji() if want('realm_filters'): state['realm_filters'] = realm_filters_for_realm(realm.id) if want('realm_user_groups'): state['realm_user_groups'] = user_groups_in_realm_serialized(realm) if user_profile is not None: settings_user = user_profile else: # When UserProfile=None, we want to serve the values for various # settings as the defaults. Instead of copying the default values # from models.py here, we access these default values from a # temporary UserProfile object that will not be saved to the database. # # We also can set various fields to avoid duplicating code # unnecessarily. settings_user = UserProfile( full_name="Anonymous User", email="*****@*****.**", delivery_email="*****@*****.**", realm=realm, # We tag logged-out users as guests because most guest # restrictions apply to these users as well, and it lets # us avoid unnecessary conditionals. role=UserProfile.ROLE_GUEST, avatar_source=UserProfile.AVATAR_FROM_GRAVATAR, # ID=0 is not used in real Zulip databases, ensuring this is unique. id=0, ) if want('realm_user'): state['raw_users'] = get_raw_user_data(realm, user_profile, client_gravatar=client_gravatar, user_avatar_url_field_optional=user_avatar_url_field_optional) state['cross_realm_bots'] = list(get_cross_realm_dicts()) # For the user's own avatar URL, we force # client_gravatar=False, since that saves some unnecessary # client-side code for handing medium-size avatars. See #8253 # for details. state['avatar_source'] = settings_user.avatar_source state['avatar_url_medium'] = avatar_url( settings_user, medium=True, client_gravatar=False, ) state['avatar_url'] = avatar_url( settings_user, medium=False, client_gravatar=False, ) state['can_create_streams'] = settings_user.can_create_streams() state['can_subscribe_other_users'] = settings_user.can_subscribe_other_users() state['is_admin'] = settings_user.is_realm_admin state['is_owner'] = settings_user.is_realm_owner state['is_guest'] = settings_user.is_guest state['user_id'] = settings_user.id state['enter_sends'] = settings_user.enter_sends state['email'] = settings_user.email state['delivery_email'] = settings_user.delivery_email state['full_name'] = settings_user.full_name if want('realm_bot'): state['realm_bots'] = [] if user_profile is None else get_owned_bot_dicts(user_profile) # This does not yet have an apply_event counterpart, since currently, # new entries for EMBEDDED_BOTS can only be added directly in the codebase. if want('realm_embedded_bots'): realm_embedded_bots = [] for bot in EMBEDDED_BOTS: realm_embedded_bots.append({'name': bot.name, 'config': load_bot_config_template(bot.name)}) state['realm_embedded_bots'] = realm_embedded_bots # This does not have an apply_events counterpart either since # this data is mostly static. if want('realm_incoming_webhook_bots'): realm_incoming_webhook_bots = [] for integration in WEBHOOK_INTEGRATIONS: realm_incoming_webhook_bots.append({ 'name': integration.name, 'config': {c[1]: c[0] for c in integration.config_options}, }) state['realm_incoming_webhook_bots'] = realm_incoming_webhook_bots if want('recent_private_conversations'): # A data structure containing records of this form: # # [{'max_message_id': 700175, 'user_ids': [801]}] # # for all recent private message conversations, ordered by the # highest message ID in the conversation. The user_ids list # is the list of users other than the current user in the # private message conversation (so it is [] for PMs to self). # Note that raw_recent_private_conversations is an # intermediate form as a dictionary keyed by recipient_id, # which is more efficient to update, and is rewritten to the # final format in post_process_state. state['raw_recent_private_conversations'] = {} if user_profile is None else get_recent_private_conversations(user_profile) if want('subscription'): if user_profile is not None: sub_info = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers, ) else: sub_info = get_web_public_subs(realm) state['subscriptions'] = sub_info.subscriptions state['unsubscribed'] = sub_info.unsubscribed state['never_subscribed'] = sub_info.never_subscribed if want('update_message_flags') and want('message'): # Keeping unread_msgs updated requires both message flag updates and # message updates. This is due to the fact that new messages will not # generate a flag update so we need to use the flags field in the # message event. if user_profile is not None: state['raw_unread_msgs'] = get_raw_unread_data(user_profile) else: # For logged-out visitors, we treat all messages as read; # calling this helper lets us return empty objects in the # appropriate format. state['raw_unread_msgs'] = extract_unread_data_from_um_rows([], user_profile) if want('starred_messages'): state['starred_messages'] = [] if user_profile is None else get_starred_message_ids(user_profile) if want('stream'): if include_streams: # The webapp doesn't use the data from here; instead, # it uses data from state["subscriptions"] and other # places. if user_profile is not None: state['streams'] = do_get_streams(user_profile) else: # TODO: This line isn't used by the webapp because it # gets these data via the `subscriptions` key; it will # be used when the mobile apps support logged-out # access. state['streams'] = get_web_public_streams(realm) # nocoverage state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH if want('default_streams'): if settings_user.is_guest: # Guest users and logged-out users don't have access to # all default streams, so we pretend the organization # doesn't have any. state['realm_default_streams'] = [] else: state['realm_default_streams'] = streams_to_dicts_sorted( get_default_streams_for_realm(realm.id)) if want('default_stream_groups'): if settings_user.is_guest: state['realm_default_stream_groups'] = [] else: state['realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted( get_default_stream_groups(realm)) if want('stop_words'): state['stop_words'] = read_stop_words() if want('update_display_settings'): for prop in UserProfile.property_types: state[prop] = getattr(settings_user, prop) state['emojiset_choices'] = UserProfile.emojiset_choices() if want('update_global_notifications'): for notification in UserProfile.notification_setting_types: state[notification] = getattr(settings_user, notification) state['available_notification_sounds'] = get_available_notification_sounds() if want('user_status'): # We require creating an account to access statuses. state['user_status'] = {} if user_profile is None else get_user_info_dict(realm_id=realm.id) if want('video_calls'): state['has_zoom_token'] = settings_user.zoom_token is not None return state
def fetch_initial_state_data( user_profile: UserProfile, event_types: Optional[Iterable[str]], queue_id: str, client_gravatar: bool, include_subscribers: bool = True) -> Dict[str, Any]: state = {'queue_id': queue_id} # type: Dict[str, Any] if event_types is None: # return True always want = always_want # type: Callable[[str], bool] else: want = set(event_types).__contains__ if want('alert_words'): state['alert_words'] = user_alert_words(user_profile) if want('custom_profile_fields'): fields = custom_profile_fields_for_realm(user_profile.realm.id) state['custom_profile_fields'] = [f.as_dict() for f in fields] if want('attachments'): state['attachments'] = user_attachments(user_profile) if want('upload_quota'): state['upload_quota'] = user_profile.quota if want('total_uploads_size'): state['total_uploads_size'] = get_total_uploads_size_for_user( user_profile) if want('hotspots'): state['hotspots'] = get_next_hotspots(user_profile) if want('message'): # The client should use get_messages() to fetch messages # starting with the max_message_id. They will get messages # newer than that ID via get_events() messages = Message.objects.filter( usermessage__user_profile=user_profile).order_by('-id')[:1] if messages: state['max_message_id'] = messages[0].id else: state['max_message_id'] = -1 if want('muted_topics'): state['muted_topics'] = get_topic_mutes(user_profile) if want('pointer'): state['pointer'] = user_profile.pointer if want('presence'): state['presences'] = get_status_dict(user_profile) if want('realm'): for property_name in Realm.property_types: state['realm_' + property_name] = getattr(user_profile.realm, property_name) # Most state is handled via the property_types framework; # these manual entries are for those realm settings that don't # fit into that framework. realm = user_profile.realm state[ 'realm_authentication_methods'] = realm.authentication_methods_dict( ) state['realm_allow_message_editing'] = realm.allow_message_editing state[ 'realm_message_content_edit_limit_seconds'] = realm.message_content_edit_limit_seconds state['realm_icon_url'] = realm_icon_url(realm) state['realm_icon_source'] = realm.icon_source state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE state['realm_bot_domain'] = realm.get_bot_domain() state['realm_uri'] = realm.uri state['realm_presence_disabled'] = realm.presence_disabled state['realm_show_digest_email'] = realm.show_digest_email state['realm_is_zephyr_mirror_realm'] = realm.is_zephyr_mirror_realm state['realm_email_auth_enabled'] = email_auth_enabled(realm) state['realm_password_auth_enabled'] = password_auth_enabled(realm) if realm.notifications_stream and not realm.notifications_stream.deactivated: notifications_stream = realm.notifications_stream state['realm_notifications_stream_id'] = notifications_stream.id else: state['realm_notifications_stream_id'] = -1 if user_profile.realm.get_signup_notifications_stream(): signup_notifications_stream = user_profile.realm.get_signup_notifications_stream( ) state[ 'realm_signup_notifications_stream_id'] = signup_notifications_stream.id else: state['realm_signup_notifications_stream_id'] = -1 if want('realm_domains'): state['realm_domains'] = get_realm_domains(user_profile.realm) if want('realm_emoji'): state['realm_emoji'] = user_profile.realm.get_emoji() if want('realm_filters'): state['realm_filters'] = realm_filters_for_realm(user_profile.realm_id) if want('realm_user_groups'): state['realm_user_groups'] = user_groups_in_realm_serialized( user_profile.realm) if want('realm_user'): state['raw_users'] = get_raw_user_data( realm_id=user_profile.realm_id, client_gravatar=client_gravatar, ) state['avatar_source'] = user_profile.avatar_source state['avatar_url_medium'] = avatar_url( user_profile, medium=True, client_gravatar=client_gravatar, ) state['avatar_url'] = avatar_url( user_profile, medium=False, client_gravatar=client_gravatar, ) state['can_create_streams'] = user_profile.can_create_streams() state['cross_realm_bots'] = list(get_cross_realm_dicts()) state['is_admin'] = user_profile.is_realm_admin state['user_id'] = user_profile.id state['enter_sends'] = user_profile.enter_sends state['email'] = user_profile.email state['full_name'] = user_profile.full_name if want('realm_bot'): state['realm_bots'] = get_owned_bot_dicts(user_profile) # This does not yet have an apply_event counterpart, since currently, # new entries for EMBEDDED_BOTS can only be added directly in the codebase. if want('realm_embedded_bots'): state['realm_embedded_bots'] = list(bot.name for bot in EMBEDDED_BOTS) if want('subscription'): subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers) state['subscriptions'] = subscriptions state['unsubscribed'] = unsubscribed state['never_subscribed'] = never_subscribed if want('update_message_flags') and want('message'): # Keeping unread_msgs updated requires both message flag updates and # message updates. This is due to the fact that new messages will not # generate a flag update so we need to use the flags field in the # message event. state['raw_unread_msgs'] = get_raw_unread_data(user_profile) if want('stream'): state['streams'] = do_get_streams(user_profile) if want('default_streams'): state['realm_default_streams'] = streams_to_dicts_sorted( get_default_streams_for_realm(user_profile.realm_id)) if want('default_stream_groups'): state[ 'realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted( get_default_stream_groups(user_profile.realm)) if want('update_display_settings'): for prop in UserProfile.property_types: state[prop] = getattr(user_profile, prop) state['emojiset_choices'] = user_profile.emojiset_choices() state['autoscroll_forever'] = user_profile.autoscroll_forever if want('update_global_notifications'): for notification in UserProfile.notification_setting_types: state[notification] = getattr(user_profile, notification) state[ 'default_desktop_notifications'] = user_profile.default_desktop_notifications if want('zulip_version'): state['zulip_version'] = ZULIP_VERSION return state