Exemple #1
0
    async def check_password(self, user_id, password):
        """ Attempt to authenticate a user against PAM
            and register an account if none exists.

            Returns:
                True if authentication against PAM was successful,
                False if not
        """
        if not password:
            return False
        # user_id is of the form @foo:bar.com
        localpart = user_id.split(":", 1)[0][1:]

        # check if localpart is a valid mxid.
        # If not, bail out without even checking PAM because
        # we can't create a user with that id anyway.
        if types.contains_invalid_mxid_characters(localpart):
            return False

        # Now check the password
        if not pam.pam().authenticate(localpart, password, service='matrix-synapse'):
            return False

        # From here on, the user is authenticated
        if (await self.account_handler.check_user_exists(user_id)):
            return True

        if self.create_users:
            user_id = await self.account_handler.register_user(localpart=localpart)
            return bool(user_id)

        return False
Exemple #2
0
    def register_saml2(self, localpart):
        """
        Registers email_id as SAML2 Based Auth.
        """
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
            )
        yield self.auth.check_auth_blocking()
        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        yield self.check_user_id_not_appservice_exclusive(user_id)
        token = self.macaroon_gen.generate_access_token(user_id)
        try:
            yield self.store.register(
                user_id=user_id,
                token=token,
                password_hash=None,
                create_profile_with_localpart=user.localpart,
            )
        except Exception as e:
            yield self.store.add_access_token_to_user(user_id, token)
            # Ignore Registration errors
            logger.exception(e)
        defer.returnValue((user_id, token))
Exemple #3
0
    def register_saml2(self, localpart):
        """
        Registers email_id as SAML2 Based Auth.
        """
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
            )
        yield self.auth.check_auth_blocking()
        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        yield self.check_user_id_not_appservice_exclusive(user_id)
        token = self.macaroon_gen.generate_access_token(user_id)
        try:
            yield self.store.register(
                user_id=user_id,
                token=token,
                password_hash=None,
                create_profile_with_localpart=user.localpart,
            )
        except Exception as e:
            yield self.store.add_access_token_to_user(user_id, token)
            # Ignore Registration errors
            logger.exception(e)
        defer.returnValue((user_id, token))
Exemple #4
0
    async def check_username_availability(
        self,
        localpart: str,
        session_id: str,
    ) -> bool:
        """Handle an "is username available" callback check

        Args:
            localpart: desired localpart
            session_id: the session id for the username picker
        Returns:
            True if the username is available
        Raises:
            SynapseError if the localpart is invalid or the session is unknown
        """

        # make sure that there is a valid mapping session, to stop people dictionary-
        # scanning for accounts
        self.get_mapping_session(session_id)

        logger.info(
            "[session %s] Checking for availability of username %s",
            session_id,
            localpart,
        )

        if contains_invalid_mxid_characters(localpart):
            raise SynapseError(400, "localpart is invalid: %s" % (localpart, ))
        user_id = UserID(localpart, self._server_name).to_string()
        user_infos = await self._store.get_users_by_id_case_insensitive(user_id
                                                                        )

        logger.info("[session %s] users: %s", session_id, user_infos)
        return not user_infos
Exemple #5
0
    def check_username(self,
                       localpart,
                       guest_access_token=None,
                       assigned_user_id=None):
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
                Codes.INVALID_USERNAME,
            )

        if not localpart:
            raise SynapseError(400, "User ID cannot be empty",
                               Codes.INVALID_USERNAME)

        if localpart[0] == "_":
            raise SynapseError(400, "User ID may not begin with _",
                               Codes.INVALID_USERNAME)

        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        if assigned_user_id:
            if user_id == assigned_user_id:
                return
            else:
                raise SynapseError(
                    400,
                    "A different user ID has already been registered for this session",
                )

        self.check_user_id_not_appservice_exclusive(user_id)

        if len(user_id) > MAX_USERID_LENGTH:
            raise SynapseError(
                400,
                "User ID may not be longer than %s characters" %
                (MAX_USERID_LENGTH, ),
                Codes.INVALID_USERNAME,
            )

        users = yield self.store.get_users_by_id_case_insensitive(user_id)
        if users:
            if not guest_access_token:
                raise SynapseError(400,
                                   "User ID already taken.",
                                   errcode=Codes.USER_IN_USE)
            user_data = yield self.auth.get_user_by_access_token(
                guest_access_token)
            if not user_data[
                    "is_guest"] or user_data["user"].localpart != localpart:
                raise AuthError(
                    403,
                    "Cannot register taken user ID without valid guest "
                    "credentials for that user.",
                    errcode=Codes.FORBIDDEN,
                )
Exemple #6
0
    async def _register_mapped_user(
        self,
        attributes: UserAttributes,
        auth_provider_id: str,
        remote_user_id: str,
        user_agent: str,
        ip_address: str,
    ) -> str:
        """Register a new SSO user.

        This is called once we have successfully mapped the remote user id onto a local
        user id, one way or another.

        Args:
             attributes: user attributes returned by the user mapping provider,
                including a non-empty localpart.

            auth_provider_id: A unique identifier for this SSO provider, e.g.
                "oidc" or "saml".

            remote_user_id: The unique identifier from the SSO provider.

            user_agent: The user-agent in the HTTP request (used for potential
                shadow-banning.)

            ip_address: The IP address of the requester (used for potential
                shadow-banning.)

        Raises:
            a MappingException if the localpart is invalid.

            a SynapseError with code 400 and errcode Codes.USER_IN_USE if the localpart
            is already taken.
        """

        # Since the localpart is provided via a potentially untrusted module,
        # ensure the MXID is valid before registering.
        if not attributes.localpart or contains_invalid_mxid_characters(
                attributes.localpart):
            raise MappingException("localpart is invalid: %s" %
                                   (attributes.localpart, ))

        logger.debug("Mapped SSO user to local part %s", attributes.localpart)
        registered_user_id = await self._registration_handler.register_user(
            localpart=attributes.localpart,
            default_display_name=attributes.display_name,
            bind_emails=attributes.emails,
            user_agent_ips=[(user_agent, ip_address)],
            auth_provider_id=auth_provider_id,
        )

        await self._store.record_user_external_id(auth_provider_id,
                                                  remote_user_id,
                                                  registered_user_id)
        return registered_user_id
Exemple #7
0
    def check_username(self, localpart, guest_access_token=None,
                       assigned_user_id=None):
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
                Codes.INVALID_USERNAME
            )

        if not localpart:
            raise SynapseError(
                400,
                "User ID cannot be empty",
                Codes.INVALID_USERNAME
            )

        if localpart[0] == '_':
            raise SynapseError(
                400,
                "User ID may not begin with _",
                Codes.INVALID_USERNAME
            )

        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        if assigned_user_id:
            if user_id == assigned_user_id:
                return
            else:
                raise SynapseError(
                    400,
                    "A different user ID has already been registered for this session",
                )

        self.check_user_id_not_appservice_exclusive(user_id)

        users = yield self.store.get_users_by_id_case_insensitive(user_id)
        if users:
            if not guest_access_token:
                raise SynapseError(
                    400,
                    "User ID already taken.",
                    errcode=Codes.USER_IN_USE,
                )
            user_data = yield self.auth.get_user_by_access_token(guest_access_token)
            if not user_data["is_guest"] or user_data["user"].localpart != localpart:
                raise AuthError(
                    403,
                    "Cannot register taken user ID without valid guest "
                    "credentials for that user.",
                    errcode=Codes.FORBIDDEN,
                )
Exemple #8
0
    async def check_username(
        self,
        localpart: str,
        guest_access_token: Optional[str] = None,
        assigned_user_id: Optional[str] = None,
    ) -> None:
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
                Codes.INVALID_USERNAME,
            )

        if not localpart:
            raise SynapseError(400, "User ID cannot be empty",
                               Codes.INVALID_USERNAME)

        if localpart[0] == "_":
            raise SynapseError(400, "User ID may not begin with _",
                               Codes.INVALID_USERNAME)

        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        if assigned_user_id:
            if user_id == assigned_user_id:
                return
            else:
                raise SynapseError(
                    400,
                    "A different user ID has already been registered for this session",
                )

        self.check_user_id_not_appservice_exclusive(user_id)

        if len(user_id) > MAX_USERID_LENGTH:
            raise SynapseError(
                400,
                "User ID may not be longer than %s characters" %
                (MAX_USERID_LENGTH, ),
                Codes.INVALID_USERNAME,
            )

        users = await self.store.get_users_by_id_case_insensitive(user_id)
        if users:
            if not guest_access_token:
                raise SynapseError(400,
                                   "User ID already taken.",
                                   errcode=Codes.USER_IN_USE)
            user_data = await self.auth.get_user_by_access_token(
                guest_access_token)
            if (not user_data.is_guest or UserID.from_string(
                    user_data.user_id).localpart != localpart):
                raise AuthError(
                    403,
                    "Cannot register taken user ID without valid guest "
                    "credentials for that user.",
                    errcode=Codes.FORBIDDEN,
                )

        if guest_access_token is None:
            try:
                int(localpart)
                raise SynapseError(
                    400,
                    "Numeric user IDs are reserved for guest users.",
                    errcode=Codes.INVALID_USERNAME,
                )
            except ValueError:
                pass
Exemple #9
0
    async def get_mxid_from_sso(
        self,
        auth_provider_id: str,
        remote_user_id: str,
        user_agent: str,
        ip_address: str,
        sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
        grandfather_existing_users: Optional[Callable[[], Awaitable[Optional[str]]]],
    ) -> str:
        """
        Given an SSO ID, retrieve the user ID for it and possibly register the user.

        This first checks if the SSO ID has previously been linked to a matrix ID,
        if it has that matrix ID is returned regardless of the current mapping
        logic.

        If a callable is provided for grandfathering users, it is called and can
        potentially return a matrix ID to use. If it does, the SSO ID is linked to
        this matrix ID for subsequent calls.

        The mapping function is called (potentially multiple times) to generate
        a localpart for the user.

        If an unused localpart is generated, the user is registered from the
        given user-agent and IP address and the SSO ID is linked to this matrix
        ID for subsequent calls.

        Args:
            auth_provider_id: A unique identifier for this SSO provider, e.g.
                "oidc" or "saml".
            remote_user_id: The unique identifier from the SSO provider.
            user_agent: The user agent of the client making the request.
            ip_address: The IP address of the client making the request.
            sso_to_matrix_id_mapper: A callable to generate the user attributes.
                The only parameter is an integer which represents the amount of
                times the returned mxid localpart mapping has failed.

                It is expected that the mapper can raise two exceptions, which
                will get passed through to the caller:

                    MappingException if there was a problem mapping the response
                        to the user.
                    RedirectException to redirect to an additional page (e.g.
                        to prompt the user for more information).
            grandfather_existing_users: A callable which can return an previously
                existing matrix ID. The SSO ID is then linked to the returned
                matrix ID.

        Returns:
             The user ID associated with the SSO response.

        Raises:
            MappingException if there was a problem mapping the response to a user.
            RedirectException: if the mapping provider needs to redirect the user
                to an additional page. (e.g. to prompt for more information)

        """
        # first of all, check if we already have a mapping for this user
        previously_registered_user_id = await self.get_sso_user_by_remote_user_id(
            auth_provider_id, remote_user_id,
        )
        if previously_registered_user_id:
            return previously_registered_user_id

        # Check for grandfathering of users.
        if grandfather_existing_users:
            previously_registered_user_id = await grandfather_existing_users()
            if previously_registered_user_id:
                # Future logins should also match this user ID.
                await self.store.record_user_external_id(
                    auth_provider_id, remote_user_id, previously_registered_user_id
                )
                return previously_registered_user_id

        # Otherwise, generate a new user.
        for i in range(self._MAP_USERNAME_RETRIES):
            try:
                attributes = await sso_to_matrix_id_mapper(i)
            except (RedirectException, MappingException):
                # Mapping providers are allowed to issue a redirect (e.g. to ask
                # the user for more information) and can issue a mapping exception
                # if a name cannot be generated.
                raise
            except Exception as e:
                # Any other exception is unexpected.
                raise MappingException(
                    "Could not extract user attributes from SSO response."
                ) from e

            logger.debug(
                "Retrieved user attributes from user mapping provider: %r (attempt %d)",
                attributes,
                i,
            )

            if not attributes.localpart:
                raise MappingException(
                    "Error parsing SSO response: SSO mapping provider plugin "
                    "did not return a localpart value"
                )

            # Check if this mxid already exists
            user_id = UserID(attributes.localpart, self.server_name).to_string()
            if not await self.store.get_users_by_id_case_insensitive(user_id):
                # This mxid is free
                break
        else:
            # Unable to generate a username in 1000 iterations
            # Break and return error to the user
            raise MappingException(
                "Unable to generate a Matrix ID from the SSO response"
            )

        # Since the localpart is provided via a potentially untrusted module,
        # ensure the MXID is valid before registering.
        if contains_invalid_mxid_characters(attributes.localpart):
            raise MappingException("localpart is invalid: %s" % (attributes.localpart,))

        logger.debug("Mapped SSO user to local part %s", attributes.localpart)
        registered_user_id = await self._registration_handler.register_user(
            localpart=attributes.localpart,
            default_display_name=attributes.display_name,
            bind_emails=attributes.emails,
            user_agent_ips=[(user_agent, ip_address)],
        )

        await self.store.record_user_external_id(
            auth_provider_id, remote_user_id, registered_user_id
        )
        return registered_user_id