Spaces:
Running
Running
♻️ refactor (api): Flask to FastAPI
Browse files- .env.example +3 -0
- .gitignore +9 -0
- Dockerfile +14 -0
- README.md +163 -1
- analyzer.py +189 -0
- app.py +34 -0
- config.py +12 -0
- models.py +4 -0
- requirements.txt +6 -0
.env.example
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
OPENROUTER_API_KEY=votre_clé_api_ici
|
2 |
+
API_URL=https://openrouter.ai/api/v1/chat/completions
|
3 |
+
MODEL=deepseek/deepseek-chat:free
|
.gitignore
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.env
|
2 |
+
__pycache__/
|
3 |
+
*.pyc
|
4 |
+
*.pyo
|
5 |
+
*.pyd
|
6 |
+
.Python
|
7 |
+
env/
|
8 |
+
venv/
|
9 |
+
*.log
|
Dockerfile
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9
|
2 |
+
|
3 |
+
RUN useradd -m -u 1000 user
|
4 |
+
USER user
|
5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
6 |
+
|
7 |
+
WORKDIR /app
|
8 |
+
|
9 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
10 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
11 |
+
|
12 |
+
COPY --chown=user . /app
|
13 |
+
|
14 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
@@ -9,4 +9,166 @@ license: mit
|
|
9 |
short_description: 'AfricTivistes Digital Security Audit '
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
short_description: 'AfricTivistes Digital Security Audit '
|
10 |
---
|
11 |
|
12 |
+
# API d'Analyse d'Audit de Sécurité
|
13 |
+
|
14 |
+
API Flask permettant d'analyser automatiquement les réponses à un audit de sécurité et de générer des recommandations via l'intelligence artificielle.
|
15 |
+
|
16 |
+
## Présentation
|
17 |
+
|
18 |
+
Cette API utilise l'intelligence artificielle pour analyser les réponses fournies lors d'un audit de sécurité informatique. Elle identifie automatiquement le type d'audit, évalue le niveau de sécurité global, détecte les points forts et les vulnérabilités, puis génère des recommandations personnalisées.
|
19 |
+
|
20 |
+
## Fonctionnalités
|
21 |
+
|
22 |
+
- **Détection automatique du type d'audit** (complet ou spécifique à un domaine)
|
23 |
+
- **Analyse des réponses** par IA spécialisée en cybersécurité
|
24 |
+
- **Évaluation du niveau de sécurité** avec score sur 10
|
25 |
+
- **Identification des points forts** et leur importance
|
26 |
+
- **Détection des vulnérabilités** et classification par niveau de risque
|
27 |
+
- **Génération de recommandations détaillées** pour remédier aux failles
|
28 |
+
- **Production d'un résumé** de la situation et des étapes prioritaires
|
29 |
+
|
30 |
+
## Prérequis
|
31 |
+
|
32 |
+
- Python 3.7+
|
33 |
+
- Bibliothèques: Flask, Requests
|
34 |
+
- Clé API OpenRouter (ou autre fournisseur de modèles LLM)
|
35 |
+
|
36 |
+
## Installation
|
37 |
+
|
38 |
+
1. Clonez le dépôt:
|
39 |
+
```bash
|
40 |
+
git clone https://github.com/AfricTivistes/adisa.git
|
41 |
+
cd adisa
|
42 |
+
```
|
43 |
+
|
44 |
+
2. Installez les dépendances:
|
45 |
+
```bash
|
46 |
+
pip install -r requirements.txt
|
47 |
+
```
|
48 |
+
|
49 |
+
3. Configurez votre clé API dans `app.py` ou via une variable d'environnement.
|
50 |
+
|
51 |
+
## Utilisation
|
52 |
+
|
53 |
+
### Démarrer l'API
|
54 |
+
|
55 |
+
```bash
|
56 |
+
uvicorn app:app --host 0.0.0.0 --port 7860
|
57 |
+
```
|
58 |
+
|
59 |
+
L'API sera accessible par défaut sur `http://localhost:7860`.
|
60 |
+
|
61 |
+
### Endpoints
|
62 |
+
|
63 |
+
#### 1. Analyse d'audit
|
64 |
+
|
65 |
+
**Endpoint**: `POST /api/analyze`
|
66 |
+
|
67 |
+
**Description**: Analyse les réponses d'un audit de sécurité et génère une évaluation complète.
|
68 |
+
|
69 |
+
**Format de requête**:
|
70 |
+
```json
|
71 |
+
{
|
72 |
+
"Question 1": "Réponse 1",
|
73 |
+
"Question 2": "Réponse 2",
|
74 |
+
"Comment gérez-vous les mots de passe?": "Nous utilisons un gestionnaire de mots de passe d'entreprise",
|
75 |
+
...
|
76 |
+
}
|
77 |
+
```
|
78 |
+
|
79 |
+
**Format de réponse**:
|
80 |
+
```json
|
81 |
+
{
|
82 |
+
"status": "success",
|
83 |
+
"audit_type": "Audit spécifique: Politique de mots de passe",
|
84 |
+
"evaluation": {
|
85 |
+
"score": 7,
|
86 |
+
"level": "Bon"
|
87 |
+
},
|
88 |
+
"strengths": [
|
89 |
+
{
|
90 |
+
"text": "Utilisation d'un gestionnaire de mots de passe centralisé",
|
91 |
+
"rating": "Fort"
|
92 |
+
},
|
93 |
+
...
|
94 |
+
],
|
95 |
+
"weaknesses": [
|
96 |
+
{
|
97 |
+
"text": "Absence de politique de changement régulier des mots de passe",
|
98 |
+
"rating": "Moyen"
|
99 |
+
},
|
100 |
+
...
|
101 |
+
],
|
102 |
+
"recommendations": [
|
103 |
+
"Mettre en place une authentification à deux facteurs pour tous les services critiques",
|
104 |
+
"Automatiser la détection des mots de passe faibles",
|
105 |
+
...
|
106 |
+
],
|
107 |
+
"summary": "La gestion des mots de passe présente un bon niveau général mais nécessite des améliorations..."
|
108 |
+
}
|
109 |
+
```
|
110 |
+
|
111 |
+
#### 2. Vérification de santé
|
112 |
+
|
113 |
+
**Endpoint**: `GET /api/health`
|
114 |
+
|
115 |
+
**Description**: Vérifie que l'API est opérationnelle.
|
116 |
+
|
117 |
+
**Format de réponse**:
|
118 |
+
```json
|
119 |
+
{
|
120 |
+
"status": "ok",
|
121 |
+
"message": "API d'analyse d'audit de sécurité opérationnelle"
|
122 |
+
}
|
123 |
+
```
|
124 |
+
|
125 |
+
## Personnalisation
|
126 |
+
|
127 |
+
### Modification du modèle d'IA
|
128 |
+
|
129 |
+
Vous pouvez modifier le modèle utilisé en changeant les paramètres dans le constructeur de la classe `SecurityAuditAnalyzer`:
|
130 |
+
|
131 |
+
```python
|
132 |
+
security_analyzer = SecurityAuditAnalyzer(
|
133 |
+
api_key="votre-clé-api",
|
134 |
+
model="autre-modèle"
|
135 |
+
)
|
136 |
+
```
|
137 |
+
|
138 |
+
### Types d'audits supportés
|
139 |
+
|
140 |
+
L'API détecte automatiquement plusieurs domaines de la cybersécurité:
|
141 |
+
|
142 |
+
- Politique de mots de passe
|
143 |
+
- Sécurité des réseaux
|
144 |
+
- Gestion des accès
|
145 |
+
- Protection des données
|
146 |
+
- Sécurité physique
|
147 |
+
- Formation et sensibilisation
|
148 |
+
- Gestion des incidents
|
149 |
+
- Conformité réglementaire
|
150 |
+
|
151 |
+
## Architecture du code
|
152 |
+
|
153 |
+
- `app.py` : Point d'entrée de l'application Flask et définition des routes
|
154 |
+
- `SecurityAuditAnalyzer` : Classe principale pour l'analyse des audits
|
155 |
+
- `analyze_responses()` : Méthode principale d'analyse
|
156 |
+
- `_build_prompt()` : Construction du prompt pour l'IA
|
157 |
+
- `_detect_audit_type()` : Détection automatique du type d'audit
|
158 |
+
- `_parse_analysis()` : Extraction des données structurées
|
159 |
+
|
160 |
+
## Sécurité
|
161 |
+
|
162 |
+
⚠️ **Attention**: Ne stockez jamais votre clé API directement dans le code en production. Utilisez plutôt des variables d'environnement ou un fichier de configuration sécurisé.
|
163 |
+
|
164 |
+
## Exemple d'utilisation avec cURL
|
165 |
+
|
166 |
+
```bash
|
167 |
+
curl -X POST http://localhost:7860/api/analyze \
|
168 |
+
-H "Content-Type: application/json" \
|
169 |
+
-d '{
|
170 |
+
"Avez-vous une politique de mots de passe?": "Oui, nous exigeons des mots de passe d'au moins 8 caractères",
|
171 |
+
"Utilisez-vous l'authentification à deux facteurs?": "Seulement pour les comptes administrateurs",
|
172 |
+
"À quelle fréquence les mots de passe sont-ils changés?": "Il n'y a pas d'obligation de changement"
|
173 |
+
}'
|
174 |
+
```
|
analyzer.py
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import re
|
3 |
+
|
4 |
+
class SecurityAuditAnalyzer:
|
5 |
+
def __init__(self, api_key, model="deepseek/deepseek-chat:free", api_url="https://openrouter.ai/api/v1/chat/completions"):
|
6 |
+
self.api_url = api_url
|
7 |
+
self.api_key = api_key
|
8 |
+
self.model = model
|
9 |
+
self.headers = {
|
10 |
+
"Content-Type": "application/json",
|
11 |
+
"Authorization": f"Bearer {self.api_key}",
|
12 |
+
"HTTP-Referer": "https://security-audit-api.com",
|
13 |
+
"X-Title": "Security Audit API"
|
14 |
+
}
|
15 |
+
|
16 |
+
def analyze_responses(self, responses):
|
17 |
+
"""Analyse les réponses d'un audit et retourne une évaluation structurée."""
|
18 |
+
prompt = self._build_prompt(responses)
|
19 |
+
payload = {
|
20 |
+
"model": self.model,
|
21 |
+
"messages": [
|
22 |
+
{"role": "system", "content": self._get_system_prompt()},
|
23 |
+
{"role": "user", "content": prompt}
|
24 |
+
],
|
25 |
+
"temperature": 0.5
|
26 |
+
}
|
27 |
+
try:
|
28 |
+
response = requests.post(self.api_url, headers=self.headers, json=payload)
|
29 |
+
response.raise_for_status()
|
30 |
+
result = response.json()
|
31 |
+
analysis = result['choices'][0]['message']['content']
|
32 |
+
return self._parse_analysis(analysis)
|
33 |
+
except requests.RequestException as e:
|
34 |
+
return {
|
35 |
+
"error": str(e),
|
36 |
+
"status": "error",
|
37 |
+
"evaluation": {"score": 0, "level": "Inconnu"},
|
38 |
+
"strengths": [], "weaknesses": [], "recommendations": [],
|
39 |
+
"summary": "Une erreur s'est produite lors de l'analyse."
|
40 |
+
}
|
41 |
+
|
42 |
+
def _get_system_prompt(self):
|
43 |
+
return """Vous êtes un expert en cybersécurité spécialisé dans l'analyse des audits de sécurité.
|
44 |
+
Votre tâche est d'analyser les réponses fournies à un audit de sécurité et de produire une évaluation détaillée.
|
45 |
+
|
46 |
+
Adaptez votre analyse au type d'audit fourni (audit complet ou audit spécifique à un domaine).
|
47 |
+
Identifiez automatiquement le domaine spécifique de l'audit si les questions sont concentrées sur un sujet particulier
|
48 |
+
(ex: sécurité des réseaux, politique de mots de passe, gestion des données, etc.).
|
49 |
+
|
50 |
+
Structurez toujours votre réponse avec les balises suivantes:
|
51 |
+
<AUDIT_TYPE>Type de l'audit identifié</AUDIT_TYPE>
|
52 |
+
<EVALUATION>Score sur 10 et niveau (Critique, Faible, Moyen, Bon, Excellent)</EVALUATION>
|
53 |
+
<POINTS_FORTS>Liste des points forts avec leur niveau d'importance (Fort, Moyen, Faible)</POINTS_FORTS>
|
54 |
+
<FAILLES>Liste des vulnérabilités identifiées avec leur niveau de risque (Critique, Élevé, Moyen, Faible)</FAILLES>
|
55 |
+
<RECOMMANDATIONS>Liste des recommandations détaillées pour chaque faille identifiée</RECOMMANDATIONS>
|
56 |
+
<RESUME>Résumé concis de la situation générale de sécurité et prochaines étapes prioritaires</RESUME>
|
57 |
+
"""
|
58 |
+
|
59 |
+
def _build_prompt(self, responses):
|
60 |
+
audit_type = self._detect_audit_type(responses)
|
61 |
+
prompt = f"Type d'audit détecté: {audit_type}\n\n"
|
62 |
+
prompt += "Voici les réponses à un audit de sécurité numérique:\n\n"
|
63 |
+
for question, answer in responses.items():
|
64 |
+
prompt += f"Question: {question}\nRéponse: {answer}\n\n"
|
65 |
+
prompt += """
|
66 |
+
Analysez ces réponses et fournissez:
|
67 |
+
1. Le type d'audit que vous avez identifié
|
68 |
+
2. Une évaluation globale du niveau de sécurité (score sur 10 et niveau: Critique, Faible, Moyen, Bon, Excellent)
|
69 |
+
3. Les points forts de la sécurité actuelle (listez-les avec leur importance: Fort, Moyen, Faible)
|
70 |
+
4. Les vulnérabilités identifiées (listez-les avec leur niveau de risque: Critique, Élevé, Moyen, Faible)
|
71 |
+
5. Des recommandations détaillées pour résoudre chaque vulnérabilité
|
72 |
+
6. Un résumé général de la situation actuelle et prochaines étapes prioritaires
|
73 |
+
|
74 |
+
Formatez votre réponse avec les sections clairement délimitées par les balises spécifiées.
|
75 |
+
"""
|
76 |
+
return prompt
|
77 |
+
|
78 |
+
def _detect_audit_type(self, responses):
|
79 |
+
domains = {
|
80 |
+
"Politique de mots de passe": ["mot de passe", "password", "authentification", "connexion"],
|
81 |
+
"Sécurité des réseaux": ["réseau", "network", "firewall", "pare-feu", "vpn", "wifi", "routeur"],
|
82 |
+
"Gestion des accès": ["accès", "access", "droits", "permission", "privilege", "utilisateur", "user"],
|
83 |
+
"Protection des données": ["donnée", "data", "chiffrement", "encryption", "backup", "sauvegarde"],
|
84 |
+
"Sécurité physique": ["physique", "physical", "bâtiment", "building", "local", "vol", "theft"],
|
85 |
+
"Formation et sensibilisation": ["formation", "training", "sensibilisation", "awareness", "employé"],
|
86 |
+
"Gestion des incidents": ["incident", "crise", "réponse", "response", "attaque", "breach"],
|
87 |
+
"Conformité réglementaire": ["rgpd", "gdpr", "conformité", "compliance", "régulation", "legal"]
|
88 |
+
}
|
89 |
+
domain_counts = {domain: 0 for domain in domains}
|
90 |
+
for question in responses.keys():
|
91 |
+
question_lower = question.lower()
|
92 |
+
for domain, keywords in domains.items():
|
93 |
+
for keyword in keywords:
|
94 |
+
if keyword.lower() in question_lower:
|
95 |
+
domain_counts[domain] += 1
|
96 |
+
main_domains = sorted(domain_counts.items(), key=lambda x: x[1], reverse=True)
|
97 |
+
if len(main_domains) > 3 and main_domains[0][1] > 0 and main_domains[1][1] > 0 and main_domains[2][1] > 0:
|
98 |
+
return "Audit de sécurité complet"
|
99 |
+
elif main_domains[0][1] > 0:
|
100 |
+
return f"Audit spécifique: {main_domains[0][0]}"
|
101 |
+
else:
|
102 |
+
return "Audit de sécurité général"
|
103 |
+
|
104 |
+
def _parse_analysis(self, analysis):
|
105 |
+
result = {
|
106 |
+
"status": "success",
|
107 |
+
"audit_type": self._extract_between(analysis, "<AUDIT_TYPE>", "</AUDIT_TYPE>"),
|
108 |
+
"evaluation": {},
|
109 |
+
"strengths": [],
|
110 |
+
"weaknesses": [],
|
111 |
+
"recommendations": [],
|
112 |
+
"summary": self._extract_between(analysis, "<RESUME>", "</RESUME>").strip()
|
113 |
+
}
|
114 |
+
eval_text = self._extract_between(analysis, "<EVALUATION>", "</EVALUATION>")
|
115 |
+
score_match = re.search(r'(\d+)[\/\s]*10', eval_text)
|
116 |
+
result["evaluation"]["score"] = int(score_match.group(1)) if score_match else 0
|
117 |
+
|
118 |
+
level_keywords = ["Critique", "Faible", "Moyen", "Bon", "Excellent"]
|
119 |
+
for keyword in level_keywords:
|
120 |
+
if keyword.lower() in eval_text.lower():
|
121 |
+
result["evaluation"]["level"] = keyword
|
122 |
+
break
|
123 |
+
else:
|
124 |
+
result["evaluation"]["level"] = "Non spécifié"
|
125 |
+
|
126 |
+
strengths_text = self._extract_between(analysis, "<POINTS_FORTS>", "</POINTS_FORTS>")
|
127 |
+
result["strengths"] = self._extract_rated_items(strengths_text, ["Fort", "Moyen", "Faible"])
|
128 |
+
|
129 |
+
weaknesses_text = self._extract_between(analysis, "<FAILLES>", "</FAILLES>")
|
130 |
+
result["weaknesses"] = self._extract_rated_items(weaknesses_text, ["Critique", "Élevé", "Moyen", "Faible"])
|
131 |
+
|
132 |
+
recommendations_text = self._extract_between(analysis, "<RECOMMANDATIONS>", "</RECOMMANDATIONS>")
|
133 |
+
result["recommendations"] = self._extract_list_items(recommendations_text)
|
134 |
+
|
135 |
+
if not result["audit_type"]:
|
136 |
+
if "audit complet" in analysis.lower():
|
137 |
+
result["audit_type"] = "Audit de sécurité complet"
|
138 |
+
elif "audit spécifique" in analysis.lower():
|
139 |
+
specific_match = re.search(r'audit spécifique[:\s]*([\w\s]+)', analysis.lower())
|
140 |
+
if specific_match:
|
141 |
+
result["audit_type"] = f"Audit spécifique: {specific_match.group(1).strip().capitalize()}"
|
142 |
+
else:
|
143 |
+
result["audit_type"] = "Audit spécifique"
|
144 |
+
else:
|
145 |
+
result["audit_type"] = "Audit de sécurité général"
|
146 |
+
|
147 |
+
return result
|
148 |
+
|
149 |
+
def _extract_between(self, text, start_marker, end_marker):
|
150 |
+
try:
|
151 |
+
start = text.index(start_marker) + len(start_marker)
|
152 |
+
end = text.index(end_marker, start)
|
153 |
+
return text[start:end].strip()
|
154 |
+
except ValueError:
|
155 |
+
return ""
|
156 |
+
|
157 |
+
def _extract_list_items(self, text):
|
158 |
+
items = []
|
159 |
+
for line in text.split('\n'):
|
160 |
+
line = line.strip()
|
161 |
+
if line.startswith(('- ', '• ', '* ', '1. ', '2. ')) and len(line) > 2:
|
162 |
+
items.append(line[2:].strip())
|
163 |
+
elif line and not any(header in line.lower() for header in ["points", "failles", "recommandations"]):
|
164 |
+
items.append(line)
|
165 |
+
return [item for item in items if item]
|
166 |
+
|
167 |
+
def _extract_rated_items(self, text, ratings):
|
168 |
+
items = []
|
169 |
+
for line in text.split('\n'):
|
170 |
+
line = line.strip()
|
171 |
+
if not line:
|
172 |
+
continue
|
173 |
+
item = {"text": line, "rating": None}
|
174 |
+
for rating in ratings:
|
175 |
+
if f"{rating}:" in line.lower() or f"{rating} :" in line.lower():
|
176 |
+
parts = re.split(f"{rating}:", line, 1, flags=re.IGNORECASE)
|
177 |
+
if len(parts) > 1:
|
178 |
+
item["text"] = parts[1].strip()
|
179 |
+
item["rating"] = rating
|
180 |
+
break
|
181 |
+
if f"({rating})" in line.lower() or f"[{rating}]" in line.lower():
|
182 |
+
item["text"] = re.sub(rf"[\(\[]?{rating}[\)\]]?", "", line, flags=re.IGNORECASE).strip()
|
183 |
+
item["rating"] = rating
|
184 |
+
break
|
185 |
+
if item["rating"] is None and line.startswith(('- ', '• ', '* ')) and len(line) > 2:
|
186 |
+
item["text"] = line[2:].strip()
|
187 |
+
if item["text"] and not any(header in item["text"].lower() for header in ["points forts", "failles"]):
|
188 |
+
items.append(item)
|
189 |
+
return items
|
app.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException
|
2 |
+
from models import AuditResponses
|
3 |
+
from analyzer import SecurityAuditAnalyzer
|
4 |
+
from config import settings
|
5 |
+
|
6 |
+
app = FastAPI(title="Security Audit API", description="API pour analyser les réponses d'audits de sécurité")
|
7 |
+
|
8 |
+
# Initialisation de l'analyseur avec les paramètres de configuration
|
9 |
+
security_analyzer = SecurityAuditAnalyzer(
|
10 |
+
api_key=settings.OPENROUTER_API_KEY,
|
11 |
+
model=settings.MODEL,
|
12 |
+
api_url=settings.API_URL
|
13 |
+
)
|
14 |
+
|
15 |
+
@app.post("/api/analyze")
|
16 |
+
async def analyze_audit(responses: AuditResponses):
|
17 |
+
"""
|
18 |
+
Analyse les réponses d'un audit de sécurité.
|
19 |
+
|
20 |
+
Args:
|
21 |
+
responses: Dictionnaire de questions/réponses
|
22 |
+
|
23 |
+
Returns:
|
24 |
+
Résultats structurés de l'analyse
|
25 |
+
"""
|
26 |
+
analysis_result = security_analyzer.analyze_responses(responses.responses)
|
27 |
+
if analysis_result.get("status") == "error":
|
28 |
+
raise HTTPException(status_code=500, detail=analysis_result["error"])
|
29 |
+
return analysis_result
|
30 |
+
|
31 |
+
@app.get("/api/health")
|
32 |
+
async def health_check():
|
33 |
+
"""Vérifie l'état de l'API."""
|
34 |
+
return {"status": "ok", "message": "API d'analyse d'audit de sécurité opérationnelle"}
|
config.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic_settings import BaseSettings
|
2 |
+
|
3 |
+
class Settings(BaseSettings):
|
4 |
+
OPENROUTER_API_KEY: str
|
5 |
+
API_URL: str = "https://openrouter.ai/api/v1/chat/completions"
|
6 |
+
MODEL: str = "deepseek/deepseek-chat:free"
|
7 |
+
|
8 |
+
class Config:
|
9 |
+
env_file = ".env"
|
10 |
+
env_file_encoding = "utf-8"
|
11 |
+
|
12 |
+
settings = Settings()
|
models.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
class AuditResponses(BaseModel):
|
4 |
+
responses: dict[str, str]
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi==0.111.0
|
2 |
+
uvicorn==0.30.1
|
3 |
+
requests==2.32.3
|
4 |
+
pydantic==2.8.2
|
5 |
+
pydantic-settings==2.3.4
|
6 |
+
python-dotenv==1.0.1
|