Repository navigation
Conversation
handleDebugMode() validated the ticket it had just issued against the service from a lowercase 'target' parameter, which no client sends: login() and the validation endpoints read 'TARGET'. With only TARGET given, debug mode therefore answered "Missing service parameter". It also passed the 'renew' query string straight to validate(), which takes a bool, so renew=true failed with a TypeError under strict types. Read 'TARGET', convert 'renew' with FILTER_VALIDATE_BOOL, and test debug mode with service and with TARGET, each with and without renew. Assisted-by: Claude Code (claude-opus-5-5, implementation & unit tests) Assisted-by: Google Antigravity (gemini-3.8-flash-high, review)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #87 +/- ##
============================================
+ Coverage 69.70% 71.42% +1.72%
Complexity 394 394
============================================
Files 25 25
Lines 1274 1274
============================================
+ Hits 888 910 +22
+ Misses 386 364 -22 🚀 New features to boost your workflow:
|
| method: 'serviceValidate', | ||
| renew: $request->get('renew', false), | ||
| target: $request->get('target'), | ||
| // The query parameter is a string, validate() takes a bool |
There was a problem hiding this comment.
Thanks for catching this! Could we go one step further and remove the $request->get(...) / $request->query->get(...) calls from handleDebugMode() entirely? login() could pass the values it has already parsed straight into it.
login() already gets bool $renew, ?string $TARGET and ?string $service through #[MapQueryParameter], so Symfony has already read and validated them. Right now handleDebugMode() reads them from the request a second time, which repeats the parsing. That's also why filter_var is needed. Reading them twice causes a few problems:
- Different sources:
Request::get()checks route attributes first, then the query string, then the POST body.login()only reads the query string. A POST body or route attribute withTARGET/renewcould therefore reach the validation even though the ticket was never issued with it. - Different parsing: an invalid value such as
renew=yes-pleaseis rejected bylogin()'s mapper, butfilter_varquietly turns it intofalse. Array input (TARGET[]=x) causes a TypeError. - Different result: in any of these cases, debug mode isn't validating the ticket under the conditions it was issued with (
forceAuthn = $renew, service =$service ?? $TARGET). Checking exactly that is the whole point of debug mode. - Deprecation:
Request::get()is deprecated in Symfony 7.4.
If login() passes its mapped values, there's only one source of truth. filter_var and its import can go, and every value is type-checked by Symfony up front. Adding the new parameters at the end with defaults keeps existing callers of this public method working:
// login(): also map pgtUrl
#[MapQueryParameter] ?string $pgtUrl = null,
// ...
[$templateName, $statusCode, $DebugModeXmlString] = $this->handleDebugMode(
$request,
$debugMode,
$serviceTicket,
$renew,
$TARGET,
$service,
$pgtUrl,
);
// handleDebugMode()
public function handleDebugMode(
Request $request,
?string $debugMode,
array $serviceTicket,
bool $renew = false,
?string $target = null,
?string $service = null,
?string $pgtUrl = null,
): array {
// ...
$xmlResponse = $this->validate(
request: $request,
method: 'serviceValidate',
renew: $renew,
target: $target,
ticket: $serviceTicket['id'],
service: $service,
pgtUrl: $pgtUrl,
);
handleDebugMode() validated the ticket it had just issued against the service from a lowercase 'target' parameter, which no client sends: login() and the validation endpoints read 'TARGET'. With only TARGET given, debug mode therefore answered "Missing service parameter". It also passed the 'renew' query string straight to validate(), which takes a bool, so renew=true failed with a TypeError under strict types.
Read 'TARGET', convert 'renew' with FILTER_VALIDATE_BOOL, and test debug mode with service and with TARGET, each with and without renew.
Assisted-by: Claude Code (claude-opus-5-5, implementation & unit tests)
Assisted-by: Google Antigravity (gemini-3.8-flash-high, review)