update mobile app proxy's usage of the Cloud Auth API (#4194)

# What this PR does

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Joey Orlando 2024-04-11 10:45:21 -04:00 committed by GitHub
parent 64bca2a2c0
commit 7c6ccd772c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 24 additions and 42 deletions

View file

@ -300,7 +300,7 @@ def test_mobile_app_gateway_proxies_headers(
):
mock_requests.post.return_value = MockResponse()
_, user, auth_token = make_organization_and_user_with_mobile_app_auth_token()
_, _, auth_token = make_organization_and_user_with_mobile_app_auth_token()
client = APIClient()
url = reverse("mobile_app:gateway", kwargs={"downstream_backend": DOWNSTREAM_BACKEND, "downstream_path": "test"})
@ -315,7 +315,6 @@ def test_mobile_app_gateway_proxies_headers(
params={},
headers={
"Authorization": f"Bearer {MOCK_AUTH_TOKEN}",
"X-Grafana-User": f"email:{user.email}",
"Content-Type": content_type_header,
},
)
@ -328,30 +327,11 @@ def test_mobile_app_gateway_properly_generates_an_auth_token(
make_organization,
make_user_for_organization,
):
user_id = 90095905
stack_id = 895
organization_id = 8905
stack_slug = "mvcmnvcmnvc"
org_slug = "raintank"
organization = make_organization(
stack_id=stack_id, org_id=organization_id, stack_slug=stack_slug, org_slug=org_slug
)
user = make_user_for_organization(organization, user_id=user_id)
organization = make_organization(stack_id=stack_id)
user = make_user_for_organization(organization)
auth_token = MobileAppGatewayView._get_auth_token(DOWNSTREAM_BACKEND, user)
assert auth_token == f"{stack_id}:{MOCK_AUTH_TOKEN}"
mock_request_signed_token.assert_called_once_with(
organization,
[CloudAuthApiClient.Scopes.INCIDENT_WRITE],
{
"user_id": user.user_id, # grafana user ID
"user_email": user.email,
"stack_id": organization.stack_id,
"organization_id": organization.org_id, # grafana org ID
"stack_slug": organization.stack_slug,
"org_slug": organization.org_slug,
},
)
mock_request_signed_token.assert_called_once_with(user, [CloudAuthApiClient.Scopes.INCIDENT_WRITE])

View file

@ -153,20 +153,11 @@ class MobileAppGatewayView(APIView):
HS256 = symmetric = shared secret (don't use this)
"""
org = user.organization
token_claims = {
"user_id": user.user_id, # grafana user ID
"user_email": user.email,
"stack_id": org.stack_id,
"organization_id": org.org_id, # grafana org ID
"stack_slug": org.stack_slug,
"org_slug": org.org_slug,
}
token_scopes = {
cls.SupportedDownstreamBackends.INCIDENT: [CloudAuthApiClient.Scopes.INCIDENT_WRITE],
}[downstream_backend]
return f"{org.stack_id}:{CloudAuthApiClient().request_signed_token(org, token_scopes, token_claims)}"
return f"{org.stack_id}:{CloudAuthApiClient().request_signed_token(user, token_scopes)}"
@classmethod
def _get_downstream_headers(
@ -174,7 +165,6 @@ class MobileAppGatewayView(APIView):
) -> typing.Dict[str, str]:
headers = {
"Authorization": f"Bearer {cls._get_auth_token(downstream_backend, user)}",
"X-Grafana-User": f"email:{user.email}",
}
if (v := request.META.get("CONTENT_TYPE", None)) is not None:

View file

@ -9,7 +9,7 @@ from django.conf import settings
from rest_framework import status
if typing.TYPE_CHECKING:
from apps.user_management.models import Organization
from apps.user_management.models import User
logger = logging.getLogger(__name__)
@ -43,8 +43,13 @@ class CloudAuthApiClient:
self.api_token = settings.GRAFANA_CLOUD_AUTH_API_SYSTEM_TOKEN
def request_signed_token(
self, org: "Organization", scopes: typing.List[Scopes], claims: typing.Dict[str, typing.Any]
self,
user: "User",
scopes: typing.List[Scopes],
extra_claims: typing.Optional[typing.Dict[str, typing.Any]] = None,
) -> str:
org = user.organization
# The Cloud Auth API expects the org_id and stack_id to be strings
org_id = str(org.org_id)
stack_id = str(org.stack_id)
@ -73,7 +78,10 @@ class CloudAuthApiClient:
url,
headers=headers,
json={
"claims": claims,
"claims": {
"sub": f"email:{user.email}",
},
"extra": extra_claims or {},
"accessPolicy": {
"scopes": scopes,
},

View file

@ -19,7 +19,7 @@ def configure_cloud_auth_api_client(settings):
@pytest.mark.django_db
@pytest.mark.parametrize("response_status_code", [status.HTTP_200_OK, status.HTTP_401_UNAUTHORIZED])
@httpretty.activate(verbose=True, allow_net_connect=False)
def test_request_signed_token(make_organization, response_status_code):
def test_request_signed_token(make_organization, make_user_for_organization, response_status_code):
mock_auth_token = ",mnasdlkjlakjoqwejroiqwejr"
mock_response_text = "error message"
@ -27,12 +27,13 @@ def test_request_signed_token(make_organization, response_status_code):
stack_id = 5
organization = make_organization(stack_id=stack_id, org_id=org_id)
user = make_user_for_organization(organization=organization)
scopes = ["incident:write", "foo:bar"]
claims = {"vegetable": "carrot", "fruit": "apple"}
extra_claims = {"vegetable": "carrot", "fruit": "apple"}
def _make_request():
return CloudAuthApiClient().request_signed_token(organization, scopes, claims)
return CloudAuthApiClient().request_signed_token(user, scopes, extra_claims)
url = f"{GRAFANA_CLOUD_AUTH_API_URL}/v1/sign"
mock_response = httpretty.Response(json.dumps({"data": {"token": mock_auth_token}}), status=response_status_code)
@ -55,7 +56,10 @@ def test_request_signed_token(make_organization, response_status_code):
# assert we're sending the right body
assert json.loads(last_request.body) == {
"claims": claims,
"claims": {
"sub": f"email:{user.email}",
},
"extra": extra_claims,
"accessPolicy": {
"scopes": scopes,
},