Esempio n. 1
0
async def _do_delete_invitation(
    conn,
    organization_id: OrganizationID,
    greeter: UserID,
    token: InvitationToken,
    on: DateTime,
    reason: InvitationDeletedReason,
):
    row = await conn.fetchrow(
        *_q_delete_invitation_info(
            organization_id=organization_id.str, greeter=greeter.str, token=token.uuid
        )
    )
    if not row:
        raise InvitationNotFoundError(token)
    row_id, deleted_on = row
    if deleted_on:
        raise InvitationAlreadyDeletedError(token)

    await conn.execute(*_q_delete_invitation(row_id=row_id, on=on, reason=reason.value))
    await send_signal(
        conn,
        BackendEvent.INVITE_STATUS_CHANGED,
        organization_id=organization_id,
        greeter=greeter,
        token=token,
        status=InvitationStatus.DELETED,
    )
Esempio n. 2
0
async def _do_delete_invitation(
    conn,
    organization_id: OrganizationID,
    greeter: UserID,
    token: UUID,
    on: Pendulum,
    reason: InvitationDeletedReason,
):
    row = await conn.fetchrow(
        *_q_delete_invitation_info(organization_id=organization_id, greeter=greeter, token=token)
    )
    if not row:
        raise InvitationNotFoundError(token)
    row_id, deleted_on = row
    if deleted_on:
        raise InvitationAlreadyDeletedError(token)

    await conn.execute(*_q_delete_invitation(row_id=row_id, on=on, reason=reason.value))
    await send_signal(
        conn,
        "invite.status_changed",
        organization_id=organization_id,
        greeter=greeter,
        token=token,
        status_str=InvitationStatus.DELETED.value,
    )
Esempio n. 3
0
 def _get_invitation_and_conduit(
     self,
     organization_id: OrganizationID,
     token: UUID,
     expected_greeter: Optional[UserID] = None,
 ) -> Tuple[Invitation, Conduit]:
     org = self._organizations[organization_id]
     invitation = org.invitations.get(token)
     if not invitation or (expected_greeter and invitation.greeter_user_id != expected_greeter):
         raise InvitationNotFoundError(token)
     if token in org.deleted_invitations:
         raise InvitationAlreadyDeletedError(token)
     return invitation, org.conduits[token]
Esempio n. 4
0
    async def info(self, organization_id: OrganizationID, token: InvitationToken) -> Invitation:
        async with self.dbh.pool.acquire() as conn:
            row = await conn.fetchrow(
                *_q_info_invitation(organization_id=organization_id.str, token=token.uuid)
            )
        if not row:
            raise InvitationNotFoundError(token)

        (
            type,
            greeter,
            greeter_human_handle_email,
            greeter_human_handle_label,
            claimer_email,
            created_on,
            deleted_on,
            deleted_reason,
        ) = row

        if deleted_on:
            raise InvitationAlreadyDeletedError(token)

        greeter_human_handle = None
        if greeter_human_handle_email:
            greeter_human_handle = HumanHandle(
                email=greeter_human_handle_email, label=greeter_human_handle_label
            )

        if type == InvitationType.USER.value:
            return UserInvitation(
                greeter_user_id=UserID(greeter),
                greeter_human_handle=greeter_human_handle,
                claimer_email=claimer_email,
                token=token,
                created_on=created_on,
                status=InvitationStatus.READY,
            )
        else:  # Device
            return DeviceInvitation(
                greeter_user_id=UserID(greeter),
                greeter_human_handle=greeter_human_handle,
                token=token,
                created_on=created_on,
                status=InvitationStatus.READY,
            )
Esempio n. 5
0
async def _conduit_listen(conn, ctx: ConduitListenCtx) -> Optional[bytes]:
    async with conn.transaction():
        if ctx.is_greeter:
            row = await conn.fetchrow(
                *_q_conduit_greeter_info(
                    organization_id=ctx.organization_id.str,
                    greeter_user_id=ctx.greeter.str,  # type: ignore[union-attr]
                    token=ctx.token.uuid,
                )
            )
        else:
            row = await conn.fetchrow(
                *_q_conduit_claimer_info(
                    organization_id=ctx.organization_id.str, token=ctx.token.uuid
                )
            )

        if not row:
            raise InvitationNotFoundError()

        row_id = row["_id"]
        curr_conduit_state = ConduitState(row["conduit_state"])

        if row["deleted_on"]:
            raise InvitationAlreadyDeletedError()

        if ctx.is_greeter:
            curr_our_payload = row["conduit_greeter_payload"]
            curr_peer_payload = row["conduit_claimer_payload"]
        else:
            curr_our_payload = row["conduit_claimer_payload"]
            curr_peer_payload = row["conduit_greeter_payload"]

        if ctx.peer_payload is None:
            # We are waiting for the peer to provite it payload

            # Only peer payload should be allowed to change
            if curr_conduit_state != ctx.state or curr_our_payload != ctx.payload:
                raise InvitationInvalidStateError()

            if curr_peer_payload is not None:
                # Our peer has provided it payload (hence it knows
                # about our payload too), we can update the conduit
                # to the next state
                await conn.execute(
                    *_q_conduit_update(
                        row_id=row_id,
                        conduit_state=NEXT_CONDUIT_STATE[ctx.state].value,
                        conduit_greeter_payload=None,
                        conduit_claimer_payload=None,
                    )
                )
                await send_signal(
                    conn,
                    BackendEvent.INVITE_CONDUIT_UPDATED,
                    organization_id=ctx.organization_id,
                    token=ctx.token,
                )
                return curr_peer_payload

        else:
            # We were waiting for the peer to take into account the
            # payload we provided. This would be done once the conduit
            # has switched to it next state.

            if curr_conduit_state == NEXT_CONDUIT_STATE[ctx.state] and curr_our_payload is None:
                return ctx.peer_payload
            elif (
                curr_conduit_state != ctx.state
                or curr_our_payload != ctx.payload
                or curr_peer_payload != ctx.peer_payload
            ):
                # Something unexpected has changed in our back...
                raise InvitationInvalidStateError()

    # Peer hasn't answered yet, we should wait and retry later...
    return None
Esempio n. 6
0
async def _conduit_talk(
    conn,
    organization_id: OrganizationID,
    greeter: Optional[UserID],
    token: InvitationToken,
    state: ConduitState,
    payload: bytes,
) -> ConduitListenCtx:
    is_greeter = greeter is not None
    async with conn.transaction():
        # On top of retrieving the invitation row, this query lock the row
        # in the database for the duration of the transaction.
        # Hence concurrent request will be on hold until the end of the transaction.

        if is_greeter:
            row = await conn.fetchrow(
                *_q_conduit_greeter_info(
                    organization_id=organization_id.str,
                    greeter_user_id=greeter.str,  # type: ignore[union-attr]
                    token=token.uuid,
                )
            )
        else:
            row = await conn.fetchrow(
                *_q_conduit_claimer_info(organization_id=organization_id.str, token=token.uuid)
            )

        if not row:
            raise InvitationNotFoundError(token)

        row_id = row["_id"]
        curr_conduit_state = ConduitState(row["conduit_state"])
        curr_greeter_payload = row["conduit_greeter_payload"]
        curr_claimer_payload = row["conduit_claimer_payload"]

        if is_greeter:
            curr_our_payload = curr_greeter_payload
            curr_peer_payload = curr_claimer_payload
        else:
            curr_our_payload = curr_claimer_payload
            curr_peer_payload = curr_greeter_payload

        if row["deleted_on"]:
            raise InvitationAlreadyDeletedError(token)

        if curr_conduit_state != state or curr_our_payload is not None:
            # We are out of sync with the conduit:
            # - the conduit state has changed in our back (maybe reseted by the peer)
            # - we want to reset the conduit
            # - we have already provided a payload for the current conduit state (most
            #   likely because a retry of a command that failed due to connection outage)
            if state == ConduitState.STATE_1_WAIT_PEERS:
                # We wait to reset the conduit
                curr_conduit_state = state
                curr_claimer_payload = None
                curr_greeter_payload = None
                curr_our_payload = None
                curr_peer_payload = None
            else:
                raise InvitationInvalidStateError()

        # Now update the conduit with our payload and send a signal if
        # the peer is already waiting for us.
        if is_greeter:
            curr_greeter_payload = payload
        else:
            curr_claimer_payload = payload
        await conn.execute(
            *_q_conduit_update(
                row_id=row_id,
                conduit_state=curr_conduit_state.value,
                conduit_greeter_payload=curr_greeter_payload,
                conduit_claimer_payload=curr_claimer_payload,
            )
        )
        # Note that in case of conduit reset, this signal will lure the peer into
        # thinking we have answered so he will wakeup and take into account the reset
        await send_signal(
            conn, BackendEvent.INVITE_CONDUIT_UPDATED, organization_id=organization_id, token=token
        )

    return ConduitListenCtx(
        organization_id=organization_id,
        greeter=greeter,
        token=token,
        state=state,
        payload=payload,
        peer_payload=curr_peer_payload,
    )