-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
ref(mediators): Make validator into a dataclass #79116
Changes from 5 commits
a91fcb9
d4d8507
65d892d
360d822
114e66d
c8ca5fc
7034eab
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 |
---|---|---|
@@ -1,3 +1,2 @@ | ||
from .mediator import Mediator # NOQA | ||
from .param import Param # NOQA | ||
from .token_exchange.util import AUTHORIZATION, REFRESH, GrantTypes # noqa: F401 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +0,0 @@ | ||
from .util import AUTHORIZATION, REFRESH, GrantTypes, token_expiration # NOQA | ||
from .validator import Validator # NOQA | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,54 @@ | ||
from django.db import router | ||
from dataclasses import dataclass | ||
|
||
from django.utils.functional import cached_property | ||
|
||
from sentry.coreapi import APIUnauthorized | ||
from sentry.mediators.mediator import Mediator | ||
from sentry.mediators.param import Param | ||
from sentry.models.apiapplication import ApiApplication | ||
from sentry.sentry_apps.models.sentry_app import SentryApp | ||
from sentry.sentry_apps.services.app import RpcSentryAppInstallation | ||
from sentry.users.models.user import User | ||
|
||
|
||
class Validator(Mediator): | ||
@dataclass | ||
class Validator: | ||
""" | ||
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. Interesting that this is just validation logic 🤔 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. yeah, I did some more reading and this class is p. simple so it doesn't really matter which way we go about it. |
||
Validates general authorization params for all types of token exchanges. | ||
""" | ||
|
||
install = Param(RpcSentryAppInstallation) | ||
client_id = Param(str) | ||
user = Param(User) | ||
using = router.db_for_write(User) | ||
install: RpcSentryAppInstallation | ||
client_id: str | ||
user: User | ||
|
||
def call(self): | ||
def run(self) -> bool: | ||
self._validate_is_sentry_app_making_request() | ||
self._validate_app_is_owned_by_user() | ||
self._validate_installation() | ||
return True | ||
|
||
def _validate_is_sentry_app_making_request(self): | ||
def _validate_is_sentry_app_making_request(self) -> None: | ||
if not self.user.is_sentry_app: | ||
raise APIUnauthorized | ||
raise APIUnauthorized("User is not a Sentry App") | ||
|
||
def _validate_app_is_owned_by_user(self): | ||
def _validate_app_is_owned_by_user(self) -> None: | ||
if self.sentry_app.proxy_user != self.user: | ||
raise APIUnauthorized | ||
raise APIUnauthorized("Sentry App does not belong to given user") | ||
|
||
def _validate_installation(self): | ||
def _validate_installation(self) -> None: | ||
if self.install.sentry_app.id != self.sentry_app.id: | ||
raise APIUnauthorized | ||
raise APIUnauthorized( | ||
f"Sentry App Installation is not for Sentry App: {self.sentry_app.slug}" | ||
) | ||
|
||
@cached_property | ||
def sentry_app(self): | ||
def sentry_app(self) -> SentryApp: | ||
try: | ||
return self.application.sentry_app | ||
except SentryApp.DoesNotExist: | ||
raise APIUnauthorized | ||
raise APIUnauthorized("Sentry App does not exist") | ||
|
||
@cached_property | ||
def application(self): | ||
def application(self) -> ApiApplication: | ||
try: | ||
return ApiApplication.objects.get(client_id=self.client_id) | ||
except ApiApplication.DoesNotExist: | ||
raise APIUnauthorized | ||
raise APIUnauthorized("Application does not exist") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,13 +35,8 @@ def test_adds_token_to_installation(self): | |
token = self.grant_exchanger.run() | ||
assert SentryAppInstallation.objects.get(id=self.install.id).api_token == token | ||
|
||
@patch("sentry.mediators.token_exchange.Validator.run") | ||
def test_validate_generic_token_exchange_requirements(self, validator): | ||
self.grant_exchanger.run() | ||
|
||
validator.assert_called_once_with( | ||
install=self.install, client_id=self.client_id, user=self.user | ||
) | ||
def test_validate_generic_token_exchange_requirements(self): | ||
assert self.grant_exchanger._validate() | ||
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. Having tests call 'protected methods' can make tests brittle as code changes over time. The mock solution also had this weakness though. It looks like we have integration tests for the validation flow below though, and we could just delete these tests without losing any coverage. |
||
|
||
def test_grant_must_belong_to_installations(self): | ||
other_install = self.create_sentry_app_installation(prevent_token_exchange=True) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These lines could be outside the transaction block 🤷