Skip to content

Commit d6cb285

Browse files
Add Codecov User Resolution
Codecov needs to map the Sentry user onto a GitHub identity to be able to link them to the associated Codecov user. This returns the external_id (GitHub id) and the auth_token to be encrypted and used for requests to Codecov.
1 parent 42983a0 commit d6cb285

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

src/sentry/codecov/helper.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from dataclasses import dataclass
2+
from typing import Any
3+
4+
from sentry.auth.services.access.service import access_service
5+
from sentry.auth.services.auth import RpcAuthIdentity
6+
from sentry.identity.services.identity import identity_service
7+
from sentry.identity.services.identity.model import RpcIdentity
8+
9+
10+
@dataclass
11+
class CodecovUser:
12+
external_id: str
13+
auth_token: Any
14+
15+
16+
def resolve_codecov_user(user_id: int, organization_id: int) -> CodecovUser | None:
17+
"""Given a Sentry User, and an organization id, find the GitHub user ID and GH access_token
18+
that is linked to Codecov's user.
19+
20+
The user resolution will "fall-through" based on whether the Organization has a GitHub Auth Provider enabled.
21+
"""
22+
# Get identity from an AuthProvider if it's available.
23+
auth_provider = access_service.get_auth_provider(organization_id)
24+
if auth_provider and auth_provider.provider == "github":
25+
auth_identity: RpcAuthIdentity | None = access_service.get_auth_identity_for_user(
26+
auth_provider.id, user_id
27+
)
28+
if auth_identity:
29+
return CodecovUser(
30+
external_id=str(auth_identity.ident),
31+
auth_token=auth_identity.data.get("access_token"),
32+
)
33+
34+
# Get identity from Identity if it's available.
35+
identities: list[RpcIdentity] = identity_service.get_user_identities_by_provider_type(
36+
user_id=user_id, provider_type="github"
37+
)
38+
if identities:
39+
# Note: There is currently either zero or one GitHub "identity" mapped to a user.
40+
identity = identities[0]
41+
return CodecovUser(
42+
external_id=identity.external_id,
43+
auth_token=identity.data.get("access_token"),
44+
)
45+
46+
# No GitHub identities tied to user, return None
47+
return None

tests/sentry/codecov/__init__.py

Whitespace-only changes.

tests/sentry/codecov/test_helper.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from sentry.codecov.helper import CodecovUser, resolve_codecov_user
2+
from sentry.testutils.cases import TestCase
3+
from sentry.testutils.silo import all_silo_test
4+
5+
6+
@all_silo_test
7+
class TestCodecovUserResolution(TestCase):
8+
def setUp(self):
9+
self.organization = self.create_organization()
10+
self.user = self.create_user()
11+
12+
def test_get_auth_provider_identity_for_user(self):
13+
auth_provider = self.create_auth_provider(
14+
organization_id=self.organization.id, provider="github"
15+
)
16+
self.create_auth_identity(
17+
auth_provider=auth_provider,
18+
user=self.user,
19+
ident=12345,
20+
data={"access_token": "this_is_the_access_token_stored_here"},
21+
)
22+
assert resolve_codecov_user(self.user.id, self.organization.id) == CodecovUser(
23+
external_id="12345", auth_token="this_is_the_access_token_stored_here"
24+
)
25+
26+
def test_get_auth_identity_for_user(self):
27+
# Create the Auth Provider to fall through
28+
self.create_auth_provider(organization_id=self.organization.id, provider="github")
29+
30+
identity_provider = self.create_identity_provider(type="github")
31+
self.create_identity(
32+
user=self.user,
33+
identity_provider=identity_provider,
34+
external_id="identity_id_12345",
35+
data={"access_token": "this_is_the_access_token_stored_here"},
36+
)
37+
38+
assert resolve_codecov_user(self.user.id, self.organization.id) == CodecovUser(
39+
external_id="identity_id_12345", auth_token="this_is_the_access_token_stored_here"
40+
)
41+
42+
def test_no_linked_identity(self):
43+
assert resolve_codecov_user(self.user.id, self.organization.id) is None

0 commit comments

Comments
 (0)