Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 65 additions & 5 deletions gitlab_matrix/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import List, Union, Dict, Optional, Type, NewType, ClassVar, Tuple, Iterable
from datetime import datetime
from typing import ClassVar, Dict, Iterable, List, NewType, Optional, Tuple, Type, Union

from jinja2 import TemplateNotFound
import attr
from attr import dataclass
from jinja2 import TemplateNotFound
from mautrix.types import (
JSON,
ExtensibleEnum,
SerializableAttrs,
deserializer,
serializer,
)
from yarl import URL
import attr

from mautrix.types import JSON, ExtensibleEnum, SerializableAttrs, serializer, deserializer

from .util import contrast, hex_to_rgb

Expand Down Expand Up @@ -929,3 +934,58 @@ def build_url(self) -> str:
"BuildStatus": BuildStatus,
"FailureReason": FailureReason,
}

@dataclass
class UserFilter(SerializableAttrs):
user_id: Optional[int]
user_name: Optional[str]


def is_userid_in_event(evt: GitlabEventType, userid:int|None):
if userid is None:
return False
if hasattr(evt, 'user') and isinstance(evt.user, GitlabUser):
if evt.user.id is not None and evt.user.id == userid:
return True
if isinstance(evt, (GitlabIssueEvent)) and evt.assignees is not None:
if any(user.id == userid for user in evt.assignees):
return True
if isinstance(evt, (GitlabPushEvent)) and evt.user_id is not None and evt.user_id == userid:
return True
if isinstance(evt, (GitlabCommentEvent)):
if evt.merge_request is not None:
if evt.merge_request.assignee is not None and evt.merge_request.assignee.id == userid:
return True
if evt.issue is not None:
if evt.issue.author_id == userid:
return True
if evt.issue.assignee_id is not None and evt.issue.assignee_id == userid:
return True
if evt.issue.assignee_ids is not None and userid in evt.issue.assignee_ids :
return True
if evt.snippet is not None and evt.snippet.author_id == userid:
return True

return False

def is_username_in_event(evt: GitlabEventType, username:str|None):
if username is None:
return False
if hasattr(evt, 'user') and isinstance(evt.user, GitlabUser):
if evt.user.username is not None and evt.user.username == username:
return True
if isinstance(evt, (GitlabIssueEvent)) and evt.assignees is not None:
if any(user.username == username for user in evt.assignees):
return True
if isinstance(evt, (GitlabPushEvent)) and evt.user_username is not None and evt.user_username == username:
return True
if isinstance(evt, (GitlabCommentEvent)):
if evt.merge_request is not None:
if evt.merge_request.assignee is not None and evt.merge_request.assignee.username == username:
return True
if evt.merge_request.last_commit.author is not None and evt.merge_request.last_commit.author.name == username:
return True
if evt.commit is not None:
if evt.commit.author is not None and evt.commit.author.name == username:
return True
return False
79 changes: 61 additions & 18 deletions gitlab_matrix/webhook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gitlab - A GitLab client and webhook receiver for maubot
# Copyright (C) 2019 Lorenz Steinert
# Copyright (C) 2021 Tulir Asokan
# opyright (C) 2021 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
Expand All @@ -14,22 +14,39 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import json
from typing import List, Set, TYPE_CHECKING
from asyncio import Task
import asyncio
import json
import re

from typing import List, Set
from typing import TYPE_CHECKING
from asyncio import Task
import attr
from maubot.handlers import web, event
from aiohttp.web import Request, Response
from jinja2 import TemplateNotFound
from aiohttp.web import Response, Request

from mautrix.types import (EventType, RoomID, StateEvent, Membership, MessageType, JSON,
TextMessageEventContent, Format, ReactionEventContent, RelationType)
from mautrix.types import (
JSON,
EventType,
Format,
Membership,
MessageType,
ReactionEventContent,
RelationType,
RoomID,
StateEvent,
TextMessageEventContent,
)
from mautrix.util.formatter import parse_html
from maubot.handlers import web, event

from .types import GitlabJobEvent, EventParse, Action, OTHER_ENUMS
from .types import (
OTHER_ENUMS,
Action,
EventParse,
GitlabJobEvent,
UserFilter,
is_userid_in_event,
is_username_in_event,
)
from .util import TemplateManager, TemplateUtil

if TYPE_CHECKING:
Expand Down Expand Up @@ -69,18 +86,35 @@ async def post_handler(self, request: Request) -> Response:
except KeyError:
return Response(text="401: Unauthorized\n"
"Missing auth token header\n", status=401)

user_filter : str | None = None
userid_filter: int | None = None
if token == self.bot.config["secret"]:
try:
room_id = RoomID(request.query["room"])
except KeyError:
return Response(text="400: Bad request\nNo room specified. "
"Did you forget the ?room query parameter?\n",
status=400)
try:
user_filter = request.query["username_filter"]
self.bot.log.trace(f"Query User name filter is {user_filter}")
except KeyError:
# user_filter is a optional query parameter, that will be ignored if it does not exist
pass
try:
userid_filter = int(request.query["userid_filter"])
self.bot.log.trace(f"Query User id filter is {userid_filter}")
except KeyError:
# userid_filter is a optional query parameter, that will be ignored if it does not exist
pass

else:
room_id = self.bot.db.get_webhook_room(token)
if not room_id:
return Response(text="401: Unauthorized\n", status=401)

filter = UserFilter(user_name= user_filter,user_id = userid_filter)
try:
evt_type = request.headers["X-Gitlab-Event"]
except KeyError:
Expand All @@ -107,14 +141,14 @@ async def post_handler(self, request: Request) -> Response:
headers={"Accept": "application/json"})

self.bot.log.trace("Accepted processing of %s", request.headers["X-Gitlab-Event"])
task = asyncio.create_task(self.try_process_hook(body, evt_type, room_id))
task = asyncio.create_task(self.try_process_hook(body, evt_type, room_id, filter))
self.task_list += [task]

return Response(status=202, text="202: Accepted\nWebhook processing started.\n")

async def try_process_hook(self, body: JSON, evt_type: str, room_id: RoomID) -> None:
async def try_process_hook(self, body: JSON, evt_type: str, room_id: RoomID, filter: UserFilter ) -> None:
try:
await self.process_hook(body, evt_type, room_id)
await self.process_hook(body, evt_type, room_id, filter)
except Exception:
self.bot.log.warning("Failed to process webhook", exc_info=True)
finally:
Expand All @@ -125,10 +159,18 @@ async def try_process_hook(self, body: JSON, evt_type: str, room_id: RoomID) ->
if task:
self.task_list.remove(task)

async def process_hook(self, body: JSON, evt_type: str, room_id: RoomID) -> None:
async def process_hook(self, body: JSON, evt_type: str, room_id: RoomID, filter: UserFilter) -> None:
msgtype = MessageType.NOTICE if self.bot.config["send_as_notice"] else MessageType.TEXT
evt = EventParse[evt_type].deserialize(body)

self.bot.log.trace(f" User id filter {filter.user_id} {filter.user_name}")
if filter.user_id is None and filter.user_name is None:
send_message = True
self.bot.log.trace(" User id filter is nothing")
else:
send_message = is_userid_in_event(evt, filter.user_id) | is_username_in_event(evt, filter.user_name)
self.bot.log.trace(f" User id filter has info. {send_message}")

was_manually_handled = True
if isinstance(evt, GitlabJobEvent):
await self.handle_job_event(evt, evt_type, room_id)
Expand Down Expand Up @@ -180,9 +222,10 @@ def abort() -> None:
edit_evt = self.bot.db.get_event(subevt.message_id, room_id)
if edit_evt:
content.set_edit(edit_evt)
event_id = await self.bot.client.send_message(room_id, content)
if not edit_evt and subevt.message_id:
self.bot.db.put_event(subevt.message_id, room_id, event_id)
if send_message:
event_id = await self.bot.client.send_message(room_id, content)
if not edit_evt and subevt.message_id:
self.bot.db.put_event(subevt.message_id, room_id, event_id)

async def handle_job_event(self, evt: GitlabJobEvent, evt_type: str, room_id: RoomID) -> None:
push_evt = self.bot.db.get_event(evt.push_id, room_id)
Expand Down