appservice.py 6.3 KB

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