Esempio n. 1
0
    def verify_ts3_accounts(self) -> None:
        if not self.bot.ts3c:
            raise ConnectionError("Not connected yet.")

        # Retrieve users
        users = self.bot.exec_("clientdblist", duration=200)
        start = 0
        while len(users) > 0:
            for counter, user in enumerate(users):
                uid = user["client_unique_identifier"]
                cldbid = user["cldbid"]

                # Skip SQ account
                if "ServerQuery" in uid:
                    continue

                # Send keepalive
                if counter % 100 == 0:
                    self.bot.ts3c.send_keepalive()

                # Get user's account
                account = models.Account.get_by_identity(self.session, uid)

                if not account:
                    self.revoke(None, cldbid)
                else:
                    # User was checked, don't check again
                    if ts3bot.timedelta_hours(datetime.datetime.today(
                    ) - account.last_check) < Config.getfloat(
                            "verify", "cycle_hours") and not (self.verify_all):
                        continue

                    LOG.info("Checking %s/%s", account, uid)

                    try:
                        account.update(self.session)
                        # Sync groups
                        ts3bot.sync_groups(self.bot, cldbid, account)
                    except ts3bot.InvalidKeyException:
                        self.revoke(account, cldbid)
                    except ts3bot.ApiErrBadData:
                        LOG.warning(
                            "Got ErrBadData for this account after multiple attempts."
                        )
                    except requests.RequestException:
                        LOG.exception("Error during API call")
                        raise

            # Skip to next user block
            start += len(users)
            try:
                users = self.bot.exec_("clientdblist",
                                       start=start,
                                       duration=200)
            except ts3.query.TS3QueryError as e:
                # Fetching users failed, most likely at end
                if e.args[0].error["id"] != "1281":
                    LOG.exception("Error retrieving user list")
                users = []
Esempio n. 2
0
def handle(bot: Bot, event: events.TextMessage, match: typing.Match):
    cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"]

    # Grab user's account
    account = models.Account.get_by_identity(bot.session, event.uid)

    if not account or not account.is_valid:
        bot.send_message(event.id, "missing_token")
        return

    on_join_hours_timeout = Config.getfloat("verify", "on_join_hours")

    # Saved account is older than x hours or has no guilds
    if (
        timedelta_hours(datetime.datetime.today() - account.last_check)
        >= on_join_hours_timeout
        or account.guilds.count() == 0
    ):
        bot.send_message(event.id, "account_updating")

        try:
            account.update(bot.session)

            # Sync groups in case the user has left a guild or similar changes
            sync_groups(bot, cldbid, account)
        except InvalidKeyException:
            # Invalidate link
            account.invalidate(bot.session)
            sync_groups(bot, cldbid, account, remove_all=True)

            logging.info("Revoked user's permissions.")
            bot.send_message(event.id, "invalid_token_admin")
            return
        except (requests.RequestException, RateLimitException, ApiErrBadData):
            logging.exception("Error during API call")
            bot.send_message(event.id, "error_api")

    # User requested guild removal
    if match.group(1) and match.group(1).lower() == "remove":
        # Get active guilds
        has_active_guilds: int = (
            account.guilds.join(models.Guild)
            .filter(models.Guild.group_id.isnot(None))
            .filter(models.LinkAccountGuild.is_active.is_(True))
            .count()
        )

        # There are no active guilds, no need to remove anything
        if not has_active_guilds:
            bot.send_message(event.id, "guild_already_removed")
            return

        # Remove guilds
        account.guilds.filter(models.LinkAccountGuild.is_active.is_(True)).update(
            {"is_active": False}
        )
        bot.session.commit()

        # Sync groups
        changes = sync_groups(bot, cldbid, account)
        if len(changes["removed"]) > 0:
            bot.send_message(event.id, "guild_removed")
        else:
            bot.send_message(event.id, "guild_error")

        return

    available_guilds = account.guilds.join(models.Guild).filter(
        models.Guild.group_id.isnot(None)
    )

    # No guild specified
    if not match.group(1):
        available_guilds = available_guilds.all()
        if len(available_guilds) > 0:
            bot.send_message(
                event.id,
                "guild_selection",
                guilds="\n- ".join([_.guild.tag for _ in available_guilds]),
            )
        else:
            bot.send_message(event.id, "guild_unknown")
    else:
        guild = match.group(1).lower()

        selected_guild: typing.Optional[
            models.LinkAccountGuild
        ] = available_guilds.filter(models.Guild.tag.ilike(guild)).one_or_none()

        # Guild not found or user not in guild
        if not selected_guild:
            bot.send_message(
                event.id, "guild_invalid_selection", timeout=on_join_hours_timeout
            )
            return

        # Toggle guild
        if selected_guild.is_active:
            selected_guild.is_active = False
        else:
            selected_guild.is_active = True

            # Remove other guilds if only one is allowed
            if not Config.getboolean("guild", "allow_multiple_guilds"):
                account.guilds.filter(
                    models.LinkAccountGuild.id != selected_guild.id
                ).update({"is_active": False})

        bot.session.commit()

        # Sync groups
        changes = sync_groups(bot, cldbid, account)
        if selected_guild.is_active and len(changes["added"]):
            bot.send_message(event.id, "guild_set", guild=selected_guild.guild.name)
        elif not selected_guild.is_active and len(changes["removed"]):
            bot.send_message(
                event.id, "guild_removed_one", guild=selected_guild.guild.name
            )
        else:
            bot.send_message(event.id, "guild_error")
Esempio n. 3
0
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None:
    key = match.group(1)

    # Check with ArenaNet's API
    try:
        account_info = fetch_api("account", api_key=key)

        # Check if one of the guilds is in the alliance
        is_part_of_alliance = (bot.session.query(models.Guild).filter(
            models.Guild.guid.in_(account_info.get("guilds", []))).filter(
                models.Guild.is_part_of_alliance.is_(True)).count() > 0)

        # One of the guilds is in the alliance
        if is_part_of_alliance:
            account: models.Account = models.Account.get_or_create(
                bot.session, account_info, key)
            identity: models.Identity = models.Identity.get_or_create(
                bot.session, event.uid)

            # Check if account is registered to anyone
            linked_identity: Optional[
                models.
                LinkAccountIdentity] = account.valid_identities.one_or_none()

            # Account is already linked
            if linked_identity:
                # Account is linked to another guid
                if linked_identity.identity.guid != event.uid:
                    try:
                        # Get user's DB id
                        cldbid: str = bot.exec_("clientgetdbidfromuid",
                                                cluid=event.uid)[0]["cldbid"]
                    except ts3.TS3Error:
                        LOG.error("Failed to get user's dbid", exc_info=True)
                        bot.send_message(event.id, "error_critical")
                        return

                    force_key_name = f"ts3bot-{cldbid}"

                    # Fetch token info
                    token_info = fetch_api("tokeninfo", api_key=key)

                    # Override registration, same as !register
                    if token_info.get("name", "") == force_key_name:
                        ts3bot.transfer_registration(bot, account, event)

                        LOG.info(
                            "%s (%s) transferred permissions of %s onto themselves.",
                            event.name,
                            event.uid,
                            account_info.get("name"),
                        )
                        return

                    LOG.warning(
                        "%s (%s) tried to use an already registered API key/account. (%s)",
                        event.name,
                        event.uid,
                        account_info.get("name"),
                    )
                    bot.send_message(event.id,
                                     "token_in_use",
                                     api_name=force_key_name)
                else:  # Account is linked to current guid
                    LOG.info(
                        "User %s (%s) tried to register a second time for whatever reason using %s",
                        event.name,
                        event.uid,
                        account_info.get("name", "Unknown account"),
                    )

                    # Save new API key
                    if account.api_key != key:
                        account.api_key = key
                        account.is_valid = True
                        bot.session.commit()
                        bot.send_message(event.id, "registration_exists")
                        return

                    # Same API key supplied, last check was over 12 minutes ago
                    if (ts3bot.timedelta_hours(datetime.datetime.today() -
                                               account.last_check) >= 0.2):
                        # Update saved account info if same API key was posted again with a reasonable time frame
                        account.update(bot.session)
                        try:
                            # Get user's DB id
                            cldbid = bot.exec_("clientgetdbidfromuid",
                                               cluid=event.uid)[0]["cldbid"]

                            # Sync groups
                            ts3bot.sync_groups(bot, cldbid, account)
                            bot.send_message(event.id,
                                             "registration_details_updated")
                        except ts3.TS3Error:
                            # User might not exist in the db
                            LOG.error("Failed to sync user", exc_info=True)
                    else:
                        # Too early
                        bot.send_message(event.id, "registration_too_early")

            else:
                # Otherwise account is not yet linked and can be used

                # Save API key
                account.api_key = key
                account.is_valid = True
                bot.session.commit()

                # Get user's DB id
                cldbid = bot.exec_("clientgetdbidfromuid",
                                   cluid=event.uid)[0]["cldbid"]

                # Unlink previous account from identity
                current_account = models.Account.get_by_identity(
                    bot.session, event.uid)
                if current_account:
                    LOG.info("Delinking %s from cldbid:%s", current_account,
                             cldbid)
                    current_account.invalidate(bot.session)

                # Register link between models
                bot.session.add(
                    models.LinkAccountIdentity(account=account,
                                               identity=identity))
                bot.session.commit()

                # Add all known guilds to user if enabled
                if Config.getboolean(
                        "guild", "assign_on_register") and Config.getboolean(
                            "guild", "allow_multiple_guilds"):
                    cast(AppenderQuery, account.guilds).filter(
                        models.LinkAccountGuild.id.in_(
                            bot.session.query(models.LinkAccountGuild.id).join(
                                models.Guild).filter(
                                    models.Guild.group_id.isnot(
                                        None)).subquery())).update(
                                            {"is_active": True},
                                            synchronize_session="fetch")
                    bot.session.commit()

                # Sync groups
                sync_groups(bot, cldbid, account)

                LOG.info(
                    "Assigned alliance permissions to %s (%s) using %s",
                    event.name,
                    event.uid,
                    account_info.get("name", "Unknown account"),
                )

                # Was registered with other account previously
                if current_account:
                    bot.send_message(event.id,
                                     "registration_update",
                                     account=account.name)
                else:
                    bot.send_message(event.id, "welcome_registered")

                    # Tell user about !guild if it's enabled
                    if Config.getboolean("commands", "guild"):
                        if Config.getboolean(
                                "guild",
                                "assign_on_register") and Config.getboolean(
                                    "guild", "allow_multiple_guilds"):
                            bot.send_message(event.id, "welcome_registered_3")
                        else:
                            bot.send_message(event.id, "welcome_registered_2")

        else:
            bot.send_message(
                event.id,
                "invalid_world",
                world=enums.World(account_info.get("world")).proper_name,
            )

    except InvalidKeyException:
        LOG.info("This seems to be an invalid API key.")
        bot.send_message(event.id, "invalid_token_retry")
    except (RateLimitException, RequestException, ApiErrBadData):
        bot.send_message(event.id, "error_api")
Esempio n. 4
0
    def verify_user(
        self, client_unique_id: str, client_database_id: str, client_id: str
    ) -> bool:
        """
        Verify a user if they are in a known group, otherwise nothing is done.
        Groups are revoked/updated if necessary

        :param client_unique_id: The client's UUID
        :param client_database_id: The database ID
        :param client_id: The client's temporary ID during the session
        :return: True if the user has/had a known group and False if the user is new
        """

        def revoked(response: str):
            if account:
                account.invalidate(self.session)

            changes = ts3bot.sync_groups(
                self, client_database_id, account, remove_all=True
            )

            reason = "unknown reason"
            if response == "groups_revoked_missing_key":
                reason = "missing API key"
            elif response == "groups_revoked_invalid_key":
                reason = "invalid API key"

            logging.info(
                "Revoked user's (cldbid:%s) groups (%s) due to %s.",
                client_database_id,
                changes["removed"],
                reason,
            )
            self.send_message(client_id, response)

        # Get all current groups
        server_groups = self.exec_("servergroupsbyclientid", cldbid=client_database_id)

        known_groups: typing.List[int] = (
            [
                _.group_id
                for _ in self.session.query(ts3bot.database.models.WorldGroup).options(
                    load_only(ts3bot.database.models.WorldGroup.group_id)
                )
            ]
            + [
                _.group_id
                for _ in self.session.query(ts3bot.database.models.Guild)
                .filter(ts3bot.database.models.Guild.group_id.isnot(None))
                .options(load_only(ts3bot.database.models.Guild.group_id))
            ]
            + [
                int(Config.get("teamspeak", "generic_world_id")),
                int(Config.get("teamspeak", "generic_guild_id")),
            ]
        )

        # Check if user has any known groups
        has_group = False
        has_skip_group = False
        for server_group in server_groups:
            if int(server_group.get("sgid", -1)) in known_groups:
                has_group = True
            if server_group.get("name") in Config.whitelist_groups:
                has_skip_group = True

        # Skip users without any known groups
        if not has_group:
            return False

        # Skip users in whitelisted groups that should be ignored like
        # guests, music bots, etc
        if has_skip_group:
            return True

        # Grab user's account info
        account = models.Account.get_by_identity(self.session, client_unique_id)

        # User does not exist in DB
        if not account:
            revoked("groups_revoked_missing_key")
            return True

        # User was checked, don't check again
        if ts3bot.timedelta_hours(
            datetime.datetime.today() - account.last_check
        ) < Config.getfloat("verify", "on_join_hours"):
            return True

        logging.debug("Checking %s/%s", account, client_unique_id)

        try:
            account.update(self.session)
            # Sync groups
            ts3bot.sync_groups(self, client_database_id, account)
        except ts3bot.InvalidKeyException:
            revoked("groups_revoked_invalid_key")
        except (
            requests.RequestException,
            ts3bot.RateLimitException,
            ts3bot.ApiErrBadData,
        ):
            logging.exception("Error during API call")

        return True