Skip to content

Commit 0717436

Browse files
committed
include unit tests for forgejo event; fixes
Update packit_service/worker/reporting/reporters/base.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/service/api/webhooks.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/worker/parser.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/events/forgejo/pr.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> pre-commit and mypy fixes test(data): add Forgejo webhooks for tests include unit tests for forgejo event; fixes add check for prioritising Forgejo before parsing events; fixes pre-commit remove redundant entries in parser list; fix bug Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> implement validate_token for ForgejoWebhooks; correct the pr.Action impl for Forgejo; fix a problem in parser that could have caused keynotfound errors. implement validate_token for ForgejoWebhooks; correct the pr.Action impl for Forgejo; fix a problem in parser that could have caused keynotfound errors. Implement Forgejo event handling in Packit Service.
1 parent 05dcfc4 commit 0717436

File tree

10 files changed

+571
-95
lines changed

10 files changed

+571
-95
lines changed

packit_service/events/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
__all__ = [
2121
abstract.__name__,
2222
anitya.__name__,
23+
forgejo.__name__,
2324
github.__name__,
2425
gitlab.__name__,
2526
forgejo.__name__,

packit_service/events/abstract/comment.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def __init__(
152152
tag_name: str = "",
153153
comment_object: Optional[Comment] = None,
154154
dist_git_project_url=None,
155+
commit_sha: Optional[str] = None,
155156
) -> None:
156157
super().__init__(
157158
project_url=project_url,
@@ -168,7 +169,7 @@ def __init__(
168169

169170
# Lazy properties
170171
self._tag_name = tag_name
171-
self._commit_sha: Optional[str] = None
172+
self._commit_sha: Optional[str] = commit_sha
172173
self._comment_object = comment_object
173174
self._issue_object: Optional[OgrIssue] = None
174175

packit_service/events/forgejo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
from . import abstract, issue, pr, push
55

6-
__all__ = [abstract.__name__, push.__name__, issue.__name__, pr.__name__]
6+
__all__ = [abstract.__name__, issue.__name__, pr.__name__, push.__name__]

packit_service/events/forgejo/issue.py

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Copyright Contributors to the Packit project.
22
# SPDX-License-Identifier: MIT
33

4-
# SPDX-License-Identifier: MIT
5-
64
from typing import Optional
75

86
from ogr.abstract import Comment as OgrComment
@@ -50,43 +48,7 @@ def __init__(
5048
def event_type(cls) -> str:
5149
return "forgejo.issue.Comment"
5250

53-
@property
54-
def tag_name(self):
55-
"""
56-
For Forgejo issue comments, return the tag_name passed in constructor
57-
without making API calls to avoid authentication issues.
58-
"""
59-
return self._tag_name
60-
61-
@tag_name.setter
62-
def tag_name(self, value: str) -> None:
63-
self._tag_name = value
64-
65-
@property
66-
def commit_sha(self) -> Optional[str]:
67-
"""
68-
For Forgejo issue comments, return the commit_sha passed in constructor
69-
without making API calls to avoid authentication issues.
70-
"""
71-
return self._commit_sha
72-
73-
@commit_sha.setter
74-
def commit_sha(self, value: Optional[str]) -> None:
75-
self._commit_sha = value
76-
7751
def get_dict(self, default_dict: Optional[dict] = None) -> dict:
78-
"""
79-
Override get_dict to avoid accessing properties that make API calls.
80-
"""
81-
# Get the basic dict from CommentEvent, not from Issue to avoid tag_name access
82-
from ..abstract.comment import CommentEvent
83-
84-
result = CommentEvent.get_dict(self, default_dict=default_dict)
85-
86-
# Add the specific fields we need without triggering API calls
52+
result = super().get_dict()
8753
result["action"] = self.action.value
88-
result["issue_id"] = self.issue_id
89-
result["tag_name"] = self._tag_name # Use the private attribute directly
90-
result["commit_sha"] = self._commit_sha # Use the private attribute directly
91-
9254
return result

packit_service/events/forgejo/pr.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,10 @@ def __init__(
3636
self.base_ref = base_ref
3737
self.target_repo_namespace = target_repo_namespace
3838
self.target_repo_name = target_repo_name
39-
self.commit_sha = commit_sha
40-
self.commit_sha_before = commit_sha_before
4139
self.actor = actor
4240
self.identifier = str(pr_id)
43-
self._pr_id = pr_id
44-
self.git_ref = None # use pr_id for checkout
41+
self.commit_sha = commit_sha
42+
self.commit_sha_before = commit_sha_before
4543
self.body = body
4644

4745
def get_dict(self, default_dict: Optional[dict] = None) -> dict:
@@ -55,8 +53,8 @@ def event_type(cls) -> str:
5553

5654
def get_base_project(self) -> GitProject:
5755
return self.project.service.get_project(
58-
namespace=self.target_repo_namespace,
59-
repo=self.target_repo_name,
56+
namespace=self.base_repo_namespace,
57+
repo=self.base_repo_name,
6058
)
6159

6260

@@ -94,7 +92,6 @@ def __init__(
9492
self.actor = actor
9593
self.identifier = str(pr_id)
9694
self.git_ref = None
97-
self.pr_id = pr_id
9895

9996
@classmethod
10097
def event_type(cls) -> str:
@@ -116,6 +113,6 @@ def get_dict(self, default_dict: Optional[dict] = None) -> dict:
116113

117114
def get_base_project(self) -> GitProject:
118115
return self.project.service.get_project(
119-
namespace=self.target_repo_namespace,
120-
repo=self.target_repo_name,
116+
namespace=self.base_repo_namespace,
117+
repo=self.base_repo_name,
121118
)

packit_service/service/api/webhooks.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ class ForgejoWebhook(Resource):
373373
@ns.response(HTTPStatus.UNAUTHORIZED.value, "X-Forgejo-Signature validation failed")
374374
@ns.expect(ping_payload_forgejo)
375375
def post(self):
376+
"""
377+
A webhook used by Packit-as-a-Service Forgejo hook.
378+
"""
376379
msg = request.json
377380

378381
if not msg:
@@ -385,17 +388,17 @@ def post(self):
385388
logger.debug(f"/webhooks/forgejo received ping event: {msg['hook']}")
386389
forgejo_webhook_calls.labels(result="pong", process_id=os.getpid()).inc()
387390
return "Pong!", HTTPStatus.OK
388-
# TODO
389-
# try:
390-
# self.validate_token()
391-
# except ValidationFailed as exc:
392-
# logger.info(f"/webhooks/forgejo {exc}")
393-
# forgejo_webhook_calls.labels(
394-
# result="invalid_signature",
395-
# process_id=os.getpid(),
396-
# ).inc()
397-
# return str(exc), HTTPStatus.UNAUTHORIZED
398-
#
391+
392+
try:
393+
self.validate_token()
394+
except ValidationFailed as exc:
395+
logger.info(f"/webhooks/forgejo {exc}")
396+
forgejo_webhook_calls.labels(
397+
result="invalid_signature",
398+
process_id=os.getpid(),
399+
).inc()
400+
return str(exc), HTTPStatus.UNAUTHORIZED
401+
399402
if not self.interested(msg):
400403
forgejo_webhook_calls.labels(
401404
result="not_interested",
@@ -419,39 +422,44 @@ def validate_token(self):
419422
"""
420423
Validate the Forgejo webhook signature.
421424
The signature is a direct SHA256 HMAC hex digest of the raw request body
422-
using the webhook secret as the key, in a similar fashion to Github.
423-
425+
using the webhook secret as the key, in a similar fashion to GitHub.
424426
"""
425427
if "X-Forgejo-Signature" not in request.headers:
428+
if config.validate_webhooks:
429+
msg = "X-Forgejo-Signature not in request.headers"
430+
logger.warning(msg)
431+
raise ValidationFailed(msg)
432+
433+
# don't validate signatures when testing locally
426434
logger.debug("Ain't validating signatures.")
427435
return
428436

429-
if not (webhook_secret := getenv("WEBHOOK_SECRET")):
437+
if not (webhook_secret := config.webhook_secret):
430438
msg = "'webhook_secret' not specified in the config."
431439
logger.error(msg)
432440
raise ValidationFailed(msg)
433441

434-
# Get raw payload
435-
436442
payload = request.get_data()
437443
if not payload:
438444
msg = "No payload received."
439445
logger.error(msg)
440446
raise ValidationFailed(msg)
441447

448+
# Calculate payload signature using HMAC-SHA256
442449
data_hmac = hmac.new(webhook_secret.encode(), msg=payload, digestmod=sha256)
443450
payload_signature = data_hmac.hexdigest()
444-
header_sig = request.headers["X-Forgejo-Signature"]
451+
header_signature = request.headers["X-Forgejo-Signature"]
445452

446-
if header_sig != payload_signature:
453+
if not hmac.compare_digest(header_signature, payload_signature):
447454
msg = "Payload signature validation failed."
448-
449455
logger.warning(msg)
450456
logger.debug(
451-
f"X-Forgejo-Signature: {header_sig!r} != computed: {payload_signature}",
457+
f"X-Forgejo-Signature: {header_signature!r} != computed: {payload_signature}",
452458
)
453459
raise ValidationFailed(msg)
454460

461+
logger.debug("Payload signature is OK.")
462+
455463
@staticmethod
456464
def interested(msg):
457465
"""
@@ -475,7 +483,7 @@ def interested(msg):
475483
"release": action == "published",
476484
"issues": action in {"opened", "edited", "closed", "reopened"},
477485
"issue_comment": action in {"created", "edited"},
478-
"pull_request": action in {"opened", "edited", "closed", "reopened", "synchronize"},
486+
"pull_request": action in {"opened", "reopened", "synchronize"},
479487
}
480488

481489
_interested = interests.get(event or "", False)

0 commit comments

Comments
 (0)