nordabiz/utils/permissions.py
Maciej Pienczyn ae70ad326e feat: Add hierarchical role system with delegated permissions
Implements 6-tier role hierarchy:
- ADMIN (100): Full system access
- OFFICE_MANAGER (50): Admin panel without user management
- MANAGER (40): Full company control + user management
- EMPLOYEE (30): Edit company data (with delegated permissions)
- MEMBER (20): Full content access (forum, contacts, chat)
- UNAFFILIATED (10): Public profiles only

Features:
- SystemRole and CompanyRole enums in database.py
- UserCompanyPermissions model for delegation
- New decorators: @role_required(), @company_permission()
- Auto-detection of MANAGER role from KRS data
- Backward compatible with is_admin flag

Migration: 035_add_role_system.sql

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 06:42:39 +01:00

355 lines
9.3 KiB
Python

"""
Permission Helpers
==================
Helper functions for complex permission checks beyond simple role checks.
This module provides:
- Content access checks (company profiles, contact info)
- Feature access checks (forum, chat, classifieds)
- Data visibility filters (what user can see)
Usage:
from utils.permissions import can_view_contact_info, filter_visible_companies
if can_view_contact_info(current_user, company):
show_contact_details()
visible_companies = filter_visible_companies(current_user, all_companies)
"""
from typing import List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from database import User, Company, SystemRole
def can_view_contact_info(user: 'User', target_company: 'Company' = None) -> bool:
"""
Check if user can view contact information (email, phone, address).
Non-members (UNAFFILIATED) cannot see contact details.
Members and above can see all contact info (respecting privacy settings).
Args:
user: The user requesting access
target_company: The company whose info is being accessed (optional)
Returns:
True if user can view contact information
"""
if not user or not user.is_authenticated:
return False
return user.can_view_contacts()
def can_view_company_dashboard(user: 'User', company_id: int) -> bool:
"""
Check if user can view a company's internal dashboard.
Args:
user: The user requesting access
company_id: ID of the company dashboard
Returns:
True if user can view the dashboard
"""
if not user or not user.is_authenticated:
return False
# Admins and Office Managers can view any dashboard
if user.can_access_admin_panel():
return True
# User can view their own company's dashboard
return user.company_id == company_id
def can_invite_user_to_company(user: 'User', company_id: int) -> bool:
"""
Check if user can invite new users to a company.
Only MANAGER role (within company) and ADMIN can invite users.
Args:
user: The user attempting to invite
company_id: ID of the company to invite to
Returns:
True if user can invite users to this company
"""
if not user or not user.is_authenticated:
return False
return user.can_manage_company(company_id)
def get_editable_company_fields(user: 'User', company_id: int) -> List[str]:
"""
Get list of company profile fields that user can edit.
Access is determined by:
1. System role (OFFICE_MANAGER/ADMIN can edit everything)
2. Company role (MANAGER has full access to their company)
3. Delegated permissions (EMPLOYEE gets specific permissions from MANAGER)
Args:
user: The user requesting edit access
company_id: ID of the company to edit
Returns:
List of field names user can edit
"""
from database import SystemRole, CompanyRole
if not user or not user.is_authenticated:
return []
# Not allowed to edit this company at all
if not user.can_edit_company(company_id):
return []
# Field categories
description_fields = [
'description_short',
'description_full',
'core_values',
'founding_history',
]
services_fields = [
'services_offered',
'technologies_used',
'operational_area',
'languages_offered',
]
contact_fields = [
'email',
'phone',
'address_street',
'address_city',
'address_postal',
]
social_fields = [
'website',
# Social media handled via related tables
]
admin_only_fields = [
'name',
'legal_name',
'nip',
'regon',
'krs',
'year_established',
'employees_count',
'capital_amount',
'status',
'category_id',
]
# Admins and Office Managers can edit everything
if user.has_role(SystemRole.OFFICE_MANAGER):
return description_fields + services_fields + contact_fields + social_fields + admin_only_fields
# Check user's own company
if user.company_id != company_id:
return []
# Managers can edit everything except admin-only fields
if user.company_role_enum >= CompanyRole.MANAGER:
return description_fields + services_fields + contact_fields + social_fields
# Employees get permissions based on delegation
if user.company_role_enum >= CompanyRole.EMPLOYEE:
fields = []
if user.can_edit_company_field('description'):
fields.extend(description_fields)
if user.can_edit_company_field('services'):
fields.extend(services_fields)
if user.can_edit_company_field('contacts'):
fields.extend(contact_fields)
if user.can_edit_company_field('social'):
fields.extend(social_fields)
return fields
return []
def filter_visible_companies(user: 'User', companies: List['Company']) -> List['Company']:
"""
Filter companies list based on user's access level.
All users can see basic company info (name, category, description).
Only members can see contact details.
This function doesn't filter the list - all companies are visible.
Use can_view_contact_info() to determine what details to show.
Args:
user: The user viewing companies
companies: List of companies to filter
Returns:
Same list (filtering happens at template level)
"""
# All companies are visible to everyone
# Contact info visibility is handled in templates using can_view_contact_info
return companies
def get_chat_access_level(user: 'User') -> str:
"""
Get user's NordaGPT chat access level.
Args:
user: The user accessing chat
Returns:
'full' - Full access to all features
'limited' - Basic Q&A only, no company recommendations
'none' - No access
"""
from database import SystemRole
if not user or not user.is_authenticated:
return 'none'
if user.has_role(SystemRole.MEMBER):
return 'full'
# UNAFFILIATED users get limited access
return 'limited'
def can_access_b2b_classifieds(user: 'User') -> bool:
"""
Check if user can access B2B classifieds (tablica ogłoszeń).
Requires at least MEMBER role.
Args:
user: The user requesting access
Returns:
True if user can access B2B classifieds
"""
from database import SystemRole
if not user or not user.is_authenticated:
return False
return user.has_role(SystemRole.MEMBER)
def can_create_classified(user: 'User') -> bool:
"""
Check if user can create B2B classified ads.
Requires at least EMPLOYEE role (must be associated with a company).
Args:
user: The user attempting to create
Returns:
True if user can create classifieds
"""
from database import SystemRole
if not user or not user.is_authenticated:
return False
# Must have a company association and be at least EMPLOYEE
return user.company_id is not None and user.has_role(SystemRole.EMPLOYEE)
def can_access_calendar(user: 'User') -> bool:
"""
Check if user can access the events calendar.
Requires at least MEMBER role.
Args:
user: The user requesting access
Returns:
True if user can access calendar
"""
from database import SystemRole
if not user or not user.is_authenticated:
return False
return user.has_role(SystemRole.MEMBER)
def can_create_event(user: 'User') -> bool:
"""
Check if user can create calendar events.
Requires OFFICE_MANAGER or ADMIN role.
Args:
user: The user attempting to create
Returns:
True if user can create events
"""
from database import SystemRole
if not user or not user.is_authenticated:
return False
return user.has_role(SystemRole.OFFICE_MANAGER)
def get_user_permissions_summary(user: 'User') -> dict:
"""
Get a summary of all permissions for a user.
Useful for debugging and displaying in user profile.
Args:
user: The user to summarize
Returns:
Dictionary with permission flags
"""
from database import SystemRole
if not user or not user.is_authenticated:
return {
'role': None,
'company_role': None,
'can_view_contacts': False,
'can_access_forum': False,
'can_access_chat': 'none',
'can_access_admin_panel': False,
'can_manage_users': False,
'can_moderate_forum': False,
'can_edit_own_company': False,
'can_manage_own_company': False,
}
return {
'role': user.role,
'role_label': dict(SystemRole.choices()).get(user.system_role.value, 'Nieznany'),
'company_role': user.company_role,
'can_view_contacts': user.can_view_contacts(),
'can_access_forum': user.can_access_forum(),
'can_access_chat': get_chat_access_level(user),
'can_access_admin_panel': user.can_access_admin_panel(),
'can_manage_users': user.can_manage_users(),
'can_moderate_forum': user.can_moderate_forum(),
'can_edit_own_company': user.can_edit_company() if user.company_id else False,
'can_manage_own_company': user.can_manage_company() if user.company_id else False,
}