bemade-addons/bemade_sports_clinic/controllers/access_control_mixin.py
Denis Durepos 53a027c87c feat: Production readiness sanitization and access control refactoring
- Remove all debug logging and statements for production deployment
- Convert operational logging from info to debug level where appropriate
- Clean up debug test files and commented code
- Refactor access control helpers into centralized AccessControlMixin
- Consolidate duplicated access control methods across controllers
- Enforce team-based access control for all portal users
- Fix access control logic to match expected security behavior
- Move TODO.md to notes/ directory for better organization
- All 76 tests passing with proper security enforcement

Production ready: Clean codebase with centralized access control and no debug noise
2025-07-31 14:55:18 -04:00

167 lines
6.9 KiB
Python

# -*- coding: utf-8 -*-
#
# Bemade Inc.
#
# Copyright (C) October 2023 Bemade Inc. (<https://www.bemade.org>).
# Author: Marc Durepos (Contact : marc@bemade.org)
#
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
# or modified copies of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
from odoo import http, _
from odoo.exceptions import UserError, AccessError, MissingError
from odoo.http import request
class AccessControlMixin:
"""
Mixin class providing common access control methods for portal controllers.
This centralizes access control logic to avoid duplication across multiple controllers
and ensures consistent security checks throughout the portal interface.
"""
def _check_team_access(self, team_id, check_staff=False):
"""
Verify the current user has access to this team.
:param int team_id: ID of the team to check access for
:param bool check_staff: If True, only allow team staff members
:return: The team record if access is granted
:raises: MissingError if team not found
:raises: AccessError if user doesn't have permission
"""
team = request.env['sports.team'].browse(int(team_id))
if not team.exists():
raise MissingError(_("Team not found"))
user = request.env.user
# Check if user is a staff member of this team
is_team_staff = team.staff_ids.filtered(
lambda s: user.partner_id in s.user_ids.partner_id
)
# Check if user is a treatment professional with access
is_treatment_professional = request.env.user.has_group(
'bemade_sports_clinic.group_portal_treatment_professional'
)
if check_staff and not is_team_staff:
# Only team staff can perform certain actions
raise AccessError(_("Only team staff members can perform this action."))
if not (is_team_staff or is_treatment_professional):
raise AccessError(_("You don't have permission to access this team."))
return team
def _check_team_staff_access(self, team):
"""
Check if the current user is a staff member of the team.
:param team: Team record to check staff access for
:return: Staff records if user is team staff, empty recordset otherwise
"""
user = request.env.user
return team.staff_ids.filtered(
lambda s: user.partner_id in s.user_ids.partner_id
)
def _check_treatment_professional_access(self):
"""
Check if the current user is a treatment professional.
:return: True if user is a treatment professional or system admin
"""
return (request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional') or
request.env.user.has_group('base.group_system'))
def _check_access_to_patient(self, patient_id):
"""
Verify the user has access to this patient.
:param int patient_id: ID of the patient to check access for
:return: The patient record if access is granted
:raises: UserError if user doesn't have permission or patient not found
"""
user = request.env.user
patient = request.env['sports.patient'].browse(int(patient_id))
if not patient.exists():
raise UserError(_('Patient not found.'))
# Check if user has access through team staff relationships (original task portal logic)
user_teams = user.partner_id.team_staff_rel_ids.mapped('team_id')
patient_teams = patient.team_ids
# User must be staff on at least one of the patient's teams
has_team_access = bool(user_teams & patient_teams)
# Treatment professionals still need to be staff on the patient's teams
# They don't get blanket access to all patients
if not has_team_access:
raise UserError(_('You do not have access to this patient.'))
return patient
def _check_access_to_injury(self, injury_id):
"""
Verify the user has access to this injury.
:param int injury_id: ID of the injury to check access for
:return: The injury record if access is granted
:raises: UserError if user doesn't have permission or injury not found
"""
user = request.env.user
injury = request.env['sports.patient.injury'].browse(int(injury_id))
if not injury.exists():
raise UserError(_('Injury not found.'))
# Check if user has access through team staff relationships (original task portal logic)
user_teams = user.partner_id.team_staff_rel_ids.mapped('team_id')
patient_teams = injury.patient_id.team_ids
# User must be staff on at least one of the patient's teams
has_team_access = bool(user_teams & patient_teams)
# Treatment professionals still need to be staff on the patient's teams
# They don't get blanket access to all injuries
if not has_team_access:
raise UserError(_('You do not have access to this injury.'))
return injury
def _check_access_to_task_model(self, model_name, record_id):
"""
Verify the user has access to a task-related model record.
:param str model_name: Name of the model ('sports.patient', 'sports.team', 'sports.patient.injury')
:param int record_id: ID of the record to check access for
:return: The record if access is granted
:raises: UserError if user doesn't have permission or record not found
"""
valid_models = ['sports.patient', 'sports.patient.injury', 'sports.team']
if model_name not in valid_models:
raise UserError(_('Invalid model specified.'))
if model_name == 'sports.patient':
return self._check_access_to_patient(record_id)
elif model_name == 'sports.patient.injury':
return self._check_access_to_injury(record_id)
elif model_name == 'sports.team':
return self._check_team_access(record_id)
# This should never be reached due to the valid_models check above
raise UserError(_('Invalid model specified.'))