Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/sentry/integrations/github/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ def sync_assignee_outbound(

# Strip the @ from the username
github_username = external_actor.external_name.lstrip("@")
# lowercase the username
github_username = github_username.lower()

# Only update GitHub if we have a username to assign or if we're explicitly deassigning
if github_username or not assign:
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/integrations/utils/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def sync_group_assignee_inbound_by_external_actor(

external_actors = ExternalActor.objects.filter(
provider=EXTERNAL_PROVIDERS_REVERSE[ExternalProviderEnum(integration.provider)].value,
external_name=external_user_name,
external_name__iexact=external_user_name,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also don't have any logic that prevents repeat external_names i believe, something we should enforce.

integration_id=integration.id,
user_id__isnull=False,
).values_list("user_id", flat=True)
Comment on lines 140 to 145

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High severity vulnerability may affect your project—review required:
Line 140 lists a dependency (django) with a known High severity vulnerability.

ℹ️ Why this matters

Affected versions of Django are vulnerable to Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'). SQL injection in Django's ORM column aliases: when using QuerySet.annotate(), QuerySet.alias(), QuerySet.aggregate(), or QuerySet.extra() with dictionary expansion (**kwargs), the dictionary keys are used unescaped as SQL column aliases. On MySQL and MariaDB backends, an attacker who can influence those keys (for example, by passing a crafted dict of annotations) can inject arbitrary SQL into the generated query.

References: GHSA, CVE

To resolve this comment:
Check if you are using Django with MySQL or MariaDB.

  • If you're affected, upgrade this dependency to at least version 5.2.7 at uv.lock.
  • If you're not affected, comment /fp we don't use this [condition]
💬 Ignore this finding

To ignore this, reply with:

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

You can view more details on this finding in the Semgrep AppSec Platform here.

Expand Down
25 changes: 25 additions & 0 deletions tests/sentry/integrations/github/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,31 @@ def test_sync_assignee_outbound(self) -> None:
assert request.url == "https://api.github.com/repos/Test-Organization/foo/issues/123"
assert orjson.loads(request.body) == {"assignees": ["octocat"]}

@responses.activate
def test_sync_assignee_outbound_case_insensitive(self) -> None:
"""Test assigning a GitHub issue to a user with linked GitHub account"""

user, installation, external_issue, _, _ = self._setup_assignee_sync_test(
external_name="@JohnDoe"
)

responses.add(
responses.PATCH,
"https://api.github.com/repos/Test-Organization/foo/issues/123",
json={"assignees": ["johndoe"]},
status=200,
)

responses.calls.reset()

with assume_test_silo_mode(SiloMode.REGION):
installation.sync_assignee_outbound(external_issue, user, assign=True)

assert len(responses.calls) == 1
request = responses.calls[0].request
assert request.url == "https://api.github.com/repos/Test-Organization/foo/issues/123"
assert orjson.loads(request.body) == {"assignees": ["johndoe"]}

@responses.activate
def test_sync_assignee_outbound_unassign(self) -> None:
"""Test unassigning a GitHub issue"""
Expand Down
35 changes: 35 additions & 0 deletions tests/sentry/integrations/utils/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,41 @@ def test_assignment_with_external_actor(
assert updated_assignee.email == "[email protected]"
mock_record_event.assert_called_with(EventLifecycleOutcome.SUCCESS, None, False, None)

@mock.patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
def test_assignment_with_external_actor_case_insensitive(
self,
mock_record_event: mock.MagicMock,
) -> None:
"""Test assigning a group to a user via external actor."""
assert self.group.get_assignee() is None

external_issue = self.create_integration_external_issue(
group=self.group,
key="JIRA-123",
integration=self.example_integration,
)

# Create external user mapping
self.create_external_user(
user=self.test_user,
external_name="@JohnDoe",
provider=ExternalProviders.GITHUB.value,
integration=self.example_integration,
)

sync_group_assignee_inbound_by_external_actor(
integration=self.example_integration,
external_user_name="@johndoe",
external_issue_key=external_issue.key,
assign=True,
)

updated_assignee = self.group.get_assignee()
assert updated_assignee is not None
assert updated_assignee.id == self.test_user.id
assert updated_assignee.email == "[email protected]"
mock_record_event.assert_called_with(EventLifecycleOutcome.SUCCESS, None, False, None)

@mock.patch("sentry.integrations.utils.sync.where_should_sync")
@mock.patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
def test_assign_with_multiple_groups(
Expand Down
Loading