Esempio n. 1
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        password_config = config.get("password_config", {})
        if password_config is None:
            password_config = {}

        passwords_enabled = password_config.get("enabled", True)
        # 'only_for_reauth' allows users who have previously set a password to use it,
        # even though passwords would otherwise be disabled.
        passwords_for_reauth_only = passwords_enabled == "only_for_reauth"

        self.password_enabled_for_login = (
            passwords_enabled and not passwords_for_reauth_only
        )
        self.password_enabled_for_reauth = (
            passwords_for_reauth_only or passwords_enabled
        )

        self.password_localdb_enabled = password_config.get("localdb_enabled", True)
        self.password_pepper = password_config.get("pepper", "")

        # Password policy
        self.password_policy = password_config.get("policy") or {}
        self.password_policy_enabled = self.password_policy.get("enabled", False)

        # User-interactive authentication
        ui_auth = config.get("ui_auth") or {}
        self.ui_auth_session_timeout = self.parse_duration(
            ui_auth.get("session_timeout", 0)
        )
Esempio n. 2
0
    def read_config(self, config: JsonDict, **kwargs):
        experimental = config.get("experimental_features") or {}

        # Whether to enable experimental MSC1849 (aka relations) support
        self.msc1849_enabled = config.get(
            "experimental_msc1849_support_enabled", True)
        # MSC3440 (thread relation)
        self.msc3440_enabled: bool = experimental.get("msc3440_enabled", False)

        # MSC3026 (busy presence state)
        self.msc3026_enabled: bool = experimental.get("msc3026_enabled", False)

        # MSC2716 (backfill existing history)
        self.msc2716_enabled: bool = experimental.get("msc2716_enabled", False)

        # MSC2285 (hidden read receipts)
        self.msc2285_enabled: bool = experimental.get("msc2285_enabled", False)

        # MSC3244 (room version capabilities)
        self.msc3244_enabled: bool = experimental.get("msc3244_enabled", True)

        # MSC3283 (set displayname, avatar_url and change 3pid capabilities)
        self.msc3283_enabled: bool = experimental.get("msc3283_enabled", False)

        # MSC3266 (room summary api)
        self.msc3266_enabled: bool = experimental.get("msc3266_enabled", False)
Esempio n. 3
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        # FIXME: federation_domain_whitelist needs sytests
        self.federation_domain_whitelist: Optional[dict] = None
        federation_domain_whitelist = config.get("federation_domain_whitelist",
                                                 None)

        if federation_domain_whitelist is not None:
            # turn the whitelist into a hash for speed of lookup
            self.federation_domain_whitelist = {}

            for domain in federation_domain_whitelist:
                self.federation_domain_whitelist[domain] = True

        federation_metrics_domains = config.get(
            "federation_metrics_domains") or []
        validate_config(
            _METRICS_FOR_DOMAINS_SCHEMA,
            federation_metrics_domains,
            ("federation_metrics_domains", ),
        )
        self.federation_metrics_domains = set(federation_metrics_domains)

        self.allow_profile_lookup_over_federation = config.get(
            "allow_profile_lookup_over_federation", True)

        self.allow_device_name_lookup_over_federation = config.get(
            "allow_device_name_lookup_over_federation", False)
Esempio n. 4
0
    async def on_POST(
        self,
        origin: str,
        content: JsonDict,
        query: Mapping[bytes, Sequence[bytes]],
        room_id: str,
    ) -> Tuple[int, JsonDict]:
        suggested_only = content.get("suggested_only", False)
        if not isinstance(suggested_only, bool):
            raise SynapseError(400, "'suggested_only' must be a boolean",
                               Codes.BAD_JSON)

        exclude_rooms = content.get("exclude_rooms", [])
        if not isinstance(exclude_rooms, list) or any(not isinstance(x, str)
                                                      for x in exclude_rooms):
            raise SynapseError(400, "bad value for 'exclude_rooms'",
                               Codes.BAD_JSON)

        max_rooms_per_space = content.get("max_rooms_per_space")
        if max_rooms_per_space is not None:
            if not isinstance(max_rooms_per_space, int):
                raise SynapseError(400, "bad value for 'max_rooms_per_space'",
                                   Codes.BAD_JSON)
            if max_rooms_per_space < 0:
                raise SynapseError(
                    400,
                    "Value for 'max_rooms_per_space' must be a non-negative integer",
                    Codes.BAD_JSON,
                )

        return 200, await self.handler.federation_space_summary(
            origin, room_id, suggested_only, max_rooms_per_space,
            exclude_rooms)
Esempio n. 5
0
def _update_device_from_client_ips(
        device: JsonDict, client_ips: Dict[Tuple[str, str], JsonDict]) -> None:
    ip = client_ips.get((device["user_id"], device["device_id"]), {})
    device.update({
        "last_seen_ts": ip.get("last_seen"),
        "last_seen_ip": ip.get("ip")
    })
Esempio n. 6
0
    def from_json_dict(cls, d: JsonDict) -> "FederationSpaceSummaryResult":
        """Parse the result of a /spaces/ request

        Args:
            d: json object to be parsed

        Raises:
            ValueError if d is not a valid /spaces/ response
        """
        rooms = d.get("rooms")
        if not isinstance(rooms, Sequence):
            raise ValueError("'rooms' must be a list")
        if any(not isinstance(r, dict) for r in rooms):
            raise ValueError("Invalid room in 'rooms' list")

        events = d.get("events")
        if not isinstance(events, Sequence):
            raise ValueError("'events' must be a list")
        if any(not isinstance(e, dict) for e in events):
            raise ValueError("Invalid event in 'events' list")
        parsed_events = [
            FederationSpaceSummaryEventResult.from_json_dict(e) for e in events
        ]

        return cls(rooms, parsed_events)
Esempio n. 7
0
    def __init__(self, option_name: str, rule: JsonDict):
        """
        Args:
            option_name: Name of the config option this rule belongs to
            rule: The rule as specified in the config
        """

        action = rule["action"]
        user_id = rule.get("user_id", "*")
        room_id = rule.get("room_id", "*")
        alias = rule.get("alias", "*")

        if action in ("allow", "deny"):
            self.action = action
        else:
            raise ConfigError(
                "%s rules can only have action of 'allow' or 'deny'" %
                (option_name, ))

        self._alias_matches_all = alias == "*"

        try:
            self._user_id_regex = glob_to_regex(user_id)
            self._alias_regex = glob_to_regex(alias)
            self._room_id_regex = glob_to_regex(room_id)
        except Exception as e:
            raise ConfigError("Failed to parse glob into regex") from e
Esempio n. 8
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        self.enable_room_list_search = config.get("enable_room_list_search",
                                                  True)

        alias_creation_rules = config.get("alias_creation_rules")

        if alias_creation_rules is not None:
            self._alias_creation_rules = [
                _RoomDirectoryRule("alias_creation_rules", rule)
                for rule in alias_creation_rules
            ]
        else:
            self._alias_creation_rules = [
                _RoomDirectoryRule("alias_creation_rules", {"action": "allow"})
            ]

        room_list_publication_rules = config.get("room_list_publication_rules")

        if room_list_publication_rules is not None:
            self._room_list_publication_rules = [
                _RoomDirectoryRule("room_list_publication_rules", rule)
                for rule in room_list_publication_rules
            ]
        else:
            self._room_list_publication_rules = [
                _RoomDirectoryRule("room_list_publication_rules",
                                   {"action": "allow"})
            ]
Esempio n. 9
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        push_config = config.get("push") or {}
        self.push_include_content = push_config.get("include_content", True)
        self.push_group_unread_count_by_room = push_config.get(
            "group_unread_count_by_room", True
        )

        # There was a a 'redact_content' setting but mistakenly read from the
        # 'email'section'. Check for the flag in the 'push' section, and log,
        # but do not honour it to avoid nasty surprises when people upgrade.
        if push_config.get("redact_content") is not None:
            print(
                "The push.redact_content content option has never worked. "
                "Please set push.include_content if you want this behaviour"
            )

        # Now check for the one in the 'email' section and honour it,
        # with a warning.
        push_config = config.get("email") or {}
        redact_content = push_config.get("redact_content")
        if redact_content is not None:
            print(
                "The 'email.redact_content' option is deprecated: "
                "please set push.include_content instead"
            )
            self.push_include_content = not redact_content
Esempio n. 10
0
    async def _do_other_login(self,
                              login_submission: JsonDict) -> Dict[str, str]:
        """Handle non-token/saml/jwt logins

        Args:
            login_submission:

        Returns:
            HTTP response
        """
        # Log the request we got, but only certain fields to minimise the chance of
        # logging someone's password (even if they accidentally put it in the wrong
        # field)
        logger.info(
            "Got login request with identifier: %r, medium: %r, address: %r, user: %r",
            login_submission.get("identifier"),
            login_submission.get("medium"),
            login_submission.get("address"),
            login_submission.get("user"),
        )
        canonical_user_id, callback = await self.auth_handler.validate_login(
            login_submission, ratelimit=True)
        result = await self._complete_login(canonical_user_id,
                                            login_submission, callback)
        return result
Esempio n. 11
0
    async def _do_other_login(
            self,
            login_submission: JsonDict,
            should_issue_refresh_token: bool = False) -> LoginResponse:
        """Handle non-token/saml/jwt logins

        Args:
            login_submission:
            should_issue_refresh_token: True if this login should issue
                a refresh token alongside the access token.

        Returns:
            HTTP response
        """
        # Log the request we got, but only certain fields to minimise the chance of
        # logging someone's password (even if they accidentally put it in the wrong
        # field)
        logger.info(
            "Got login request with identifier: %r, medium: %r, address: %r, user: %r",
            login_submission.get("identifier"),
            login_submission.get("medium"),
            login_submission.get("address"),
            login_submission.get("user"),
        )
        canonical_user_id, callback = await self.auth_handler.validate_login(
            login_submission, ratelimit=True)
        result = await self._complete_login(
            canonical_user_id,
            login_submission,
            callback,
            should_issue_refresh_token=should_issue_refresh_token,
        )
        return result
Esempio n. 12
0
def _parse_oidc_provider_configs(
        config: JsonDict) -> Iterable["OidcProviderConfig"]:
    """extract and parse the OIDC provider configs from the config dict

    The configuration may contain either a single `oidc_config` object with an
    `enabled: True` property, or a list of provider configurations under
    `oidc_providers`, *or both*.

    Returns a generator which yields the OidcProviderConfig objects
    """
    validate_config(MAIN_CONFIG_SCHEMA, config, ())

    for i, p in enumerate(config.get("oidc_providers") or []):
        yield _parse_oidc_config_dict(p,
                                      ("oidc_providers", "<item %i>" % (i, )))

    # for backwards-compatibility, it is also possible to provide a single "oidc_config"
    # object with an "enabled: True" property.
    oidc_config = config.get("oidc_config")
    if oidc_config and oidc_config.get("enabled", False):
        # MAIN_CONFIG_SCHEMA checks that `oidc_config` is an object, but not that
        # it matches OIDC_PROVIDER_CONFIG_SCHEMA (see the comments on OIDC_CONFIG_SCHEMA
        # above), so now we need to validate it.
        validate_config(OIDC_PROVIDER_CONFIG_SCHEMA, oidc_config,
                        ("oidc_config", ))
        yield _parse_oidc_config_dict(oidc_config, ("oidc_config", ))
Esempio n. 13
0
def add_hashes_and_signatures(
    room_version: RoomVersion,
    event_dict: JsonDict,
    signature_name: str,
    signing_key: SigningKey,
) -> None:
    """Add content hash and sign the event

    Args:
        room_version: the version of the room this event is in

        event_dict: The event to add hashes to and sign
        signature_name: The name of the entity signing the event
            (typically the server's hostname).
        signing_key: The key to sign with
    """

    name, digest = compute_content_hash(event_dict,
                                        hash_algorithm=hashlib.sha256)

    event_dict.setdefault("hashes", {})[name] = encode_base64(digest)

    event_dict["signatures"] = compute_event_signature(
        room_version,
        event_dict,
        signature_name=signature_name,
        signing_key=signing_key)
Esempio n. 14
0
    def from_json_dict(cls,
                       d: JsonDict) -> "FederationSpaceSummaryEventResult":
        """Parse an event within the result of a /spaces/ request

        Args:
            d: json object to be parsed

        Raises:
            ValueError if d is not a valid event
        """

        event_type = d.get("type")
        if not isinstance(event_type, str):
            raise ValueError("Invalid event: 'event_type' must be a str")

        state_key = d.get("state_key")
        if not isinstance(state_key, str):
            raise ValueError("Invalid event: 'state_key' must be a str")

        content = d.get("content")
        if not isinstance(content, dict):
            raise ValueError("Invalid event: 'content' must be a dict")

        via = content.get("via")
        if not isinstance(via, Sequence):
            raise ValueError("Invalid event: 'via' must be a list")
        if any(not isinstance(v, str) for v in via):
            raise ValueError("Invalid event: 'via' must be a list of strings")

        return cls(event_type, state_key, via, d)
Esempio n. 15
0
    def __init__(self, hs: "HomeServer", filter_json: JsonDict):
        self._filter_json = filter_json

        room_filter_json = self._filter_json.get("room", {})

        self._room_filter = Filter(
            hs,
            {
                k: v
                for k, v in room_filter_json.items()
                if k in ("rooms", "not_rooms")
            },
        )

        self._room_timeline_filter = Filter(
            hs, room_filter_json.get("timeline", {}))
        self._room_state_filter = Filter(hs, room_filter_json.get("state", {}))
        self._room_ephemeral_filter = Filter(
            hs, room_filter_json.get("ephemeral", {}))
        self._room_account_data = Filter(
            hs, room_filter_json.get("account_data", {}))
        self._presence_filter = Filter(hs, filter_json.get("presence", {}))
        self._account_data = Filter(hs, filter_json.get("account_data", {}))

        self.include_leave = filter_json.get("room",
                                             {}).get("include_leave", False)
        self.event_fields = filter_json.get("event_fields", [])
        self.event_format = filter_json.get("event_format", "client")
Esempio n. 16
0
    async def incoming_device_list_update(self, origin: str,
                                          edu_content: JsonDict) -> None:
        """Called on incoming device list update from federation. Responsible
        for parsing the EDU and adding to pending updates list.
        """

        set_tag("origin", origin)
        set_tag("edu_content", edu_content)
        user_id = edu_content.pop("user_id")
        device_id = edu_content.pop("device_id")
        stream_id = str(edu_content.pop("stream_id"))  # They may come as ints
        prev_ids = edu_content.pop("prev_id", [])
        if not isinstance(prev_ids, list):
            raise SynapseError(
                400, "Device list update had an invalid 'prev_ids' field")
        prev_ids = [str(p) for p in prev_ids]  # They may come as ints

        if get_domain_from_id(user_id) != origin:
            # TODO: Raise?
            logger.warning(
                "Got device list update edu for %r/%r from %r",
                user_id,
                device_id,
                origin,
            )

            set_tag("error", True)
            log_kv({
                "message": "Got a device list update edu from a user and "
                "device which does not match the origin of the request.",
                "user_id": user_id,
                "device_id": device_id,
            })
            return

        room_ids = await self.store.get_rooms_for_user(user_id)
        if not room_ids:
            # We don't share any rooms with this user. Ignore update, as we
            # probably won't get any further updates.
            set_tag("error", True)
            log_kv({
                "message": "Got an update from a user for which "
                "we don't share any rooms",
                "other user_id": user_id,
            })
            logger.warning(
                "Got device list update edu for %r/%r, but don't share a room",
                user_id,
                device_id,
            )
            return

        logger.debug("Received device list update for %r/%r", user_id,
                     device_id)

        self._pending_updates.setdefault(user_id, []).append(
            (device_id, stream_id, prev_ids, edu_content))

        await self._handle_device_updates(user_id)
Esempio n. 17
0
 def read_config(self, config: JsonDict, **kwargs: Any) -> None:
     self.turn_uris = config.get("turn_uris", [])
     self.turn_shared_secret = config.get("turn_shared_secret")
     self.turn_username = config.get("turn_username")
     self.turn_password = config.get("turn_password")
     self.turn_user_lifetime = self.parse_duration(
         config.get("turn_user_lifetime", "1h"))
     self.turn_allow_guests = config.get("turn_allow_guests", True)
Esempio n. 18
0
    async def _complete_login(
        self,
        user_id: str,
        login_submission: JsonDict,
        callback: Optional[Callable[[Dict[str, str]], Awaitable[None]]] = None,
        create_non_existent_users: bool = False,
        ratelimit: bool = True,
    ) -> Dict[str, str]:
        """Called when we've successfully authed the user and now need to
        actually login them in (e.g. create devices). This gets called on
        all successful logins.

        Applies the ratelimiting for successful login attempts against an
        account.

        Args:
            user_id: ID of the user to register.
            login_submission: Dictionary of login information.
            callback: Callback function to run after login.
            create_non_existent_users: Whether to create the user if they don't
                exist. Defaults to False.
            ratelimit: Whether to ratelimit the login request.

        Returns:
            result: Dictionary of account information after successful login.
        """

        # Before we actually log them in we check if they've already logged in
        # too often. This happens here rather than before as we don't
        # necessarily know the user before now.
        if ratelimit:
            self._account_ratelimiter.ratelimit(user_id.lower())

        if create_non_existent_users:
            canonical_uid = await self.auth_handler.check_user_exists(user_id)
            if not canonical_uid:
                canonical_uid = await self.registration_handler.register_user(
                    localpart=UserID.from_string(user_id).localpart
                )
            user_id = canonical_uid

        device_id = login_submission.get("device_id")
        initial_display_name = login_submission.get("initial_device_display_name")
        device_id, access_token = await self.registration_handler.register_device(
            user_id, device_id, initial_display_name
        )

        result = {
            "user_id": user_id,
            "access_token": access_token,
            "home_server": self.hs.hostname,
            "device_id": device_id,
        }

        if callback is not None:
            await callback(result)

        return result
Esempio n. 19
0
    async def _do_ver_code_email_login(
            self, login_submission: JsonDict) -> Dict[str, str]:
        email = login_submission.get("email", None)
        if email is None:
            raise LoginError(410,
                             "Email field for ver_code_email is missing",
                             errcode=Codes.FORBIDDEN)
        # verify email and send to email
        user_id = await self.hs.get_datastore().get_user_id_by_threepid(
            "email", email)
        if user_id is None:
            raise SynapseError(400, "Email not found",
                               Codes.THREEPID_NOT_FOUND)

        ver_code = login_submission.get("ver_code", None)
        if ver_code is None:
            raise LoginError(410,
                             "ver_code field for ver_code_email is missing",
                             errcode=Codes.FORBIDDEN)

        # ver_code_service_host = "192.168.0.4"
        # ver_code_service_port = "8080"
        # ver_code_service_validation_api = "/api/services/auth/v1/code/validation"
        params = {"value": email, "type": "email", "code": ver_code}
        try:
            ver_code_res = await self.http_client.post_json_get_json(
                self.hs.config.auth_baseurl +
                self.hs.config.auth_code_validation,
                params,
            )
            logger.info("email ver_code_res: %s" % (str(ver_code_res)))
            if ver_code_res["code"] != 200:
                raise LoginError(412,
                                 "ver_code invalid",
                                 errcode=Codes.FORBIDDEN)
        except HttpResponseException as e:
            logger.info("Proxied validation vercode failed: %r", e)
            raise e.to_synapse_error()
        except RequestTimedOutError:
            raise SynapseError(
                500,
                "Timed out contacting extral server:ver_code_send_service")

        # lookup cache_ver_code from redis by email
        # self.hs.get_redis
        # ver_code == cache_ver_code

        # call IS for verify email ver_code
        # identity_handler = self.hs.get_identity_handler()
        # result = await identity_handler.request_validate_threepid_ver_code(email, ver_code)

        result = await self._complete_login(user_id,
                                            login_submission,
                                            create_non_existent_users=True)
        return result
Esempio n. 20
0
    async def _create_registration_details(
        self,
        user_id: str,
        params: JsonDict,
        is_appservice_ghost: bool = False,
        should_issue_refresh_token: bool = False,
    ) -> JsonDict:
        """Complete registration of newly-registered user

        Allocates device_id if one was not given; also creates access_token.

        Args:
            user_id: full canonical @user:id
            params: registration parameters, from which we pull device_id,
                initial_device_name and inhibit_login
            is_appservice_ghost
            should_issue_refresh_token: True if this registration should issue
                a refresh token alongside the access token.
        Returns:
             dictionary for response from /register
        """
        result: JsonDict = {
            "user_id": user_id,
            "home_server": self.hs.hostname,
        }
        if not params.get("inhibit_login", False):
            device_id = params.get("device_id")
            initial_display_name = params.get("initial_device_display_name")
            (
                device_id,
                access_token,
                valid_until_ms,
                refresh_token,
            ) = await self.registration_handler.register_device(
                user_id,
                device_id,
                initial_display_name,
                is_guest=False,
                is_appservice_ghost=is_appservice_ghost,
                should_issue_refresh_token=should_issue_refresh_token,
            )

            result.update({
                "access_token": access_token,
                "device_id": device_id
            })

            if valid_until_ms is not None:
                expires_in_ms = valid_until_ms - self.clock.time_msec()
                result["expires_in_ms"] = expires_in_ms

            if refresh_token is not None:
                result["refresh_token"] = refresh_token

        return result
Esempio n. 21
0
def convert_client_dict_legacy_fields_to_identifier(
    submission: JsonDict, ) -> Dict[str, str]:
    """
    Convert a legacy-formatted login submission to an identifier dict.

    Legacy login submissions (used in both login and user-interactive authentication)
    provide user-identifying information at the top-level instead.

    These are now deprecated and replaced with identifiers:
    https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types

    Args:
        submission: The client dict to convert

    Returns:
        The matching identifier dict

    Raises:
        SynapseError: If the format of the client dict is invalid
    """
    identifier = submission.get("identifier", {})

    # Generate an m.id.user identifier if "user" parameter is present
    user = submission.get("user")
    if user:
        identifier = {"type": "m.id.user", "user": user}

    # Generate an m.id.thirdparty identifier if "medium" and "address" parameters are present
    medium = submission.get("medium")
    address = submission.get("address")
    if medium and address:
        identifier = {
            "type": "m.id.thirdparty",
            "medium": medium,
            "address": address,
        }

    # We've converted valid, legacy login submissions to an identifier. If the
    # submission still doesn't have an identifier, it's invalid
    if not identifier:
        raise SynapseError(400, "Invalid login submission",
                           Codes.INVALID_PARAM)

    # Ensure the identifier has a type
    if "type" not in identifier:
        raise SynapseError(
            400,
            "'identifier' dict has no key 'type'",
            errcode=Codes.MISSING_PARAM,
        )

    return identifier
Esempio n. 22
0
def format_event_for_client_v2(d: JsonDict) -> JsonDict:
    drop_keys = (
        "auth_events",
        "prev_events",
        "hashes",
        "signatures",
        "depth",
        "origin",
        "prev_state",
    )
    for key in drop_keys:
        d.pop(key, None)
    return d
Esempio n. 23
0
def _matches_room_entry(room_entry: JsonDict, search_filter: dict) -> bool:
    if search_filter and search_filter.get("generic_search_term", None):
        generic_search_term = search_filter["generic_search_term"].upper()
        if generic_search_term in room_entry.get("name", "").upper():
            return True
        elif generic_search_term in room_entry.get("topic", "").upper():
            return True
        elif generic_search_term in room_entry.get("canonical_alias", "").upper():
            return True
    else:
        return True

    return False
Esempio n. 24
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        """Parses the old password auth providers config. The config format looks like this:

        password_providers:
           # Example config for an LDAP auth provider
           - module: "ldap_auth_provider.LdapAuthProvider"
             config:
               enabled: true
               uri: "ldap://ldap.example.com:389"
               start_tls: true
               base: "ou=users,dc=example,dc=com"
               attributes:
                  uid: "cn"
                  mail: "email"
                  name: "givenName"
               #bind_dn:
               #bind_password:
               #filter: "(objectClass=posixAccount)"

        We expect admins to use modules for this feature (which is why it doesn't appear
        in the sample config file), but we want to keep support for it around for a bit
        for backwards compatibility.
        """

        self.password_providers: List[Tuple[Type, Any]] = []
        providers = []

        # We want to be backwards compatible with the old `ldap_config`
        # param.
        ldap_config = config.get("ldap_config", {})
        if ldap_config.get("enabled", False):
            providers.append({"module": LDAP_PROVIDER, "config": ldap_config})

        providers.extend(config.get("password_providers") or [])
        for i, provider in enumerate(providers):
            mod_name = provider["module"]

            # This is for backwards compat when the ldap auth provider resided
            # in this package.
            if mod_name == "synapse.util.ldap_auth_provider.LdapAuthProvider":
                mod_name = LDAP_PROVIDER

            (provider_class, provider_config) = load_module(
                {
                    "module": mod_name,
                    "config": provider["config"]
                },
                ("password_providers", "<item %i>" % i),
            )

            self.password_providers.append((provider_class, provider_config))
Esempio n. 25
0
    def read_config(self, config: JsonDict, **kwargs: Any) -> None:
        # We *experimentally* support specifying multiple databases via the
        # `databases` key. This is a map from a label to database config in the
        # same format as the `database` config option, plus an extra
        # `data_stores` key to specify which data store goes where. For example:
        #
        #   databases:
        #       master:
        #           name: psycopg2
        #           data_stores: ["main"]
        #           args: {}
        #       state:
        #           name: psycopg2
        #           data_stores: ["state"]
        #           args: {}

        multi_database_config = config.get("databases")
        database_config = config.get("database")
        database_path = config.get("database_path")

        if multi_database_config and database_config:
            raise ConfigError(
                "Can't specify both 'database' and 'databases' in config")

        if multi_database_config:
            if database_path:
                raise ConfigError(
                    "Can't specify 'database_path' with 'databases'")

            self.databases = [
                DatabaseConnectionConfig(name, db_conf)
                for name, db_conf in multi_database_config.items()
            ]

        if database_config:
            self.databases = [
                DatabaseConnectionConfig("master", database_config)
            ]

        if database_path:
            if self.databases and self.databases[0].name != "sqlite3":
                logger.warning(NON_SQLITE_DATABASE_PATH_WARNING)
                return

            database_config = {"name": "sqlite3", "args": {}}
            self.databases = [
                DatabaseConnectionConfig("master", database_config)
            ]
            self.set_databasepath(database_path)
Esempio n. 26
0
    async def _do_sso_ldap_login(self,
                                 login_submission: JsonDict) -> Dict[str, str]:
        login_type = login_submission.get("type", None)
        username = login_submission.get("user", None)
        if username is None:
            raise LoginError(410,
                             "user field for ldap login is missing",
                             errcode=Codes.FORBIDDEN)
        password = login_submission.get("password", None)
        # logger.debug("----------------------password:%s" % (password,))
        if password is None:
            raise LoginError(410,
                             "password field for ldap login is missing",
                             errcode=Codes.FORBIDDEN)

        if username.startswith("@"):
            qualified_user_id = username
        else:
            qualified_user_id = UserID(username, self.hs.hostname).to_string()
        # verify bind_type and openid
        user_id = await self.auth_handler.check_user_exists(qualified_user_id)
        logger.info("----------------------exists user_id:%s" % (user_id, ))
        if user_id is None:
            raise SynapseError(400, "user not exists", Codes.INVALID_USERNAME)

        params = {
            "account": username,
            "password": password,
        }
        try:
            ldap_ver_res = await self.http_client.post_json_get_json(
                self.hs.config.auth_baseurl +
                self.hs.config.auth_sso_ldap_validation,
                params,
            )
            logger.info("ldap verification result: %s" % (str(ldap_ver_res)))
            if ldap_ver_res["code"] != 200:
                raise SynapseError(500, ldap_ver_res["message"])
        except HttpResponseException as e:
            logger.info("Proxied ldap verification failed: %r", e)
            raise e.to_synapse_error()
        except RequestTimedOutError:
            raise SynapseError(
                500, "Timed out contacting extral server:ldap verification")

        result = await self._complete_login(user_id,
                                            login_submission,
                                            create_non_existent_users=True)
        return result
Esempio n. 27
0
    async def _received_remote_receipt(self, origin: str,
                                       content: JsonDict) -> None:
        """Called when we receive an EDU of type m.receipt from a remote HS."""
        receipts = []
        for room_id, room_values in content.items():
            for receipt_type, users in room_values.items():
                for user_id, user_values in users.items():
                    if get_domain_from_id(user_id) != origin:
                        logger.info(
                            "Received receipt for user %r from server %s, ignoring",
                            user_id,
                            origin,
                        )
                        continue

                    receipts.append(
                        ReadReceipt(
                            room_id=room_id,
                            receipt_type=receipt_type,
                            user_id=user_id,
                            event_ids=user_values["event_ids"],
                            data=user_values.get("data", {}),
                        ))

        await self._handle_new_receipts(receipts)
Esempio n. 28
0
    async def _do_jwt_login(self,
                            login_submission: JsonDict) -> Dict[str, str]:
        token = login_submission.get("token", None)
        if token is None:
            raise LoginError(403,
                             "Token field for JWT is missing",
                             errcode=Codes.FORBIDDEN)

        import jwt

        try:
            payload = jwt.decode(
                token,
                self.jwt_secret,
                algorithms=[self.jwt_algorithm],
                issuer=self.jwt_issuer,
                audience=self.jwt_audiences,
            )
        except jwt.PyJWTError as e:
            # A JWT error occurred, return some info back to the client.
            raise LoginError(
                403,
                "JWT validation failed: %s" % (str(e), ),
                errcode=Codes.FORBIDDEN,
            )

        user = payload.get("sub", None)
        if user is None:
            raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN)

        user_id = UserID(user, self.hs.hostname).to_string()
        result = await self._complete_login(user_id,
                                            login_submission,
                                            create_non_existent_users=True)
        return result
Esempio n. 29
0
    async def _do_appservice_login(self, login_submission: JsonDict,
                                   appservice: ApplicationService):
        identifier = login_submission.get("identifier")
        logger.info("Got appservice login request with identifier: %r",
                    identifier)

        if not isinstance(identifier, dict):
            raise SynapseError(400, "Invalid identifier in login submission",
                               Codes.INVALID_PARAM)

        # this login flow only supports identifiers of type "m.id.user".
        if identifier.get("type") != "m.id.user":
            raise SynapseError(400, "Unknown login identifier type",
                               Codes.INVALID_PARAM)

        user = identifier.get("user")
        if not isinstance(user, str):
            raise SynapseError(400, "Invalid user in identifier",
                               Codes.INVALID_PARAM)

        if user.startswith("@"):
            qualified_user_id = user
        else:
            qualified_user_id = UserID(user, self.hs.hostname).to_string()

        if not appservice.is_interested_in_user(qualified_user_id):
            raise LoginError(403,
                             "Invalid access_token",
                             errcode=Codes.FORBIDDEN)

        return await self._complete_login(
            qualified_user_id,
            login_submission,
            ratelimit=appservice.is_rate_limited())
Esempio n. 30
0
    async def on_claim_client_keys(self, origin: str,
                                   content: JsonDict) -> Dict[str, Any]:
        query = []
        for user_id, device_keys in content.get("one_time_keys", {}).items():
            for device_id, algorithm in device_keys.items():
                query.append((user_id, device_id, algorithm))

        log_kv({
            "message": "Claiming one time keys.",
            "user, device pairs": query
        })
        results = await self.store.claim_e2e_one_time_keys(query)

        json_result = {}  # type: Dict[str, Dict[str, dict]]
        for user_id, device_keys in results.items():
            for device_id, keys in device_keys.items():
                for key_id, json_bytes in keys.items():
                    json_result.setdefault(user_id, {})[device_id] = {
                        key_id: json.loads(json_bytes)
                    }

        logger.info(
            "Claimed one-time-keys: %s",
            ",".join(("%s for %s:%s" % (key_id, user_id, device_id)
                      for user_id, user_keys in json_result.items()
                      for device_id, device_keys in user_keys.items()
                      for key_id, _ in device_keys.items())),
        )

        return {"one_time_keys": json_result}