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>
291 lines
9.3 KiB
Python
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()
|