feat(biometrics): enforce strict branch scoping on biometric attendance and sync
- Restrict biometric attendances query and grouped records strictly to employees of assigned branch - Scope bulk sync (syncAll) to only sync biometric records for employees within the user's branch - Guard individual sync and custom sync against processing employees from foreign branches - Tighten attendance records query to strictly filter by employee assigned branch - Add feature test verifying biometric attendance isolation and permission guards
This commit is contained in:
@@ -169,20 +169,22 @@ class BiometricAttendanceController extends Controller
|
||||
// Filter by permissions
|
||||
if ($scopedBranchId) {
|
||||
$branchEmpCodes = \App\Models\Employee::where('branch_id', $scopedBranchId)->whereNotNull('biometric_emp_id')->pluck('biometric_emp_id');
|
||||
$query->where(function($q) use ($scopedBranchId, $branchEmpCodes) {
|
||||
$q->where('branch_id', $scopedBranchId)
|
||||
->orWhereIn('biometric_emp_id', $branchEmpCodes);
|
||||
});
|
||||
if ($branchEmpCodes->isEmpty()) {
|
||||
$query->whereRaw('1 = 0');
|
||||
} else {
|
||||
$query->whereIn('biometric_emp_id', $branchEmpCodes);
|
||||
}
|
||||
} elseif ($canManageAny) {
|
||||
// Full company access
|
||||
} elseif ($canManageBranch) {
|
||||
$branchId = $user->branch_id ?? $user->employee?->branch_id;
|
||||
if ($branchId) {
|
||||
$branchEmpCodes = \App\Models\Employee::where('branch_id', $branchId)->whereNotNull('biometric_emp_id')->pluck('biometric_emp_id');
|
||||
$query->where(function($q) use ($branchId, $branchEmpCodes) {
|
||||
$q->where('branch_id', $branchId)
|
||||
->orWhereIn('biometric_emp_id', $branchEmpCodes);
|
||||
});
|
||||
if ($branchEmpCodes->isEmpty()) {
|
||||
$query->whereRaw('1 = 0');
|
||||
} else {
|
||||
$query->whereIn('biometric_emp_id', $branchEmpCodes);
|
||||
}
|
||||
} else {
|
||||
// Manager without a specific branch assigned has company-wide management
|
||||
}
|
||||
@@ -266,6 +268,13 @@ class BiometricAttendanceController extends Controller
|
||||
})
|
||||
->values();
|
||||
|
||||
if ($scopedBranchId) {
|
||||
$groupedAttendances = $groupedAttendances->filter(function ($item) use ($employees, $scopedBranchId) {
|
||||
$emp = $employees->get($item['employee_code']);
|
||||
return $emp && (int)$emp->branch_id === (int)$scopedBranchId;
|
||||
})->values();
|
||||
}
|
||||
|
||||
// Attach employee names
|
||||
$groupedAttendances = $groupedAttendances->map(function($item) use ($employees) {
|
||||
$emp = $employees->get($item['employee_code']);
|
||||
@@ -437,7 +446,19 @@ class BiometricAttendanceController extends Controller
|
||||
|
||||
$overwrite = $request->boolean('overwrite', false);
|
||||
|
||||
$scopedBranchId = authBranchId();
|
||||
$query = \App\Models\BiometricAttendance::query();
|
||||
|
||||
if ($scopedBranchId) {
|
||||
$branchEmpCodes = Employee::where('branch_id', $scopedBranchId)
|
||||
->whereNotNull('biometric_emp_id')
|
||||
->pluck('biometric_emp_id');
|
||||
if ($branchEmpCodes->isEmpty()) {
|
||||
$query->whereRaw('1 = 0');
|
||||
} else {
|
||||
$query->whereIn('biometric_emp_id', $branchEmpCodes);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$overwrite) {
|
||||
$query->where('sync_status', 'pending');
|
||||
@@ -456,11 +477,15 @@ class BiometricAttendanceController extends Controller
|
||||
|
||||
// Eager-load employees with shifts
|
||||
$empIds = $pendingRecords->pluck('biometric_emp_id')->unique();
|
||||
$employees = Employee::with(['user', 'shift'])
|
||||
$empQuery = Employee::with(['user', 'shift'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('biometric_emp_id', $empIds)
|
||||
->get()
|
||||
->keyBy('biometric_emp_id');
|
||||
->whereIn('biometric_emp_id', $empIds);
|
||||
|
||||
if ($scopedBranchId) {
|
||||
$empQuery->where('branch_id', $scopedBranchId);
|
||||
}
|
||||
|
||||
$employees = $empQuery->get()->keyBy('biometric_emp_id');
|
||||
|
||||
$groupedAttendances = $pendingRecords->groupBy(function ($item) use ($employees) {
|
||||
$employee = $employees->get($item->biometric_emp_id);
|
||||
@@ -591,6 +616,11 @@ class BiometricAttendanceController extends Controller
|
||||
|
||||
$employee = Employee::with(['user', 'shift', 'branch'])->whereIn('created_by', getCompanyAndUsersId())->where('biometric_emp_id', $biometricEmpId)->first();
|
||||
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId && $employee && (int)$employee->branch_id !== (int)$scopedBranchId) {
|
||||
return redirect()->back()->with('error', __('Permission Denied. Employee belongs to another branch.'));
|
||||
}
|
||||
|
||||
if ($employee) {
|
||||
// Check if record already exists
|
||||
$exists = AttendanceRecord::where('employee_id', $employee->user_id)
|
||||
@@ -690,6 +720,11 @@ class BiometricAttendanceController extends Controller
|
||||
return redirect()->back()->with('error', __('Employee not found.'));
|
||||
}
|
||||
|
||||
$scopedBranchId = authBranchId();
|
||||
if ($scopedBranchId && (int)$employee->branch_id !== (int)$scopedBranchId) {
|
||||
return redirect()->back()->with('error', __('Permission Denied. Employee belongs to another branch.'));
|
||||
}
|
||||
|
||||
$shift = Shift::where('id', $employee->shift_id)->where('status', 'active')->first() ?? Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
// Check if record already exists
|
||||
|
||||
Reference in New Issue
Block a user