Coverage for /usr/local/lib/python3.10/site-packages/opal_server-0.0.0-py3.10.egg/opal_server/policy/webhook/api.py: 30%
61 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from typing import Callable, List
2from urllib.parse import SplitResult, urlparse
4from fastapi import APIRouter, Depends, Request, status
5from fastapi_websocket_pubsub.pub_sub_server import PubSubEndpoint
6from opal_common.authentication.deps import JWTAuthenticator
7from opal_common.logger import logger
8from opal_common.schemas.webhook import GitWebhookRequestParams
9from opal_server.config import PolicySourceTypes, opal_server_config
10from opal_server.policy.webhook.deps import (
11 GitChanges,
12 extracted_git_changes,
13 validate_git_secret_or_throw,
14)
17def init_git_webhook_router(
18 pubsub_endpoint: PubSubEndpoint, authenticator: JWTAuthenticator
19):
20 async def dummy_affected_repo_urls(request: Request) -> List[str]:
21 return []
23 source_type = opal_server_config.POLICY_SOURCE_TYPE
24 if source_type == PolicySourceTypes.Api: 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true
25 route_dependency = authenticator
26 func_dependency = dummy_affected_repo_urls
27 else:
28 route_dependency = validate_git_secret_or_throw
29 func_dependency = extracted_git_changes
31 return get_webhook_router(
32 [Depends(route_dependency)],
33 Depends(func_dependency),
34 source_type,
35 pubsub_endpoint.publish,
36 )
39def is_matching_webhook_url(input_url: str, urls: List[str], names: List[str]) -> bool:
40 parsed = urlparse(input_url)
41 netloc = parsed.hostname
43 if parsed.port:
44 netloc = f"{parsed.hostname}:{parsed.port}"
46 normalized = SplitResult(
47 scheme=parsed.scheme, netloc=netloc, path=parsed.path, query="", fragment=""
48 )
50 if urls:
51 return str(normalized.geturl()) in urls
52 else:
53 repo_name_from_path = normalized.path.removeprefix("/").removesuffix(".git")
54 return repo_name_from_path in names
57def get_webhook_router(
58 route_dependencies: List[Depends],
59 git_changes: Depends,
60 source_type: PolicySourceTypes,
61 publish: Callable,
62 webhook_config: GitWebhookRequestParams = opal_server_config.POLICY_REPO_WEBHOOK_PARAMS,
63):
64 if webhook_config is None: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true
65 webhook_config = opal_server_config.POLICY_REPO_WEBHOOK_PARAMS
66 router = APIRouter()
68 @router.post(
69 "/webhook",
70 status_code=status.HTTP_200_OK,
71 dependencies=route_dependencies,
72 )
73 async def trigger_webhook(request: Request, git_changes: GitChanges = git_changes):
74 # TODO: breaking change: change "repo_url" to "remote_url" in next major
75 if source_type == PolicySourceTypes.Git:
76 # look at values extracted from request
77 urls = git_changes.urls
78 branch = git_changes.branch
79 names = git_changes.names
81 # Enforce branch matching (webhook to config) if turned on via config
82 if (
83 opal_server_config.POLICY_REPO_WEBHOOK_ENFORCE_BRANCH
84 and opal_server_config.POLICY_REPO_MAIN_BRANCH != branch
85 ):
86 logger.warning(
87 "Git Webhook ignored - POLICY_REPO_WEBHOOK_ENFORCE_BRANCH is enabled, and POLICY_REPO_MAIN_BRANCH is `{tracking}` but received webhook for a different branch ({branch})",
88 tracking=opal_server_config.POLICY_REPO_MAIN_BRANCH,
89 branch=branch,
90 )
91 return None
93 # parse event from header
94 if webhook_config.event_header_name is not None:
95 event = request.headers.get(webhook_config.event_header_name, "ping")
96 # parse event from request body
97 elif webhook_config.event_request_key is not None:
98 payload = await request.json()
99 event = payload.get(webhook_config.event_request_key, "ping")
100 else:
101 logger.error(
102 "Webhook config is missing both event_request_key and event_header_name. Must have at least one."
103 )
105 policy_repo_url = opal_server_config.POLICY_REPO_URL
107 # Check if the URL we are tracking is mentioned in the webhook
108 if policy_repo_url and (
109 is_matching_webhook_url(policy_repo_url, urls, names)
110 or not webhook_config.match_sender_url
111 ):
112 logger.info(
113 "triggered webhook on repo: {repo}",
114 repo=opal_server_config.POLICY_REPO_URL,
115 hook_event=event,
116 )
117 # Check if this it the right event (push)
118 if event == webhook_config.push_event_value:
119 # notifies the webhook listener via the pubsub broadcaster
120 await publish(opal_server_config.POLICY_REPO_WEBHOOK_TOPIC)
121 return {
122 "status": "ok",
123 "event": event,
124 "repo_url": opal_server_config.POLICY_REPO_URL,
125 }
126 else:
127 logger.warning(
128 "Got an unexpected webhook not matching the tracked repo ({repo}) - with these URLS: {urls} and those names: {names}.",
129 repo=opal_server_config.POLICY_REPO_URL,
130 urls=urls,
131 names=names,
132 hook_event=event,
133 )
135 elif source_type == PolicySourceTypes.Api:
136 logger.info("Triggered webhook to check API bundle URL")
137 await publish(opal_server_config.POLICY_REPO_WEBHOOK_TOPIC)
138 return {
139 "status": "ok",
140 "event": "webhook_trigger",
141 "repo_url": opal_server_config.POLICY_BUNDLE_URL,
142 }
144 return {"status": "ignored", "event": event}
146 return router