You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

651 lines
24 KiB

  1. # Copyright 2014 - 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 typing import List, Optional, Tuple
  16. import pymacaroons
  17. from netaddr import IPAddress
  18. from twisted.web.server import Request
  19. import synapse.types
  20. from synapse import event_auth
  21. from synapse.api.auth_blocking import AuthBlocking
  22. from synapse.api.constants import EventTypes, HistoryVisibility, Membership
  23. from synapse.api.errors import (
  24. AuthError,
  25. Codes,
  26. InvalidClientTokenError,
  27. MissingClientTokenError,
  28. )
  29. from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
  30. from synapse.appservice import ApplicationService
  31. from synapse.events import EventBase
  32. from synapse.http import get_request_user_agent
  33. from synapse.http.site import SynapseRequest
  34. from synapse.logging import opentracing as opentracing
  35. from synapse.storage.databases.main.registration import TokenLookupResult
  36. from synapse.types import StateMap, UserID
  37. from synapse.util.caches.lrucache import LruCache
  38. from synapse.util.macaroons import get_value_from_macaroon, satisfy_expiry
  39. from synapse.util.metrics import Measure
  40. logger = logging.getLogger(__name__)
  41. AuthEventTypes = (
  42. EventTypes.Create,
  43. EventTypes.Member,
  44. EventTypes.PowerLevels,
  45. EventTypes.JoinRules,
  46. EventTypes.RoomHistoryVisibility,
  47. EventTypes.ThirdPartyInvite,
  48. )
  49. # guests always get this device id.
  50. GUEST_DEVICE_ID = "guest_device"
  51. class _InvalidMacaroonException(Exception):
  52. pass
  53. class Auth:
  54. """
  55. FIXME: This class contains a mix of functions for authenticating users
  56. of our client-server API and authenticating events added to room graphs.
  57. """
  58. def __init__(self, hs):
  59. self.hs = hs
  60. self.clock = hs.get_clock()
  61. self.store = hs.get_datastore()
  62. self.state = hs.get_state_handler()
  63. self.token_cache = LruCache(
  64. 10000, "token_cache"
  65. ) # type: LruCache[str, Tuple[str, bool]]
  66. self._auth_blocking = AuthBlocking(self.hs)
  67. self._account_validity = hs.config.account_validity
  68. self._track_appservice_user_ips = hs.config.track_appservice_user_ips
  69. self._macaroon_secret_key = hs.config.macaroon_secret_key
  70. async def check_from_context(
  71. self, room_version: str, event, context, do_sig_check=True
  72. ):
  73. prev_state_ids = await context.get_prev_state_ids()
  74. auth_events_ids = self.compute_auth_events(
  75. event, prev_state_ids, for_verification=True
  76. )
  77. auth_events = await self.store.get_events(auth_events_ids)
  78. auth_events = {(e.type, e.state_key): e for e in auth_events.values()}
  79. room_version_obj = KNOWN_ROOM_VERSIONS[room_version]
  80. event_auth.check(
  81. room_version_obj, event, auth_events=auth_events, do_sig_check=do_sig_check
  82. )
  83. async def check_user_in_room(
  84. self,
  85. room_id: str,
  86. user_id: str,
  87. current_state: Optional[StateMap[EventBase]] = None,
  88. allow_departed_users: bool = False,
  89. ) -> EventBase:
  90. """Check if the user is in the room, or was at some point.
  91. Args:
  92. room_id: The room to check.
  93. user_id: The user to check.
  94. current_state: Optional map of the current state of the room.
  95. If provided then that map is used to check whether they are a
  96. member of the room. Otherwise the current membership is
  97. loaded from the database.
  98. allow_departed_users: if True, accept users that were previously
  99. members but have now departed.
  100. Raises:
  101. AuthError if the user is/was not in the room.
  102. Returns:
  103. Membership event for the user if the user was in the
  104. room. This will be the join event if they are currently joined to
  105. the room. This will be the leave event if they have left the room.
  106. """
  107. if current_state:
  108. member = current_state.get((EventTypes.Member, user_id), None)
  109. else:
  110. member = await self.state.get_current_state(
  111. room_id=room_id, event_type=EventTypes.Member, state_key=user_id
  112. )
  113. if member:
  114. membership = member.membership
  115. if membership == Membership.JOIN:
  116. return member
  117. # XXX this looks totally bogus. Why do we not allow users who have been banned,
  118. # or those who were members previously and have been re-invited?
  119. if allow_departed_users and membership == Membership.LEAVE:
  120. forgot = await self.store.did_forget(user_id, room_id)
  121. if not forgot:
  122. return member
  123. raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
  124. async def check_host_in_room(self, room_id, host):
  125. with Measure(self.clock, "check_host_in_room"):
  126. latest_event_ids = await self.store.is_host_joined(room_id, host)
  127. return latest_event_ids
  128. def can_federate(self, event, auth_events):
  129. creation_event = auth_events.get((EventTypes.Create, ""))
  130. return creation_event.content.get("m.federate", True) is True
  131. def get_public_keys(self, invite_event):
  132. return event_auth.get_public_keys(invite_event)
  133. async def get_user_by_req(
  134. self,
  135. request: SynapseRequest,
  136. allow_guest: bool = False,
  137. rights: str = "access",
  138. allow_expired: bool = False,
  139. ) -> synapse.types.Requester:
  140. """Get a registered user's ID.
  141. Args:
  142. request: An HTTP request with an access_token query parameter.
  143. allow_guest: If False, will raise an AuthError if the user making the
  144. request is a guest.
  145. rights: The operation being performed; the access token must allow this
  146. allow_expired: If True, allow the request through even if the account
  147. is expired, or session token lifetime has ended. Note that
  148. /login will deliver access tokens regardless of expiration.
  149. Returns:
  150. Resolves to the requester
  151. Raises:
  152. InvalidClientCredentialsError if no user by that token exists or the token
  153. is invalid.
  154. AuthError if access is denied for the user in the access token
  155. """
  156. try:
  157. ip_addr = request.getClientIP()
  158. user_agent = get_request_user_agent(request)
  159. access_token = self.get_access_token_from_request(request)
  160. user_id, app_service = await self._get_appservice_user_id(request)
  161. if user_id:
  162. if ip_addr and self._track_appservice_user_ips:
  163. await self.store.insert_client_ip(
  164. user_id=user_id,
  165. access_token=access_token,
  166. ip=ip_addr,
  167. user_agent=user_agent,
  168. device_id="dummy-device", # stubbed
  169. )
  170. requester = synapse.types.create_requester(
  171. user_id, app_service=app_service
  172. )
  173. request.requester = user_id
  174. opentracing.set_tag("authenticated_entity", user_id)
  175. opentracing.set_tag("user_id", user_id)
  176. opentracing.set_tag("appservice_id", app_service.id)
  177. return requester
  178. user_info = await self.get_user_by_access_token(
  179. access_token, rights, allow_expired=allow_expired
  180. )
  181. token_id = user_info.token_id
  182. is_guest = user_info.is_guest
  183. shadow_banned = user_info.shadow_banned
  184. # Deny the request if the user account has expired.
  185. if self._account_validity.enabled and not allow_expired:
  186. if await self.store.is_account_expired(
  187. user_info.user_id, self.clock.time_msec()
  188. ):
  189. raise AuthError(
  190. 403, "User account has expired", errcode=Codes.EXPIRED_ACCOUNT
  191. )
  192. device_id = user_info.device_id
  193. if access_token and ip_addr:
  194. await self.store.insert_client_ip(
  195. user_id=user_info.token_owner,
  196. access_token=access_token,
  197. ip=ip_addr,
  198. user_agent=user_agent,
  199. device_id=device_id,
  200. )
  201. if is_guest and not allow_guest:
  202. raise AuthError(
  203. 403,
  204. "Guest access not allowed",
  205. errcode=Codes.GUEST_ACCESS_FORBIDDEN,
  206. )
  207. requester = synapse.types.create_requester(
  208. user_info.user_id,
  209. token_id,
  210. is_guest,
  211. shadow_banned,
  212. device_id,
  213. app_service=app_service,
  214. authenticated_entity=user_info.token_owner,
  215. )
  216. request.requester = requester
  217. opentracing.set_tag("authenticated_entity", user_info.token_owner)
  218. opentracing.set_tag("user_id", user_info.user_id)
  219. if device_id:
  220. opentracing.set_tag("device_id", device_id)
  221. return requester
  222. except KeyError:
  223. raise MissingClientTokenError()
  224. async def _get_appservice_user_id(self, request):
  225. app_service = self.store.get_app_service_by_token(
  226. self.get_access_token_from_request(request)
  227. )
  228. if app_service is None:
  229. return None, None
  230. if app_service.ip_range_whitelist:
  231. ip_address = IPAddress(request.getClientIP())
  232. if ip_address not in app_service.ip_range_whitelist:
  233. return None, None
  234. if b"user_id" not in request.args:
  235. return app_service.sender, app_service
  236. user_id = request.args[b"user_id"][0].decode("utf8")
  237. if app_service.sender == user_id:
  238. return app_service.sender, app_service
  239. if not app_service.is_interested_in_user(user_id):
  240. raise AuthError(403, "Application service cannot masquerade as this user.")
  241. if not (await self.store.get_user_by_id(user_id)):
  242. raise AuthError(403, "Application service has not registered this user")
  243. return user_id, app_service
  244. async def get_user_by_access_token(
  245. self,
  246. token: str,
  247. rights: str = "access",
  248. allow_expired: bool = False,
  249. ) -> TokenLookupResult:
  250. """Validate access token and get user_id from it
  251. Args:
  252. token: The access token to get the user by
  253. rights: The operation being performed; the access token must
  254. allow this
  255. allow_expired: If False, raises an InvalidClientTokenError
  256. if the token is expired
  257. Raises:
  258. InvalidClientTokenError if a user by that token exists, but the token is
  259. expired
  260. InvalidClientCredentialsError if no user by that token exists or the token
  261. is invalid
  262. """
  263. if rights == "access":
  264. # first look in the database
  265. r = await self.store.get_user_by_access_token(token)
  266. if r:
  267. valid_until_ms = r.valid_until_ms
  268. if (
  269. not allow_expired
  270. and valid_until_ms is not None
  271. and valid_until_ms < self.clock.time_msec()
  272. ):
  273. # there was a valid access token, but it has expired.
  274. # soft-logout the user.
  275. raise InvalidClientTokenError(
  276. msg="Access token has expired", soft_logout=True
  277. )
  278. return r
  279. # otherwise it needs to be a valid macaroon
  280. try:
  281. user_id, guest = self._parse_and_validate_macaroon(token, rights)
  282. if rights == "access":
  283. if not guest:
  284. # non-guest access tokens must be in the database
  285. logger.warning("Unrecognised access token - not in store.")
  286. raise InvalidClientTokenError()
  287. # Guest access tokens are not stored in the database (there can
  288. # only be one access token per guest, anyway).
  289. #
  290. # In order to prevent guest access tokens being used as regular
  291. # user access tokens (and hence getting around the invalidation
  292. # process), we look up the user id and check that it is indeed
  293. # a guest user.
  294. #
  295. # It would of course be much easier to store guest access
  296. # tokens in the database as well, but that would break existing
  297. # guest tokens.
  298. stored_user = await self.store.get_user_by_id(user_id)
  299. if not stored_user:
  300. raise InvalidClientTokenError("Unknown user_id %s" % user_id)
  301. if not stored_user["is_guest"]:
  302. raise InvalidClientTokenError(
  303. "Guest access token used for regular user"
  304. )
  305. ret = TokenLookupResult(
  306. user_id=user_id,
  307. is_guest=True,
  308. # all guests get the same device id
  309. device_id=GUEST_DEVICE_ID,
  310. )
  311. elif rights == "delete_pusher":
  312. # We don't store these tokens in the database
  313. ret = TokenLookupResult(user_id=user_id, is_guest=False)
  314. else:
  315. raise RuntimeError("Unknown rights setting %s", rights)
  316. return ret
  317. except (
  318. _InvalidMacaroonException,
  319. pymacaroons.exceptions.MacaroonException,
  320. TypeError,
  321. ValueError,
  322. ) as e:
  323. logger.warning("Invalid macaroon in auth: %s %s", type(e), e)
  324. raise InvalidClientTokenError("Invalid macaroon passed.")
  325. def _parse_and_validate_macaroon(self, token, rights="access"):
  326. """Takes a macaroon and tries to parse and validate it. This is cached
  327. if and only if rights == access and there isn't an expiry.
  328. On invalid macaroon raises _InvalidMacaroonException
  329. Returns:
  330. (user_id, is_guest)
  331. """
  332. if rights == "access":
  333. cached = self.token_cache.get(token, None)
  334. if cached:
  335. return cached
  336. try:
  337. macaroon = pymacaroons.Macaroon.deserialize(token)
  338. except Exception: # deserialize can throw more-or-less anything
  339. # doesn't look like a macaroon: treat it as an opaque token which
  340. # must be in the database.
  341. # TODO: it would be nice to get rid of this, but apparently some
  342. # people use access tokens which aren't macaroons
  343. raise _InvalidMacaroonException()
  344. try:
  345. user_id = get_value_from_macaroon(macaroon, "user_id")
  346. guest = False
  347. for caveat in macaroon.caveats:
  348. if caveat.caveat_id == "guest = true":
  349. guest = True
  350. self.validate_macaroon(macaroon, rights, user_id=user_id)
  351. except (
  352. pymacaroons.exceptions.MacaroonException,
  353. KeyError,
  354. TypeError,
  355. ValueError,
  356. ):
  357. raise InvalidClientTokenError("Invalid macaroon passed.")
  358. if rights == "access":
  359. self.token_cache[token] = (user_id, guest)
  360. return user_id, guest
  361. def validate_macaroon(self, macaroon, type_string, user_id):
  362. """
  363. validate that a Macaroon is understood by and was signed by this server.
  364. Args:
  365. macaroon(pymacaroons.Macaroon): The macaroon to validate
  366. type_string(str): The kind of token required (e.g. "access",
  367. "delete_pusher")
  368. user_id (str): The user_id required
  369. """
  370. v = pymacaroons.Verifier()
  371. # the verifier runs a test for every caveat on the macaroon, to check
  372. # that it is met for the current request. Each caveat must match at
  373. # least one of the predicates specified by satisfy_exact or
  374. # specify_general.
  375. v.satisfy_exact("gen = 1")
  376. v.satisfy_exact("type = " + type_string)
  377. v.satisfy_exact("user_id = %s" % user_id)
  378. v.satisfy_exact("guest = true")
  379. satisfy_expiry(v, self.clock.time_msec)
  380. # access_tokens include a nonce for uniqueness: any value is acceptable
  381. v.satisfy_general(lambda c: c.startswith("nonce = "))
  382. v.verify(macaroon, self._macaroon_secret_key)
  383. def get_appservice_by_req(self, request: SynapseRequest) -> ApplicationService:
  384. token = self.get_access_token_from_request(request)
  385. service = self.store.get_app_service_by_token(token)
  386. if not service:
  387. logger.warning("Unrecognised appservice access token.")
  388. raise InvalidClientTokenError()
  389. request.requester = synapse.types.create_requester(
  390. service.sender, app_service=service
  391. )
  392. return service
  393. async def is_server_admin(self, user: UserID) -> bool:
  394. """Check if the given user is a local server admin.
  395. Args:
  396. user: user to check
  397. Returns:
  398. True if the user is an admin
  399. """
  400. return await self.store.is_server_admin(user)
  401. def compute_auth_events(
  402. self,
  403. event,
  404. current_state_ids: StateMap[str],
  405. for_verification: bool = False,
  406. ) -> List[str]:
  407. """Given an event and current state return the list of event IDs used
  408. to auth an event.
  409. If `for_verification` is False then only return auth events that
  410. should be added to the event's `auth_events`.
  411. Returns:
  412. List of event IDs.
  413. """
  414. if event.type == EventTypes.Create:
  415. return []
  416. # Currently we ignore the `for_verification` flag even though there are
  417. # some situations where we can drop particular auth events when adding
  418. # to the event's `auth_events` (e.g. joins pointing to previous joins
  419. # when room is publicly joinable). Dropping event IDs has the
  420. # advantage that the auth chain for the room grows slower, but we use
  421. # the auth chain in state resolution v2 to order events, which means
  422. # care must be taken if dropping events to ensure that it doesn't
  423. # introduce undesirable "state reset" behaviour.
  424. #
  425. # All of which sounds a bit tricky so we don't bother for now.
  426. auth_ids = []
  427. for etype, state_key in event_auth.auth_types_for_event(event):
  428. auth_ev_id = current_state_ids.get((etype, state_key))
  429. if auth_ev_id:
  430. auth_ids.append(auth_ev_id)
  431. return auth_ids
  432. async def check_can_change_room_list(self, room_id: str, user: UserID):
  433. """Determine whether the user is allowed to edit the room's entry in the
  434. published room list.
  435. Args:
  436. room_id
  437. user
  438. """
  439. is_admin = await self.is_server_admin(user)
  440. if is_admin:
  441. return True
  442. user_id = user.to_string()
  443. await self.check_user_in_room(room_id, user_id)
  444. # We currently require the user is a "moderator" in the room. We do this
  445. # by checking if they would (theoretically) be able to change the
  446. # m.room.canonical_alias events
  447. power_level_event = await self.state.get_current_state(
  448. room_id, EventTypes.PowerLevels, ""
  449. )
  450. auth_events = {}
  451. if power_level_event:
  452. auth_events[(EventTypes.PowerLevels, "")] = power_level_event
  453. send_level = event_auth.get_send_level(
  454. EventTypes.CanonicalAlias, "", power_level_event
  455. )
  456. user_level = event_auth.get_user_power_level(user_id, auth_events)
  457. return user_level >= send_level
  458. @staticmethod
  459. def has_access_token(request: Request):
  460. """Checks if the request has an access_token.
  461. Returns:
  462. bool: False if no access_token was given, True otherwise.
  463. """
  464. # This will always be set by the time Twisted calls us.
  465. assert request.args is not None
  466. query_params = request.args.get(b"access_token")
  467. auth_headers = request.requestHeaders.getRawHeaders(b"Authorization")
  468. return bool(query_params) or bool(auth_headers)
  469. @staticmethod
  470. def get_access_token_from_request(request: Request):
  471. """Extracts the access_token from the request.
  472. Args:
  473. request: The http request.
  474. Returns:
  475. unicode: The access_token
  476. Raises:
  477. MissingClientTokenError: If there isn't a single access_token in the
  478. request
  479. """
  480. # This will always be set by the time Twisted calls us.
  481. assert request.args is not None
  482. auth_headers = request.requestHeaders.getRawHeaders(b"Authorization")
  483. query_params = request.args.get(b"access_token")
  484. if auth_headers:
  485. # Try the get the access_token from a "Authorization: Bearer"
  486. # header
  487. if query_params is not None:
  488. raise MissingClientTokenError(
  489. "Mixing Authorization headers and access_token query parameters."
  490. )
  491. if len(auth_headers) > 1:
  492. raise MissingClientTokenError("Too many Authorization headers.")
  493. parts = auth_headers[0].split(b" ")
  494. if parts[0] == b"Bearer" and len(parts) == 2:
  495. return parts[1].decode("ascii")
  496. else:
  497. raise MissingClientTokenError("Invalid Authorization header.")
  498. else:
  499. # Try to get the access_token from the query params.
  500. if not query_params:
  501. raise MissingClientTokenError()
  502. return query_params[0].decode("ascii")
  503. async def check_user_in_room_or_world_readable(
  504. self, room_id: str, user_id: str, allow_departed_users: bool = False
  505. ) -> Tuple[str, Optional[str]]:
  506. """Checks that the user is or was in the room or the room is world
  507. readable. If it isn't then an exception is raised.
  508. Args:
  509. room_id: room to check
  510. user_id: user to check
  511. allow_departed_users: if True, accept users that were previously
  512. members but have now departed
  513. Returns:
  514. Resolves to the current membership of the user in the room and the
  515. membership event ID of the user. If the user is not in the room and
  516. never has been, then `(Membership.JOIN, None)` is returned.
  517. """
  518. try:
  519. # check_user_in_room will return the most recent membership
  520. # event for the user if:
  521. # * The user is a non-guest user, and was ever in the room
  522. # * The user is a guest user, and has joined the room
  523. # else it will throw.
  524. member_event = await self.check_user_in_room(
  525. room_id, user_id, allow_departed_users=allow_departed_users
  526. )
  527. return member_event.membership, member_event.event_id
  528. except AuthError:
  529. visibility = await self.state.get_current_state(
  530. room_id, EventTypes.RoomHistoryVisibility, ""
  531. )
  532. if (
  533. visibility
  534. and visibility.content.get("history_visibility")
  535. == HistoryVisibility.WORLD_READABLE
  536. ):
  537. return Membership.JOIN, None
  538. raise AuthError(
  539. 403,
  540. "User %s not in room %s, and room previews are disabled"
  541. % (user_id, room_id),
  542. )
  543. def check_auth_blocking(self, *args, **kwargs):
  544. return self._auth_blocking.check_auth_blocking(*args, **kwargs)