Skip to content
Draft
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
13 changes: 10 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['8.2']
databases: ['sqlite']
server-versions: ['master', 'stable33']
# Pair each server branch with a PHP version it actually supports. A plain
# cross-product put PHP 8.2 against master, which requires 8.3 or newer, so
# `occ maintenance:install` aborted before any test ran.
include:
- php-versions: '8.2'
databases: 'sqlite'
server-versions: 'stable33'
- php-versions: '8.3'
databases: 'sqlite'
server-versions: 'master'

name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 3.2.8

### Fixed

- Update `league/commonmark` to 2.10.0, closing four high-severity denial-of-service
advisories and an `AttributesExtension` unsafe-link filter bypass on the converter
used to render issue and pull request bodies.
- Update bundled JavaScript dependencies to close the remaining high-severity
advisories (dompurify, js-yaml, nanoid).

## 3.2.7 - 2026-08-18

### Added
Expand Down
24 changes: 12 additions & 12 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
"node": "^24.0.0",
"npm": "^11.3.0"
},
"overrides": {
"dompurify": "^3.4.13",
"js-yaml": "^4.3.1",
"nanoid": "^3.3.18"
},
"dependencies": {
"@alchemyalcove/rgb-to-hsl": "^1.0.5",
"@highlightjs/vue-plugin": "^2.1.0",
Expand Down
21 changes: 21 additions & 0 deletions tests/integration/GitHubHtml.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ public static function findTwoFactorCheckupDelayForm(DOMXPath $selector): ?DOMEl
]);
}

/**
* Whether this is GitHub's "Verify your two-factor authentication (2FA) settings"
* checkup page, regardless of whether a dismissable delay form is present.
*
* The page is sometimes served with only a client-rendered
* `/settings/two_factor_checkup` form carrying no named inputs, which cannot be
* posted back. Detecting it separately from the delay form lets the caller report
* that specifically instead of failing with a generic "no form found".
*/
public static function isTwoFactorCheckupPage(DOMXPath $selector, string $url = ''): bool {
if (str_contains($url, 'two_factor_checkup')) {
return true;
}

$checkupForm = self::findForm($selector, [
'//form[contains(@action, "two_factor_checkup")]',
]);

return $checkupForm !== null;
}

public static function findTotpAlternativeUrl(DOMXPath $selector): ?string {
$linkSelectors = [
'//a[contains(@href, "two-factor/app")]',
Expand Down
20 changes: 20 additions & 0 deletions tests/integration/GithubOauthIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ private function interpretAuthenticatedResponse(string $body, string $finalUrl,
];
}

// The same checkup, but served with only a client-rendered form carrying no
// named inputs. There is nothing to post back, so it cannot be dismissed from
// here; report it distinctly so the caller can skip rather than fail on what is
// account state rather than a regression.
if (GitHubHtml::isTwoFactorCheckupPage($selector, $finalUrl)) {
return [
'status' => 'two_factor_checkup_blocked',
'checkup_url' => $finalUrl,
'body' => $body,
];
}

$isTwoFactorPage = GitHubHtml::findTwoFactorForm($selector) !== null
|| str_contains($finalUrl, 'two-factor')
|| str_contains($title, 'Two-factor authentication');
Expand Down Expand Up @@ -506,6 +518,14 @@ public function testOAuthLogin(): array {
$loginResult = $this->dismissTwoFactorCheckup($loginResult['body'], $loginResult['checkup_url'] ?? $authorizeUrl);
}

if ($loginResult['status'] === 'two_factor_checkup_blocked') {
$this->markTestSkipped(
'GitHub is showing the two-factor authentication checkup page for the CI account at '
. ($loginResult['checkup_url'] ?? $authorizeUrl) . ' and served no dismissable delay form. '
. 'Sign in as the CI account once and complete or postpone the checkup to re-enable this test.'
);
}

if ($loginResult['status'] === 'invalid_credentials') {
$this->fail('Invalid GitHub credentials');
}
Expand Down
Loading