You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

131 lines
4.6 KiB

  1. # Copyright 2018 New Vector Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import logging
  15. from typing import TYPE_CHECKING, Any, Set
  16. from synapse.api.errors import SynapseError
  17. from synapse.api.urls import ConsentURIBuilder
  18. from synapse.config import ConfigError
  19. from synapse.types import get_localpart_from_id
  20. if TYPE_CHECKING:
  21. from synapse.server import HomeServer
  22. logger = logging.getLogger(__name__)
  23. class ConsentServerNotices:
  24. """Keeps track of whether we need to send users server_notices about
  25. privacy policy consent, and sends one if we do.
  26. """
  27. def __init__(self, hs: "HomeServer"):
  28. self._server_notices_manager = hs.get_server_notices_manager()
  29. self._store = hs.get_datastores().main
  30. self._users_in_progress: Set[str] = set()
  31. self._current_consent_version = hs.config.consent.user_consent_version
  32. self._server_notice_content = (
  33. hs.config.consent.user_consent_server_notice_content
  34. )
  35. self._send_to_guests = hs.config.consent.user_consent_server_notice_to_guests
  36. if self._server_notice_content is not None:
  37. if not self._server_notices_manager.is_enabled():
  38. raise ConfigError(
  39. "user_consent configuration requires server notices, but "
  40. "server notices are not enabled."
  41. )
  42. if "body" not in self._server_notice_content:
  43. raise ConfigError(
  44. "user_consent server_notice_consent must contain a 'body' key."
  45. )
  46. self._consent_uri_builder = ConsentURIBuilder(hs.config)
  47. async def maybe_send_server_notice_to_user(self, user_id: str) -> None:
  48. """Check if we need to send a notice to this user, and does so if so
  49. Args:
  50. user_id: user to check
  51. """
  52. if self._server_notice_content is None:
  53. # not enabled
  54. return
  55. # A consent version must be given.
  56. assert self._current_consent_version is not None
  57. # make sure we don't send two messages to the same user at once
  58. if user_id in self._users_in_progress:
  59. return
  60. self._users_in_progress.add(user_id)
  61. try:
  62. u = await self._store.get_user_by_id(user_id)
  63. # The user doesn't exist.
  64. if u is None:
  65. return
  66. if u.is_guest and not self._send_to_guests:
  67. # don't send to guests
  68. return
  69. if u.consent_version == self._current_consent_version:
  70. # user has already consented
  71. return
  72. if u.consent_server_notice_sent == self._current_consent_version:
  73. # we've already sent a notice to the user
  74. return
  75. # need to send a message.
  76. try:
  77. consent_uri = self._consent_uri_builder.build_user_consent_uri(
  78. get_localpart_from_id(user_id)
  79. )
  80. content = copy_with_str_subst(
  81. self._server_notice_content, {"consent_uri": consent_uri}
  82. )
  83. await self._server_notices_manager.send_notice(user_id, content)
  84. await self._store.user_set_consent_server_notice_sent(
  85. user_id, self._current_consent_version
  86. )
  87. except SynapseError as e:
  88. logger.error("Error sending server notice about user consent: %s", e)
  89. finally:
  90. self._users_in_progress.remove(user_id)
  91. def copy_with_str_subst(x: Any, substitutions: Any) -> Any:
  92. """Deep-copy a structure, carrying out string substitutions on any strings
  93. Args:
  94. x: structure to be copied
  95. substitutions: substitutions to be made - passed into the string '%' operator
  96. Returns:
  97. copy of x
  98. """
  99. if isinstance(x, str):
  100. return x % substitutions
  101. if isinstance(x, dict):
  102. return {k: copy_with_str_subst(v, substitutions) for (k, v) in x.items()}
  103. if isinstance(x, (list, tuple)):
  104. return [copy_with_str_subst(y, substitutions) for y in x]
  105. # assume it's uninterested and can be shallow-copied.
  106. return x