appservice.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. # Copyright 2015, 2016 OpenMarket 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 six import string_types
  16. from six.moves.urllib import parse as urlparse
  17. import yaml
  18. from netaddr import IPSet
  19. from synapse.appservice import ApplicationService
  20. from synapse.types import UserID
  21. from ._base import Config, ConfigError
  22. logger = logging.getLogger(__name__)
  23. class AppServiceConfig(Config):
  24. def read_config(self, config):
  25. self.app_service_config_files = config.get("app_service_config_files", [])
  26. self.notify_appservices = config.get("notify_appservices", True)
  27. self.track_appservice_user_ips = config.get("track_appservice_user_ips", False)
  28. def default_config(cls, **kwargs):
  29. return """\
  30. # A list of application service config file to use
  31. app_service_config_files: []
  32. # Whether or not to track application service IP addresses. Implicitly
  33. # enables MAU tracking for application service users.
  34. track_appservice_user_ips: False
  35. """
  36. def load_appservices(hostname, config_files):
  37. """Returns a list of Application Services from the config files."""
  38. if not isinstance(config_files, list):
  39. logger.warning(
  40. "Expected %s to be a list of AS config files.", config_files
  41. )
  42. return []
  43. # Dicts of value -> filename
  44. seen_as_tokens = {}
  45. seen_ids = {}
  46. appservices = []
  47. for config_file in config_files:
  48. try:
  49. with open(config_file, 'r') as f:
  50. appservice = _load_appservice(
  51. hostname, yaml.load(f), config_file
  52. )
  53. if appservice.id in seen_ids:
  54. raise ConfigError(
  55. "Cannot reuse ID across application services: "
  56. "%s (files: %s, %s)" % (
  57. appservice.id, config_file, seen_ids[appservice.id],
  58. )
  59. )
  60. seen_ids[appservice.id] = config_file
  61. if appservice.token in seen_as_tokens:
  62. raise ConfigError(
  63. "Cannot reuse as_token across application services: "
  64. "%s (files: %s, %s)" % (
  65. appservice.token,
  66. config_file,
  67. seen_as_tokens[appservice.token],
  68. )
  69. )
  70. seen_as_tokens[appservice.token] = config_file
  71. logger.info("Loaded application service: %s", appservice)
  72. appservices.append(appservice)
  73. except Exception as e:
  74. logger.error("Failed to load appservice from '%s'", config_file)
  75. logger.exception(e)
  76. raise
  77. return appservices
  78. def _load_appservice(hostname, as_info, config_filename):
  79. required_string_fields = [
  80. "id", "as_token", "hs_token", "sender_localpart"
  81. ]
  82. for field in required_string_fields:
  83. if not isinstance(as_info.get(field), string_types):
  84. raise KeyError("Required string field: '%s' (%s)" % (
  85. field, config_filename,
  86. ))
  87. # 'url' must either be a string or explicitly null, not missing
  88. # to avoid accidentally turning off push for ASes.
  89. if (not isinstance(as_info.get("url"), string_types) and
  90. as_info.get("url", "") is not None):
  91. raise KeyError(
  92. "Required string field or explicit null: 'url' (%s)" % (config_filename,)
  93. )
  94. localpart = as_info["sender_localpart"]
  95. if urlparse.quote(localpart) != localpart:
  96. raise ValueError(
  97. "sender_localpart needs characters which are not URL encoded."
  98. )
  99. user = UserID(localpart, hostname)
  100. user_id = user.to_string()
  101. # Rate limiting for users of this AS is on by default (excludes sender)
  102. rate_limited = True
  103. if isinstance(as_info.get("rate_limited"), bool):
  104. rate_limited = as_info.get("rate_limited")
  105. # namespace checks
  106. if not isinstance(as_info.get("namespaces"), dict):
  107. raise KeyError("Requires 'namespaces' object.")
  108. for ns in ApplicationService.NS_LIST:
  109. # specific namespaces are optional
  110. if ns in as_info["namespaces"]:
  111. # expect a list of dicts with exclusive and regex keys
  112. for regex_obj in as_info["namespaces"][ns]:
  113. if not isinstance(regex_obj, dict):
  114. raise ValueError(
  115. "Expected namespace entry in %s to be an object,"
  116. " but got %s", ns, regex_obj
  117. )
  118. if not isinstance(regex_obj.get("regex"), string_types):
  119. raise ValueError(
  120. "Missing/bad type 'regex' key in %s", regex_obj
  121. )
  122. if not isinstance(regex_obj.get("exclusive"), bool):
  123. raise ValueError(
  124. "Missing/bad type 'exclusive' key in %s", regex_obj
  125. )
  126. # protocols check
  127. protocols = as_info.get("protocols")
  128. if protocols:
  129. # Because strings are lists in python
  130. if isinstance(protocols, str) or not isinstance(protocols, list):
  131. raise KeyError("Optional 'protocols' must be a list if present.")
  132. for p in protocols:
  133. if not isinstance(p, str):
  134. raise KeyError("Bad value for 'protocols' item")
  135. if as_info["url"] is None:
  136. logger.info(
  137. "(%s) Explicitly empty 'url' provided. This application service"
  138. " will not receive events or queries.",
  139. config_filename,
  140. )
  141. ip_range_whitelist = None
  142. if as_info.get('ip_range_whitelist'):
  143. ip_range_whitelist = IPSet(
  144. as_info.get('ip_range_whitelist')
  145. )
  146. return ApplicationService(
  147. token=as_info["as_token"],
  148. hostname=hostname,
  149. url=as_info["url"],
  150. namespaces=as_info["namespaces"],
  151. hs_token=as_info["hs_token"],
  152. sender=user_id,
  153. id=as_info["id"],
  154. protocols=protocols,
  155. rate_limited=rate_limited,
  156. ip_range_whitelist=ip_range_whitelist,
  157. )