fix(iam): align cross-app proofs and add native IAM target launch
This commit is contained in:
@@ -7,17 +7,17 @@ use RuntimeException;
|
||||
use think\facade\Config;
|
||||
use think\facade\Db;
|
||||
use app\adminapi\service\iam\IamOidcClient;
|
||||
use app\adminapi\service\iam\v2\{IamV2Client, IamV2Source, IamV2Manager, IamV2Flow};
|
||||
use app\adminapi\service\iam\v2\{IamV2Client, IamV2Source, IamV2Manager, IamV2Flow, IamV2Target};
|
||||
|
||||
/** Every endpoint authorizes itself. Never relies on legacy unregistered-route allow rules. */
|
||||
final class IamV2Controller extends BaseAdminController
|
||||
{
|
||||
public array $notNeedLogin = ['status', 'introspect', 'checkSession', 'revocation', 'launchContinue', 'selfCallback', 'passwordContinue'];
|
||||
public array $notNeedLogin = ['status', 'introspect', 'checkSession', 'revocation', 'launchContinue', 'selfCallback', 'passwordContinue', 'targetConfig', 'targetPrepare', 'targetCallback', 'targetExchange', 'targetReset'];
|
||||
private function invokeV2(callable $fn)
|
||||
{
|
||||
try {
|
||||
if (!IamV2Client::enabled()) { throw new RuntimeException('FEATURE_DISABLED'); }
|
||||
$reads = ['status','accounts','identities','operationresult','audit','job'];
|
||||
$reads = ['status','accounts','identities','operationresult','audit','job','targetconfig'];
|
||||
$read = in_array(strtolower((string) $this->request->action()), $reads, true);
|
||||
if (($read && !$this->request->isGet()) || (!$read && !$this->request->isPost())) { throw new RuntimeException('METHOD_NOT_ALLOWED'); }
|
||||
return $this->data($fn())->header(['Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer']);
|
||||
@@ -57,7 +57,9 @@ final class IamV2Controller extends BaseAdminController
|
||||
$fresh = false; try { $s = IamV2Source::current($token); $fresh = $s['auth_time'] >= time() - 300; } catch (\Throwable $e) { $s = null; }
|
||||
$b = (new IamV2Manager())->binding((int) $legacy['admin_id']);
|
||||
return ['enabled' => true, 'session_ready' => $s !== null, 'reauth_required' => !$fresh,
|
||||
'management_capabilities' => IamV2Source::capabilities((int) $legacy['admin_id'])] + $b;
|
||||
'management_capabilities' => IamV2Source::capabilities((int) $legacy['admin_id']),
|
||||
'protected_review_operator' => IamV2Source::reviewOperator((int) $legacy['admin_id']),
|
||||
'protected_account' => IamV2Source::protectedAccount((int) $legacy['admin_id'])] + $b;
|
||||
});
|
||||
}
|
||||
public function reauth()
|
||||
@@ -100,8 +102,9 @@ final class IamV2Controller extends BaseAdminController
|
||||
}
|
||||
public function checkSession()
|
||||
{
|
||||
return $this->serviceResult(function () { $this->serviceAuth(); $b = $this->input(); $s = IamV2Source::check((string) ($b['sessionId'] ?? ''), (int) ($b['sessionVersion'] ?? 0));
|
||||
return ['active' => true, 'actorLocalId' => (string) $s['admin_id'], 'authMethod' => $s['auth_method'], 'authTime' => (int) $s['auth_time'], 'expiresAt' => (int) $s['expires_at']]; });
|
||||
return $this->serviceResult(function () { $this->serviceAuth(); $b = $this->input();
|
||||
return IamV2Source::liveStatus((string) ($b['sessionId'] ?? ''), (int) ($b['sessionVersion'] ?? 0),
|
||||
(string) ($b['purpose'] ?? ''), (array) ($b['targetLocalIds'] ?? [])); });
|
||||
}
|
||||
public function launchStart()
|
||||
{
|
||||
@@ -123,7 +126,9 @@ final class IamV2Controller extends BaseAdminController
|
||||
if (($transaction['sourceApplicationId'] ?? '') !== 'zyt' || ($transaction['targetApplicationId'] ?? '') !== 'zmxy-erp') { throw new RuntimeException('ACCOUNT_MISMATCH'); }
|
||||
(new IamV2Manager($client))->assertLinked((int) $s['admin_id']);
|
||||
$grant = $client->call('POST', '/launch-grants', ['transactionId' => $tx, 'proof' => IamV2Source::proof($s, 'launch', [(int) $s['admin_id']], [], $tx)]);
|
||||
$delivery = IamV2Client::origin((string) Config::get('iam.erp_url')) . '/api/erp/iam/v2/launch/callback';
|
||||
$erpOrigin = IamV2Client::origin((string) Config::get('iam.erp_url'));
|
||||
$delivery = IamV2Client::https((string) Config::get('iam.erp_callback_url') ?: $erpOrigin . '/api/erp/iam/v2/launch/callback');
|
||||
if (!in_array($delivery, [$erpOrigin . '/api/erp/iam/v2/launch/callback', $erpOrigin . '/basic-api/api/erp/iam/v2/launch/callback'], true)) { throw new RuntimeException('INVALID_FIXED_URL'); }
|
||||
if (($grant['deliveryUrl'] ?? '') !== $delivery) { throw new RuntimeException('INVALID_FIXED_URL'); }
|
||||
return response(IamV2Flow::form($delivery, ['transaction_id' => $tx, 'grant' => $grant['grant']]))->header(['Content-Type' => 'text/html; charset=utf-8', 'Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer']);
|
||||
} catch (\Throwable $e) { return redirect('/admin/user/setting?iam_result=launch_failed')->header(['Cache-Control' => 'no-store']); }
|
||||
@@ -176,6 +181,55 @@ final class IamV2Controller extends BaseAdminController
|
||||
return response(IamV2Flow::form($url, ['request_id' => $id, 'state' => $state, 'proof' => $proof]))->header(['Content-Type' => 'text/html; charset=utf-8', 'Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer']);
|
||||
} catch (\Throwable $e) { return redirect('/admin/user/setting?iam_result=password_failed'); }
|
||||
}
|
||||
private function targetOrigin(): void
|
||||
{
|
||||
if (!$this->request->isPost() || (string) $this->request->header('origin') !== IamV2Client::origin((string) Config::get('iam.public_url'))) { throw new RuntimeException('INVALID_ORIGIN'); }
|
||||
}
|
||||
public function targetConfig()
|
||||
{
|
||||
if (!IamV2Client::enabled()) { return $this->data(['enabled' => false]); }
|
||||
return $this->invokeV2(function () {
|
||||
$source = (string) $this->request->get('source', 'iam-portal');
|
||||
return ['enabled' => true, 'restart_url' => IamV2Client::origin((string) Config::get('iam.portal_url')) . ($source === 'iam-admin' ? '/admin' : '/portal')];
|
||||
});
|
||||
}
|
||||
public function targetPrepare()
|
||||
{
|
||||
return $this->invokeV2(function () { $this->targetOrigin();
|
||||
$out = (new IamV2Target())->prepare((string) $this->request->post('source', ''), (string) $this->request->post('intent', ''),
|
||||
(string) $this->request->header('token', ''), (string) ($_COOKIE[IamV2Source::COOKIE] ?? ''));
|
||||
setcookie(IamV2Target::COOKIE, $out['browser'], ['expires' => time() + 300, 'path' => '/adminapi/iam/v2/launch', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
|
||||
return ['url' => $out['url']];
|
||||
});
|
||||
}
|
||||
public function targetCallback()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) { throw new RuntimeException('METHOD_NOT_ALLOWED'); }
|
||||
$out = (new IamV2Target())->callback((string) $this->request->post('transaction_id', ''), (string) $this->request->post('grant', ''),
|
||||
(string) ($_COOKIE[IamV2Target::COOKIE] ?? ''), (string) $this->request->header('origin', ''));
|
||||
return redirect('/admin/iam/launch?' . http_build_query($out))->header(['Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer']);
|
||||
} catch (\Throwable $e) {
|
||||
$code = preg_match('/^[A-Z_]{3,64}$/D', $e->getMessage()) ? $e->getMessage() : 'SERVICE_UNAVAILABLE';
|
||||
return redirect('/admin/iam/launch?iam_error=' . rawurlencode($code))->header(['Cache-Control' => 'no-store', 'Referrer-Policy' => 'no-referrer']);
|
||||
}
|
||||
}
|
||||
public function targetExchange()
|
||||
{
|
||||
return $this->invokeV2(function () { $this->targetOrigin();
|
||||
return (new IamV2Target())->exchange((string) $this->request->post('ticket', ''), (string) ($_COOKIE[IamV2Target::COOKIE] ?? ''),
|
||||
(string) $this->request->header('token', ''), (string) ($_COOKIE[IamV2Source::COOKIE] ?? ''));
|
||||
});
|
||||
}
|
||||
public function targetReset()
|
||||
{
|
||||
return $this->invokeV2(function () { $this->targetOrigin();
|
||||
IamV2Source::revokeSession((string) ($_COOKIE[IamV2Source::COOKIE] ?? ''));
|
||||
setcookie(IamV2Source::COOKIE, '', ['expires' => 1, 'path' => '/adminapi', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
|
||||
setcookie(IamV2Target::COOKIE, '', ['expires' => 1, 'path' => '/adminapi/iam/v2/launch', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
|
||||
return ['reset' => true];
|
||||
});
|
||||
}
|
||||
public function revocation()
|
||||
{
|
||||
return $this->serviceResult(fn() => \app\adminapi\service\iam\v2\IamV2Events::accept(
|
||||
|
||||
Reference in New Issue
Block a user