- 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
167 lines
6.9 KiB
Python
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.'))
|