consent_server_notices.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 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. logger = logging.getLogger(__name__)
  21. class ConsentServerNotices(object):
  22. """Keeps track of whether we need to send users server_notices about
  23. privacy policy consent, and sends one if we do.
  24. """
  25. def __init__(self, hs):
  26. """
  27. Args:
  28. hs (synapse.server.HomeServer):
  29. """
  30. self._server_notices_manager = hs.get_server_notices_manager()
  31. self._store = hs.get_datastore()
  32. self._users_in_progress = set()
  33. self._current_consent_version = hs.config.user_consent_version
  34. self._server_notice_content = hs.config.user_consent_server_notice_content
  35. self._send_to_guests = hs.config.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):
  48. """Check if we need to send a notice to this user, and does so if so
  49. Args:
  50. user_id (str): user to check
  51. Returns:
  52. Deferred
  53. """
  54. if self._server_notice_content is None:
  55. # not enabled
  56. return
  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. if u["is_guest"] and not self._send_to_guests:
  64. # don't send to guests
  65. return
  66. if u["consent_version"] == self._current_consent_version:
  67. # user has already consented
  68. return
  69. if u["consent_server_notice_sent"] == self._current_consent_version:
  70. # we've already sent a notice to the user
  71. return
  72. # need to send a message.
  73. try:
  74. consent_uri = self._consent_uri_builder.build_user_consent_uri(
  75. get_localpart_from_id(user_id)
  76. )
  77. content = copy_with_str_subst(
  78. self._server_notice_content, {"consent_uri": consent_uri}
  79. )
  80. await self._server_notices_manager.send_notice(user_id, content)
  81. await self._store.user_set_consent_server_notice_sent(
  82. user_id, self._current_consent_version
  83. )
  84. except SynapseError as e:
  85. logger.error("Error sending server notice about user consent: %s", e)
  86. finally:
  87. self._users_in_progress.remove(user_id)
  88. def copy_with_str_subst(x, substitutions):
  89. """Deep-copy a structure, carrying out string substitions on any strings
  90. Args:
  91. x (object): structure to be copied
  92. substitutions (object): substitutions to be made - passed into the
  93. string '%' operator
  94. Returns:
  95. copy of x
  96. """
  97. if isinstance(x, str):
  98. return x % substitutions
  99. if isinstance(x, dict):
  100. return {k: copy_with_str_subst(v, substitutions) for (k, v) in x.items()}
  101. if isinstance(x, (list, tuple)):
  102. return [copy_with_str_subst(y) for y in x]
  103. # assume it's uninterested and can be shallow-copied.
  104. return x