consent_server_notices.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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, string_types
  17. from twisted.internet import defer
  18. from synapse.api.errors import SynapseError
  19. from synapse.api.urls import ConsentURIBuilder
  20. from synapse.config import ConfigError
  21. from synapse.types import get_localpart_from_id
  22. logger = logging.getLogger(__name__)
  23. class ConsentServerNotices(object):
  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):
  28. """
  29. Args:
  30. hs (synapse.server.HomeServer):
  31. """
  32. self._server_notices_manager = hs.get_server_notices_manager()
  33. self._store = hs.get_datastore()
  34. self._users_in_progress = set()
  35. self._current_consent_version = hs.config.user_consent_version
  36. self._server_notice_content = hs.config.user_consent_server_notice_content
  37. self._send_to_guests = hs.config.user_consent_server_notice_to_guests
  38. if self._server_notice_content is not None:
  39. if not self._server_notices_manager.is_enabled():
  40. raise ConfigError(
  41. "user_consent configuration requires server notices, but "
  42. "server notices are not enabled."
  43. )
  44. if "body" not in self._server_notice_content:
  45. raise ConfigError(
  46. "user_consent server_notice_consent must contain a 'body' key."
  47. )
  48. self._consent_uri_builder = ConsentURIBuilder(hs.config)
  49. @defer.inlineCallbacks
  50. def maybe_send_server_notice_to_user(self, user_id):
  51. """Check if we need to send a notice to this user, and does so if so
  52. Args:
  53. user_id (str): user to check
  54. Returns:
  55. Deferred
  56. """
  57. if self._server_notice_content is None:
  58. # not enabled
  59. return
  60. # make sure we don't send two messages to the same user at once
  61. if user_id in self._users_in_progress:
  62. return
  63. self._users_in_progress.add(user_id)
  64. try:
  65. u = yield self._store.get_user_by_id(user_id)
  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. yield self._server_notices_manager.send_notice(user_id, content)
  84. yield 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, substitutions):
  92. """Deep-copy a structure, carrying out string substitions on any strings
  93. Args:
  94. x (object): structure to be copied
  95. substitutions (object): substitutions to be made - passed into the
  96. string '%' operator
  97. Returns:
  98. copy of x
  99. """
  100. if isinstance(x, string_types):
  101. return x % substitutions
  102. if isinstance(x, dict):
  103. return {k: copy_with_str_subst(v, substitutions) for (k, v) in iteritems(x)}
  104. if isinstance(x, (list, tuple)):
  105. return [copy_with_str_subst(y) for y in x]
  106. # assume it's uninterested and can be shallow-copied.
  107. return x