Users - Permissions
Location: api/nextango/apps/users/permissions.py
Last Updated: 2026-07-08
Overview
Role-based and store-scoped access control for the Users domain and, via cross-imports, for other domains that need to check a caller's role or store assignment. Every permission class reads roles from request.user.get_all_roles(); there is no separate permissions table, so a role granted or revoked in user_roles_data takes effect on the next request.
UserViewSet itself declares no permission_classes, so it falls back to DRF's DEFAULT_PERMISSION_CLASSES, which resolves to IsActiveUser in production and AllowAny when DEBUG=True (see Views). The classes below are used explicitly by other views and by domains outside users.
Permission Classes
IsActiveUser
Denies the request unless request.user is authenticated and request.user.status == 'active'. This is also the platform's production-wide default permission class.
IsPosSession
Allows a request authenticated via PosGatewayAuthentication whose request.auth carries type: 'pos_session'. Pair with IsAuthenticated so an anonymous caller gets a 401 rather than a 403.
HasRole and its role-specific subclasses
HasRole is the base class: it denies unless the caller is authenticated and has at least one of required_roles. The concrete subclasses set that list:
| Class | Required roles |
|---|---|
IsEmployee | employee |
IsManager | manager |
IsEmployeeOrManager | employee, manager |
IsCustomer | customer |
IsInfluencer | influencer |
IsVIP | vip |
ManagerOrOwnerOnly
Grants object-level access to any manager, or to the caller if they own the object (obj.user == request.user) or the object is the caller's own User record.
POSTerminalAccess
Requires the caller to hold the employee or manager role. At the object level, checks that the caller's store assignments include the terminal's store, using the tolerant reference match described below.
ManagerApprovalAccess
Requires employee or manager. At the object level, a manager may access approval requests from a store they are assigned to; an employee may only access their own requests (matched by employee_id).
StoreAccessPermission
Requires the caller to hold employee, manager, or customer. At the object level: managers and employees are checked against their store assignments; customers are checked against their preferred_store.
Store-Reference Matching
store_ref_matches(ref, store) is the shared comparison every store-scoped check in this file (and in authentication.py) goes through. A stored reference matches a Store instance if it equals either store.sanity_id (the shape real Sanity-synced store_assignments and preferred_store carry) or str(store.id) (the shape legacy or test fixtures use). Comparing only against the Django UUID, as an earlier version of this gate did, incorrectly denied real staff and customers whose stored references carry the Sanity ID.
user_can_access_store(user, store) walks the caller's roles (manager store assignments, then employee store assignments, then customer preferred_store) and returns True on the first match via store_ref_matches().
user_store_gate
user_store_gate(user, target_store) is a role-aware bypass-or-check gate used where a request needs a single yes/no answer rather than object-level permission wiring:
- Unauthenticated caller: denied.
- Manager role with a Manager row present:
- empty
store_assignments: allowed (unrestricted access, matching the PIN-login precedent) - non-empty
store_assignments: checked viauser_can_access_store()
- empty
- Manager role claimed but no Manager row exists: falls through to the employee check if the caller also holds
employee; otherwise denied. This is a fail-closed response to role/row drift, not a silent bypass. - Employee role: checked via
user_can_access_store(). - Customer-only, service-token, or role-less active caller: allowed (customers are scoped by
preferred_store, notstore_assignments).
Helper Functions
user_has_role(user, role)/user_has_any_role(user, roles): direct role membership checks againstget_all_roles().get_user_role_hierarchy_level(user): returns a numeric level for the caller's highest role (customer<influencer<vip<employee<manager), for comparisons rather than gating decisions.
Related Documentation
- Models: role model definitions and
store_assignments/preferred_storefields - Authentication: PIN login, which uses the same store-reference matching
- Views: default permission resolution for the domain's ViewSets