Skip to content

[benchmark][Python] GitHub OAuth Security Enhancement - #11

Open
hsander wants to merge 1 commit into
benchmark/sentry/pr-67876-basefrom
benchmark/sentry/pr-67876
Open

hsander wants to merge 1 commit into
benchmark/sentry/pr-67876-basefrom
benchmark/sentry/pr-67876

Conversation

@hsander

@hsander hsander commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Benchmark fixture for sentry PR #67876.\n\nGolden comments: benchmark/golden_comments/sentry.json — PR 67876.

# OrganizationIntegration does not exist, but Integration does exist.
pipeline.bind_state("installation_id", request.GET["installation_id"])
try:
integration = Integration.objects.get(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 Небезопасный доступ к integration.metadata["sender"]["login"] без проверки наличия ключа sender

В строке 175 код обращается к integration.metadata["sender"]["login"] напрямую, без проверки существования ключа sender. Поле sender заполняется вебхуком установки GitHub, но OAuth-поток и доставка вебхука асинхронны — пользователь может завершить OAuth до обработки вебхука. Если sender отсутствует, возникает необработанный KeyError (HTTP 500) вместо корректного ответа с ошибкой.

)

# At this point, we are past the GitHub "authorize" step
if request.GET.get("state") != pipeline.signature:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Параметры URL авторизации OAuth не кодируются (URL-encoding)

Значения client_id, state и redirect_uri подставляются в строку запроса через f-интерполяцию без URL-кодирования. Хотя state — шестнадцатеричный хеш, а client_id обычно алфавитно-цифровой, redirect_uri — полный URL, который согласно спецификации OAuth 2.0 должен быть percent-encoded. Некорректно закодированный redirect_uri может привести к отклонению запроса GitHub или неверной интерпретации callback URL.

# Check that the authenticated GitHub user is the same as who installed the app.
if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Прямой доступ к integration.metadata["sender"]["login"] в GitHubInstallation.dispatch может вызвать KeyError

В строке 184, после повторного получения активной интеграции, новая проверка несоответствия пользователя обращается к integration.metadata["sender"]["login"] через прямой индекс. integration.metadata — JSON-поле, структура которого зависит от данных, заполненных вебхуком установки GitHub. Если вебхук не был обработан (например, старая интеграция, созданная до этого изменения, сбой доставки вебхука), ключ sender будет отсутствовать, что приведёт к необработанному KeyError.

pipeline.bind_state("installation_id", request.GET["installation_id"])
return pipeline.next_step()

if installations_exist:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Неактивные интеграции теперь возвращают ошибку вместо разрешения на повторную установку

Ранее, если Integration существовала, но OrganizationIntegration отсутствовала, код безусловно переходил к pipeline.next_step(). Теперь, если статус интеграции не ObjectStatus.ACTIVE (например, отключена или ожидает удаления), возвращается ошибка. Это блокирует повторную установку ранее отключенных интеграций через данный поток, что может быть регрессией для пользователей, пытающихся переустановить интеграцию.

if "login" not in authenticated_user_info:
return error(request, self.active_organization)

pipeline.bind_state("github_authenticated_user", authenticated_user_info["login"])

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Результат вызова get_user_info() не защищён от исключений

После получения payload["access_token"] код вызывает get_user_info(payload["access_token"]) без блока try/except. В отличие от блока обмена токеном выше, который обёрнут в except Exception, сетевая ошибка или ошибка парсинга из get_user_info приведёт к необработанному HTTP 500 вместо ответа error(...), используемого в остальных местах OAuthLoginView.

@hsander

hsander commented Jul 1, 2026

Copy link
Copy Markdown
Owner Author

🔍 Автоматическое ревью кода

Краткий итог

Синтез ревью: 10 уникальных групп замечаний по 2 исходн. выполнениям; 6 для публикации (критических: 0, высоких: 1, средних: 3, низких: 2, мелких: 0).

Замечания

🟠 Важно

  • [src/python/src/sentry/integrations/github/integration.py:175] Небезопасный доступ к integration.metadata["sender"]["login"] без проверки наличия ключа sender — В строке 175 код обращается к integration.metadata["sender"]["login"] напрямую, без проверки существования ключа sender. Поле sender заполняется вебхуком установки GitHub, но OAuth-поток и доставка вебхука асинхронны — пользователь может завершить OAuth до обработки вебхука. Если sender отсутствует, возникает необработанный KeyError (HTTP 500) вместо корректного ответа с ошибкой.

💡 Дополнительно

  • [src/python/src/sentry/integrations/github/integration.py:100] Параметры URL авторизации OAuth не кодируются (URL-encoding) — Значения client_id, state и redirect_uri подставляются в строку запроса через f-интерполяцию без URL-кодирования. Хотя state — шестнадцатеричный хеш, а client_id обычно алфавитно-цифровой, redirect_uri — полный URL, который согласно спецификации OAuth 2.0 должен быть percent-encoded. Некорректно закодированный redirect_uri может привести к отклонению запроса GitHub или неверной интерпретации callback URL.
  • [src/python/src/sentry/integrations/github/integration.py:184] Прямой доступ к integration.metadata["sender"]["login"] в GitHubInstallation.dispatch может вызвать KeyError — В строке 184, после повторного получения активной интеграции, новая проверка несоответствия пользователя обращается к integration.metadata["sender"]["login"] через прямой индекс. integration.metadata — JSON-поле, структура которого зависит от данных, заполненных вебхуком установки GitHub. Если вебхук не был обработан (например, старая интеграция, созданная до этого изменения, сбой доставки вебхука), ключ sender будет отсутствовать, что приведёт к необработанному KeyError.
  • [src/python/src/sentry/integrations/github/integration.py:165] Неактивные интеграции теперь возвращают ошибку вместо разрешения на повторную установку — Ранее, если Integration существовала, но OrganizationIntegration отсутствовала, код безусловно переходил к pipeline.next_step(). Теперь, если статус интеграции не ObjectStatus.ACTIVE (например, отключена или ожидает удаления), возвращается ошибка. Это блокирует повторную установку ранее отключенных интеграций через данный поток, что может быть регрессией для пользователей, пытающихся переустановить интеграцию.
  • [src/python/src/sentry/integrations/github/integration.py:126] Результат вызова get_user_info() не защищён от исключений — После получения payload["access_token"] код вызывает get_user_info(payload["access_token"]) без блока try/except. В отличие от блока обмена токеном выше, который обёрнут в except Exception, сетевая ошибка или ошибка парсинга из get_user_info приведёт к необработанному HTTP 500 вместо ответа error(...), используемого в остальных местах OAuthLoginView.
  • [src/python/src/sentry/integrations/github/integration.py:115] Обмен токена молча поглощает все исключения, возвращая пустой payload — Широкий except Exception вокруг парсинга ответа обмена токена перехватывает все ошибки и устанавливает payload = {}, что затем приводит к проверке "access_token" not in payload. Это предотвращает падения, но делает отладку затруднительной — сетевые ошибки, проблемы кодировки и неожиданные форматы ответа неразличимы. Пользователь видит общий текст «Invalid installation request» без возможности определить реальную причину.

Автоматическое ревью от Review Engine v2.0


try:
body = safe_urlread(req).decode("utf-8")
payload = dict(parse_qsl(body))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Обмен токена молча поглощает все исключения, возвращая пустой payload

Широкий except Exception вокруг парсинга ответа обмена токена перехватывает все ошибки и устанавливает payload = {}, что затем приводит к проверке "access_token" not in payload. Это предотвращает падения, но делает отладку затруднительной — сетевые ошибки, проблемы кодировки и неожиданные форматы ответа неразличимы. Пользователь видит общий текст «Invalid installation request» без возможности определить реальную причину.

integration=Integration.objects.get(external_id=installation_id)
).exists()

except Integration.DoesNotExist:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 Проверка соответствия пользователя обходится, если запись Integration ещё не существует (race condition)

Новая проверка sender выполняется только после ветки, в которой повторно извлекается активная Integration. Однако когда Integration.objects.get(external_id=installation_id) выбрасывает DoesNotExist (строки 158–163), код немедленно вызывает pipeline.next_step() и создаёт интеграцию для текущей организации без проверки, что аутентифицированный пользователь совпадает с установщиком. Запись Integration заполняется асинхронно через webhook, поэтому атакующий, получивший чужой installation_id и успевший завершить настройку в Sentry до обработки webhook, может обойти всю проверку. Рекомендуется выполнять проверку sender даже в ветке DoesNotExist, например сверяя данные из сессии/pipeline с payload установки.

pipeline.bind_state("installation_id", request.GET["installation_id"])
return pipeline.next_step()

if installations_exist:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 Проверка sender пропускается при отсутствии записи Integration — обход OAuth-верификации

Когда Integration.objects.get(external_id=installation_id) выбрасывает DoesNotExist, код привязывает installation_id и переходит к pipeline.next_step() без какой-либо проверки sender. Это открывает окно для атаки: злоумышленник, знающий чужой installation_id и опередивший обработку webhook установки GitHub, может обойти всё улучшение OAuth-верификации. Рекомендуется добавить проверку sender в ветку DoesNotExist, используя данные из payload или сессии.

# Check that the authenticated GitHub user is the same as who installed the app.
if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 KeyError при отсутствии ключа sender/login в metadata интеграции

Новая проверка обращается к integration.metadata["sender"]["login"] через прямой индекс. Если запись Integration существует, но её metadata не содержит ключ sender (или sender не содержит login) — например, записи, созданные старым кодом, частичные метаданные или вариации схемы — это вызовет необработанный KeyError и HTTP 500 вместо предполагаемой страницы ошибки. Рекомендуется использовать .get() с проверкой или обернуть доступ в try/except с вызовом error().

if (
pipeline.fetch_state("github_authenticated_user")
!= integration.metadata["sender"]["login"]
):

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Незащищённый доступ к integration.metadata["sender"]["login"] — риск KeyError

Сравнение sender-login напрямую индексирует integration.metadata["sender"]["login"] без проверки существования ключей. Интеграции, созданные через старые пути кода, webhook-пейлоады с отсутствующими полями sender или частично обработанные метаданные вызовут необработанный KeyError и 500 вместо дружелюбной страницы ошибки. Это сводит на нет назначение хелпера error(). Рекомендуется использовать безопасный доступ через .get() с fallback.

}

# similar to OAuth2CallbackView.exchange_token
req = safe_urlopen(url=ghip.get_oauth_access_token_url(), data=data)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Исключение safe_urlopen при обмене токена не обрабатывается — 500 вместо страницы ошибки

Вызов req = safe_urlopen(url=..., data=data) находится вне блока try/except, который обёртывает safe_urlread/parse_qsl. Если исходящий запрос обмена токена завершается неудачей (сетевая ошибка, таймаут, DNS, отказ соединения), safe_urlopen выбрасывает исключение, которое распространяется необработанным и приводит к HTTP 500 вместо дружелюбной страницы ошибки, которую возвращают все остальные ветки неудач. Рекомендуется обернуть safe_urlopen в тот же try/except или добавить отдельную обработку.

)

# At this point, we are past the GitHub "authorize" step
if request.GET.get("state") != pipeline.signature:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Параметры OAuth-редиректа не URL-кодируются

Значение redirect_uri интерполируется напрямую в строку запроса без URL-кодирования. В тестах http://testserver/extensions/github/setup/ работает, но в production-URL с доменами клиентов или спецсимволами OAuth-редирект может сломаться или GitHub может отклонить redirect_uri. Параметр state также не кодируется, хотя, вероятно, является hex-строкой. Рекомендуется использовать urllib.parse.quote или urlencode для всех параметров.

# GitHub apps may be installed directly from GitHub, in which case
# they will redirect here *without* being in the pipeline. If that happens
# redirect to the integration install org picker.
if (

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

💡 Конфигурируемый список FORWARD_INSTALL_FOR заменён на хардкод строки "github"

MR удаляет список FORWARD_INSTALL_FOR и инлайнит проверку provider_id == "github". Функционально эквивалентно сегодня, но это reintroduces magic string и убирает точку расширения, которую предыдущий список предоставлял, слегка усложняя добавление другого провайдера с аналогичным поведением прямой установки в будущем.

@hsander

hsander commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

🔍 Автоматическое ревью кода

Краткий итог

Синтез ревью: 8 уникальных групп замечаний по 2 исходн. выполнениям; 7 для публикации (критических: 0, высоких: 2, средних: 3, низких: 1, мелких: 1).

Замечания

🟠 Важно

  • [src/python/src/sentry/integrations/github/integration.py:162] Проверка соответствия пользователя обходится, если запись Integration ещё не существует (race condition) — Новая проверка sender выполняется только после ветки, в которой повторно извлекается активная Integration. Однако когда Integration.objects.get(external_id=installation_id) выбрасывает DoesNotExist (строки 158–163), код немедленно вызывает pipeline.next_step() и создаёт интеграцию для текущей организации без проверки, что аутентифицированный пользователь совпадает с установщиком. Запись Integration заполняется асинхронно через webhook, поэтому атакующий, получивший чужой installation_id и успевший завершить настройку в Sentry до обработки webhook, может обойти всю проверку. Рекомендуется выполнять проверку sender даже в ветке DoesNotExist, например сверяя данные из сессии/pipeline с payload установки.
  • [src/python/src/sentry/integrations/github/integration.py:165] Проверка sender пропускается при отсутствии записи Integration — обход OAuth-верификации — Когда Integration.objects.get(external_id=installation_id) выбрасывает DoesNotExist, код привязывает installation_id и переходит к pipeline.next_step() без какой-либо проверки sender. Это открывает окно для атаки: злоумышленник, знающий чужой installation_id и опередивший обработку webhook установки GitHub, может обойти всё улучшение OAuth-верификации. Рекомендуется добавить проверку sender в ветку DoesNotExist, используя данные из payload или сессии.

💡 Дополнительно

  • [src/python/src/sentry/integrations/github/integration.py:184] KeyError при отсутствии ключа sender/login в metadata интеграции — Новая проверка обращается к integration.metadata["sender"]["login"] через прямой индекс. Если запись Integration существует, но её metadata не содержит ключ sender (или sender не содержит login) — например, записи, созданные старым кодом, частичные метаданные или вариации схемы — это вызовет необработанный KeyError и HTTP 500 вместо предполагаемой страницы ошибки. Рекомендуется использовать .get() с проверкой или обернуть доступ в try/except с вызовом error().
  • [src/python/src/sentry/integrations/github/integration.py:185] Незащищённый доступ к integration.metadata["sender"]["login"] — риск KeyError — Сравнение sender-login напрямую индексирует integration.metadata["sender"]["login"] без проверки существования ключей. Интеграции, созданные через старые пути кода, webhook-пейлоады с отсутствующими полями sender или частично обработанные метаданные вызовут необработанный KeyError и 500 вместо дружелюбной страницы ошибки. Это сводит на нет назначение хелпера error(). Рекомендуется использовать безопасный доступ через .get() с fallback.
  • [src/python/src/sentry/integrations/github/integration.py:111] Исключение safe_urlopen при обмене токена не обрабатывается — 500 вместо страницы ошибки — Вызов req = safe_urlopen(url=..., data=data) находится вне блока try/except, который обёртывает safe_urlread/parse_qsl. Если исходящий запрос обмена токена завершается неудачей (сетевая ошибка, таймаут, DNS, отказ соединения), safe_urlopen выбрасывает исключение, которое распространяется необработанным и приводит к HTTP 500 вместо дружелюбной страницы ошибки, которую возвращают все остальные ветки неудач. Рекомендуется обернуть safe_urlopen в тот же try/except или добавить отдельную обработку.
  • [src/python/src/sentry/integrations/github/integration.py:100] Параметры OAuth-редиректа не URL-кодируются — Значение redirect_uri интерполируется напрямую в строку запроса без URL-кодирования. В тестах http://testserver/extensions/github/setup/ работает, но в production-URL с доменами клиентов или спецсимволами OAuth-редирект может сломаться или GitHub может отклонить redirect_uri. Параметр state также не кодируется, хотя, вероятно, является hex-строкой. Рекомендуется использовать urllib.parse.quote или urlencode для всех параметров.
  • [src/python/src/sentry/web/frontend/pipeline_advancer.py:13] Конфигурируемый список FORWARD_INSTALL_FOR заменён на хардкод строки "github" — MR удаляет список FORWARD_INSTALL_FOR и инлайнит проверку provider_id == "github". Функционально эквивалентно сегодня, но это reintroduces magic string и убирает точку расширения, которую предыдущий список предоставлял, слегка усложняя добавление другого провайдера с аналогичным поведением прямой установки в будущем.

Автоматическое ревью от Review Engine v2.0

@hsander hsander left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔍 Автоматическое ревью кода

Краткий итог

Синтез ревью: 12 уникальных групп замечаний по 1 исходн. выполнениям; 10 для публикации (критических: 0, высоких: 2, средних: 3, низких: 3, мелких: 2).

Замечания

🟠 Важно

  • [src/python/src/sentry/integrations/github/integration.py:136] GET-параметр installation_id переопределяет состояние пайплайна после OAuth — GitHubInstallation.dispatch выбирает installation_id из request.GET, если он есть, игнорируя значение, сохранённое OAuthLoginView в состояние пайплайна. Это позволяет подменить установку через URL.
  • [src/python/src/sentry/integrations/github/integration.py:182] KeyError при обращении к integration.metadata['sender']['login'] — Новая проверка авторизации разыменовывает integration.metadata['sender']['login'] без проверок. Если в метаданных нет 'sender' или в нём нет 'login' (устаревшие/частичные вебхуки, установки вне вебхука), возникает KeyError и запрос падает.

💡 Дополнительно

  • [src/python/src/sentry/integrations/github/integration.py:111] Слишком широкий except Exception скрывает ошибки обмена токеном — Ошибки сети, не-200 ответы, некорректный JSON, отсутствие code/access_token схлопываются в одну общую ошибку без логирования и метрик, что затрудняет диагностику.
  • [src/python/src/sentry/integrations/github/integration.py:85] Принудительный OAuth-редирект при отсутствии installation_id — OAuthLoginView сразу редиректит на GitHub authorize при отсутствии state, даже если installation_id не передан. Далее dispatch обнаруживает отсутствие installation_id и возвращает ошибку, создавая лишний круг.
  • [src/python/src/sentry/integrations/github/integration.py:174] Неявное изменение поведения для не-ACTIVE интеграций — Ранее интеграции со статусом != ACTIVE (например, DISABLED) могли продолжить привязку installation_id. Теперь добавлен фильтр status=ACTIVE и возвращается общая ошибка без детализации и наблюдаемости.
  • [src/python/src/sentry/integrations/github/integration.py:87] Дублирование привязки installation_id в состояние пайплайна — OAuthLoginView и GitHubInstallation.dispatch оба вызывают pipeline.bind_state('installation_id', ...). Второе присваивание избыточно и может перезаписать значение неочевидным образом.
  • [src/python/src/sentry/integrations/github/integration.py:89] Проверка валидности state выполняется после редиректа — При отсутствии 'state' диспетчер сразу редиректит с state=pipeline.signature, не проверяя, что pipeline.signature установлен и непуст. Возможен редирект с пустым state.
  • [src/python/src/sentry/integrations/github/integration.py:38] Допущение о форме self.active_organization в error()/get_document_origin() — get_document_origin разыменовывает org.organization.slug, а error() вызывается до того, как determine_active_organization гарантированно установит active_organization. Возможен AttributeError на edge-case.
  • [src/python/src/sentry/web/frontend/pipeline_advancer.py:13] Потеря расширяемости FORWARD_INSTALL_FOR из-за инлайнинга проверки — Замена списка FORWARD_INSTALL_FOR на жёсткое сравнение provider_id == 'github' убирает единый источник истины для провайдеров, поддерживающих установку вне пайплайна.
  • [src/python/tests/sentry/integrations/github/test_integration.py:84] Тест не покрывает ветку 'integration exists but metadata.sender missing' — В тесте state не совпадает с сигнатурой пайплайна, что проверяет только несовпадение state. Нет теста на отсутствие sender/login в метаданных — самую хрупкую ветку.

Автоматическое ревью от Review Engine v2.0

По вопросам и замечаниям, обращайтесь:

Александр Липкин
AlgLipkin@sberbank.ru

Хоруженко Александр
AVKhoruzhenko@sberbank.ru


def dispatch(self, request: Request, pipeline: Pipeline) -> HttpResponse:
if "installation_id" not in request.GET:
installation_id = request.GET.get(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 GET-параметр installation_id переопределяет состояние пайплайна после OAuth

GitHubInstallation.dispatch выбирает installation_id из request.GET, если он есть, игнорируя значение, сохранённое OAuthLoginView в состояние пайплайна. Это позволяет подменить установку через URL.

return error(request, self.active_organization)

# Check that the authenticated GitHub user is the same as who installed the app.
if (

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟠 KeyError при обращении к integration.metadata['sender']['login']

Новая проверка авторизации разыменовывает integration.metadata['sender']['login'] без проверок. Если в метаданных нет 'sender' или в нём нет 'login' (устаревшие/частичные вебхуки, установки вне вебхука), возникает KeyError и запрос падает.

}

# similar to OAuth2CallbackView.exchange_token
req = safe_urlopen(url=ghip.get_oauth_access_token_url(), data=data)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Слишком широкий except Exception скрывает ошибки обмена токеном

Ошибки сети, не-200 ответы, некорректный JSON, отсутствие code/access_token схлопываются в одну общую ошибку без логирования и метрик, что затрудняет диагностику.

github_client_id = ghip.get_oauth_client_id()
github_client_secret = ghip.get_oauth_client_secret()

installation_id = request.GET.get("installation_id")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Принудительный OAuth-редирект при отсутствии installation_id

OAuthLoginView сразу редиректит на GitHub authorize при отсутствии state, даже если installation_id не передан. Далее dispatch обнаруживает отсутствие installation_id и возвращает ошибку, создавая лишний круг.


# OrganizationIntegration does not exist, but Integration does exist.
pipeline.bind_state("installation_id", request.GET["installation_id"])
try:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 Неявное изменение поведения для не-ACTIVE интеграций

Ранее интеграции со статусом != ACTIVE (например, DISABLED) могли продолжить привязку installation_id. Теперь добавлен фильтр status=ACTIVE и возвращается общая ошибка без детализации и наблюдаемости.


installation_id = request.GET.get("installation_id")
if installation_id:
pipeline.bind_state("installation_id", installation_id)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Дублирование привязки installation_id в состояние пайплайна

OAuthLoginView и GitHubInstallation.dispatch оба вызывают pipeline.bind_state('installation_id', ...). Второе присваивание избыточно и может перезаписать значение неочевидным образом.

if installation_id:
pipeline.bind_state("installation_id", installation_id)

if not request.GET.get("state"):

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Проверка валидности state выполняется после редиректа

При отсутствии 'state' диспетчер сразу редиректит с state=pipeline.signature, не проверяя, что pipeline.signature установлен и непуст. Возможен редирект с пустым state.

return f"{account_type}:{name} {query}".encode()


def error(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 Допущение о форме self.active_organization в error()/get_document_origin()

get_document_origin разыменовывает org.organization.slug, а error() вызывается до того, как determine_active_organization гарантированно установит active_organization. Возможен AttributeError на edge-case.

# GitHub apps may be installed directly from GitHub, in which case
# they will redirect here *without* being in the pipeline. If that happens
# redirect to the integration install org picker.
if (

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

💡 Потеря расширяемости FORWARD_INSTALL_FOR из-за инлайнинга проверки

Замена списка FORWARD_INSTALL_FOR на жёсткое сравнение provider_id == 'github' убирает единый источник истины для провайдеров, поддерживающих установку вне пайплайна.

self.assertDialogSuccess(resp)
integration = Integration.objects.get(external_id=self.installation_id)
assert integration.provider == "github"
resp = self.client.get(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

💡 Тест не покрывает ветку 'integration exists but metadata.sender missing'

В тесте state не совпадает с сигнатурой пайплайна, что проверяет только несовпадение state. Нет теста на отсутствие sender/login в метаданных — самую хрупкую ветку.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant