nordabiz/blueprints/it_audit/routes_api.py
Maciej Pienczyn 4181a2e760 refactor: Migrate access control from is_admin to role-based system
Replace ~170 manual `if not current_user.is_admin` checks with:
- @role_required(SystemRole.ADMIN) for user management, security, ZOPK
- @role_required(SystemRole.OFFICE_MANAGER) for content management
- current_user.can_access_admin_panel() for admin UI access
- current_user.can_moderate_forum() for forum moderation
- current_user.can_edit_company(id) for company permissions

Add @office_manager_required decorator shortcut.
Add SQL migration to sync existing users' role field.

Role hierarchy: UNAFFILIATED(10) < MEMBER(20) < EMPLOYEE(30) < MANAGER(40) < OFFICE_MANAGER(50) < ADMIN(100)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:05:22 +01:00

291 lines
9.3 KiB
Python

"""
IT Audit API Routes - IT Audit blueprint
Migrated from app.py as part of the blueprint refactoring.
Contains API routes for IT audit: matches, history, and export.
"""
import csv
import logging
from io import StringIO
from flask import jsonify, request, Response
from flask_login import current_user, login_required
from database import SessionLocal, Company
from . import bp
logger = logging.getLogger(__name__)
# ============================================================
# IT AUDIT API ROUTES
# ============================================================
@bp.route('/api/it-audit/matches/<int:company_id>')
@login_required
def api_it_audit_matches(company_id):
"""
API: Get IT audit collaboration matches for a company.
Returns all collaboration matches where the specified company
is either company_a or company_b in the match pair.
This endpoint is admin-only as collaboration matches
are not visible to regular users.
Args:
company_id: Company ID to get matches for
Returns:
JSON with list of matches including:
- match_id, match_type, match_score, status
- partner company info (id, name, slug)
- match_reason and shared_attributes
"""
# Only users with admin panel access can view collaboration matches
if not current_user.can_access_admin_panel():
return jsonify({
'success': False,
'error': 'Brak uprawnień. Tylko administrator może przeglądać dopasowania.'
}), 403
db = SessionLocal()
try:
from it_audit_service import ITAuditService
from database import ITCollaborationMatch
# Verify company exists
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({
'success': False,
'error': 'Firma nie znaleziona'
}), 404
# Get matches for this company
service = ITAuditService(db)
matches = service.get_matches_for_company(company_id)
# Format matches for JSON response
matches_data = []
for match in matches:
# Determine partner company (the other company in the match)
if match.company_a_id == company_id:
partner = match.company_b
else:
partner = match.company_a
matches_data.append({
'id': match.id,
'match_type': match.match_type,
'match_type_label': match.match_type_label,
'match_score': match.match_score,
'match_reason': match.match_reason,
'status': match.status,
'status_label': match.status_label,
'shared_attributes': match.shared_attributes,
'created_at': match.created_at.isoformat() if match.created_at else None,
'partner': {
'id': partner.id if partner else None,
'name': partner.name if partner else None,
'slug': partner.slug if partner else None,
}
})
return jsonify({
'success': True,
'company_id': company_id,
'company_name': company.name,
'matches_count': len(matches_data),
'matches': matches_data
}), 200
except Exception as e:
logger.error(f"Error fetching IT audit matches for company {company_id}: {e}")
return jsonify({
'success': False,
'error': f'Błąd podczas pobierania dopasowań: {str(e)}'
}), 500
finally:
db.close()
@bp.route('/api/it-audit/history/<int:company_id>')
@login_required
def api_it_audit_history(company_id):
"""
API: Get IT audit history for a company.
Returns a list of all IT audits for a company, ordered by date descending.
The first item in the list is always the latest (current) audit.
Access:
- Admin: Can view history for any company
- User: Can only view history for their own company
Args:
company_id: Company ID to get audit history for
Query params:
limit: Maximum number of audits to return (default: 10)
Returns:
JSON with list of audits including:
- audit_id, audit_date, overall_score, scores, maturity_level
- is_current flag (True for the most recent audit)
"""
from it_audit_service import get_company_audit_history
# Access control: users with company edit rights can view history
if not current_user.can_edit_company(company_id):
return jsonify({
'success': False,
'error': 'Brak uprawnień do przeglądania historii audytów tej firmy.'
}), 403
# Parse limit from query params
limit = request.args.get('limit', 10, type=int)
limit = min(max(limit, 1), 50) # Clamp to 1-50
db = SessionLocal()
try:
# Verify company exists
company = db.query(Company).filter_by(id=company_id).first()
if not company:
return jsonify({
'success': False,
'error': 'Firma nie znaleziona'
}), 404
# Get audit history
audits = get_company_audit_history(db, company_id, limit)
# Format response
history = []
for idx, audit in enumerate(audits):
history.append({
'id': audit.id,
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
'audit_source': audit.audit_source,
'overall_score': audit.overall_score,
'security_score': audit.security_score,
'collaboration_score': audit.collaboration_score,
'completeness_score': audit.completeness_score,
'maturity_level': audit.maturity_level,
'is_current': idx == 0, # First item is most recent
'is_partial': (audit.completeness_score or 0) < 100,
})
return jsonify({
'success': True,
'company_id': company_id,
'company_name': company.name,
'company_slug': company.slug,
'total_audits': len(history),
'history': history
}), 200
except Exception as e:
logger.error(f"Error fetching IT audit history for company {company_id}: {e}")
return jsonify({
'success': False,
'error': f'Błąd podczas pobierania historii audytów: {str(e)}'
}), 500
finally:
db.close()
@bp.route('/api/it-audit/export')
@login_required
def api_it_audit_export():
"""
API: Export IT audit data as CSV.
Exports all IT audits with company information and scores.
Admin-only endpoint.
Returns:
CSV file with IT audit data
"""
if not current_user.can_access_admin_panel():
return jsonify({
'success': False,
'error': 'Tylko administrator może eksportować dane audytów.'
}), 403
db = SessionLocal()
try:
from database import ITAudit
# Get all latest audits per company
audits = db.query(ITAudit, Company).join(
Company, ITAudit.company_id == Company.id
).order_by(
ITAudit.company_id,
ITAudit.audit_date.desc()
).all()
# Deduplicate to get only latest audit per company
seen_companies = set()
latest_audits = []
for audit, company in audits:
if company.id not in seen_companies:
seen_companies.add(company.id)
latest_audits.append((audit, company))
# Create CSV
output = StringIO()
writer = csv.writer(output)
# Header
writer.writerow([
'Firma', 'NIP', 'Kategoria', 'Data audytu',
'Wynik ogólny', 'Bezpieczeństwo', 'Współpraca', 'Kompletność',
'Poziom dojrzałości', 'Azure AD', 'M365', 'EDR', 'MFA',
'Proxmox PBS', 'Monitoring'
])
# Data rows
for audit, company in latest_audits:
writer.writerow([
company.name,
company.nip or '',
company.category.name if company.category else '',
audit.audit_date.strftime('%Y-%m-%d') if audit.audit_date else '',
audit.overall_score or '',
audit.security_score or '',
audit.collaboration_score or '',
audit.completeness_score or '',
audit.maturity_level or '',
'Tak' if audit.has_azure_ad else 'Nie',
'Tak' if audit.has_m365 else 'Nie',
'Tak' if audit.has_edr else 'Nie',
'Tak' if audit.has_mfa else 'Nie',
'Tak' if audit.has_proxmox_pbs else 'Nie',
audit.monitoring_solution or 'Brak'
])
# Create response
output.seek(0)
return Response(
output.getvalue(),
mimetype='text/csv',
headers={
'Content-Disposition': 'attachment; filename=it_audit_export.csv',
'Content-Type': 'text/csv; charset=utf-8'
}
)
except Exception as e:
logger.error(f"Error exporting IT audits: {e}")
return jsonify({
'success': False,
'error': f'Błąd podczas eksportu: {str(e)}'
}), 500
finally:
db.close()