Exemplo n.º 1
0
 def _get_current_version(txn, user_id):
     txn.execute(
         "SELECT MAX(version) FROM e2e_room_keys_versions "
         "WHERE user_id=? AND deleted=0", (user_id, ))
     row = txn.fetchone()
     if not row:
         raise StoreError(404, 'No current backup version')
     return row[0]
Exemplo n.º 2
0
 def get_user_by_token(self, token):
     try:
         return {
             "name": self.tokens_to_users[token],
             "admin": 0,
             "device_id": None,
         }
     except:
         raise StoreError(400, "User does not exist.")
Exemplo n.º 3
0
    def _simple_delete_one_txn(txn, table, keyvalues):
        """Executes a DELETE query on the named table, expecting to delete a
        single row.

        Args:
            table : string giving the table name
            keyvalues : dict of column names and values to select the row with
        """
        sql = "DELETE FROM %s WHERE %s" % (
            table,
            " AND ".join("%s = ?" % (k, ) for k in keyvalues)
        )

        txn.execute(sql, keyvalues.values())
        if txn.rowcount == 0:
            raise StoreError(404, "No row found")
        if txn.rowcount > 1:
            raise StoreError(500, "more than one row matched")
Exemplo n.º 4
0
    async def create_ui_auth_session(
        self, clientdict: JsonDict, uri: str, method: str, description: str,
    ) -> UIAuthSessionData:
        """
        Creates a new user interactive authentication session.

        The session can be used to track the stages necessary to authenticate a
        user across multiple HTTP requests.

        Args:
            clientdict:
                The dictionary from the client root level, not the 'auth' key.
            uri:
                The URI this session was initiated with, this is checked at each
                stage of the authentication to ensure that the asked for
                operation has not changed.
            method:
                The method this session was initiated with, this is checked at each
                stage of the authentication to ensure that the asked for
                operation has not changed.
            description:
                A string description of the operation that the current
                authentication is authorising.
        Returns:
            The newly created session.
        Raises:
            StoreError if a unique session ID cannot be generated.
        """
        # The clientdict gets stored as JSON.
        clientdict_json = json.dumps(clientdict)

        # autogen a session ID and try to create it. We may clash, so just
        # try a few times till one goes through, giving up eventually.
        attempts = 0
        while attempts < 5:
            session_id = stringutils.random_string(24)

            try:
                await self.db.simple_insert(
                    table="ui_auth_sessions",
                    values={
                        "session_id": session_id,
                        "clientdict": clientdict_json,
                        "uri": uri,
                        "method": method,
                        "description": description,
                        "serverdict": "{}",
                        "creation_time": self.hs.get_clock().time_msec(),
                    },
                    desc="create_ui_auth_session",
                )
                return UIAuthSessionData(
                    session_id, clientdict, uri, method, description
                )
            except self.db.engine.module.IntegrityError:
                attempts += 1
        raise StoreError(500, "Couldn't generate a session ID.")
Exemplo n.º 5
0
    def _query_for_auth(self, txn, token):
        txn.execute(
            "SELECT users.name FROM access_tokens LEFT JOIN users" +
            " ON users.id = access_tokens.user_id WHERE token = ?", [token])
        row = txn.fetchone()
        if row:
            return row[0]

        raise StoreError(404, "Token not found.")
Exemplo n.º 6
0
    def store_room(self, room_id, room_creator_user_id, is_public):
        if room_id in self.rooms:
            raise StoreError(409, "Conflicting room!")

        room = MemoryDataStore.Room(
            room_id=room_id,
            is_public=is_public,
            creator=room_creator_user_id
        )
        self.rooms[room_id] = room
Exemplo n.º 7
0
    def _simple_select_one_txn(txn, table, keyvalues, retcols,
                               allow_none=False):
        select_sql = "SELECT %s FROM %s WHERE %s" % (
            ", ".join(retcols),
            table,
            " AND ".join("%s = ?" % (k,) for k in keyvalues)
        )

        txn.execute(select_sql, keyvalues.values())

        row = txn.fetchone()
        if not row:
            if allow_none:
                return None
            raise StoreError(404, "No row found")
        if txn.rowcount > 1:
            raise StoreError(500, "More than one row matched")

        return dict(zip(retcols, row))
    def _simple_update_one_txn(txn, table, keyvalues, updatevalues):
        if keyvalues:
            where = "WHERE %s" % " AND ".join("%s = ?" % k
                                              for k in keyvalues.iterkeys())
        else:
            where = ""

        update_sql = "UPDATE %s SET %s %s" % (
            table,
            ", ".join("%s = ?" % (k, ) for k in updatevalues),
            where,
        )

        txn.execute(update_sql, updatevalues.values() + keyvalues.values())

        if txn.rowcount == 0:
            raise StoreError(404, "No row found")
        if txn.rowcount > 1:
            raise StoreError(500, "More than one row matched")
Exemplo n.º 9
0
 def _get_current_version(txn: LoggingTransaction, user_id: str) -> int:
     txn.execute(
         "SELECT MAX(version) FROM e2e_room_keys_versions "
         "WHERE user_id=? AND deleted=0",
         (user_id, ),
     )
     # `SELECT MAX() FROM ...` will always return 1 row. The value in that row will
     # be `NULL` when there are no available versions.
     row = cast(Tuple[Optional[int]], txn.fetchone())
     if row[0] is None:
         raise StoreError(404, "No current backup version")
     return row[0]
Exemplo n.º 10
0
    def _query_for_auth(self, txn, token):
        sql = ("SELECT users.name, users.admin, access_tokens.device_id"
               " FROM users"
               " INNER JOIN access_tokens on users.id = access_tokens.user_id"
               " WHERE token = ?")

        cursor = txn.execute(sql, (token, ))
        rows = self.cursor_to_dict(cursor)
        if rows:
            return rows[0]

        raise StoreError(404, "Token not found.")
Exemplo n.º 11
0
    def store_room(self, room_id, room_creator_user_id, is_public):
        """Stores a room.

        Args:
            room_id (str): The desired room ID, can be None.
            room_creator_user_id (str): The user ID of the room creator.
            is_public (bool): True to indicate that this room should appear in
            public room lists.
        Raises:
            StoreError if the room could not be stored.
        """
        try:
            yield self._simple_insert(
                RoomsTable.table_name,
                dict(room_id=room_id,
                     creator=room_creator_user_id,
                     is_public=is_public))
        except IntegrityError:
            raise StoreError(409, "Room ID in use.")
        except Exception as e:
            logger.error("store_room with room_id=%s failed: %s", room_id, e)
            raise StoreError(500, "Problem creating room.")
Exemplo n.º 12
0
    async def add_e2e_room_keys(
            self, user_id: str, version: str,
            room_keys: Iterable[Tuple[str, str, RoomKey]]) -> None:
        """Bulk add room keys to a given backup.

        Args:
            user_id: the user whose backup we're adding to
            version: the version ID of the backup for the set of keys we're adding to
            room_keys: the keys to add, in the form (roomID, sessionID, keyData)
        """
        try:
            version_int = int(version)
        except ValueError:
            # Our versions are all ints so if we can't convert it to an integer,
            # it doesn't exist.
            raise StoreError(404, "No backup with that version exists")

        values = []
        for (room_id, session_id, room_key) in room_keys:
            values.append((
                user_id,
                version_int,
                room_id,
                session_id,
                room_key["first_message_index"],
                room_key["forwarded_count"],
                room_key["is_verified"],
                json_encoder.encode(room_key["session_data"]),
            ))
            log_kv({
                "message": "Set room key",
                "room_id": room_id,
                "session_id": session_id,
                StreamKeyType.ROOM: room_key,
            })

        await self.db_pool.simple_insert_many(
            table="e2e_room_keys",
            keys=(
                "user_id",
                "version",
                "room_id",
                "session_id",
                "first_message_index",
                "forwarded_count",
                "is_verified",
                "session_data",
            ),
            values=values,
            desc="add_e2e_room_keys",
        )
Exemplo n.º 13
0
    async def get_access_token_for_user_id(
        self, user_id: str, device_id: Optional[str], valid_until_ms: Optional[int]
    ):
        """
        Creates a new access token for the user with the given user ID.

        The user is assumed to have been authenticated by some other
        machanism (e.g. CAS), and the user_id converted to the canonical case.

        The device will be recorded in the table if it is not there already.

        Args:
            user_id: canonical User ID
            device_id: the device ID to associate with the tokens.
               None to leave the tokens unassociated with a device (deprecated:
               we should always have a device ID)
            valid_until_ms: when the token is valid until. None for
                no expiry.
        Returns:
              The access token for the user's session.
        Raises:
            StoreError if there was a problem storing the token.
        """
        fmt_expiry = ""
        if valid_until_ms is not None:
            fmt_expiry = time.strftime(
                " until %Y-%m-%d %H:%M:%S", time.localtime(valid_until_ms / 1000.0)
            )
        logger.info("Logging in user %s on device %s%s", user_id, device_id, fmt_expiry)

        await self.auth.check_auth_blocking(user_id)

        access_token = self.macaroon_gen.generate_access_token(user_id)
        await self.store.add_access_token_to_user(
            user_id, access_token, device_id, valid_until_ms
        )

        # the device *should* have been registered before we got here; however,
        # it's possible we raced against a DELETE operation. The thing we
        # really don't want is active access_tokens without a record of the
        # device, so we double-check it here.
        if device_id is not None:
            try:
                await self.store.get_device(user_id, device_id)
            except StoreError:
                await self.store.delete_access_token(access_token)
                raise StoreError(400, "Login raced against device deletion")

        return access_token
Exemplo n.º 14
0
        def _get_session(txn: LoggingTransaction, session_type: str,
                         session_id: str, ts: int) -> JsonDict:
            # This includes the expiry time since items are only periodically
            # deleted, not upon expiry.
            select_sql = """
            SELECT value FROM sessions WHERE
            session_type = ? AND session_id = ? AND expiry_time_ms > ?
            """
            txn.execute(select_sql, [session_type, session_id, ts])
            row = txn.fetchone()

            if not row:
                raise StoreError(404, "No session")

            return db_to_json(row[0])
Exemplo n.º 15
0
    def store_room(
        self,
        room_id: str,
        room_creator_user_id: str,
        is_public: bool,
        room_version: RoomVersion,
    ):
        """Stores a room.

        Args:
            room_id: The desired room ID, can be None.
            room_creator_user_id: The user ID of the room creator.
            is_public: True to indicate that this room should appear in
                public room lists.
            room_version: The version of the room
        Raises:
            StoreError if the room could not be stored.
        """
        try:

            def store_room_txn(txn, next_id):
                self.db.simple_insert_txn(
                    txn,
                    "rooms",
                    {
                        "room_id": room_id,
                        "creator": room_creator_user_id,
                        "is_public": is_public,
                        "room_version": room_version.identifier,
                    },
                )
                if is_public:
                    self.db.simple_insert_txn(
                        txn,
                        table="public_room_list_stream",
                        values={
                            "stream_id": next_id,
                            "room_id": room_id,
                            "visibility": is_public,
                        },
                    )

            with self._public_room_id_gen.get_next() as next_id:
                yield self.db.runInteraction("store_room_txn", store_room_txn,
                                             next_id)
        except Exception as e:
            logger.error("store_room with room_id=%s failed: %s", room_id, e)
            raise StoreError(500, "Problem creating room.")
Exemplo n.º 16
0
    def _register(self, txn, user_id, token, password_hash):
        now = int(self.clock.time())

        try:
            txn.execute("INSERT INTO users(name, password_hash, creation_ts) "
                        "VALUES (?,?,?)",
                        [user_id, password_hash, now])
        except IntegrityError:
            raise StoreError(
                400, "User ID already taken.", errcode=Codes.USER_IN_USE
            )

        # it's possible for this to get a conflict, but only for a single user
        # since tokens are namespaced based on their user ID
        txn.execute("INSERT INTO access_tokens(user_id, token) " +
                    "VALUES (?,?)", [txn.lastrowid, token])
Exemplo n.º 17
0
    def _simple_select_one_onecol_txn(cls, txn, table, keyvalues, retcol,
                                      allow_none=False):
        ret = cls._simple_select_onecol_txn(
            txn,
            table=table,
            keyvalues=keyvalues,
            retcol=retcol,
        )

        if ret:
            return ret[0]
        else:
            if allow_none:
                return None
            else:
                raise StoreError(404, "No row found")
Exemplo n.º 18
0
    def _register(self, txn, user_id, token, password_hash, was_guest,
                  make_guest, appservice_id):
        now = int(self.clock.time())

        next_id = self._access_tokens_id_gen.get_next()

        try:
            if was_guest:
                txn.execute(
                    "UPDATE users SET"
                    " password_hash = ?,"
                    " upgrade_ts = ?,"
                    " is_guest = ?"
                    " WHERE name = ?",
                    [password_hash, now, 1 if make_guest else 0, user_id])
            else:
                txn.execute(
                    "INSERT INTO users "
                    "("
                    "   name,"
                    "   password_hash,"
                    "   creation_ts,"
                    "   is_guest,"
                    "   appservice_id"
                    ") "
                    "VALUES (?,?,?,?,?)", [
                        user_id,
                        password_hash,
                        now,
                        1 if make_guest else 0,
                        appservice_id,
                    ])
        except self.database_engine.module.IntegrityError:
            raise StoreError(400,
                             "User ID already taken.",
                             errcode=Codes.USER_IN_USE)

        if token:
            # it's possible for this to get a conflict, but only for a single user
            # since tokens are namespaced based on their user ID
            txn.execute(
                "INSERT INTO access_tokens(id, user_id, token)"
                " VALUES (?,?,?)", (
                    next_id,
                    user_id,
                    token,
                ))
Exemplo n.º 19
0
    def add_access_token_to_user(self, user_id, token):
        """Adds an access token for the given user.

        Args:
            user_id (str): The user ID.
            token (str): The new access token to add.
        Raises:
            StoreError if there was a problem adding this.
        """
        row = yield self._simple_select_one("users", {"name": user_id}, ["id"])
        if not row:
            raise StoreError(400, "Bad user ID supplied.")
        row_id = row["id"]
        yield self._simple_insert("access_tokens", {
            "user_id": row_id,
            "token": token
        })
Exemplo n.º 20
0
    async def create_session(self, session_type: str, value: JsonDict,
                             expiry_ms: int) -> str:
        """
        Creates a new pagination session for the room hierarchy endpoint.

        Args:
            session_type: The type for this session.
            value: The value to store.
            expiry_ms: How long before an item is evicted from the cache
                in milliseconds. Default is 0, indicating items never get
                evicted based on time.

        Returns:
            The newly created session ID.

        Raises:
            StoreError if a unique session ID cannot be generated.
        """
        # autogen a session ID and try to create it. We may clash, so just
        # try a few times till one goes through, giving up eventually.
        attempts = 0
        while attempts < 5:
            session_id = stringutils.random_string(24)

            try:
                await self.db_pool.simple_insert(
                    table="sessions",
                    values={
                        "session_id":
                        session_id,
                        "session_type":
                        session_type,
                        "value":
                        json_encoder.encode(value),
                        "expiry_time_ms":
                        self.hs.get_clock().time_msec() + expiry_ms,
                    },
                    desc="create_session",
                )

                return session_id
            except self.db_pool.engine.module.IntegrityError:
                attempts += 1
        raise StoreError(500, "Couldn't generate a session ID.")
Exemplo n.º 21
0
    def get_user_by_token(self, token):
        """ Get a registered user's ID.

        Args:
            token (str)- The access token to get the user by.
        Returns:
            UserID : User ID object of the user who has that access token.
        Raises:
            AuthError if no user by that token exists or the token is invalid.
        """
        try:
            user_id = yield self.store.get_user_by_token(token=token)
            if not user_id:
                raise StoreError()
            defer.returnValue(self.hs.parse_userid(user_id))
        except StoreError:
            raise AuthError(403,
                            "Unrecognised access token.",
                            errcode=Codes.UNKNOWN_TOKEN)
Exemplo n.º 22
0
 def _generate_room_id(self, creator_id, is_public):
     # autogen room IDs and try to create it. We may clash, so just
     # try a few times till one goes through, giving up eventually.
     attempts = 0
     while attempts < 5:
         try:
             random_string = stringutils.random_string(18)
             gen_room_id = RoomID(random_string, self.hs.hostname).to_string()
             if isinstance(gen_room_id, bytes):
                 gen_room_id = gen_room_id.decode("utf-8")
             yield self.store.store_room(
                 room_id=gen_room_id,
                 room_creator_user_id=creator_id,
                 is_public=is_public,
             )
             return gen_room_id
         except StoreError:
             attempts += 1
     raise StoreError(500, "Couldn't generate a room ID.")
Exemplo n.º 23
0
    def _exchange_refresh_token(self, txn, old_token, token_generator):
        sql = "SELECT user_id FROM refresh_tokens WHERE token = ?"
        txn.execute(sql, (old_token, ))
        rows = self.cursor_to_dict(txn)
        if not rows:
            raise StoreError(403, "Did not recognize refresh token")
        user_id = rows[0]["user_id"]

        # TODO(danielwh): Maybe perform a validation on the macaroon that
        # macaroon.user_id == user_id.

        new_token = token_generator(user_id)
        sql = "UPDATE refresh_tokens SET token = ? WHERE token = ?"
        txn.execute(sql, (
            new_token,
            old_token,
        ))

        return user_id, new_token
Exemplo n.º 24
0
    async def update_e2e_room_key(
        self,
        user_id: str,
        version: str,
        room_id: str,
        session_id: str,
        room_key: RoomKey,
    ) -> None:
        """Replaces the encrypted E2E room key for a given session in a given backup

        Args:
            user_id: the user whose backup we're setting
            version: the version ID of the backup we're updating
            room_id: the ID of the room whose keys we're setting
            session_id: the session whose room_key we're setting
            room_key: the room_key being set
        Raises:
            StoreError
        """
        try:
            version_int = int(version)
        except ValueError:
            # Our versions are all ints so if we can't convert it to an integer,
            # it doesn't exist.
            raise StoreError(404, "No backup with that version exists")

        await self.db_pool.simple_update_one(
            table="e2e_room_keys",
            keyvalues={
                "user_id": user_id,
                "version": version_int,
                "room_id": room_id,
                "session_id": session_id,
            },
            updatevalues={
                "first_message_index": room_key["first_message_index"],
                "forwarded_count": room_key["forwarded_count"],
                "is_verified": room_key["is_verified"],
                "session_data": json_encoder.encode(room_key["session_data"]),
            },
            desc="update_e2e_room_key",
        )
Exemplo n.º 25
0
    def store_device(self, user_id, device_id, initial_device_display_name):
        """Ensure the given device is known; add it to the store if not

        Args:
            user_id (str): id of user associated with the device
            device_id (str): id of device
            initial_device_display_name (str): initial displayname of the
               device. Ignored if device exists.
        Returns:
            defer.Deferred: boolean whether the device was inserted or an
                existing device existed with that ID.
        """
        key = (user_id, device_id)
        if self.device_id_exists_cache.get(key, None):
            return False

        try:
            inserted = yield self._simple_insert(
                "devices",
                values={
                    "user_id": user_id,
                    "device_id": device_id,
                    "display_name": initial_device_display_name,
                },
                desc="store_device",
                or_ignore=True,
            )
            self.device_id_exists_cache.prefill(key, True)
            return inserted
        except Exception as e:
            logger.error(
                "store_device with device_id=%s(%r) user_id=%s(%r)"
                " display_name=%s(%r) failed: %s",
                type(device_id).__name__,
                device_id,
                type(user_id).__name__,
                user_id,
                type(initial_device_display_name).__name__,
                initial_device_display_name,
                e,
            )
            raise StoreError(500, "Problem storing device.")
Exemplo n.º 26
0
        def _delete_e2e_room_keys_version_txn(txn):
            if version is None:
                this_version = self._get_current_version(txn, user_id)
                if this_version is None:
                    raise StoreError(404, "No current backup version")
            else:
                this_version = version

            self.db.simple_delete_txn(
                txn,
                table="e2e_room_keys",
                keyvalues={"user_id": user_id, "version": this_version},
            )

            return self.db.simple_update_one_txn(
                txn,
                table="e2e_room_keys_versions",
                keyvalues={"user_id": user_id, "version": this_version},
                updatevalues={"deleted": 1},
            )
Exemplo n.º 27
0
 def add_pusher(self, user_name, profile_tag, kind, app_id,
                app_display_name, device_display_name, pushkey, pushkey_ts,
                lang, data):
     try:
         yield self._simple_upsert(
             PushersTable.table_name, dict(
                 app_id=app_id,
                 pushkey=pushkey,
             ),
             dict(user_name=user_name,
                  kind=kind,
                  profile_tag=profile_tag,
                  app_display_name=app_display_name,
                  device_display_name=device_display_name,
                  ts=pushkey_ts,
                  lang=lang,
                  data=data))
     except Exception as e:
         logger.error("create_pusher with failed: %s", e)
         raise StoreError(500, "Problem creating pusher.")
Exemplo n.º 28
0
        def _get_e2e_room_keys_version_info_txn(txn):
            if version is None:
                this_version = self._get_current_version(txn, user_id)
            else:
                try:
                    this_version = int(version)
                except ValueError:
                    # Our versions are all ints so if we can't convert it to an integer,
                    # it isn't there.
                    raise StoreError(404, "No row found")

            result = self._simple_select_one_txn(
                txn,
                table="e2e_room_keys_versions",
                keyvalues={"user_id": user_id, "version": this_version, "deleted": 0},
                retcols=("version", "algorithm", "auth_data"),
            )
            result["auth_data"] = json.loads(result["auth_data"])
            result["version"] = str(result["version"])
            return result
Exemplo n.º 29
0
    async def update_e2e_room_keys_version(
        self,
        user_id: str,
        version: str,
        info: Optional[JsonDict] = None,
        version_etag: Optional[int] = None,
    ) -> None:
        """Update a given backup version

        Args:
            user_id: the user whose backup version we're updating
            version: the version ID of the backup version we're updating
            info: the new backup version info to store. If None, then the backup
                version info is not updated.
            version_etag: etag of the keys in the backup. If None, then the etag
                is not updated.
        """
        updatevalues: Dict[str, object] = {}

        if info is not None and "auth_data" in info:
            updatevalues["auth_data"] = json_encoder.encode(info["auth_data"])
        if version_etag is not None:
            updatevalues["etag"] = version_etag

        if updatevalues:
            try:
                version_int = int(version)
            except ValueError:
                # Our versions are all ints so if we can't convert it to an integer,
                # it doesn't exist.
                raise StoreError(404, "No backup with that version exists")

            await self.db_pool.simple_update_one(
                table="e2e_room_keys_versions",
                keyvalues={
                    "user_id": user_id,
                    "version": version_int
                },
                updatevalues=updatevalues,
                desc="update_e2e_room_keys_version",
            )
Exemplo n.º 30
0
    def store_device(self,
                     user_id,
                     device_id,
                     initial_device_display_name,
                     ignore_if_known=True):
        """Ensure the given device is known; add it to the store if not

        Args:
            user_id (str): id of user associated with the device
            device_id (str): id of device
            initial_device_display_name (str): initial displayname of the
               device
            ignore_if_known (bool): ignore integrity errors which mean the
               device is already known
        Returns:
            defer.Deferred
        Raises:
            StoreError: if ignore_if_known is False and the device was already
               known
        """
        try:
            yield self._simple_insert(
                "devices",
                values={
                    "user_id": user_id,
                    "device_id": device_id,
                    "display_name": initial_device_display_name
                },
                desc="store_device",
                or_ignore=ignore_if_known,
            )
        except Exception as e:
            logger.error(
                "store_device with device_id=%s(%r) user_id=%s(%r)"
                " display_name=%s(%r) failed: %s",
                type(device_id).__name__, device_id,
                type(user_id).__name__, user_id,
                type(initial_device_display_name).__name__,
                initial_device_display_name, e)
            raise StoreError(500, "Problem storing device.")