fix(iam): keep revocation core active when new flows are disabled

This commit is contained in:
2026-10-29 20:38:08 +08:00
parent 3f3b4b400a
commit 27cab30c48
13 changed files with 59 additions and 20 deletions
@@ -22,6 +22,12 @@ final class IamV2Client
$settings ??= (array) Config::get('iam', []);
return filter_var($settings['v2_enabled'] ?? false, FILTER_VALIDATE_BOOL);
}
public static function feature(string $feature, ?array $settings = null): bool
{
$settings ??= (array) Config::get('iam', []);
if (!in_array($feature, ['management', 'launch'], true)) { return false; }
return self::enabled($settings) && filter_var($settings['v2_' . $feature . '_enabled'] ?? false, FILTER_VALIDATE_BOOL);
}
public static function https(string $url): string
{
$p = parse_url($url);
@@ -13,6 +13,11 @@ use app\common\cache\AdminAccountSafeCache;
final class IamV2Source
{
public const COOKIE = 'ZYT_IAM_V2_SESSION';
public static function requirePurpose(string $purpose): void
{
$feature = in_array($purpose, ['management', 'self_link', 'password_setup'], true) ? 'management' : ($purpose === 'launch' ? 'launch' : null);
if ($feature !== null && !IamV2Client::feature($feature)) { throw new RuntimeException('FEATURE_DISABLED'); }
}
public static function enroll(string $token, string $method, bool $cookie = true, ?int $authTime = null): array
{
if (!in_array($method, ['password', 'wecom', 'oidc', 'iam_launch'], true)) { throw new RuntimeException('REAUTH_REQUIRED'); }
@@ -70,10 +75,11 @@ final class IamV2Source
/** Service callback: local-only live authorization, safe while central holds its state lock. */
public static function liveStatus(string $id, int $version, string $purpose = '', array $targets = []): array
{
self::requirePurpose($purpose);
$s = self::check($id, $version);
$out = ['active' => true, 'actorLocalId' => (string) $s['admin_id'], 'sessionId' => $s['session_id'], 'sessionVersion' => (int) $s['version'],
'authMethod' => $s['auth_method'], 'authTime' => (int) $s['auth_time'], 'expiresAt' => (int) $s['expires_at']];
if ($purpose !== 'management') { return $out; }
if ($purpose !== 'management') { return $out + ['protectedAccount' => self::protectedAccount((int) $s['admin_id'])]; }
if ($s['auth_time'] < time() - 300) { throw new RuntimeException('REAUTH_REQUIRED'); }
if (!$targets || count($targets) > 100) { throw new RuntimeException('INVALID_INPUT'); }
foreach ($targets as $target) {
@@ -147,6 +153,7 @@ final class IamV2Source
}
public static function proof(array $session, string $purpose, array $targets = [], array $capabilities = [], ?string $transaction = null, array $extra = []): string
{
self::requirePurpose($purpose);
self::check($session['session_id'], (int) $session['version']);
$secret = bin2hex(random_bytes(32));
$payload = ['applicationId' => (string) Config::get('iam.application_id', 'zyt'), 'actorLocalId' => (string) $session['admin_id'],
@@ -160,6 +167,7 @@ final class IamV2Source
}
public static function introspect(string $proof, string $purpose, ?string $transaction = null): array
{
self::requirePurpose($purpose);
// The central Go attestor serializes an absent transaction as an empty string.
$transaction = $transaction === '' ? null : $transaction;
return Db::transaction(function () use ($proof, $purpose, $transaction) {
@@ -19,6 +19,7 @@ final class IamV2Target
public function __construct(private ?IamV2Client $client = null, private $resolve = null, private $login = null)
{
$this->settings = (array) Config::get('iam');
if (!IamV2Client::feature('launch', $this->settings)) { throw new RuntimeException('FEATURE_DISABLED'); }
$this->client ??= new IamV2Client();
$this->resolve ??= fn(array $result) => $this->resolveAdmin($result);
$this->login ??= function (int $id, array $authentication): array {