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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ the EngineBlock wiki.

Fixes:
* Fix Dotenv never loading `.env.{ENV}` overrides.
* Fix login failing with `ValueNotConvertible: ... Undefined array key "certData"` when reading `certificates` metadata written by EngineBlock < 7.2 (#2044). The PHP 8.5 Rector upgrade changed `X509CertificateLazyProxy` serialization from `__sleep()` to `__serialize()`, which silently changed the serialized key format stored in `sso_provider_roles_eb5`. The serialized format is restored to the legacy `__sleep()` format so old and new versions can read each other's data (red/green safe); no metadata re-push or migration is needed.

Features:
* Added `coin:azure_domain_hint` configuration option for IdPs. When set, EngineBlock appends a `whr=<domain>` query parameter to the HTTP-Redirect AuthnRequest sent to the IdP, allowing Microsoft Azure / EntraID to skip the account picker (#1864).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,23 @@ public function __call($methodName, $methodArguments)

/**
* Take care not to serialize the openSSL resource ($this->certificate).
*
* Keys use the mangled private-property format that __sleep() produced, so the
* serialized representation stored in the database stays readable by older
* EngineBlock versions (red/green deployments).
*/
public function __serialize(): array
{
return array('certData' => $this->certData, 'factory' => $this->factory);
$prefix = "\0" . self::class . "\0";

return array($prefix . 'certData' => $this->certData, $prefix . 'factory' => $this->factory);
}

public function __unserialize(array $data): void
{
$this->certData = $data['certData'];
$this->factory = $data['factory'];
$prefix = "\0" . self::class . "\0";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Man, thats ugly 🙈

But after digging in, it does seem like its the correct, robust fix.

https://github.com/DanieleAlessandra/php-serialize-compat/blob/main/SerializeCompat.php#L46

otoh, can we revert to the old sleep? I guess we should not, as it is 'soft deprecated'.

https://www.php.net/manual/en/language.oop5.magic.php#object.sleep


$this->certData = $data[$prefix . 'certData'] ?? $data['certData'];
$this->factory = $data[$prefix . 'factory'] ?? $data['factory'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,78 @@ public function test_survives_serialization_roundtrip()

$this->assertEquals($pemBefore, $restored->toPem(), 'Certificate data survives serialization roundtrip');
}

public function test_unserializes_legacy_sleep_format()
{
$certData = $this->validCertData();
$expectedPem = (new X509CertificateLazyProxy(new X509CertificateFactory(), $certData))->toPem();

$restored = unserialize($this->legacySerializedPayload($certData));

$this->assertEquals(
$expectedPem,
$restored->toPem(),
'Rows serialized by the pre-__serialize() code (mangled private property keys) must still unserialize'
);
}

public function test_unserializes_plain_key_format()
{
$certData = $this->validCertData();
$expectedPem = (new X509CertificateLazyProxy(new X509CertificateFactory(), $certData))->toPem();

$proxyClass = X509CertificateLazyProxy::class;
$factoryClass = X509CertificateFactory::class;
$payload = sprintf(
'O:%d:"%s":2:{%s%ss:7:"factory";O:%d:"%s":0:{}}',
strlen($proxyClass),
$proxyClass,
serialize('certData'),
serialize($certData),
strlen($factoryClass),
$factoryClass
);

$restored = unserialize($payload);

$this->assertEquals(
$expectedPem,
$restored->toPem(),
'Rows serialized with plain __serialize() keys must also unserialize'
);
}

public function test_serializes_to_legacy_sleep_format()
{
$certData = $this->validCertData();
$proxy = new X509CertificateLazyProxy(new X509CertificateFactory(), $certData);

$this->assertSame(
$this->legacySerializedPayload($certData),
serialize($proxy),
'Serialized format must stay identical to the legacy __sleep() format so older EngineBlock versions can read it'
);
}

private function legacySerializedPayload(string $certData): string
{
$proxyClass = X509CertificateLazyProxy::class;
$factoryClass = X509CertificateFactory::class;

return sprintf(
'O:%d:"%s":2:{%s%s%sO:%d:"%s":0:{}}',
strlen($proxyClass),
$proxyClass,
serialize("\0" . $proxyClass . "\0certData"),
serialize($certData),
serialize("\0" . $proxyClass . "\0factory"),
strlen($factoryClass),
$factoryClass
);
}

private function validCertData(): string
{
return "MIIDXzCCAkegAwIBAgIJAM4CwNsdIhJ3MA0GCSqGSIb3DQEBBQUAMEYxDzANBgNVBAMMBkVuZ2luZTERMA8GA1UECwwIU2VydmljZXMxEzARBgNVBAoMCk9wZW5Db25leHQxCzAJBgNVBAYTAk5MMB4XDTE0MDUxMjEzMjIxNloXDTI0MDUxMTEzMjIxNlowRjEPMA0GA1UEAwwGRW5naW5lMREwDwYDVQQLDAhTZXJ2aWNlczETMBEGA1UECgwKT3BlbkNvbmV4dDELMAkGA1UEBhMCTkwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2aQ9OYsAASWR/aN5NB2mQqFsBc13uN0nSbjkk1Um8VouGo7OmSM0eiur5my8UvPYth1DXQM6u2wiFq19RtfVZWJOmrzAfVHc9VRj9Xj4T+MVpR4bDWctvxVT1OPm9L23KQKvaqDmUo7uSPsBD36EIH7dFOBydDtSXfZTW0ien+lZr6C4nPuxzDbHJ+Jlo2brieimUBQNetX/ettnAglJ9536sJDkhsa120mkYPhVnvepbOtxPyU5ZDUpDNmMQR2/SORCBJcfvLSVZ4It4O67l6/EJnkFRLerIqOpk/W8jY3USQaLM2WM7sWBGxEFKDVcTFgrOH50Z94K2M/KweY2bAgMBAAGjUDBOMB0GA1UdDgQWBBSewI9OzfzbIxnl6XMkaQkYY1hHPjAfBgNVHSMEGDAWgBSewI9OzfzbIxnl6XMkaQkYY1hHPjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAFfYPZbsYPHz4ypV/aO59do7CtHPnAMWr0NcQt4h9IW8gjihaNHt12V30QtHrVaXejXybB/LaGbPPyA64+l/SeC7ksrRxlitCwFqnws6ISXJaYU0iEFHGUD/cAj1iGloIsOm5IOdb3sdG/SsBv49G8es2wG0rDd0/s2fBVvXd4qUoXzKJAjYk1MFQxnGHomlt67SBrr2QLh+m2VHg+mkdi6yrdm9B9ylF8V55Vl82pPZXxphIRgqdos5YWeALS7dr5dSw9s5smFBxyy8IfCQMxagfNC59w22w2ULC/J7au/oP8ylusuxncxizdR/+5UazzAlOWtkjzaABzzBWM4hEK";
}
}