Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

317 rader
8.9 KiB

  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector
  3. # Copyright 2021-22 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import argparse
  17. import getpass
  18. import hashlib
  19. import hmac
  20. import logging
  21. import sys
  22. from typing import Any, Callable, Dict, Optional
  23. import requests
  24. import yaml
  25. _CONFLICTING_SHARED_SECRET_OPTS_ERROR = """\
  26. Conflicting options 'registration_shared_secret' and 'registration_shared_secret_path'
  27. are both defined in config file.
  28. """
  29. _NO_SHARED_SECRET_OPTS_ERROR = """\
  30. No 'registration_shared_secret' or 'registration_shared_secret_path' defined in config.
  31. """
  32. _DEFAULT_SERVER_URL = "http://localhost:8008"
  33. def request_registration(
  34. user: str,
  35. password: str,
  36. server_location: str,
  37. shared_secret: str,
  38. admin: bool = False,
  39. user_type: Optional[str] = None,
  40. _print: Callable[[str], None] = print,
  41. exit: Callable[[int], None] = sys.exit,
  42. ) -> None:
  43. url = "%s/_synapse/admin/v1/register" % (server_location.rstrip("/"),)
  44. # Get the nonce
  45. r = requests.get(url)
  46. if r.status_code != 200:
  47. _print("ERROR! Received %d %s" % (r.status_code, r.reason))
  48. if 400 <= r.status_code < 500:
  49. try:
  50. _print(r.json()["error"])
  51. except Exception:
  52. pass
  53. return exit(1)
  54. nonce = r.json()["nonce"]
  55. mac = hmac.new(key=shared_secret.encode("utf8"), digestmod=hashlib.sha1)
  56. mac.update(nonce.encode("utf8"))
  57. mac.update(b"\x00")
  58. mac.update(user.encode("utf8"))
  59. mac.update(b"\x00")
  60. mac.update(password.encode("utf8"))
  61. mac.update(b"\x00")
  62. mac.update(b"admin" if admin else b"notadmin")
  63. if user_type:
  64. mac.update(b"\x00")
  65. mac.update(user_type.encode("utf8"))
  66. hex_mac = mac.hexdigest()
  67. data = {
  68. "nonce": nonce,
  69. "username": user,
  70. "password": password,
  71. "mac": hex_mac,
  72. "admin": admin,
  73. "user_type": user_type,
  74. }
  75. _print("Sending registration request...")
  76. r = requests.post(url, json=data)
  77. if r.status_code != 200:
  78. _print("ERROR! Received %d %s" % (r.status_code, r.reason))
  79. if 400 <= r.status_code < 500:
  80. try:
  81. _print(r.json()["error"])
  82. except Exception:
  83. pass
  84. return exit(1)
  85. _print("Success!")
  86. def register_new_user(
  87. user: str,
  88. password: str,
  89. server_location: str,
  90. shared_secret: str,
  91. admin: Optional[bool],
  92. user_type: Optional[str],
  93. ) -> None:
  94. if not user:
  95. try:
  96. default_user: Optional[str] = getpass.getuser()
  97. except Exception:
  98. default_user = None
  99. if default_user:
  100. user = input("New user localpart [%s]: " % (default_user,))
  101. if not user:
  102. user = default_user
  103. else:
  104. user = input("New user localpart: ")
  105. if not user:
  106. print("Invalid user name")
  107. sys.exit(1)
  108. if not password:
  109. password = getpass.getpass("Password: ")
  110. if not password:
  111. print("Password cannot be blank.")
  112. sys.exit(1)
  113. confirm_password = getpass.getpass("Confirm password: ")
  114. if password != confirm_password:
  115. print("Passwords do not match")
  116. sys.exit(1)
  117. if admin is None:
  118. admin_inp = input("Make admin [no]: ")
  119. if admin_inp in ("y", "yes", "true"):
  120. admin = True
  121. else:
  122. admin = False
  123. request_registration(
  124. user, password, server_location, shared_secret, bool(admin), user_type
  125. )
  126. def main() -> None:
  127. logging.captureWarnings(True)
  128. parser = argparse.ArgumentParser(
  129. description="Used to register new users with a given homeserver when"
  130. " registration has been disabled. The homeserver must be"
  131. " configured with the 'registration_shared_secret' option"
  132. " set."
  133. )
  134. parser.add_argument(
  135. "-u",
  136. "--user",
  137. default=None,
  138. help="Local part of the new user. Will prompt if omitted.",
  139. )
  140. parser.add_argument(
  141. "-p",
  142. "--password",
  143. default=None,
  144. help="New password for user. Will prompt if omitted.",
  145. )
  146. parser.add_argument(
  147. "-t",
  148. "--user_type",
  149. default=None,
  150. help="User type as specified in synapse.api.constants.UserTypes",
  151. )
  152. admin_group = parser.add_mutually_exclusive_group()
  153. admin_group.add_argument(
  154. "-a",
  155. "--admin",
  156. action="store_true",
  157. help=(
  158. "Register new user as an admin. "
  159. "Will prompt if --no-admin is not set either."
  160. ),
  161. )
  162. admin_group.add_argument(
  163. "--no-admin",
  164. action="store_true",
  165. help=(
  166. "Register new user as a regular user. "
  167. "Will prompt if --admin is not set either."
  168. ),
  169. )
  170. group = parser.add_mutually_exclusive_group(required=True)
  171. group.add_argument(
  172. "-c",
  173. "--config",
  174. type=argparse.FileType("r"),
  175. help="Path to server config file. Used to read in shared secret.",
  176. )
  177. group.add_argument(
  178. "-k", "--shared-secret", help="Shared secret as defined in server config file."
  179. )
  180. parser.add_argument(
  181. "server_url",
  182. nargs="?",
  183. help="URL to use to talk to the homeserver. By default, tries to find a "
  184. "suitable URL from the configuration file. Otherwise, defaults to "
  185. f"'{_DEFAULT_SERVER_URL}'.",
  186. )
  187. args = parser.parse_args()
  188. config: Optional[Dict[str, Any]] = None
  189. if "config" in args and args.config:
  190. config = yaml.safe_load(args.config)
  191. if args.shared_secret:
  192. secret = args.shared_secret
  193. else:
  194. # argparse should check that we have either config or shared secret
  195. assert config is not None
  196. secret = config.get("registration_shared_secret")
  197. secret_file = config.get("registration_shared_secret_path")
  198. if secret_file:
  199. if secret:
  200. print(_CONFLICTING_SHARED_SECRET_OPTS_ERROR, file=sys.stderr)
  201. sys.exit(1)
  202. secret = _read_file(secret_file, "registration_shared_secret_path").strip()
  203. if not secret:
  204. print(_NO_SHARED_SECRET_OPTS_ERROR, file=sys.stderr)
  205. sys.exit(1)
  206. if args.server_url:
  207. server_url = args.server_url
  208. elif config is not None:
  209. server_url = _find_client_listener(config)
  210. if not server_url:
  211. server_url = _DEFAULT_SERVER_URL
  212. print(
  213. "Unable to find a suitable HTTP listener in the configuration file. "
  214. f"Trying {server_url} as a last resort.",
  215. file=sys.stderr,
  216. )
  217. else:
  218. server_url = _DEFAULT_SERVER_URL
  219. print(
  220. f"No server url or configuration file given. Defaulting to {server_url}.",
  221. file=sys.stderr,
  222. )
  223. admin = None
  224. if args.admin or args.no_admin:
  225. admin = args.admin
  226. register_new_user(
  227. args.user, args.password, server_url, secret, admin, args.user_type
  228. )
  229. def _read_file(file_path: Any, config_path: str) -> str:
  230. """Check the given file exists, and read it into a string
  231. If it does not, exit with an error indicating the problem
  232. Args:
  233. file_path: the file to be read
  234. config_path: where in the configuration file_path came from, so that a useful
  235. error can be emitted if it does not exist.
  236. Returns:
  237. content of the file.
  238. """
  239. if not isinstance(file_path, str):
  240. print(f"{config_path} setting is not a string", file=sys.stderr)
  241. sys.exit(1)
  242. try:
  243. with open(file_path) as file_stream:
  244. return file_stream.read()
  245. except OSError as e:
  246. print(f"Error accessing file {file_path}: {e}", file=sys.stderr)
  247. sys.exit(1)
  248. def _find_client_listener(config: Dict[str, Any]) -> Optional[str]:
  249. # try to find a listener in the config. Returns a host:port pair
  250. for listener in config.get("listeners", []):
  251. if listener.get("type") != "http" or listener.get("tls", False):
  252. continue
  253. if not any(
  254. name == "client"
  255. for resource in listener.get("resources", [])
  256. for name in resource.get("names", [])
  257. ):
  258. continue
  259. # TODO: consider bind_addresses
  260. return f"http://localhost:{listener['port']}"
  261. # no suitable listeners?
  262. return None
  263. if __name__ == "__main__":
  264. main()