CaiWuXiTong/app/Traits/Companies.php
WuYongJie 9595267ee7
Some checks failed
Tests / PHP 8.1 (push) Has been cancelled
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
chore: 导入 Akaunting 初始源码与团队协作规则
2026-07-20 18:20:25 +08:00

209 lines
6.7 KiB
PHP

<?php
namespace App\Traits;
use App\Traits\Users;
trait Companies
{
use Users;
public $request = null;
public function getCompanyId(): ?int
{
if ($company_id = company_id()) {
return $company_id;
}
$request = $this->request ?: request();
if (request_is_api($request)) {
return $this->getCompanyIdFromApi($request);
}
if (request_is_mcp($request)) {
return $this->getCompanyIdFromMcp($request);
}
return $this->getCompanyIdFromWeb($request);
}
public function getCompanyIdFromWeb($request): ?int
{
return $this->getCompanyIdFromRoute($request) ?: ($this->getCompanyIdFromQuery($request) ?: $this->getCompanyIdFromHeader($request));
}
public function getCompanyIdFromApi($request): ?int
{
// Priority 0: OAuth token — when the request carries a Bearer token, the token's company_id is the authoritative source.
// This ensures an OAuth client can only access the company the token was issued for, regardless of URL or headers.
if (config('oauth.enabled', false) && $request->bearerToken()) {
$company_id = $this->getCompanyIdFromToken($request);
if ($company_id) {
return $company_id;
}
}
// Priority 1: Query string (?company_id=2)
$company_id = $this->getCompanyIdFromQuery($request);
// Priority 2: Header (X-Company: 2)
if (! $company_id) {
$company_id = $this->getCompanyIdFromHeader($request);
}
// Priority 3: First company of the user (fallback)
if (! $company_id) {
$company_id = $this->getFirstCompanyOfUser()?->id;
}
return $company_id;
}
public function getCompanyIdFromMcp($request): ?int
{
// Priority 0: OAuth Token (must be first since it doesn't rely on session or route)
$company_id = $this->getCompanyIdFromToken($request);
// Priority 1: Query string (?company_id=2)
if (! $company_id) {
$company_id = $this->getCompanyIdFromQuery($request);
}
// Priority 2: Header (X-Company: 2)
if (! $company_id) {
$company_id = $this->getCompanyIdFromHeader($request);
}
// Priority 3: First company of the user (fallback)
if (! $company_id) {
$user = null;
try {
$user = auth()->guard('passport')->user();
} catch (\Throwable $e) {
logger()->debug('OAuth: Passport guard unavailable during MCP company resolution', [
'error' => $e->getMessage(),
]);
}
if (! $user) {
$user = auth()->user();
}
if (! $user) {
logger()->debug('OAuth: No authenticated user found when trying to get first company');
return null;
}
$company = $user->withoutEvents(fn () => $user->companies()->enabled()->first());
$company_id = $company?->id;
}
return $company_id;
}
/**
* Get company ID from OAuth access token.
*
* When a user is authenticated via OAuth (Passport), extract the company_id
* that was assigned to the token during authorization approval.
*
* @param \Illuminate\Http\Request $request
* @return int|null
*/
protected function getCompanyIdFromToken($request): ?int
{
// Check if OAuth is enabled and user is authenticated via Passport
if (! config('oauth.enabled')) {
logger()->debug('OAuth: Disabled, skipping token company_id extraction');
return null;
}
try {
// Try passport guard first
if (auth()->guard('passport')->check()) {
$user = auth()->guard('passport')->user();
if ($user && method_exists($user, 'token')) {
$token = $user->token();
if ($token && isset($token->company_id) && $token->company_id) {
logger()->debug('OAuth: Extracted company_id from token', [
'company_id' => $token->company_id,
'user_id' => $user->id,
]);
return (int) $token->company_id;
}
// Token exists but no company_id stored — use the passport user's first company
logger()->debug('OAuth: No company_id on token, falling back to passport user\'s first company');
$company = $user->withoutEvents(fn () => $user->companies()->enabled()->first());
if ($company) {
return (int) $company->id;
}
logger()->debug('OAuth: No companies found for authenticated passport user');
}
logger()->debug('OAuth: No company_id found on authenticated user\'s token');
}
// Fallback: Try to get token from request directly
if ($request->bearerToken()) {
$tokenId = $request->bearerToken();
// Try to find the token in database
$tokenModel = config('oauth.company_aware', true)
? \Modules\Oauth\Models\AccessToken::withoutGlobalScope('company')->where('id', $tokenId)->first()
: null;
if ($tokenModel && isset($tokenModel->company_id)) {
logger()->debug('OAuth: Extracted company_id from token model', [
'company_id' => $tokenModel->company_id,
'token_id' => $tokenModel->id,
]);
return (int) $tokenModel->company_id;
}
logger()->debug('OAuth: No company_id found on token model for bearer token');
}
} catch (\Exception $e) {
// Silently fail if OAuth token checking fails
// This allows fallback to other methods
logger()->debug('OAuth: company_id extraction failed', [
'error' => $e->getMessage(),
]);
}
logger()->debug('OAuth: No company_id found in token');
return null;
}
public function getCompanyIdFromRoute($request): ?int
{
$route_id = (int) $request->route('company_id');
$segment_id = (int) $request->segment(1);
return $route_id ?: $segment_id;
}
public function getCompanyIdFromQuery($request): ?int
{
return (int) $request->query('company_id');
}
public function getCompanyIdFromHeader($request): ?int
{
return (int) $request->header('X-Company');
}
}