Skip to content

Commit

Permalink
More robust handling of broken room permissions: skipping and logging
Browse files Browse the repository at this point in the history
  • Loading branch information
gnunicorn committed May 28, 2024
1 parent 08fbe2e commit 97f545a
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 20 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ To run the unit tests, you can either use:
tox -e py
```

or
To run the linters and `mypy` type checker, use `./scripts-dev/lint.sh`.

```shell
trial tests
```
### Generating new db version

To run the linters and `mypy` type checker, use `./scripts-dev/lint.sh`.
Make your changes in `model/`, then run:

```
alembic revision --autogenerate -m "Description message"
```
2 changes: 1 addition & 1 deletion alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = sqlite:///
sqlalchemy.url = sqlite:///build/local.db


[post_write_hooks]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Add error logs to acceptance
Revision ID: 8c8c90d89eac
Revises: 25e4ec3cea32
Create Date: 2024-05-28 17:39:39.516462
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '8c8c90d89eac'
down_revision: Union[str, None] = '25e4ec3cea32'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('accepted', sa.Column('errors', sa.String(length=1024), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('accepted', 'errors')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions synapse_super_invites/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Accepted(Base):
__tablename__ = "accepted"
id: Mapped[int] = mapped_column(primary_key=True)
user: Mapped[str] = mapped_column(String(255))
errors: Mapped[str] = mapped_column(String(1024), nullable=True)

token_id = mapped_column(ForeignKey("tokens.token"))
token = relationship("Token", back_populates="accepted")
Expand Down
49 changes: 35 additions & 14 deletions synapse_super_invites/resource/redeem.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

import logging

from sqlalchemy import select
from synapse.http.servlet import parse_string
from synapse.http.site import SynapseRequest
Expand All @@ -7,13 +10,15 @@

from .base import SuperInviteResourceBase, token_query

logger = logging.getLogger(__name__)

class RedeemResource(SuperInviteResourceBase):
async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
requester = await self.api.get_user_by_req(request, allow_guest=False)
my_id = str(requester.user)
token_id = parse_string(request, "token", required=True)
invited_rooms = []
errors = []
with self.db.begin() as session:
token = session.scalar(token_query(token_id))
if not token:
Expand All @@ -35,20 +40,32 @@ async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, JsonDi

owner = token.owner
for room in token.rooms:
await self.api.update_room_membership(
sender=owner,
target=my_id,
room_id=room.nameOrAlias,
new_membership="invite",
)
try:
await self.api.update_room_membership(
sender=owner,
target=my_id,
room_id=room.nameOrAlias,
new_membership="invite",
)

await self.api.update_room_membership(
sender=my_id,
target=my_id,
room_id=room.nameOrAlias,
new_membership="join",
)
invited_rooms.append(room.nameOrAlias)
await self.api.update_room_membership(
sender=my_id,
target=my_id,
room_id=room.nameOrAlias,
new_membership="join",
)
invited_rooms.append(room.nameOrAlias)
except Exception as e:
errors.append("{room_id} skipped: '{error}'".format(
room_id=room.nameOrAlias,
error=e))
logger.warning(
"Skipping super invite{token}: Failed to add {user_id} to {room_id}: {error}".format(
token=token_id,
user_id=my_id,
room_id=room.nameOrAlias,
error=e)
)

if token.create_dm:
dm_data = await self.api.create_room(
Expand All @@ -61,8 +78,12 @@ async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, JsonDi
)
invited_rooms.append(dm_data[0])

error_msg = None
if len(errors) > 0:
error_msg = '\n'.join(errors)[:1024]

# keep the accepted record
session.add(Accepted(token=token, user=my_id))
session.add(Accepted(token=token, user=my_id, errors=error_msg))
session.flush()

return 200, {"rooms": invited_rooms}
104 changes: 104 additions & 0 deletions tests/test_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ def create_room(self, user_id: str) -> str:
)[0]
return room_id


# leave a room with the given access_token
def leave_room(self, user_id: str, room_id: str):
self.get_success(
self.module_api.update_room_membership(sender=user_id, target=user_id, room_id=room_id, new_membership='leave')
)

# create a room with the given access_token, return the roomId
def create_public_room(self, user_id: str) -> str:
room_id: str = self.get_success(
Expand Down Expand Up @@ -231,6 +238,103 @@ def test_simple_invite_token_test(self) -> None:
channel.json_body["rooms"]["join"].keys(), rooms_to_invite
)


@override_config(DEFAULT_CONFIG) # type: ignore[misc]
def test_skip_if_broken_now(self) -> None:
m_id = self.register_user("meeko", "password")
m_access_token = self.login("meeko", "password")

# this is our new backend.
channel = self.make_request(
"GET", "/_synapse/client/super_invites/tokens", access_token=m_access_token
)
self.assertEqual(channel.code, 200, msg=channel.result)
self.assertEqual(channel.json_body["tokens"], [])

# creating five channel
roomB = self.create_room(m_id)
roomC = self.create_room(m_id)
roomD = self.create_room(m_id)

rooms_to_invite = [
roomB,
roomC,
roomD,
]
# create a new one for testing.
channel = self.make_request(
"POST",
"/_synapse/client/super_invites/tokens",
access_token=m_access_token,
content={"rooms": rooms_to_invite},
)
self.assertEqual(channel.code, 200, msg=channel.result)
token_data = channel.json_body["token"]
self.assertCountEqual(token_data["rooms"], rooms_to_invite)
self.assertEquals(token_data["accepted_count"], 0)
self.assertFalse(token_data["create_dm"])
token = token_data["token"]

room_left_after = rooms_to_invite.pop(0)
self.leave_room(m_id, room_left_after)

# redeem the new token

_f_id = self.register_user("flit", "flit")
f_access_token = self.login("flit", "flit")

channel = self.make_request(
"GET",
"/_synapse/client/super_invites/info?token={token}".format(token=token),
access_token=f_access_token,
)
self.assertEqual(channel.code, 200, msg=channel.result)
# list the rooms we were invited to

self.assertEqual(channel.json_body["rooms_count"], 3) # info shows 3
self.assertEqual(channel.json_body["create_dm"], False)
self.assertEqual(channel.json_body["has_redeemed"], False)
self.assertEqual(channel.json_body["inviter"]["user_id"], "@meeko:test")
self.assertEqual(channel.json_body["inviter"]["display_name"], "meeko")

channel = self.make_request(
"POST",
"/_synapse/client/super_invites/redeem?token={token}".format(token=token),
access_token=f_access_token,
)
self.assertEqual(channel.code, 200, msg=channel.result)
# list the rooms we were invited to
self.assertCountEqual(channel.json_body["rooms"], rooms_to_invite) # but working are only two

# we see it has been redeemed
channel = self.make_request(
"GET",
"/_synapse/client/super_invites/tokens?token={token}".format(token=token),
access_token=m_access_token,
)
self.assertEqual(channel.code, 200, msg=channel.result)
token_data = channel.json_body["token"]
self.assertEquals(token_data["accepted_count"], 1)

channel = self.make_request(
"GET",
"/_synapse/client/super_invites/info?token={token}".format(token=token),
access_token=f_access_token,
)
self.assertEqual(channel.code, 200, msg=channel.result)
self.assertEqual(channel.json_body["has_redeemed"], True)

# and flit was invited to these, too:
channel = self.make_request(
"GET", "/_matrix/client/v3/sync", access_token=f_access_token
)
self.assertEqual(channel.code, 200, msg=channel.result)
self.assertEqual(channel.json_body["rooms"].get("invite"), None)
self.assertCountEqual(
channel.json_body["rooms"]["join"].keys(), rooms_to_invite
)


@override_config(DEFAULT_CONFIG) # type: ignore[misc]
def test_simple_can_join_public_room_test(self) -> None:
m_id = self.register_user("meeko", "password")
Expand Down

0 comments on commit 97f545a

Please sign in to comment.