Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
000ea0d
Add auto auth session setting setup
MattyTheHacker May 3, 2025
734a4ef
refactor and implement task
MattyTheHacker May 3, 2025
2a70103
fix quotes
MattyTheHacker May 3, 2025
79ed652
fix ruff
MattyTheHacker May 3, 2025
e6b5762
fix settings names
MattyTheHacker May 3, 2025
6230e26
fix
MattyTheHacker May 3, 2025
8d76c8b
ruff
MattyTheHacker May 3, 2025
42e0f44
actually call the method
MattyTheHacker May 3, 2025
c0debd4
fix again
MattyTheHacker May 3, 2025
be9036b
change
MattyTheHacker May 3, 2025
9010469
add logging
MattyTheHacker May 3, 2025
35eb61c
suck my ass
MattyTheHacker May 3, 2025
3f01780
Implement fix
MattyTheHacker May 4, 2025
507d42e
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 4, 2025
e6cff0a
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 4, 2025
b9c9964
Improve debug messages
MattyTheHacker May 4, 2025
b8bfc9e
refactor to make it nicer
MattyTheHacker May 4, 2025
6f29bef
minor refactor
MattyTheHacker May 4, 2025
5751f18
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 5, 2025
8c6273f
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 6, 2025
af878ef
Fix some stuff
MattyTheHacker May 9, 2025
f36602d
Refactor the method to only return the list
MattyTheHacker May 9, 2025
9d41e52
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 11, 2025
564b6e6
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 12, 2025
6bb88f1
Improve token status check
MattyTheHacker May 12, 2025
67bceb1
fix debug messages
MattyTheHacker May 12, 2025
5d7d952
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 13, 2025
b117a20
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 18, 2025
52cb2a4
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 20, 2025
3b93dc3
add missing check
MattyTheHacker May 21, 2025
61d64a4
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 21, 2025
8ef7e05
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 21, 2025
a9244dc
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 25, 2025
6288b28
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 27, 2025
88f57b0
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker May 27, 2025
a7acdb5
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker Jun 3, 2025
a5ce2d8
Merge main into 481-auto-auth-check
cssbhamdev Jun 12, 2025
e28b8f7
Merge main into 481-auto-auth-check
cssbhamdev Jun 13, 2025
34d10af
Merge main into 481-auto-auth-check
cssbhamdev Jun 13, 2025
e0eb7b5
Merge main into 481-auto-auth-check
cssbhamdev Jun 14, 2025
1a6ff75
Allow committee-elect to update actions (and appear in auto-complete)…
Thatsmusic99 Jun 15, 2025
2551458
Merge main into 481-auto-auth-check
cssbhamdev Jun 15, 2025
0d24293
Merge main into 481-auto-auth-check
cssbhamdev Jun 15, 2025
c290039
Merge main into 481-auto-auth-check
cssbhamdev Jun 15, 2025
f29438f
Merge main into 481-auto-auth-check
cssbhamdev Jun 15, 2025
3f9c902
Merge main into 481-auto-auth-check
cssbhamdev Jun 16, 2025
ded1461
Merge main into 481-auto-auth-check
cssbhamdev Jun 16, 2025
13c9194
Merge main into 481-auto-auth-check
cssbhamdev Jun 17, 2025
d676377
Merge main into 481-auto-auth-check
cssbhamdev Jun 19, 2025
aabce00
Merge main into 481-auto-auth-check
cssbhamdev Jun 19, 2025
7066be0
Merge main into 481-auto-auth-check
cssbhamdev Jun 22, 2025
f38cfe3
Merge main into 481-auto-auth-check
cssbhamdev Jun 24, 2025
b2fed62
Merge main into 481-auto-auth-check
cssbhamdev Jun 24, 2025
6ceb054
Merge main into 481-auto-auth-check
cssbhamdev Jun 24, 2025
49888a3
Merge main into 481-auto-auth-check
cssbhamdev Jun 25, 2025
d6756f9
Merge main into 481-auto-auth-check
cssbhamdev Jun 30, 2025
01d20dc
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jun 30, 2025
f505586
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 2, 2025
4b90b2c
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 2, 2025
310f988
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker Jul 3, 2025
fe57278
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker Jul 4, 2025
383fe39
Merge main
MattyTheHacker Jul 4, 2025
cc54a4b
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker Jul 4, 2025
ef045f2
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 4, 2025
837d69b
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 4, 2025
a2473f0
Change names
MattyTheHacker Jul 4, 2025
11d401a
add file
MattyTheHacker Jul 4, 2025
55a5e94
more changes
MattyTheHacker Jul 4, 2025
f3d62f0
more
MattyTheHacker Jul 4, 2025
56749c9
More changes
MattyTheHacker Jul 4, 2025
d945acc
Fix
MattyTheHacker Jul 4, 2025
7db7115
More renaming
MattyTheHacker Jul 4, 2025
ab35042
Fix method call
MattyTheHacker Jul 4, 2025
879a741
Merge branch 'main' into 481-auto-auth-check
MattyTheHacker Jul 4, 2025
bd93d40
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jul 4, 2025
948c35d
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 4, 2025
37917ae
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 5, 2025
e238701
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 5, 2025
eac5872
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 5, 2025
1e8ce4e
Merge main into 481-auto-auth-check
automatic-pr-updater[bot] Jul 5, 2025
7b56cf7
Fix some stuff
MattyTheHacker Jul 5, 2025
9a26cbf
minor fixes
MattyTheHacker Jul 5, 2025
a0ece95
Apply suggestions from code review
MattyTheHacker Jul 5, 2025
255eae7
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jul 5, 2025
699642c
Fix shit
MattyTheHacker Jul 5, 2025
cb0a68a
Fixes
MattyTheHacker Jul 5, 2025
95ae35f
Apply stuff
MattyTheHacker Jul 5, 2025
bc661f2
variable
MattyTheHacker Jul 5, 2025
742439a
Apply suggestions from code review
MattyTheHacker Jul 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cogs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
)
from .delete_all import DeleteAllCommandsCog
from .edit_message import EditMessageCommandCog
from .get_token_authorisation import GetTokenAuthorisationCommandCog
from .get_token_authorisation import (
GetTokenAuthorisationCommandCog,
TokenAuthorisationCheckTaskCog,
)
from .induct import (
EnsureMembersInductedCommandCog,
InductContextCommandsCog,
Expand Down Expand Up @@ -78,6 +81,7 @@
"StatsCommandsCog",
"StrikeCommandCog",
"StrikeContextCommandsCog",
"TokenAuthorisationCheckTaskCog",
"WriteRolesCommandCog",
"setup",
)
Expand Down Expand Up @@ -117,6 +121,7 @@ def setup(bot: "TeXBot") -> None:
StatsCommandsCog,
StrikeCommandCog,
StrikeContextCommandsCog,
TokenAuthorisationCheckTaskCog,
WriteRolesCommandCog,
)
Cog: type[TeXBotBaseCog]
Expand Down
160 changes: 119 additions & 41 deletions cogs/get_token_authorisation.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
"""Contains cog classes for token authorisation check interactions."""

import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override

import aiohttp
import bs4
import discord
from bs4 import BeautifulSoup
from discord.ext import tasks

from config import settings
from utils import CommandChecks, TeXBotBaseCog
from utils.error_capture_decorators import (
capture_guild_does_not_exist_error,
)

if TYPE_CHECKING:
from collections.abc import Iterable, Mapping, Sequence
from logging import Logger
from typing import Final

from utils import TeXBotApplicationContext
from utils import TeXBot, TeXBotApplicationContext

__all__: "Sequence[str]" = ("GetTokenAuthorisationCommandCog",)
__all__: "Sequence[str]" = (
"GetTokenAuthorisationCommandCog",
"TokenAuthorisationCheckTaskCog",
)

logger: "Final[Logger]" = logging.getLogger("TeX-Bot")

Expand All @@ -35,22 +42,39 @@
REQUEST_URL: "Final[str]" = "https://guildofstudents.com/profile"


class GetTokenAuthorisationCommandCog(TeXBotBaseCog):
"""Cog class that defines the "/get_token_authorisation" command."""
class TokenAuthorisationBaseCog(TeXBotBaseCog):
"""Cog class that defines the base for token authorisation functions."""

@discord.slash_command( # type: ignore[no-untyped-call, misc]
name="get-token-authorisation",
description="Checks the authorisations held by the token.",
)
@CommandChecks.check_interaction_user_has_committee_role
@CommandChecks.check_interaction_user_in_main_guild
async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None: # type: ignore[misc]
async def is_token_valid(self) -> bool:
"""
Definition of the "get_token_authorisation" command.
Definition of method to check if the authorisation token is valid.

The "get_token_authorisation" command will retrieve the profile for the token user.
The profile page will contain the user's name and a list of the MSL organisations
the user has administrative access to.
This is done by requesting the user profile page and
checking if the page title contains "Login".
"""
http_session: aiohttp.ClientSession = aiohttp.ClientSession(
headers=REQUEST_HEADERS,
cookies=REQUEST_COOKIES,
)

async with http_session, http_session.get(REQUEST_URL) as http_response:
response_html: str = await http_response.text()

response_object: bs4.BeautifulSoup = BeautifulSoup(
response_html,
"html.parser",
)

page_title: bs4.Tag | bs4.NavigableString | None = response_object.find("title")

return "Login" in str(page_title)

async def get_token_groups(self, iterable: bool) -> str | "Iterable"[str]: # noqa: FBT001
"""
Definition of method to get the groups the token has access to.

This is done by requesting the user profile page and
scraping the HTML for the list of groups.
"""
http_session: aiohttp.ClientSession = aiohttp.ClientSession(
headers=REQUEST_HEADERS,
Expand All @@ -68,19 +92,15 @@ async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None
page_title: bs4.Tag | bs4.NavigableString | None = response_object.find("title")

if not page_title:
await self.command_send_error(
ctx=ctx,
message="Profile page returned no content when checking token authorisation!",
PROFILE_PAGE_INVALID: Final[str] = (
"Profile page returned no content when checking token authorisation."
)
return
logger.warning(PROFILE_PAGE_INVALID)
return PROFILE_PAGE_INVALID

if "Login" in str(page_title):
BAD_TOKEN_MESSAGE: Final[str] = (
"Unable to fetch profile page because the token was not valid." # noqa: S105
)
logger.warning(BAD_TOKEN_MESSAGE)
await ctx.respond(content=BAD_TOKEN_MESSAGE)
return
logger.warning("Unable to fetch profile page because the token was not valid.")
return []

profile_section_html: bs4.Tag | bs4.NavigableString | None = response_object.find(
"div",
Expand All @@ -93,21 +113,17 @@ async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None
"when scraping the website's HTML!",
)
logger.debug("Retrieved HTML: %s", response_html)
await ctx.respond(
"Couldn't find the profile of the user! "
"This should never happen, please check the logs!",
)
return
return "Something went wrong when fetching the profile page!"

user_name: bs4.Tag | bs4.NavigableString | int | None = profile_section_html.find("h1")

if not isinstance(user_name, bs4.Tag):
NO_PROFILE_DEBUG_MESSAGE: Final[str] = (
"Found user profile but couldn't find their name!"
)
logger.debug(NO_PROFILE_DEBUG_MESSAGE)
await ctx.respond(NO_PROFILE_DEBUG_MESSAGE)
return
logger.warning(NO_PROFILE_DEBUG_MESSAGE)
logger.debug("Retrieved HTML: %s", response_html)
return "Something went wrong when fetching the profile page!"

parsed_html: bs4.Tag | bs4.NavigableString | None = response_object.find(
"ul",
Expand All @@ -120,8 +136,7 @@ async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None
"Please check you have used the correct token!"
)
logger.warning(NO_ADMIN_TABLE_MESSAGE)
await ctx.respond(content=NO_ADMIN_TABLE_MESSAGE)
return
return NO_ADMIN_TABLE_MESSAGE

organisations: Iterable[str] = [
list_item.get_text(strip=True) for list_item in parsed_html.find_all("li")
Expand All @@ -133,10 +148,73 @@ async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None
user_name.text,
)

await ctx.respond(
constructed_organisations: str = (
f"Admin token has access to the following MSL Organisations as "
f"{user_name.text}:\n{
', \n'.join(organisation for organisation in organisations)
}",
ephemeral=True,
f"{user_name.text}:\n{', \n'.join(organisation for organisation in organisations)}"
)

return organisations if iterable else constructed_organisations


class GetTokenAuthorisationCommandCog(TokenAuthorisationBaseCog):
"""Cog class that defines the "/get_token_authorisation" command."""

@discord.slash_command( # type: ignore[no-untyped-call, misc]
name="get-token-authorisation",
description="Checks the authorisations held by the token.",
)
@CommandChecks.check_interaction_user_has_committee_role
@CommandChecks.check_interaction_user_in_main_guild
async def get_token_authorisation(self, ctx: "TeXBotApplicationContext") -> None: # type: ignore[misc]
"""
Definition of the "get_token_authorisation" command.

The "get_token_authorisation" command will retrieve the profile for the token user.
The profile page will contain the user's name and a list of the MSL organisations
the user has administrative access to.
"""
await ctx.defer(ephemeral=True)
async with ctx.typing():
await ctx.followup.send(
content=str(await self.get_token_groups(iterable=False)),
ephemeral=True,
)


class TokenAuthorisationCheckTaskCog(TokenAuthorisationBaseCog):
"""Cog class that defines the background task for token authorisation checks."""

@override
def __init__(self, bot: "TeXBot") -> None:
"""Start all task managers when this cog is initialised."""
if settings["AUTO_AUTH_SESSION_COOKIE_CHECKING"]:
_ = self.token_authorisation_check_task.start()

super().__init__(bot)

@override
def cog_unload(self) -> None:
"""
Unload-hook that ends all running tasks whenever the tasks cog is unloaded.

This may be run dynamically or when the bot closes.
"""
self.token_authorisation_check_task.cancel()

@tasks.loop(**settings["AUTO_AUTH_SESSION_COOKIE_CHECKING"])
@capture_guild_does_not_exist_error
async def token_authorisation_check_task(self) -> None:
"""
Definition of the background task that checks the token authorisation.

The task will check if the token is valid and if it is, it will retrieve the
groups the token has access to.
"""
logger.debug("Running token authorisation check task...")

token_valid: bool = await self.is_token_valid()

if not token_valid:
logger.warning("Token is not valid!")

await self.bot.fetch_log_channel().send("Auth token has expired!")
47 changes: 47 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,53 @@ def _setup_members_list_auth_session_cookie(cls) -> None:
raw_members_list_auth_session_cookie
)

@classmethod
def _setup_auto_auth_session_cookie_checking(cls) -> None:
raw_auto_auth_session_cookie_checking: str | bool = str(
os.getenv("AUTO_AUTH_SESSION_COOKIE_CHECKING", "False"),
)

if raw_auto_auth_session_cookie_checking in FALSE_VALUES:
cls._settings["AUTO_AUTH_SESSION_COOKIE_CHECKING"] = False
return

if raw_auto_auth_session_cookie_checking in TRUE_VALUES:
raw_auto_auth_session_cookie_checking = "24h"

raw_auto_auth_session_cookie_checking_delay: re.Match[str] | None = re.fullmatch(
r"\A(?:(?P<seconds>(?:\d*\.)?\d+)s)?(?:(?P<minutes>(?:\d*\.)?\d+)m)?(?:(?P<hours>(?:\d*\.)?\d+)h)?(?:(?P<days>(?:\d*\.)?\d+)d)?(?:(?P<weeks>(?:\d*\.)?\d+)w)?\Z",
str(raw_auto_auth_session_cookie_checking),
)

if not raw_auto_auth_session_cookie_checking_delay:
INVALID_SEND_INTRODUCTION_REMINDERS_DELAY_MESSAGE: Final[str] = (
"SEND_INTRODUCTION_REMINDERS_DELAY must contain the delay "
"in any combination of seconds, minutes, hours, days or weeks."
)
raise ImproperlyConfiguredError(
INVALID_SEND_INTRODUCTION_REMINDERS_DELAY_MESSAGE,
)

raw_timedelta_auto_auth_session_cookie_checking_delay: timedelta = timedelta(
**{
key: float(value)
for key, value in (
raw_auto_auth_session_cookie_checking_delay.groupdict().items()
)
if value
},
)

if raw_timedelta_auto_auth_session_cookie_checking_delay < timedelta(days=1):
logger.warning(
"Automatic checking of the MSL session cookie is below the "
"recommended minimum (24h) which could cause performance issues."
)

cls._settings["AUTO_AUTH_SESSION_COOKIE_CHECKING"] = (
raw_timedelta_auto_auth_session_cookie_checking_delay
)

@classmethod
def _setup_send_introduction_reminders(cls) -> None:
raw_send_introduction_reminders: str | bool = str(
Expand Down