-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat(integrations): Add webhook_headers field to SentryApp API
#117089
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2763887
c59540f
f39e99b
9bc6330
3bbbc6d
ee06717
c1a02bd
8e1f808
cd19151
535e519
b695797
ea56424
d57a8da
8929168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ | |
| SentryAppInteractionType, | ||
| ) | ||
| from sentry.sentry_apps.models.sentry_app import ( | ||
| MASKED_VALUE, | ||
| REQUIRED_EVENT_PERMISSIONS, | ||
| UUID_CHARS_IN_SLUG, | ||
| SentryApp, | ||
|
|
@@ -114,6 +115,7 @@ class SentryAppUpdater: | |
| schema: Schema | None = None | ||
| overview: str | None = None | ||
| allowed_origins: list[str] | None = None | ||
| webhook_headers: list[str] | None = None | ||
| popularity: int | None = None | ||
| features: list[int] | None = None | ||
| is_disabled: bool | None = None | ||
|
|
@@ -137,6 +139,7 @@ def run(self, user: User | RpcUser) -> SentryApp: | |
| self._update_verify_install() | ||
| self._update_overview() | ||
| self._update_allowed_origins() | ||
| self._update_webhook_headers() | ||
| new_schema_elements = self._update_schema() | ||
| self._update_popularity(user=user) | ||
| self.sentry_app.save() | ||
|
|
@@ -286,6 +289,40 @@ def _update_allowed_origins(self) -> None: | |
| self.sentry_app.application.allowed_origins = "\n".join(self.allowed_origins) | ||
| self.sentry_app.application.save() | ||
|
|
||
| def _update_webhook_headers(self) -> None: | ||
| # None means "not provided" (leave unchanged); an empty list clears all headers. | ||
| if self.webhook_headers is None: | ||
| return | ||
|
|
||
| # The serializer masks header values on read, so an unchanged entry comes back | ||
| # as "Header-Name: <MASKED_VALUE>". Substitute the stored value for any masked | ||
| # entry (matched by name) so a prefill+resave doesn't overwrite real secrets. | ||
| # Drop masked entries with no stored match. | ||
| # | ||
| # Names are unique (the parser rejects duplicates), so this re-pairing is | ||
| # unambiguous. Known limitation: renaming a header while leaving its value | ||
| # masked can't be matched by the new name and will drop the entry — only | ||
| # reachable by an editor who sees masks (org:write without scope coverage); | ||
| # they should re-enter the value when renaming. | ||
| existing_by_name = {} | ||
| for header in self.sentry_app.webhook_headers: | ||
| name, separator, _value = header.partition(":") | ||
| if separator: | ||
| existing_by_name[name.strip().lower()] = header | ||
|
Comment on lines
+307
to
+311
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. create a map of existing header names to the header so that we can check if the user has updated an existing header's value |
||
|
|
||
| resolved: list[str] = [] | ||
| for header in self.webhook_headers: | ||
| name, separator, value = header.partition(":") | ||
| if separator and value.strip() == MASKED_VALUE: | ||
| stored = existing_by_name.get(name.strip().lower()) | ||
| if stored is not None: | ||
| resolved.append(stored) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since the read api only sends masked values, the user will be sending us a mix of masked and unmasked. assume that they are not modifying a header if its value is the masked value, also SOL if you want to change to the masked value. |
||
| else: | ||
| resolved.append(header) | ||
|
|
||
| self.sentry_app.webhook_headers = resolved | ||
| # Persisted by the sentry_app.save() call at the end of run(). | ||
|
|
||
| def _update_popularity(self, user: User | RpcUser) -> None: | ||
| if self.popularity is not None: | ||
| if _is_elevated_user(user): | ||
|
|
@@ -343,6 +380,7 @@ class SentryAppCreator: | |
| schema: Schema = dataclasses.field(default_factory=dict) | ||
| overview: str | None = None | ||
| allowed_origins: list[str] = dataclasses.field(default_factory=list) | ||
| webhook_headers: list[str] = dataclasses.field(default_factory=list) | ||
| popularity: int | None = None | ||
| metadata: dict | None = field(default_factory=dict) | ||
|
|
||
|
|
@@ -426,6 +464,7 @@ def _create_sentry_app( | |
| "events": expand_events(self.events), | ||
| "schema": self.schema or {}, | ||
| "webhook_url": self.webhook_url, | ||
| "webhook_headers": self.webhook_headers, | ||
| "redirect_url": self.redirect_url, | ||
| "is_alertable": self.is_alertable, | ||
| "verify_install": self.verify_install, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.