钉钉登录权限:财务中心看全部,其他在职员工只看风险管控两个页面 #489
@ -28,6 +28,7 @@ class DingTalkClosedAccess
|
||||
}
|
||||
|
||||
$roster = app(FinanceRoster::class)->findEligible((string) ($profile['dingtalk_user_id'] ?? ''));
|
||||
$restricted = $roster !== null && ($roster['restricted'] ?? false) === true;
|
||||
$supplierRoster = $roster ? null : app(SupplierPortalAccess::class)->resolve(
|
||||
(string) ($profile['dingtalk_user_id'] ?? ''),
|
||||
(string) ($profile['union_id'] ?? ''),
|
||||
@ -77,6 +78,9 @@ class DingTalkClosedAccess
|
||||
'nickname' => $profile['nickname'] ?: $identity->nickname,
|
||||
])->save();
|
||||
|
||||
// 按当前部门刷新访问范围:财务中心=全部,其它在职员工=风险管控两个页面。
|
||||
$this->applyAccessScope($identity->user, $restricted);
|
||||
|
||||
return $identity;
|
||||
}
|
||||
|
||||
@ -90,7 +94,7 @@ class DingTalkClosedAccess
|
||||
return null;
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($profile, $roster, $supplierRoster, $request): ?DingTalkIdentity {
|
||||
return DB::transaction(function () use ($profile, $roster, $restricted, $supplierRoster, $request): ?DingTalkIdentity {
|
||||
// Serialize first-time provisioning on the target company. Both the
|
||||
// account and unique identity are committed together, including retries.
|
||||
$companies = Company::enabled()
|
||||
@ -135,8 +139,10 @@ class DingTalkClosedAccess
|
||||
'created_by' => 0,
|
||||
'created_from' => $supplierPortal
|
||||
? SupplierPortalAccess::CREATED_FROM
|
||||
: (FinanceRoster::isLimitedLoginUser((string) $profile['dingtalk_user_id'])
|
||||
? 'dingtalk_roster_limited' : 'dingtalk_roster'),
|
||||
: ($restricted
|
||||
? 'dingtalk_restricted'
|
||||
: (FinanceRoster::isLimitedLoginUser((string) $profile['dingtalk_user_id'])
|
||||
? 'dingtalk_roster_limited' : 'dingtalk_roster')),
|
||||
]);
|
||||
$account->roles()->attach($role->id);
|
||||
// Explicit designation controls the role; ordinary roster users get shell access.
|
||||
@ -168,6 +174,24 @@ class DingTalkClosedAccess
|
||||
}, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动开通的钉钉账号按当前部门刷新访问范围;手工账号不受影响。
|
||||
*/
|
||||
private function applyAccessScope(User $user, bool $restricted): void
|
||||
{
|
||||
$createdFrom = (string) $user->created_from;
|
||||
|
||||
if (! in_array($createdFrom, ['dingtalk_roster', 'dingtalk_restricted'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target = $restricted ? 'dingtalk_restricted' : 'dingtalk_roster';
|
||||
|
||||
if ($createdFrom !== $target) {
|
||||
$user->forceFill(['created_from' => $target])->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function recordLogin(DingTalkIdentity $identity, Request $request): void
|
||||
{
|
||||
$this->audit('login_success', 'success', $identity->user_id, null, $identity->union_id, $request);
|
||||
|
||||
@ -87,16 +87,17 @@ class FinanceRoster
|
||||
return null;
|
||||
}
|
||||
$allowed = array_map('strval', config('dingtalk.finance_department_ids', []));
|
||||
if (! self::isDesignatedSuperAdmin($userId)
|
||||
&& ! self::isLimitedLoginUser($userId)
|
||||
&& ! array_intersect($allowed, array_map('strval', array_column($ancestors, 'Id')))) {
|
||||
return null;
|
||||
}
|
||||
// 财务中心(或指定超级管理员/受限登录名单)拥有完整权限;
|
||||
// 其它在职员工返回 restricted,只能访问风险管控下的两个页面。
|
||||
$inFinance = self::isDesignatedSuperAdmin($userId)
|
||||
|| self::isLimitedLoginUser($userId)
|
||||
|| (bool) array_intersect($allowed, array_map('strval', array_column($ancestors, 'Id')));
|
||||
|
||||
return [
|
||||
'roster_id' => (string) $flower['Id'],
|
||||
'display_name' => trim((string) ($flower['flowerName'] ?: $flower['name'])),
|
||||
'department_ids' => $departmentIds,
|
||||
'restricted' => ! $inFinance,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -50,6 +50,11 @@ class SidebarPermissions
|
||||
return [SupplierPortalAccess::PAGE_KEY];
|
||||
}
|
||||
|
||||
// 非财务中心员工:只能看到财务经营风险管控下的两个页面。
|
||||
if (self::hasRestrictedDingTalkAccess($user)) {
|
||||
return self::normalizeAllowedKeys((array) config('dingtalk.restricted_page_keys', []));
|
||||
}
|
||||
|
||||
if (self::hasLimitedDingTalkAccess($user)) {
|
||||
return config('dingtalk.limited_login_page_keys', []);
|
||||
}
|
||||
@ -88,6 +93,11 @@ class SidebarPermissions
|
||||
&& FinanceRoster::isLimitedLoginUser((string) $user->dingtalkIdentity?->dingtalk_user_id));
|
||||
}
|
||||
|
||||
public static function hasRestrictedDingTalkAccess(User $user): bool
|
||||
{
|
||||
return $user->created_from === 'dingtalk_restricted';
|
||||
}
|
||||
|
||||
public static function navigationFor(User $user): array
|
||||
{
|
||||
$navigation = BusinessNavigation::all();
|
||||
|
||||
@ -20,6 +20,11 @@ return [
|
||||
'courier-payables/courier-information',
|
||||
'courier-payables/quote-management',
|
||||
],
|
||||
// 非财务中心员工:可以登录,但只能访问财务经营风险管控下的这两个页面。
|
||||
'restricted_page_keys' => [
|
||||
'financial-risk-control/store-violation-alert',
|
||||
'financial-risk-control/process-statistics',
|
||||
],
|
||||
// When omitted, automatic provisioning requires exactly one enabled company.
|
||||
'company_id' => env('DINGTALK_COMPANY_ID'),
|
||||
|
||||
|
||||
@ -138,6 +138,46 @@ class DingTalkLoginTest extends FeatureTestCase
|
||||
->assertSee('data-cockpit', false);
|
||||
}
|
||||
|
||||
public function test_non_finance_employee_only_gets_the_two_risk_control_pages(): void
|
||||
{
|
||||
$this->mock(FinanceRoster::class)->shouldReceive('findEligible')->andReturn([
|
||||
'roster_id' => '99001',
|
||||
'display_name' => '运营中心员工',
|
||||
'department_ids' => ['777001'],
|
||||
'restricted' => true,
|
||||
]);
|
||||
Http::preventStrayRequests();
|
||||
Http::fake($this->dingtalkResponses('union-restricted', 'open-restricted'));
|
||||
$state = $this->startAuthorization();
|
||||
$this->get(route('login.dingtalk.callback', ['code' => 'fixture', 'state' => $state]))
|
||||
->assertRedirect(route('dashboard', ['company_id' => $this->company->id]));
|
||||
|
||||
$created = auth()->user();
|
||||
$this->assertSame('dingtalk_restricted', $created->created_from);
|
||||
$this->assertSame([
|
||||
'financial-risk-control/store-violation-alert',
|
||||
'financial-risk-control/process-statistics',
|
||||
], \App\Utilities\SidebarPermissions::allowedKeys($created));
|
||||
|
||||
$this->get(route('business.pages.show', [
|
||||
'company_id' => $this->company->id,
|
||||
'section' => 'financial-risk-control',
|
||||
'page' => 'store-violation-alert',
|
||||
]))->assertOk();
|
||||
|
||||
$this->get(route('business.pages.show', [
|
||||
'company_id' => $this->company->id,
|
||||
'section' => 'financial-risk-control',
|
||||
'page' => 'process-statistics',
|
||||
]))->assertOk();
|
||||
|
||||
$this->withExceptionHandling()->get(route('business.pages.show', [
|
||||
'company_id' => $this->company->id,
|
||||
'section' => 'order-data',
|
||||
'page' => 'raw-data',
|
||||
]))->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_limited_login_gets_only_three_courier_pages_and_blocks_direct_urls(): void
|
||||
{
|
||||
config(['dingtalk.limited_login_user_ids' => ['ding-user-1']]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user