Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
CompanyRolePlayPolicy
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
7 / 7
13
100.00% covered (success)
100.00%
1 / 1
 view
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 update
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 delete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 manageSessions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 sameCompany
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isCompanyAdmin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Policies;
4
5use App\Http\Models\Auth\Role;
6use App\Http\Models\Auth\User;
7use App\Http\Models\CompanyRolePlayProject;
8
9/**
10 * Authorization policy for corporate {@see CompanyRolePlayProject} personas.
11 *
12 * The rules are deliberately company-scoped and role-aware: mutating a
13 * persona is restricted to company Global Admins (or Vengreso super-admins,
14 * who surface as Global Admins after a company masquerade). Cross-company
15 * access is never permitted, even for Vengreso admins — the effective-user
16 * check (`auth()->user()->company_id`) is sufficient because masquerade
17 * swaps the token. See Phase 1 brief decision #7.
18 */
19class CompanyRolePlayPolicy
20{
21    /**
22     * Determine whether the user may read a specific corporate persona.
23     *
24     * Any user in the owning company can read; visibility (group/user
25     * assignment) is enforced separately at query time via
26     * {@see CompanyRolePlayProject::scopeAvailableToUser()}.
27     */
28    public function view(User $user, CompanyRolePlayProject $persona): bool
29    {
30        return $this->sameCompany($user, $persona);
31    }
32
33    /**
34     * Determine whether the user may create a corporate persona.
35     *
36     * Requires a Global Admin (or Vengreso Admin) role and a non-null
37     * `company_id` on the authenticated user.
38     */
39    public function create(User $user): bool
40    {
41        return $this->isCompanyAdmin($user) && ! empty($user->company_id);
42    }
43
44    /**
45     * Determine whether the user may update a specific corporate persona.
46     */
47    public function update(User $user, CompanyRolePlayProject $persona): bool
48    {
49        return $this->sameCompany($user, $persona) && $this->isCompanyAdmin($user);
50    }
51
52    /**
53     * Determine whether the user may delete a specific corporate persona.
54     */
55    public function delete(User $user, CompanyRolePlayProject $persona): bool
56    {
57        return $this->sameCompany($user, $persona) && $this->isCompanyAdmin($user);
58    }
59
60    /**
61     * Determine whether the user may browse aggregate sessions/analytics
62     * belonging to a specific corporate persona.
63     */
64    public function manageSessions(User $user, CompanyRolePlayProject $persona): bool
65    {
66        return $this->sameCompany($user, $persona) && $this->isCompanyAdmin($user);
67    }
68
69    /**
70     * Company match — the single invariant that gates every method.
71     */
72    private function sameCompany(User $user, CompanyRolePlayProject $persona): bool
73    {
74        if (empty($user->company_id) || empty($persona->company_id)) {
75            return false;
76        }
77
78        return (string) $user->company_id === (string) $persona->company_id;
79    }
80
81    /**
82     * Whether the user has a role capable of mutating company-scoped resources.
83     */
84    private function isCompanyAdmin(User $user): bool
85    {
86        $role = role($user->role);
87
88        return in_array($role, [Role::GLOBAL_ADMIN, Role::VENGRESO_ADMIN], true);
89    }
90}