def get_topics_backend( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], stream_id: int = REQ(converter=to_non_negative_int, path_only=True), ) -> HttpResponse: if not maybe_user_profile.is_authenticated: is_web_public_query = True user_profile: Optional[UserProfile] = None else: is_web_public_query = False assert isinstance(maybe_user_profile, UserProfile) user_profile = maybe_user_profile assert user_profile is not None if is_web_public_query: realm = get_valid_realm_from_request(request) stream = access_web_public_stream(stream_id, realm) result = get_topic_history_for_public_stream( recipient_id=stream.recipient_id) else: assert user_profile is not None (stream, sub) = access_stream_by_id(user_profile, stream_id) result = get_topic_history_for_stream( user_profile=user_profile, recipient_id=stream.recipient_id, public_history=stream.is_history_public_to_subscribers(), ) return json_success(dict(topics=result))
def report_unnarrow_times(request: HttpRequest, user_profile: Union[UserProfile, AnonymousUser], initial_core: int=REQ(converter=to_non_negative_int), initial_free: int=REQ(converter=to_non_negative_int)) -> HttpResponse: request._log_data["extra"] = f"[{initial_core}ms/{initial_free}ms]" realm = get_valid_realm_from_request(request) base_key = statsd_key(realm.string_id, clean_periods=True) statsd.timing(f"unnarrow.initial_core.{base_key}", initial_core) statsd.timing(f"unnarrow.initial_free.{base_key}", initial_free) return json_success()
def avatar( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], email_or_id: str, medium: bool = False, ) -> HttpResponse: """Accepts an email address or user ID and returns the avatar""" is_email = False try: int(email_or_id) except ValueError: is_email = True if not maybe_user_profile.is_authenticated: # Allow anonynous access to avatars only if spectators are # enabled in the organization. realm = get_valid_realm_from_request(request) # TODO: Replace with realm.allow_web_public_streams_access() # when the method is available. if not realm.has_web_public_streams(): raise MissingAuthenticationError() # We only allow the ID format for accessing a user's avatar # for spectators. This is mainly for defense in depth, since # email_address_visibility should mean spectators only # interact with fake email addresses anyway. if is_email: raise MissingAuthenticationError() else: realm = maybe_user_profile.realm try: if is_email: avatar_user_profile = get_user_including_cross_realm( email_or_id, realm) else: avatar_user_profile = get_user_by_id_in_realm_including_cross_realm( int(email_or_id), realm) # If there is a valid user account passed in, use its avatar url = avatar_url(avatar_user_profile, medium=medium) except UserProfile.DoesNotExist: # If there is no such user, treat it as a new gravatar email = email_or_id avatar_version = 1 url = get_gravatar_url(email, avatar_version, medium) # We can rely on the URL already having query parameters. Because # our templates depend on being able to use the ampersand to # add query parameters to our url, get_avatar_url does '?x=x' # hacks to prevent us from having to jump through decode/encode hoops. assert url is not None url = append_url_query_string(url, request.META["QUERY_STRING"]) return redirect(url)
def json_fetch_raw_message( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], message_id: int = REQ(converter=to_non_negative_int, path_only=True), ) -> HttpResponse: if not maybe_user_profile.is_authenticated: realm = get_valid_realm_from_request(request) message = access_web_public_message(realm, message_id) else: (message, user_message) = access_message(maybe_user_profile, message_id) return json_success(request, data={"raw_content": message.content})
def report_narrow_times( request: HttpRequest, user_profile: Union[UserProfile, AnonymousUser], initial_core: int = REQ(converter=to_non_negative_int), initial_free: int = REQ(converter=to_non_negative_int), network: int = REQ(converter=to_non_negative_int), ) -> HttpResponse: log_data = RequestNotes.get_notes(request).log_data assert log_data is not None log_data["extra"] = f"[{initial_core}ms/{initial_free}ms/{network}ms]" realm = get_valid_realm_from_request(request) base_key = statsd_key(realm.string_id, clean_periods=True) statsd.timing(f"narrow.initial_core.{base_key}", initial_core) statsd.timing(f"narrow.initial_free.{base_key}", initial_free) statsd.timing(f"narrow.network.{base_key}", network) return json_success(request)
def backend_serve_thumbnail( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], url: str = REQ(), size_requested: str = REQ("size"), ) -> HttpResponse: if not maybe_user_profile.is_authenticated: realm = get_valid_realm_from_request(request) else: assert isinstance(maybe_user_profile, UserProfile) realm = maybe_user_profile.realm if not validate_thumbnail_request(realm, maybe_user_profile, url): return HttpResponseForbidden( _("<p>You are not authorized to view this file.</p>")) thumbnail_url = generate_thumbnail_url(url) return redirect(thumbnail_url)
def home(request: HttpRequest) -> HttpResponse: subdomain = get_subdomain(request) # If settings.ROOT_DOMAIN_LANDING_PAGE and this is the root # domain, send the user the landing page. if settings.ROOT_DOMAIN_LANDING_PAGE and subdomain == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN: return hello_view(request) # TODO: The following logic is a bit hard to read. We save a # database query in the common case by avoiding the call to # `get_valid_realm_from_request` if user hasn't requested # web-public access. if (request.POST.get("prefers_web_public_view") == "true" or request.session.get("prefers_web_public_view") ) and get_valid_realm_from_request( request).allow_web_public_streams_access(): return web_public_view(home_real)(request) return zulip_login_required(home_real)(request)
def json_fetch_raw_message( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], message_id: int = REQ(converter=to_non_negative_int, path_only=True), apply_markdown: bool = REQ(json_validator=check_bool, default=True), ) -> HttpResponse: if not maybe_user_profile.is_authenticated: realm = get_valid_realm_from_request(request) message = access_web_public_message(realm, message_id) else: (message, user_message) = access_message(maybe_user_profile, message_id) flags = ["read"] if not maybe_user_profile.is_authenticated: allow_edit_history = realm.allow_edit_history else: if user_message: flags = user_message.flags_list() else: flags = ["read", "historical"] allow_edit_history = maybe_user_profile.realm.allow_edit_history # Security note: It's important that we call this only with a # message already fetched via `access_message` type methods, # as we do above. message_dict_list = messages_for_ids( message_ids=[message.id], user_message_flags={message_id: flags}, search_fields={}, apply_markdown=apply_markdown, client_gravatar=True, allow_edit_history=allow_edit_history, ) response = dict( message=message_dict_list[0], # raw_content is deprecated; we will need to wait until # clients have been fully migrated to using the modern API # before removing this, probably in 2023. raw_content=message.content, ) return json_success(request, response)
def serve_file( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], realm_id_str: str, filename: str, url_only: bool = False, download: bool = False, ) -> HttpResponse: path_id = f"{realm_id_str}/{filename}" realm = get_valid_realm_from_request(request) is_authorized = validate_attachment_request(maybe_user_profile, path_id, realm) if is_authorized is None: return HttpResponseNotFound(_("<p>File not found.</p>")) if not is_authorized: return HttpResponseForbidden(_("<p>You are not authorized to view this file.</p>")) if settings.LOCAL_UPLOADS_DIR is not None: return serve_local(request, path_id, url_only, download=download) return serve_s3(request, path_id, url_only, download=download)
def home_real(request: HttpRequest) -> HttpResponse: # Before we do any real work, check if the app is banned. client_user_agent = request.META.get("HTTP_USER_AGENT", "") (insecure_desktop_app, banned_desktop_app, auto_update_broken) = is_outdated_desktop_app(client_user_agent) if banned_desktop_app: return render( request, "zerver/insecure_desktop_app.html", context={ "auto_update_broken": auto_update_broken, }, ) (unsupported_browser, browser_name) = is_unsupported_browser(client_user_agent) if unsupported_browser: return render( request, "zerver/unsupported_browser.html", context={ "browser_name": browser_name, }, ) # We need to modify the session object every two weeks or it will expire. # This line makes reloading the page a sufficient action to keep the # session alive. request.session.modified = True if request.user.is_authenticated: user_profile = request.user realm = user_profile.realm else: # user_profile=None corresponds to the logged-out "web_public" visitor case. user_profile = None realm = get_valid_realm_from_request(request) update_last_reminder(user_profile) statsd.incr("views.home") # If a user hasn't signed the current Terms of Service, send them there if need_accept_tos(user_profile): return accounts_accept_terms(request) narrow, narrow_stream, narrow_topic = detect_narrowed_window( request, user_profile) if user_profile is not None: first_in_realm = realm_user_count(user_profile.realm) == 1 # If you are the only person in the realm and you didn't invite # anyone, we'll continue to encourage you to do so on the frontend. prompt_for_invites = (first_in_realm and not PreregistrationUser.objects.filter( referred_by=user_profile).count()) needs_tutorial = user_profile.tutorial_status == UserProfile.TUTORIAL_WAITING else: first_in_realm = False prompt_for_invites = False # The current tutorial doesn't super make sense for logged-out users. needs_tutorial = False queue_id, page_params = build_page_params_for_home_page_load( request=request, user_profile=user_profile, realm=realm, insecure_desktop_app=insecure_desktop_app, narrow=narrow, narrow_stream=narrow_stream, narrow_topic=narrow_topic, first_in_realm=first_in_realm, prompt_for_invites=prompt_for_invites, needs_tutorial=needs_tutorial, ) show_invites, show_add_streams = compute_show_invites_and_add_streams( user_profile) request._log_data["extra"] = "[{}]".format(queue_id) csp_nonce = secrets.token_hex(24) user_permission_info = get_user_permission_info(user_profile) response = render( request, "zerver/app/index.html", context={ "user_profile": user_profile, "page_params": page_params, "csp_nonce": csp_nonce, "search_pills_enabled": settings.SEARCH_PILLS_ENABLED, "show_invites": show_invites, "show_add_streams": show_add_streams, "is_owner": user_permission_info.is_realm_owner, "is_admin": user_permission_info.is_realm_admin, "is_guest": user_permission_info.is_guest, "color_scheme": user_permission_info.color_scheme, "embedded": narrow_stream is not None, "max_file_upload_size_mib": settings.MAX_FILE_UPLOAD_SIZE, }, ) patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True) return response
def events_register_backend( request: HttpRequest, maybe_user_profile: Union[UserProfile, AnonymousUser], apply_markdown: bool = REQ(default=False, json_validator=check_bool), client_gravatar: bool = REQ(default=True, json_validator=check_bool), slim_presence: bool = REQ(default=False, json_validator=check_bool), all_public_streams: Optional[bool] = REQ(default=None, json_validator=check_bool), include_subscribers: bool = REQ(default=False, json_validator=check_bool), client_capabilities: Optional[Dict[str, bool]] = REQ( json_validator=check_dict( [ # This field was accidentally made required when it was added in v2.0.0-781; # this was not realized until after the release of Zulip 2.1.2. (It remains # required to help ensure backwards compatibility of client code.) ("notification_settings_null", check_bool), ], [ # Any new fields of `client_capabilities` should be optional. Add them here. ("bulk_message_deletion", check_bool), ("user_avatar_url_field_optional", check_bool), ("stream_typing_notifications", check_bool), ("user_settings_object", check_bool), ], value_validator=check_bool, ), default=None, ), event_types: Optional[Sequence[str]] = REQ( json_validator=check_list(check_string), default=None ), fetch_event_types: Optional[Sequence[str]] = REQ( json_validator=check_list(check_string), default=None ), narrow: NarrowT = REQ( json_validator=check_list(check_list(check_string, length=2)), default=[] ), queue_lifespan_secs: int = REQ(json_validator=check_int, default=0, documentation_pending=True), ) -> HttpResponse: if maybe_user_profile.is_authenticated: user_profile = maybe_user_profile assert isinstance(user_profile, UserProfile) realm = user_profile.realm if all_public_streams and not user_profile.can_access_public_streams(): raise JsonableError(_("User not authorized for this query")) all_public_streams = _default_all_public_streams(user_profile, all_public_streams) narrow = _default_narrow(user_profile, narrow) else: user_profile = None realm = get_valid_realm_from_request(request) if not realm.allow_web_public_streams_access(): raise MissingAuthenticationError() all_public_streams = False if client_capabilities is None: client_capabilities = {} client = RequestNotes.get_notes(request).client assert client is not None ret = do_events_register( user_profile, realm, client, apply_markdown, client_gravatar, slim_presence, event_types, queue_lifespan_secs, all_public_streams, narrow=narrow, include_subscribers=include_subscribers, client_capabilities=client_capabilities, fetch_event_types=fetch_event_types, ) return json_success(request, data=ret)
def home_real(request: HttpRequest) -> HttpResponse: # Before we do any real work, check if the app is banned. client_user_agent = request.META.get("HTTP_USER_AGENT", "") (insecure_desktop_app, banned_desktop_app, auto_update_broken) = is_outdated_desktop_app(client_user_agent) if banned_desktop_app: return render( request, "zerver/insecure_desktop_app.html", context={ "auto_update_broken": auto_update_broken, }, ) (unsupported_browser, browser_name) = is_unsupported_browser(client_user_agent) if unsupported_browser: return render( request, "zerver/unsupported_browser.html", context={ "browser_name": browser_name, }, ) # We need to modify the session object every two weeks or it will expire. # This line makes reloading the page a sufficient action to keep the # session alive. request.session.modified = True if request.user.is_authenticated: user_profile = request.user realm = user_profile.realm # User is logged in and hence no longer `prefers_web_public_view`. if "prefers_web_public_view" in request.session.keys(): del request.session["prefers_web_public_view"] else: realm = get_valid_realm_from_request(request) # TODO: Ideally, we'd open Zulip directly as a spectator if # the URL had clicked a link to content on a web-public # stream. We could maybe do this by parsing `next`, but it's # not super convenient with Zulip's hash-based URL scheme. # The "Access without an account" button on the login page # submits a POST to this page with this hidden field set. if request.POST.get("prefers_web_public_view") == "true": request.session["prefers_web_public_view"] = True # We serve a redirect here, rather than serving a page, to # avoid browser "Confirm form resubmission" prompts on reload. redirect_to = get_safe_redirect_to(request.POST.get("next"), realm.uri) return redirect(redirect_to) prefers_web_public_view = request.session.get( "prefers_web_public_view") if not prefers_web_public_view: # For users who haven't opted into the spectator # experience, we redirect to the login page. return zulip_redirect_to_login(request, settings.HOME_NOT_LOGGED_IN) # For users who have selected public access, we load the # spectator experience. We fall through to the shared code # for loading the application, with user_profile=None encoding # that we're a spectator, not a logged-in user. user_profile = None update_last_reminder(user_profile) statsd.incr("views.home") # If a user hasn't signed the current Terms of Service, send them there if need_accept_tos(user_profile): return accounts_accept_terms(request) narrow, narrow_stream, narrow_topic = detect_narrowed_window( request, user_profile) if user_profile is not None: first_in_realm = realm_user_count(user_profile.realm) == 1 # If you are the only person in the realm and you didn't invite # anyone, we'll continue to encourage you to do so on the frontend. prompt_for_invites = (first_in_realm and not PreregistrationUser.objects.filter( referred_by=user_profile).count()) needs_tutorial = user_profile.tutorial_status == UserProfile.TUTORIAL_WAITING else: first_in_realm = False prompt_for_invites = False # The current tutorial doesn't super make sense for logged-out users. needs_tutorial = False queue_id, page_params = build_page_params_for_home_page_load( request=request, user_profile=user_profile, realm=realm, insecure_desktop_app=insecure_desktop_app, narrow=narrow, narrow_stream=narrow_stream, narrow_topic=narrow_topic, first_in_realm=first_in_realm, prompt_for_invites=prompt_for_invites, needs_tutorial=needs_tutorial, ) log_data = RequestNotes.get_notes(request).log_data assert log_data is not None log_data["extra"] = f"[{queue_id}]" csp_nonce = secrets.token_hex(24) user_permission_info = get_user_permission_info(user_profile) response = render( request, "zerver/app/index.html", context={ "user_profile": user_profile, "page_params": page_params, "csp_nonce": csp_nonce, "color_scheme": user_permission_info.color_scheme, }, ) patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True) return response
def home_real(request: HttpRequest) -> HttpResponse: # Before we do any real work, check if the app is banned. client_user_agent = request.headers.get("User-Agent", "") (insecure_desktop_app, banned_desktop_app, auto_update_broken) = is_outdated_desktop_app(client_user_agent) if banned_desktop_app: return render( request, "zerver/insecure_desktop_app.html", context={ "auto_update_broken": auto_update_broken, }, ) (unsupported_browser, browser_name) = is_unsupported_browser(client_user_agent) if unsupported_browser: return render( request, "zerver/unsupported_browser.html", context={ "browser_name": browser_name, }, ) # We need to modify the session object every two weeks or it will expire. # This line makes reloading the page a sufficient action to keep the # session alive. request.session.modified = True if request.user.is_authenticated: user_profile = request.user realm = user_profile.realm else: realm = get_valid_realm_from_request(request) # We load the spectator experience. We fall through to the shared code # for loading the application, with user_profile=None encoding # that we're a spectator, not a logged-in user. user_profile = None update_last_reminder(user_profile) statsd.incr("views.home") # If a user hasn't signed the current Terms of Service, send them there if need_accept_tos(user_profile): return accounts_accept_terms(request) narrow, narrow_stream, narrow_topic = detect_narrowed_window( request, user_profile) if user_profile is not None: first_in_realm = realm_user_count(user_profile.realm) == 1 # If you are the only person in the realm and you didn't invite # anyone, we'll continue to encourage you to do so on the frontend. prompt_for_invites = (first_in_realm and not PreregistrationUser.objects.filter( referred_by=user_profile).count()) needs_tutorial = user_profile.tutorial_status == UserProfile.TUTORIAL_WAITING else: first_in_realm = False prompt_for_invites = False # The current tutorial doesn't super make sense for logged-out users. needs_tutorial = False queue_id, page_params = build_page_params_for_home_page_load( request=request, user_profile=user_profile, realm=realm, insecure_desktop_app=insecure_desktop_app, narrow=narrow, narrow_stream=narrow_stream, narrow_topic=narrow_topic, first_in_realm=first_in_realm, prompt_for_invites=prompt_for_invites, needs_tutorial=needs_tutorial, ) log_data = RequestNotes.get_notes(request).log_data assert log_data is not None log_data["extra"] = f"[{queue_id}]" csp_nonce = secrets.token_hex(24) user_permission_info = get_user_permission_info(user_profile) response = render( request, "zerver/app/index.html", context={ "user_profile": user_profile, "page_params": page_params, "csp_nonce": csp_nonce, "color_scheme": user_permission_info.color_scheme, }, ) patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True) return response