resource_limits_server_notices.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. from six import iteritems
  17. from twisted.internet import defer
  18. from synapse.api.constants import (
  19. EventTypes,
  20. ServerNoticeLimitReached,
  21. ServerNoticeMsgType,
  22. )
  23. from synapse.api.errors import AuthError, ResourceLimitError, SynapseError
  24. from synapse.server_notices.server_notices_manager import SERVER_NOTICE_ROOM_TAG
  25. logger = logging.getLogger(__name__)
  26. class ResourceLimitsServerNotices(object):
  27. """ Keeps track of whether the server has reached it's resource limit and
  28. ensures that the client is kept up to date.
  29. """
  30. def __init__(self, hs):
  31. """
  32. Args:
  33. hs (synapse.server.HomeServer):
  34. """
  35. self._server_notices_manager = hs.get_server_notices_manager()
  36. self._store = hs.get_datastore()
  37. self._auth = hs.get_auth()
  38. self._config = hs.config
  39. self._resouce_limited = False
  40. self._message_handler = hs.get_message_handler()
  41. self._state = hs.get_state_handler()
  42. self._notifier = hs.get_notifier()
  43. @defer.inlineCallbacks
  44. def maybe_send_server_notice_to_user(self, user_id):
  45. """Check if we need to send a notice to this user, this will be true in
  46. two cases.
  47. 1. The server has reached its limit does not reflect this
  48. 2. The room state indicates that the server has reached its limit when
  49. actually the server is fine
  50. Args:
  51. user_id (str): user to check
  52. Returns:
  53. Deferred
  54. """
  55. if self._config.hs_disabled is True:
  56. return
  57. if self._config.limit_usage_by_mau is False:
  58. return
  59. if not self._server_notices_manager.is_enabled():
  60. # Don't try and send server notices unles they've been enabled
  61. return
  62. timestamp = yield self._store.user_last_seen_monthly_active(user_id)
  63. if timestamp is None:
  64. # This user will be blocked from receiving the notice anyway.
  65. # In practice, not sure we can ever get here
  66. return
  67. # Determine current state of room
  68. room_id = yield self._server_notices_manager.get_notice_room_for_user(user_id)
  69. if not room_id:
  70. logger.warn("Failed to get server notices room")
  71. return
  72. yield self._check_and_set_tags(user_id, room_id)
  73. currently_blocked, ref_events = yield self._is_room_currently_blocked(room_id)
  74. try:
  75. # Normally should always pass in user_id if you have it, but in
  76. # this case are checking what would happen to other users if they
  77. # were to arrive.
  78. try:
  79. yield self._auth.check_auth_blocking()
  80. is_auth_blocking = False
  81. except ResourceLimitError as e:
  82. is_auth_blocking = True
  83. event_content = e.msg
  84. event_limit_type = e.limit_type
  85. if currently_blocked and not is_auth_blocking:
  86. # Room is notifying of a block, when it ought not to be.
  87. # Remove block notification
  88. content = {"pinned": ref_events}
  89. yield self._server_notices_manager.send_notice(
  90. user_id, content, EventTypes.Pinned, ""
  91. )
  92. elif not currently_blocked and is_auth_blocking:
  93. # Room is not notifying of a block, when it ought to be.
  94. # Add block notification
  95. content = {
  96. "body": event_content,
  97. "msgtype": ServerNoticeMsgType,
  98. "server_notice_type": ServerNoticeLimitReached,
  99. "admin_contact": self._config.admin_contact,
  100. "limit_type": event_limit_type,
  101. }
  102. event = yield self._server_notices_manager.send_notice(
  103. user_id, content, EventTypes.Message
  104. )
  105. content = {"pinned": [event.event_id]}
  106. yield self._server_notices_manager.send_notice(
  107. user_id, content, EventTypes.Pinned, ""
  108. )
  109. except SynapseError as e:
  110. logger.error("Error sending resource limits server notice: %s", e)
  111. @defer.inlineCallbacks
  112. def _check_and_set_tags(self, user_id, room_id):
  113. """
  114. Since server notices rooms were originally not with tags,
  115. important to check that tags have been set correctly
  116. Args:
  117. user_id(str): the user in question
  118. room_id(str): the server notices room for that user
  119. """
  120. tags = yield self._store.get_tags_for_room(user_id, room_id)
  121. need_to_set_tag = True
  122. if tags:
  123. if SERVER_NOTICE_ROOM_TAG in tags:
  124. # tag already present, nothing to do here
  125. need_to_set_tag = False
  126. if need_to_set_tag:
  127. max_id = yield self._store.add_tag_to_room(
  128. user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}
  129. )
  130. self._notifier.on_new_event("account_data_key", max_id, users=[user_id])
  131. @defer.inlineCallbacks
  132. def _is_room_currently_blocked(self, room_id):
  133. """
  134. Determines if the room is currently blocked
  135. Args:
  136. room_id(str): The room id of the server notices room
  137. Returns:
  138. bool: Is the room currently blocked
  139. list: The list of pinned events that are unrelated to limit blocking
  140. This list can be used as a convenience in the case where the block
  141. is to be lifted and the remaining pinned event references need to be
  142. preserved
  143. """
  144. currently_blocked = False
  145. pinned_state_event = None
  146. try:
  147. pinned_state_event = yield self._state.get_current_state(
  148. room_id, event_type=EventTypes.Pinned
  149. )
  150. except AuthError:
  151. # The user has yet to join the server notices room
  152. pass
  153. referenced_events = []
  154. if pinned_state_event is not None:
  155. referenced_events = list(pinned_state_event.content.get("pinned", []))
  156. events = yield self._store.get_events(referenced_events)
  157. for event_id, event in iteritems(events):
  158. if event.type != EventTypes.Message:
  159. continue
  160. if event.content.get("msgtype") == ServerNoticeMsgType:
  161. currently_blocked = True
  162. # remove event in case we need to disable blocking later on.
  163. if event_id in referenced_events:
  164. referenced_events.remove(event.event_id)
  165. return currently_blocked, referenced_events