def rate_limit_user(request: HttpRequest, user: UserProfile, domain: str) -> None: """Returns whether or not a user was rate limited. Will raise a RateLimited exception if the user has been rate limited, otherwise returns and modifies request to contain the rate limit information""" entity = RateLimitedUser(user, domain=domain) ratelimited, time = is_ratelimited(entity) request._ratelimit_applied_limits = True request._ratelimit_secs_to_freedom = time request._ratelimit_over_limit = ratelimited # Abort this request if the user is over their rate limits if ratelimited: statsd.incr("ratelimiter.limited.%s.%s" % (type(user), user.id)) raise RateLimited() try: incr_ratelimit(entity) except RateLimiterLockingException: logging.warning("Deadlock trying to incr_ratelimit for %s on %s" % ( user.id, request.path)) # rate-limit users who are hitting the API so hard we can't update our stats. raise RateLimited() calls_remaining, time_reset = api_calls_left(entity) request._ratelimit_remaining = calls_remaining request._ratelimit_secs_to_freedom = time_reset
def rate_limit_mirror_by_realm(recipient_realm: Realm) -> None: # Code based on the rate_limit_user function: entity = RateLimitedRealmMirror(recipient_realm) ratelimited, time = is_ratelimited(entity) if ratelimited: statsd.incr("ratelimiter.limited.%s.%s" % (type(recipient_realm), recipient_realm.id)) raise RateLimited() try: incr_ratelimit(entity) except RateLimiterLockingException: logger.warning("Email mirror rate limiter: Deadlock trying to " "incr_ratelimit for realm %s" % (recipient_realm.name, )) raise RateLimited()
def rate_limit_request(self, request: HttpRequest) -> None: ratelimited, time = self.rate_limit() entity_type = type(self).__name__ if not hasattr(request, '_ratelimit'): request._ratelimit = {} request._ratelimit[entity_type] = RateLimitResult( entity=self, secs_to_freedom=time, 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(entity_type) calls_remaining, time_reset = self.api_calls_left() request._ratelimit[entity_type].remaining = calls_remaining request._ratelimit[entity_type].secs_to_freedom = time_reset
def rate_limit_request(self, request: HttpRequest) -> None: ratelimited, time = self.rate_limit() if not hasattr(request, '_ratelimits_applied'): request._ratelimits_applied = [] request._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(str(time)) calls_remaining, seconds_until_reset = self.api_calls_left() request._ratelimits_applied[-1].remaining = calls_remaining request._ratelimits_applied[-1].secs_to_freedom = seconds_until_reset
def rate_limit_user(request: HttpRequest, user: UserProfile, domain: str) -> None: """Returns whether or not a user was rate limited. Will raise a RateLimited exception if the user has been rate limited, otherwise returns and modifies request to contain the rate limit information""" entity = RateLimitedUser(user, domain=domain) ratelimited, time = rate_limit_entity(entity) request._ratelimit_applied_limits = True request._ratelimit_secs_to_freedom = time request._ratelimit_over_limit = ratelimited # Abort this request if the user is over their rate limits if ratelimited: raise RateLimited() calls_remaining, time_reset = api_calls_left(entity) request._ratelimit_remaining = calls_remaining request._ratelimit_secs_to_freedom = time_reset
def rate_limit_request_by_entity(request: HttpRequest, entity: RateLimitedObject) -> None: ratelimited, time = rate_limit_entity(entity) entity_type = type(entity).__name__ if not hasattr(request, '_ratelimit'): request._ratelimit = {} request._ratelimit[entity_type] = {} request._ratelimit[entity_type]['applied_limits'] = True request._ratelimit[entity_type]['secs_to_freedom'] = time request._ratelimit[entity_type]['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(entity_type) calls_remaining, time_reset = api_calls_left(entity) request._ratelimit[entity_type]['remaining'] = calls_remaining request._ratelimit[entity_type]['secs_to_freedom'] = time_reset
def rate_limit_user(request, user, domain): # type: (HttpRequest, UserProfile, Text) -> None """Returns whether or not a user was rate limited. Will raise a RateLimited exception if the user has been rate limited, otherwise returns and modifies request to contain the rate limit information""" ratelimited, time = is_ratelimited(user, domain) request._ratelimit_applied_limits = True request._ratelimit_secs_to_freedom = time request._ratelimit_over_limit = ratelimited # Abort this request if the user is over their rate limits if ratelimited: statsd.incr("ratelimiter.limited.%s.%s" % (type(user), user.id)) raise RateLimited() incr_ratelimit(user, domain) calls_remaining, time_reset = api_calls_left(user, domain) request._ratelimit_remaining = calls_remaining request._ratelimit_secs_to_freedom = time_reset
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 rate_limit_mirror_by_realm(recipient_realm: Realm) -> None: entity = RateLimitedRealmMirror(recipient_realm) ratelimited = rate_limit_entity(entity)[0] if ratelimited: raise RateLimited()
def rate_limit_mirror_by_realm(recipient_realm: Realm) -> None: ratelimited = RateLimitedRealmMirror(recipient_realm).rate_limit()[0] if ratelimited: raise RateLimited()
def rate_limit_mirror_by_realm(recipient_realm: Realm) -> None: ratelimited, secs_to_freedom = RateLimitedRealmMirror( recipient_realm).rate_limit() if ratelimited: raise RateLimited(secs_to_freedom)
def json_change_settings( request: HttpRequest, user_profile: UserProfile, full_name: str = REQ(default=""), email: str = REQ(default=""), old_password: str = REQ(default=""), new_password: str = REQ(default=""), twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool, default=None), 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_language: Optional[str] = REQ(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), timezone: Optional[str] = REQ(str_validator=check_string_in( pytz.all_timezones_set), default=None), email_notifications_batching_period_seconds: Optional[int] = REQ( json_validator=check_int, default=None), enable_drafts_synchronization: Optional[bool] = REQ( json_validator=check_bool, 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: Optional[bool] = REQ(json_validator=check_bool, default=None), enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), 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), send_private_typing_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), send_stream_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 (default_language is not None or notification_sound is not None or email_notifications_batching_period_seconds is not None): check_settings_values(notification_sound, email_notifications_batching_period_seconds, default_language) if new_password != "": return_data: Dict[str, Any] = {} if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email): raise JsonableError(_("Your Zulip password is managed in LDAP")) try: if not authenticate( request, username=user_profile.delivery_email, password=old_password, realm=user_profile.realm, return_data=return_data, ): raise JsonableError(_("Wrong password!")) except RateLimited as e: assert e.secs_to_freedom is not None secs_to_freedom = int(e.secs_to_freedom) raise JsonableError( _("You're making too many attempts! Try again in {} seconds."). format(secs_to_freedom), ) if not check_password_strength(new_password): raise JsonableError(_("New password is too weak!")) do_change_password(user_profile, new_password) # Password changes invalidates sessions, see # https://docs.djangoproject.com/en/3.2/topics/auth/default/#session-invalidation-on-password-change # for details. To avoid this logging the user out of their own # session (which would provide a confusing UX at best), we # update the session hash here. update_session_auth_hash(request, user_profile) # We also save the session to the DB immediately to mitigate # race conditions. In theory, there is still a race condition # and to completely avoid it we will have to use some kind of # mutex lock in `django.contrib.auth.get_user` where session # is verified. To make that lock work we will have to control # the AuthenticationMiddleware which is currently controlled # by Django, request.session.save() result: Dict[str, Any] = {} new_email = email.strip() if user_profile.delivery_email != new_email and new_email != "": if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin: raise JsonableError( _("Email address changes are disabled in this organization.")) error = validate_email_is_valid( new_email, get_realm_email_validator(user_profile.realm), ) if error: raise JsonableError(error) try: validate_email_not_already_in_realm( user_profile.realm, new_email, verbose=False, ) except ValidationError as e: raise JsonableError(e.message) ratelimited, time_until_free = RateLimitedUser( user_profile, domain="email_change_by_user").rate_limit() if ratelimited: raise RateLimited(time_until_free) do_start_email_change_process(user_profile, new_email) if user_profile.full_name != full_name and full_name.strip() != "": if name_changes_disabled( user_profile.realm) and not user_profile.is_realm_admin: # Failingly silently is fine -- they can't do it through the UI, so # they'd have to be trying to break the rules. pass else: # Note that check_change_full_name strips the passed name automatically check_change_full_name(user_profile, full_name, user_profile) # Loop over user_profile.property_types request_settings = { k: v for k, v in list(locals().items()) if k in user_profile.property_types } for k, v in list(request_settings.items()): if v is not None and getattr(user_profile, k) != v: do_change_user_setting(user_profile, k, v, acting_user=user_profile) if timezone is not None and user_profile.timezone != timezone: do_change_user_setting(user_profile, "timezone", timezone, acting_user=user_profile) # TODO: Do this more generally. 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) if len(request_notes.ignored_parameters) > 0: result["ignored_parameters_unsupported"] = list( request_notes.ignored_parameters) return json_success(result)
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 anonymous access to avatars only if spectators are # enabled in the organization. realm = get_valid_realm_from_request(request) if not realm.allow_web_public_streams_access(): 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() if settings.RATE_LIMITING: try: unique_avatar_key = f"{realm.id}/{email_or_id}/{medium}" rate_limit_spectator_attachment_access_by_file( unique_avatar_key) except RateLimited: return json_response_from_error( RateLimited( _("Too many attempts, please try after some time."))) 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 rate_limit_password_reset_form_by_email(email: str) -> None: ratelimited, secs_to_freedom = RateLimitedPasswordResetByEmail( email).rate_limit() if ratelimited: raise RateLimited(secs_to_freedom)