def wrapper(request: HttpRequest, *args: object, **kwargs: object) -> HttpResponse: if request.method != "POST": err_method = request.method logging.warning( "Method Not Allowed (%s): %s", err_method, request.path, extra={"status_code": 405, "request": request}, ) if RequestNotes.get_notes(request).error_format == "JSON": return json_method_not_allowed(["POST"]) else: return TemplateResponse( request, "404.html", context={"status_code": 405}, status=405 ) return func(request, *args, **kwargs)
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()
def process_request(self, request: HttpRequest) -> None: maybe_tracemalloc_listen() request_notes = RequestNotes.get_notes(request) if request_notes.log_data is not None: # Sanity check to ensure this is being called from the # Tornado code path that returns responses asynchronously. assert request_notes.saved_response is not None # Avoid re-initializing request_notes.log_data if it's already there. return request_notes.client_name, request_notes.client_version = parse_client( request) request_notes.log_data = {} record_request_start_data(request_notes.log_data)
def _wrapped_func_arguments(request: HttpRequest, *args: object, **kwargs: object) -> HttpResponse: if not authenticate_notify(request): raise AccessDeniedError() request_notes = RequestNotes.get_notes(request) is_tornado_request = request_notes.tornado_handler is not None # These next 2 are not security checks; they are internal # assertions to help us find bugs. if is_tornado_view and not is_tornado_request: raise RuntimeError( "Tornado notify view called with no Tornado handler") if not is_tornado_view and is_tornado_request: raise RuntimeError( "Django notify view called with Tornado handler") request_notes.requestor_for_logs = "internal" return view_func(request, *args, **kwargs)
def format(self, record: logging.LogRecord) -> str: from zerver.lib.request import get_current_request request = get_current_request() if not request: setattr(record, "user", None) setattr(record, "client", None) setattr(record, "url", None) setattr(record, "content_type", None) setattr(record, "custom_headers", None) setattr(record, "payload", None) return super().format(record) if request.content_type == "application/json": payload = request.body else: payload = request.POST.get("payload") try: payload = orjson.dumps(orjson.loads(payload), option=orjson.OPT_INDENT_2).decode() except orjson.JSONDecodeError: pass custom_header_template = "{header}: {value}\n" header_text = "" for header in request.META.keys(): if header.lower().startswith("http_x"): header_text += custom_header_template.format( header=header, value=request.META[header]) header_message = header_text if header_text else None from zerver.lib.request import RequestNotes client = RequestNotes.get_notes(request).client assert client is not None setattr( record, "user", f"{request.user.delivery_email} ({request.user.realm.string_id})") setattr(record, "client", client.name) setattr(record, "url", request.META.get("PATH_INFO", None)) setattr(record, "content_type", request.content_type) setattr(record, "custom_headers", header_message) setattr(record, "payload", payload) return super().format(record)
def process_response(self, request: HttpRequest, response: HttpResponseBase) -> HttpResponseBase: if getattr(response, "asynchronous", False): # This special Tornado "asynchronous" response is # discarded after going through this code path as Tornado # intends to block, so we stop here to avoid unnecessary work. return response remote_ip = request.META["REMOTE_ADDR"] # Get the requestor's identifier and client, if available. request_notes = RequestNotes.get_notes(request) requestor_for_logs = request_notes.requestor_for_logs if requestor_for_logs is None: # Note that request.user is a Union[RemoteZulipServer, UserProfile, AnonymousUser], # if it is present. if hasattr(request, "user") and hasattr( request.user, "format_requestor_for_logs"): requestor_for_logs = request.user.format_requestor_for_logs() else: requestor_for_logs = "unauth@{}".format( get_subdomain(request) or "root") if response.streaming: assert isinstance(response, StreamingHttpResponse) content_iter: Optional[ Iterator[bytes]] = response.streaming_content content = None else: content = response.content content_iter = None assert request_notes.client_name is not None and request_notes.log_data is not None write_log_line( request_notes.log_data, request.path, request.method, remote_ip, requestor_for_logs, request_notes.client_name, client_version=request_notes.client_version, status_code=response.status_code, error_content=content, error_content_iter=content_iter, ) return response
def update_message_flags( request: HttpRequest, user_profile: UserProfile, messages: List[int] = REQ(json_validator=check_list(check_int)), operation: str = REQ("op"), flag: str = REQ(), ) -> HttpResponse: request_notes = RequestNotes.get_notes(request) assert request_notes.log_data is not None count = do_update_message_flags(user_profile, operation, flag, messages) target_count_str = str(len(messages)) log_data_str = f"[{operation} {flag}/{target_count_str}] actually {count}" request_notes.log_data["extra"] = log_data_str return json_success(request, data={"messages": messages})
def get_and_set_request_language( request: HttpRequest, user_configured_language: str, testing_url_language: Optional[str] = None ) -> str: # We pick a language for the user as follows: # * First priority is the language in the URL, for debugging. # * If not in the URL, we use the language from the user's settings. request_language = testing_url_language if request_language is None: request_language = user_configured_language translation.activate(request_language) # We also want to save the language to the user's cookies, so that # something reasonable will happen in logged-in portico pages. # We accomplish that by setting a flag on the request which signals # to LocaleMiddleware to set the cookie on the response. RequestNotes.get_notes(request).set_language = translation.get_language() return request_language
def handle(self, *args: Any, **options: Any) -> None: realm = self.get_realm(options) user = self.get_user(options["email"], realm) anchor = UserMessage.objects.filter( user_profile=user).order_by("-message")[200].message_id mock_request = HostRequestMock( post_data={ "anchor": anchor, "num_before": 1200, "num_after": 200, }, user_profile=user, meta_data={"REMOTE_ADDR": "127.0.0.1"}, path="/", ) mock_request.session = MockSession() RequestNotes.get_notes(mock_request).log_data = None profile_request(mock_request)
def api_fetch_api_key( request: HttpRequest, username: str = REQ(), password: str = REQ() ) -> HttpResponse: return_data: Dict[str, bool] = {} realm = get_realm_from_request(request) if realm is None: raise InvalidSubdomainError() if not ldap_auth_enabled(realm=realm): # In case we don't authenticate against LDAP, check for a valid # email. LDAP backend can authenticate against a non-email. validate_login_email(username) user_profile = authenticate( request=request, username=username, password=password, realm=realm, return_data=return_data ) if return_data.get("inactive_user"): raise UserDeactivatedError() if return_data.get("inactive_realm"): raise RealmDeactivatedError() if return_data.get("password_auth_disabled"): raise PasswordAuthDisabledError() if return_data.get("password_reset_needed"): raise PasswordResetRequiredError() if user_profile is None: raise AuthenticationFailedError() assert user_profile.is_authenticated # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) RequestNotes.get_notes(request).requestor_for_logs = user_profile.format_requestor_for_logs() api_key = get_api_key(user_profile) return json_success({"api_key": api_key, "email": user_profile.delivery_email})
def finish_handler(handler_id: int, event_queue_id: str, contents: List[Dict[str, Any]], apply_markdown: bool) -> None: err_msg = f"Got error finishing handler for queue {event_queue_id}" try: # We do the import during runtime to avoid cyclic dependency # with zerver.lib.request from zerver.lib.request import RequestNotes from zerver.middleware import async_request_timer_restart # We call async_request_timer_restart here in case we are # being finished without any events (because another # get_events request has supplanted this request) handler = get_handler_by_id(handler_id) request = handler._request async_request_timer_restart(request) log_data = RequestNotes.get_notes(request).log_data assert log_data is not None if len(contents) != 1: log_data["extra"] = f"[{event_queue_id}/1]" else: log_data["extra"] = "[{}/1/{}]".format(event_queue_id, contents[0]["type"]) tornado.ioloop.IOLoop.current().add_callback( handler.zulip_finish, dict(result="success", msg="", events=contents, queue_id=event_queue_id), request, apply_markdown=apply_markdown, ) except OSError as e: if str(e) != "Stream is closed": logging.exception(err_msg, stack_info=True) except AssertionError as e: if str(e) != "Request closed": logging.exception(err_msg, stack_info=True) except Exception: logging.exception(err_msg, stack_info=True)
def format(self, record: logging.LogRecord) -> str: from zerver.lib.request import get_current_request request = get_current_request() if not request: setattr(record, "user", None) setattr(record, "client", None) setattr(record, "url", None) setattr(record, "content_type", None) setattr(record, "custom_headers", None) setattr(record, "payload", None) return super().format(record) if request.content_type == "application/json": payload: Union[str, bytes] = request.body else: payload = request.POST["payload"] try: payload = orjson.dumps(orjson.loads(payload), option=orjson.OPT_INDENT_2).decode() except orjson.JSONDecodeError: pass header_text = "".join( f"{header}: {value}\n" for header, value in request.headers.items() if header.lower().startswith("x-") ) from zerver.lib.request import RequestNotes client = RequestNotes.get_notes(request).client assert client is not None setattr(record, "user", f"{request.user.delivery_email} ({request.user.realm.string_id})") setattr(record, "client", client.name) setattr(record, "url", request.META.get("PATH_INFO", None)) setattr(record, "content_type", request.content_type) setattr(record, "custom_headers", header_text or None) setattr(record, "payload", payload) return super().format(record)
def add_context(event: "Event", hint: "Hint") -> Optional["Event"]: if "exc_info" in hint: _, exc_value, _ = hint["exc_info"] # Ignore GeneratorExit, KeyboardInterrupt, and SystemExit exceptions if not isinstance(exc_value, Exception): return None from django.conf import settings from zerver.lib.request import RequestNotes, get_current_request from zerver.models import get_user_profile_by_id with capture_internal_exceptions(): # event.user is the user context, from Sentry, which is # pre-populated with some keys via its Django integration: # https://docs.sentry.io/platforms/python/guides/django/enriching-error-data/additional-data/identify-user/ event.setdefault("tags", {}) user_info = event.get("user", {}) if user_info.get("id"): user_profile = get_user_profile_by_id(user_info["id"]) event["tags"]["realm"] = user_info[ "realm"] = user_profile.realm.string_id or "root" with override_language(settings.LANGUAGE_CODE): # str() to force the lazy-translation to apply now, # since it won't serialize into json for Sentry otherwise user_info["role"] = str(user_profile.get_role_name()) # These are PII, and should be scrubbed if "username" in user_info: del user_info["username"] if "email" in user_info: del user_info["email"] request = get_current_request() if request: request_notes = RequestNotes.get_notes(request) if request_notes.client is not None: event["tags"]["client"] = request_notes.client.name if request_notes.realm is not None: event["tags"].setdefault("realm", request_notes.realm.string_id) return event
def get_extras( cls, q: str, request: Optional[HttpRequest] = None) -> Tuple[str, List[object]]: """ Return extra SQL and params to be attached to end of current Query's SQL and params. The return format matches the format that should be used for providing raw SQL with params to Django's .raw(): https://docs.djangoproject.com/en/3.2/topics/db/sql/#passing-parameters-into-raw Here we ensure that results are limited to the subdomain of the request and also exclude bots, as we currently don't want them to be managed by SCIM2. """ assert request is not None realm = RequestNotes.get_notes(request).realm assert realm is not None return ( "AND zerver_realm.id = %s AND zerver_userprofile.is_bot = False ORDER BY zerver_userprofile.id", [realm.id], )
def create_mirrored_message_users(request: HttpRequest, user_profile: UserProfile, recipients: Iterable[str]) -> UserProfile: if "sender" not in request.POST: raise InvalidMirrorInput("No sender") sender_email = request.POST["sender"].strip().lower() referenced_users = {sender_email} if request.POST["type"] == "private": for email in recipients: referenced_users.add(email.lower()) client = RequestNotes.get_notes(request).client assert client is not None if client.name == "zephyr_mirror": user_check = same_realm_zephyr_user fullname_function = compute_mit_user_fullname elif client.name == "irc_mirror": user_check = same_realm_irc_user fullname_function = compute_irc_user_fullname elif client.name in ("jabber_mirror", "JabberMirror"): user_check = same_realm_jabber_user fullname_function = compute_jabber_user_fullname else: raise InvalidMirrorInput("Unrecognized mirroring client") for email in referenced_users: # Check that all referenced users are in our realm: if not user_check(user_profile, email): raise InvalidMirrorInput("At least one user cannot be mirrored") # Create users for the referenced users, if needed. for email in referenced_users: create_mirror_user_if_needed(user_profile.realm, email, fullname_function) sender = get_user_including_cross_realm(sender_email, user_profile.realm) return sender
def rate_limit_request(self, request: HttpRequest) -> None: from zerver.lib.request import RequestNotes ratelimited, time = self.rate_limit() request_notes = RequestNotes.get_notes(request) request_notes.ratelimits_applied.append( RateLimitResult( entity=self, secs_to_freedom=time, remaining=0, over_limit=ratelimited, )) # Abort this request if the user is over their rate limits if ratelimited: # Pass information about what kind of entity got limited in the exception: raise RateLimited(time) calls_remaining, seconds_until_reset = self.api_calls_left() request_notes.ratelimits_applied[-1].remaining = calls_remaining request_notes.ratelimits_applied[ -1].secs_to_freedom = seconds_until_reset
def update_user_activity(request: HttpRequest, user_profile: UserProfile, query: Optional[str]) -> None: # update_active_status also pushes to RabbitMQ, and it seems # redundant to log that here as well. if request.META["PATH_INFO"] == "/json/users/me/presence": return request_notes = RequestNotes.get_notes(request) if query is not None: pass elif request_notes.query is not None: query = request_notes.query else: query = request.META["PATH_INFO"] assert request_notes.client is not None event = { "query": query, "user_profile_id": user_profile.id, "time": datetime_to_timestamp(timezone_now()), "client_id": request_notes.client.id, } queue_json_publish("user_activity", event, lambda event: None)
def finish_mobile_flow(request: HttpRequest, user_profile: UserProfile, otp: str) -> HttpResponse: # For the mobile OAuth flow, we send the API key and other # necessary details in a redirect to a zulip:// URI scheme. api_key = get_api_key(user_profile) response = create_response_for_otp_flow( api_key, otp, user_profile, encrypted_key_field_name="otp_encrypted_api_key" ) # Since we are returning an API key instead of going through # the Django login() function (which creates a browser # session, etc.), the "new login" signal handler (which # triggers an email notification new logins) will not run # automatically. So we call it manually here. # # Arguably, sending a fake 'user_logged_in' signal would be a better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) RequestNotes.get_notes(request).requestor_for_logs = user_profile.format_requestor_for_logs() return response
def update_active_status_backend( request: HttpRequest, user_profile: UserProfile, status: str = REQ(), ping_only: bool = REQ(json_validator=check_bool, default=False), new_user_input: bool = REQ(json_validator=check_bool, default=False), slim_presence: bool = REQ(json_validator=check_bool, default=False), ) -> HttpResponse: status_val = UserPresence.status_from_string(status) if status_val is None: raise JsonableError(_("Invalid status: {}").format(status)) elif user_profile.presence_enabled: client = RequestNotes.get_notes(request).client assert client is not None update_user_presence(user_profile, client, timezone_now(), status_val, new_user_input) if ping_only: ret: Dict[str, Any] = {} else: ret = get_presence_response(user_profile, slim_presence) if user_profile.realm.is_zephyr_mirror_realm: # In zephyr mirroring realms, users can't see the presence of other # users, but each user **is** interested in whether their mirror bot # (running as their user) has been active. try: activity = UserActivity.objects.get( user_profile=user_profile, query="get_events", client__name="zephyr_mirror" ) ret["zephyr_mirror_active"] = activity.last_visit > timezone_now() - datetime.timedelta( minutes=5 ) except UserActivity.DoesNotExist: ret["zephyr_mirror_active"] = False return json_success(request, data=ret)
def api_slack_webhook( request: HttpRequest, user_profile: UserProfile, user_name: str = REQ(), text: str = REQ(), channel_name: str = REQ(), stream: str = REQ(default="slack"), channels_map_to_topics: str = REQ(default="1"), ) -> HttpResponse: if channels_map_to_topics not in list(VALID_OPTIONS.values()): raise JsonableError(_("Error: channels_map_to_topics parameter other than 0 or 1")) if channels_map_to_topics == VALID_OPTIONS["SHOULD_BE_MAPPED"]: subject = f"channel: {channel_name}" else: stream = channel_name subject = _("Message from Slack") content = ZULIP_MESSAGE_TEMPLATE.format(message_sender=user_name, text=text) client = RequestNotes.get_notes(request).client assert client is not None check_send_stream_message(user_profile, client, stream, subject, content) return json_success()
def convert_tornado_request_to_django_request(self) -> HttpRequest: # This takes the WSGI environment that Tornado received (which # fully describes the HTTP request that was sent to Tornado) # and pass it to Django's WSGIRequest to generate a Django # HttpRequest object with the original Tornado request's HTTP # headers, parameters, etc. environ = WSGIContainer.environ(self.request) environ["PATH_INFO"] = urllib.parse.unquote(environ["PATH_INFO"]) # Django WSGIRequest setup code that should match logic from # Django's WSGIHandler.__call__ before the call to # `get_response()`. set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__) request = WSGIRequest(environ) # We do the import during runtime to avoid cyclic dependency from zerver.lib.request import RequestNotes # Provide a way for application code to access this handler # given the HttpRequest object. RequestNotes.get_notes(request).tornado_handler = weakref.ref(self) return request
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]: # Match against ALLOWED_HOSTS, which is rather permissive; # failure will raise DisallowedHost, which is a 400. request.get_host() # This check is important to avoid doing the extra work of # `get_realm` (which does a database query that could be # problematic for Tornado). Also the error page below is only # appropriate for a page visited in a browser, not the API. # # API authentication will end up checking for an invalid # realm, and throw a JSON-format error if appropriate. if request.path.startswith(("/static/", "/api/", "/json/")): return None subdomain = get_subdomain(request) if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN: request_notes = RequestNotes.get_notes(request) try: request_notes.realm = get_realm(subdomain) except Realm.DoesNotExist: return render(request, "zerver/invalid_realm.html", status=404) request_notes.has_fetched_realm = True return None
def update_subscription_properties_backend( request: HttpRequest, user_profile: UserProfile, subscription_data: List[Dict[str, Any]] = REQ(json_validator=check_list( check_dict([ ("stream_id", check_int), ("property", check_string), ("value", check_union([check_string, check_bool])), ]), ), ), ) -> HttpResponse: """ This is the entry point to changing subscription properties. This is a bulk endpoint: requestors always provide a subscription_data list containing dictionaries for each stream of interest. Requests are of the form: [{"stream_id": "1", "property": "is_muted", "value": False}, {"stream_id": "1", "property": "color", "value": "#c2c2c2"}] """ property_converters = { "color": check_color, "in_home_view": check_bool, "is_muted": check_bool, "desktop_notifications": check_bool, "audible_notifications": check_bool, "push_notifications": check_bool, "email_notifications": check_bool, "pin_to_top": check_bool, "wildcard_mentions_notify": check_bool, } for change in subscription_data: stream_id = change["stream_id"] property = change["property"] value = change["value"] if property not in property_converters: raise JsonableError( _("Unknown subscription property: {}").format(property)) (stream, sub) = access_stream_by_id(user_profile, stream_id) if sub is None: raise JsonableError( _("Not subscribed to stream id {}").format(stream_id)) try: value = property_converters[property](property, value) except ValidationError as error: raise JsonableError(error.message) do_change_subscription_property(user_profile, sub, stream, property, value, acting_user=user_profile) # TODO: Do this more generally, see update_realm_user_settings_defaults.realm.py from zerver.lib.request import RequestNotes request_notes = RequestNotes.get_notes(request) for req_var in request.POST: if req_var not in request_notes.processed_parameters: request_notes.ignored_parameters.add(req_var) result: Dict[str, Any] = {} if len(request_notes.ignored_parameters) > 0: result["ignored_parameters_unsupported"] = list( request_notes.ignored_parameters) return json_success(request, data=result)
def check_send_webhook_message( request: HttpRequest, user_profile: UserProfile, topic: str, body: str, complete_event_type: Optional[str] = None, stream: Optional[str] = REQ(default=None), user_specified_topic: Optional[str] = REQ("topic", default=None), only_events: Optional[List[str]] = REQ( default=None, json_validator=check_list(check_string)), exclude_events: Optional[List[str]] = REQ( default=None, json_validator=check_list(check_string)), unquote_url_parameters: bool = False, ) -> None: if complete_event_type is not None: # Here, we implement Zulip's generic support for filtering # events sent by the third-party service. # # If complete_event_type is passed to this function, we will check the event # type against user configured lists of only_events and exclude events. # If the event does not satisfy the configuration, the function will return # without sending any messages. # # We match items in only_events and exclude_events using Unix # shell-style wildcards. if (only_events is not None and all(not fnmatch.fnmatch(complete_event_type, pattern) for pattern in only_events)) or ( exclude_events is not None and any( fnmatch.fnmatch(complete_event_type, pattern) for pattern in exclude_events)): return client = RequestNotes.get_notes(request).client assert client is not None if stream is None: assert user_profile.bot_owner is not None check_send_private_message(user_profile, client, user_profile.bot_owner, body) else: # Some third-party websites (such as Atlassian's Jira), tend to # double escape their URLs in a manner that escaped space characters # (%20) are never properly decoded. We work around that by making sure # that the URL parameters are decoded on our end. if unquote_url_parameters: stream = unquote(stream) if user_specified_topic is not None: topic = user_specified_topic if unquote_url_parameters: topic = unquote(topic) try: if stream.isdecimal(): check_send_stream_message_by_id(user_profile, client, int(stream), topic, body) else: check_send_stream_message(user_profile, client, stream, topic, body) except StreamDoesNotExistError: # A PM will be sent to the bot_owner by check_message, notifying # that the webhook bot just tried to send a message to a non-existent # stream, so we don't need to re-raise it since it clutters up # webhook-errors.log pass
def update_user_status_backend( request: HttpRequest, user_profile: UserProfile, away: Optional[bool] = REQ(json_validator=check_bool, default=None), status_text: Optional[str] = REQ(str_validator=check_capped_string(60), default=None), emoji_name: Optional[str] = REQ(default=None), emoji_code: Optional[str] = REQ(default=None), # TODO: emoji_type is the more appropriate name for this parameter, but changing # that requires nontrivial work on the API documentation, since it's not clear # that the reactions endpoint would prefer such a change. emoji_type: Optional[str] = REQ("reaction_type", default=None), ) -> HttpResponse: if status_text is not None: status_text = status_text.strip() if (away is None) and (status_text is None) and (emoji_name is None): raise JsonableError(_("Client did not pass any new values.")) if emoji_name == "": # Reset the emoji_code and reaction_type if emoji_name is empty. # This should clear the user's configured emoji. emoji_code = "" emoji_type = UserStatus.UNICODE_EMOJI elif emoji_name is not None: if emoji_code is None: # The emoji_code argument is only required for rare corner # cases discussed in the long block comment below. For simple # API clients, we allow specifying just the name, and just # look up the code using the current name->code mapping. emoji_code = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[0] if emoji_type is None: emoji_type = emoji_name_to_emoji_code(user_profile.realm, emoji_name)[1] elif emoji_type or emoji_code: raise JsonableError( _("Client must pass emoji_name if they pass either emoji_code or reaction_type.") ) # If we're asking to set an emoji (not clear it ("") or not adjust # it (None)), we need to verify the emoji is valid. if emoji_name not in ["", None]: assert emoji_name is not None assert emoji_code is not None assert emoji_type is not None check_emoji_request(user_profile.realm, emoji_name, emoji_code, emoji_type) client = RequestNotes.get_notes(request).client assert client is not None do_update_user_status( user_profile=user_profile, away=away, status_text=status_text, client_id=client.id, emoji_name=emoji_name, emoji_code=emoji_code, reaction_type=emoji_type, ) return json_success(request)
def save(self) -> None: """ This method is called at the end of operations modifying a user, and is responsible for actually applying the requested changes, writing them to the database. """ realm = RequestNotes.get_notes(self._request).realm assert realm is not None email_new_value = getattr(self, "_email_new_value", None) is_active_new_value = getattr(self, "_is_active_new_value", None) full_name_new_value = getattr(self, "_full_name_new_value", None) password = getattr(self, "_password_set_to", None) # Clean up the internal "pending change" state, now that we've # fetched the values: self._email_new_value = None self._is_active_new_value = None self._full_name_new_value = None self._password_set_to = None if email_new_value: try: # Note that the validate_email check that usually # appears adjacent to email_allowed_for_realm is # present in save(). email_allowed_for_realm(email_new_value, realm) except DomainNotAllowedForRealmError: raise scim_exceptions.BadRequestError( "This email domain isn't allowed in this organization.") except DisposableEmailError: # nocoverage raise scim_exceptions.BadRequestError( "Disposable email domains are not allowed for this realm.") except EmailContainsPlusError: # nocoverage raise scim_exceptions.BadRequestError( "Email address can't contain + characters.") try: validate_email_not_already_in_realm(realm, email_new_value) except ValidationError as e: raise ConflictError("Email address already in use: " + str(e)) if self.is_new_user(): assert full_name_new_value is not None self.obj = do_create_user( email_new_value, password, realm, full_name_new_value, acting_user=None, ) return # TODO: The below operations should ideally be executed in a single # atomic block to avoid failing with partial changes getting saved. # This can be fixed once we figure out how do_deactivate_user can be run # inside an atomic block. # We process full_name first here, since it's the only one that can fail. if full_name_new_value: check_change_full_name(self.obj, full_name_new_value, acting_user=None) if email_new_value: do_change_user_delivery_email(self.obj, email_new_value) if is_active_new_value is not None and is_active_new_value: do_reactivate_user(self.obj, acting_user=None) elif is_active_new_value is not None and not is_active_new_value: do_deactivate_user(self.obj, acting_user=None)
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 build_page_params_for_home_page_load( request: HttpRequest, user_profile: Optional[UserProfile], realm: Realm, insecure_desktop_app: bool, narrow: List[List[str]], narrow_stream: Optional[Stream], narrow_topic: Optional[str], first_in_realm: bool, prompt_for_invites: bool, needs_tutorial: bool, ) -> Tuple[int, Dict[str, Any]]: """ This function computes page_params for when we load the home page. The page_params data structure gets sent to the client. """ client_capabilities = { "notification_settings_null": True, "bulk_message_deletion": True, "user_avatar_url_field_optional": True, "stream_typing_notifications": False, # Set this to True when frontend support is implemented. "user_settings_object": True, } if user_profile is not None: client = RequestNotes.get_notes(request).client assert client is not None register_ret = do_events_register( user_profile, client, apply_markdown=True, client_gravatar=True, slim_presence=True, client_capabilities=client_capabilities, narrow=narrow, include_streams=False, ) else: # Since events for spectator is not implemented, we only fetch the data # at the time of request and don't register for any events. # TODO: Implement events for spectator. from zerver.lib.events import fetch_initial_state_data, post_process_state register_ret = fetch_initial_state_data( user_profile, realm=realm, event_types=None, queue_id=None, client_gravatar=False, user_avatar_url_field_optional=client_capabilities["user_avatar_url_field_optional"], user_settings_object=client_capabilities["user_settings_object"], slim_presence=False, include_subscribers=False, include_streams=False, ) post_process_state(user_profile, register_ret, False) furthest_read_time = get_furthest_read_time(user_profile) request_language = get_and_set_request_language( request, register_ret["user_settings"]["default_language"], translation.get_language_from_path(request.path_info), ) two_fa_enabled = settings.TWO_FACTOR_AUTHENTICATION_ENABLED and user_profile is not None billing_info = get_billing_info(user_profile) user_permission_info = get_user_permission_info(user_profile) # Pass parameters to the client-side JavaScript code. # These end up in a JavaScript Object named 'page_params'. page_params = dict( ## Server settings. test_suite=settings.TEST_SUITE, insecure_desktop_app=insecure_desktop_app, login_page=settings.HOME_NOT_LOGGED_IN, warn_no_email=settings.WARN_NO_EMAIL, search_pills_enabled=settings.SEARCH_PILLS_ENABLED, # Only show marketing email settings if on Zulip Cloud corporate_enabled=settings.CORPORATE_ENABLED, ## Misc. extra data. language_list=get_language_list(), needs_tutorial=needs_tutorial, first_in_realm=first_in_realm, prompt_for_invites=prompt_for_invites, furthest_read_time=furthest_read_time, bot_types=get_bot_types(user_profile), two_fa_enabled=two_fa_enabled, apps_page_url=get_apps_page_url(), show_billing=billing_info.show_billing, promote_sponsoring_zulip=promote_sponsoring_zulip_in_realm(realm), show_plans=billing_info.show_plans, show_webathena=user_permission_info.show_webathena, # Adding two_fa_enabled as condition saves us 3 queries when # 2FA is not enabled. two_fa_enabled_user=two_fa_enabled and bool(default_device(user_profile)), is_spectator=user_profile is None, # There is no event queue for spectators since # events support for spectators is not implemented yet. no_event_queue=user_profile is None, ) for field_name in register_ret.keys(): page_params[field_name] = register_ret[field_name] if narrow_stream is not None: # In narrow_stream context, initial pointer is just latest message recipient = narrow_stream.recipient try: max_message_id = ( Message.objects.filter(recipient=recipient).order_by("id").reverse()[0].id ) except IndexError: max_message_id = -1 page_params["narrow_stream"] = narrow_stream.name if narrow_topic is not None: page_params["narrow_topic"] = narrow_topic page_params["narrow"] = [dict(operator=term[0], operand=term[1]) for term in narrow] page_params["max_message_id"] = max_message_id assert isinstance(page_params["user_settings"], dict) page_params["user_settings"]["enable_desktop_notifications"] = False page_params["translation_data"] = get_language_translation_data(request_language) return register_ret["queue_id"], page_params
def update_realm_user_settings_defaults( request: HttpRequest, user_profile: UserProfile, dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None), starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None), fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool, default=None), high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool, default=None), color_scheme: Optional[int] = REQ(json_validator=check_int_in( UserProfile.COLOR_SCHEME_CHOICES), default=None), translate_emoticons: Optional[bool] = REQ(json_validator=check_bool, default=None), default_view: Optional[str] = REQ( str_validator=check_string_in(default_view_options), default=None), escape_navigates_to_default_view: Optional[bool] = REQ( json_validator=check_bool, default=None), left_side_userlist: Optional[bool] = REQ(json_validator=check_bool, default=None), emojiset: Optional[str] = REQ( str_validator=check_string_in(emojiset_choices), default=None), demote_inactive_streams: Optional[int] = REQ(json_validator=check_int_in( UserProfile.DEMOTE_STREAMS_CHOICES), default=None), enable_stream_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_stream_audible_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None), notification_sound: Optional[str] = REQ(default=None), enable_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_offline_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_offline_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_online_push_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), # enable_login_emails is not included here, because we don't want # security-related settings to be controlled by organization administrators. # enable_marketing_emails is not included here, since we don't at # present allow organizations to customize this. (The user's selection # in the signup form takes precedence over RealmUserDefault). # # We may want to change this model in the future, since some SSO signups # do not offer an opportunity to prompt the user at all during signup. message_content_in_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), pm_content_in_desktop_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), desktop_icon_count_display: Optional[int] = REQ( json_validator=check_int_in( UserProfile.DESKTOP_ICON_COUNT_DISPLAY_CHOICES), default=None), realm_name_in_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None), enter_sends: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_drafts_synchronization: Optional[bool] = REQ( json_validator=check_bool, default=None), email_notifications_batching_period_seconds: Optional[int] = REQ( json_validator=check_int, default=None), twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool, default=None), send_stream_typing_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), send_private_typing_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None), ) -> HttpResponse: if notification_sound is not None or email_notifications_batching_period_seconds is not None: check_settings_values(notification_sound, email_notifications_batching_period_seconds) realm_user_default = RealmUserDefault.objects.get(realm=user_profile.realm) request_settings = { k: v for k, v in list(locals().items()) if (k in RealmUserDefault.property_types) } for k, v in list(request_settings.items()): if v is not None and getattr(realm_user_default, k) != v: do_set_realm_user_default_setting(realm_user_default, k, v, acting_user=user_profile) # TODO: Extract `ignored_parameters_unsupported` to be a common feature of the REQ framework. from zerver.lib.request import RequestNotes request_notes = RequestNotes.get_notes(request) for req_var in request.POST: if req_var not in request_notes.processed_parameters: request_notes.ignored_parameters.add(req_var) result: Dict[str, Any] = {} if len(request_notes.ignored_parameters) > 0: result["ignored_parameters_unsupported"] = list( request_notes.ignored_parameters) return json_success(result)
def get_extra_filter_kwargs(request: HttpRequest, *args: Any, **kwargs: Any) -> Dict[str, object]: realm = RequestNotes.get_notes(request).realm assert realm is not None return {"realm_id": realm.id, "is_bot": False}