183 lines
7.4 KiB
Python
183 lines
7.4 KiB
Python
"""Outlook Auth Routes - GMAO Collège"""
|
|
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify, send_file
|
|
from flask_login import login_required, current_user
|
|
from app_new.extensions import db
|
|
from ..outlook.models import OutlookAccount, OutlookMail, OutlookFolder, OutlookAttachment, OutlookMailInterpretation, GmaoContext
|
|
import os
|
|
import subprocess
|
|
import json
|
|
import tempfile
|
|
|
|
auth_bp = Blueprint('outlook_auth', __name__, template_folder='templates')
|
|
|
|
# ─── API OAuth2 ───────────────────────────────────────────────────────────
|
|
|
|
@auth_bp.route('/api/start-auth', methods=['POST'])
|
|
@login_required
|
|
def api_start_auth():
|
|
"""Démarre le processus d'authentification OAuth2."""
|
|
import requests
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
email = request.json.get('email')
|
|
if not email:
|
|
return jsonify({'error': 'Email requis'}), 400
|
|
|
|
# Constantes OAuth2
|
|
CLIENT_ID = "9199bf20-a13f-4107-85dc-02114787ef48"
|
|
SCOPES = "https://outlook.office365.com/Mail.ReadWrite https://outlook.office365.com/Mail.Send offline_access"
|
|
|
|
# Vérifier si le compte existe déjà — si oui, le supprimer pour re-authentifier
|
|
existing = OutlookAccount.query.filter_by(email=email, user_id=current_user.id).first()
|
|
if existing:
|
|
# Supprimer les données associées puis le compte
|
|
from app_new.outlook.models import OutlookMail, OutlookFolder
|
|
OutlookMail.query.filter_by(account_id=existing.id).delete()
|
|
OutlookFolder.query.filter_by(account_id=existing.id).delete()
|
|
db.session.delete(existing)
|
|
db.session.commit()
|
|
|
|
try:
|
|
# Découvrir le tenant ID
|
|
domain = email.split('@')[1]
|
|
discovery_url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration"
|
|
resp = requests.get(discovery_url, timeout=10)
|
|
tenant_id = resp.json().get('token_endpoint', '').split('/')[3] if resp.status_code == 200 else "befa6ca1-bada-43da-958c-cafc3c57ec70"
|
|
|
|
# Demander le device code
|
|
token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/devicecode"
|
|
dev_resp = requests.post(token_url, data={'client_id': CLIENT_ID, 'scope': SCOPES})
|
|
if dev_resp.status_code != 200:
|
|
return jsonify({'error': 'Erreur de configuration OAuth2'}), 500
|
|
|
|
device_data = dev_resp.json()
|
|
|
|
# Créer le compte en attente
|
|
account = OutlookAccount(
|
|
user_id=current_user.id,
|
|
email=email,
|
|
tenant_id=tenant_id,
|
|
refresh_token='',
|
|
device_code=device_data.get('device_code'),
|
|
user_code=device_data.get('user_code'),
|
|
connection_status='pending',
|
|
device_code_expires_at=datetime.now(timezone.utc) + timedelta(minutes=device_data.get('expires_in', 900))
|
|
)
|
|
db.session.add(account)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'verification_uri': device_data.get('verification_uri'),
|
|
'user_code': device_data.get('user_code'),
|
|
'expires_in': device_data.get('expires_in'),
|
|
'account_id': account.id
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': f'Erreur: {str(e)}'}), 500
|
|
|
|
|
|
@auth_bp.route('/api/poll-auth/<int:account_id>', methods=['POST'])
|
|
@login_required
|
|
def api_poll_auth(account_id):
|
|
"""Polling pour vérifier si l'authentification est terminée."""
|
|
import requests
|
|
from datetime import datetime, timezone
|
|
|
|
account = OutlookAccount.query.get(account_id)
|
|
if not account or account.user_id != current_user.id:
|
|
return jsonify({'error': 'Compte non trouvé'}), 404
|
|
|
|
if not account.device_code:
|
|
return jsonify({'error': 'Device code non trouvé'}), 400
|
|
|
|
# Vérifier si le device code a expiré
|
|
expires_at = account.device_code_expires_at
|
|
if expires_at:
|
|
if expires_at.tzinfo is None:
|
|
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
|
if expires_at < datetime.now(timezone.utc):
|
|
db.session.delete(account)
|
|
db.session.commit()
|
|
return jsonify({'error': 'Code expiré, veuillez recommencer', 'expired': True}), 400
|
|
|
|
CLIENT_ID = "9199bf20-a13f-4107-85dc-02114787ef48"
|
|
|
|
try:
|
|
# Polling Microsoft pour le token
|
|
token_url = f"https://login.microsoftonline.com/{account.tenant_id}/oauth2/v2.0/token"
|
|
resp = requests.post(token_url, data={
|
|
'client_id': CLIENT_ID,
|
|
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
|
'device_code': account.device_code
|
|
})
|
|
|
|
data = resp.json()
|
|
|
|
if resp.status_code == 200 and 'refresh_token' in data:
|
|
# Succès - sauvegarder le refresh token
|
|
from cryptography.fernet import Fernet
|
|
import os
|
|
|
|
key = os.environ.get('OUTLOOK_ENCRYPTION_KEY', Fernet.generate_key())
|
|
cipher = Fernet(key if isinstance(key, bytes) else key.encode())
|
|
|
|
account.refresh_token = cipher.encrypt(data['refresh_token'].encode()).decode()
|
|
account.connection_status = 'active'
|
|
account.last_connected_at = datetime.now(timezone.utc)
|
|
account.device_code = None
|
|
account.user_code = None
|
|
account.device_code_expires_at = None
|
|
account.is_active = True
|
|
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Authentification réussie',
|
|
'account_id': account.id
|
|
})
|
|
|
|
if data.get('error') == 'authorization_pending':
|
|
return jsonify({'pending': True, 'message': "En attente de l'autorisation"})
|
|
|
|
return jsonify({'error': data.get('error_description', data.get('error', 'Erreur inconnue'))}), 400
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': f'Erreur: {str(e)}'}), 500
|
|
|
|
|
|
# ─── API Synchronisation ───────────────────────────────────────────────────
|
|
|
|
def get_access_token(account):
|
|
"""Obtient un access_token à partir du refresh_token."""
|
|
import requests
|
|
from cryptography.fernet import Fernet
|
|
import os
|
|
|
|
if not account.refresh_token:
|
|
return None, "Aucun token d'authentification"
|
|
|
|
key = os.environ.get('OUTLOOK_ENCRYPTION_KEY')
|
|
if not key:
|
|
return None, "Clé de chiffrement non configurée"
|
|
|
|
cipher = Fernet(key if isinstance(key, bytes) else key.encode())
|
|
refresh_token = cipher.decrypt(account.refresh_token.encode()).decode()
|
|
|
|
CLIENT_ID = "9199bf20-a13f-4107-85dc-02114787ef48"
|
|
token_url = f"https://login.microsoftonline.com/{account.tenant_id}/oauth2/v2.0/token"
|
|
|
|
resp = requests.post(token_url, data={
|
|
'client_id': CLIENT_ID,
|
|
'grant_type': 'refresh_token',
|
|
'refresh_token': refresh_token,
|
|
'scope': 'https://outlook.office365.com/Mail.ReadWrite https://outlook.office365.com/Mail.Send offline_access'
|
|
})
|
|
|
|
if resp.status_code != 200:
|
|
return None, f"Erreur d'authentification: {resp.json().get('error_description', resp.text)}"
|
|
|
|
return resp.json().get('access_token'), None
|
|
|
|
|