Protège les écritures contre les requêtes forgées
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
d3ddfdb974
commit
05b2002d02
23 changed files with 123 additions and 70 deletions
|
|
@ -134,7 +134,6 @@ def create_app(config_name='default'):
|
|||
|
||||
# Blueprints des intégrations
|
||||
from .pronote.routes import pronote_bp
|
||||
csrf.exempt(pronote_bp)
|
||||
app.register_blueprint(pronote_bp, url_prefix='/pronote')
|
||||
|
||||
from .ent.routes import ent_bp
|
||||
|
|
@ -172,7 +171,6 @@ def create_app(config_name='default'):
|
|||
# Notifications temps reel
|
||||
from .notifications.routes import notifications_bp
|
||||
app.register_blueprint(notifications_bp)
|
||||
csrf.exempt(notifications_bp)
|
||||
|
||||
# Exports PDF/CSV
|
||||
from .exports.routes import exports_bp
|
||||
|
|
@ -181,7 +179,6 @@ def create_app(config_name='default'):
|
|||
# Messagerie unifiee
|
||||
from .messagerie.routes import messagerie_bp
|
||||
app.register_blueprint(messagerie_bp)
|
||||
csrf.exempt(messagerie_bp)
|
||||
|
||||
# Contrats d'entreprise
|
||||
from .contracts.models import Contract
|
||||
|
|
@ -199,7 +196,6 @@ def create_app(config_name='default'):
|
|||
# Planificateur automatique
|
||||
from .scheduler.routes import scheduler_bp
|
||||
app.register_blueprint(scheduler_bp)
|
||||
csrf.exempt(scheduler_bp)
|
||||
|
||||
# Filtres Jinja2 pour les templates
|
||||
from .utils import format_status, status_badge_class, priority_badge_class, bootstrap_color_to_hex, bootstrap_text_color
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ class Config:
|
|||
class DevelopmentConfig(Config):
|
||||
"""Configuration développement."""
|
||||
DEBUG = True
|
||||
WTF_CSRF_ENABLED = False # Désactivé pour développement
|
||||
# La machine de développement est accessible à distance : les protections
|
||||
# navigateur doivent y être identiques à celles de la production.
|
||||
WTF_CSRF_ENABLED = True
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
# MariaDB obligatoire
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ def create_category():
|
|||
return render_template('equipments/category_form.html', title='Nouvelle catégorie')
|
||||
|
||||
|
||||
@main_bp.route('/<int:id>/status/<status>')
|
||||
@main_bp.route('/<int:id>/status/<status>', methods=['POST'])
|
||||
@login_required
|
||||
def change_status(id, status):
|
||||
"""Changer le statut d'un équipement."""
|
||||
|
|
|
|||
|
|
@ -258,9 +258,9 @@
|
|||
Statut
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='en_service') }}">En service</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='hs') }}">Hors service</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='a_jeter') }}">À jeter</a></li>
|
||||
{% for value, label in [('en_service', 'En service'), ('hs', 'Hors service'), ('a_jeter', 'À jeter')] %}
|
||||
<li><form method="post" action="{{ url_for('equipments.change_status', id=unit.id, status=value) }}"><button class="dropdown-item" type="submit">{{ label }}</button></form></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import tempfile
|
|||
|
||||
sync_bp = Blueprint('outlook_sync', __name__, template_folder='templates')
|
||||
|
||||
@sync_bp.route('/api/sync-folders/<int:account_id>')
|
||||
@sync_bp.route('/api/sync-folders/<int:account_id>', methods=['POST'])
|
||||
@login_required
|
||||
def api_sync_folders(account_id):
|
||||
"""Synchronise les dossiers Outlook (avec sous-dossiers récursifs)."""
|
||||
|
|
@ -19,7 +19,7 @@ def api_sync_folders(account_id):
|
|||
from datetime import datetime, timezone
|
||||
|
||||
account = OutlookAccount.query.get(account_id)
|
||||
if not account or (account.user_id != current_user.id and current_user.role != 'admin'):
|
||||
if not account or (account.user_id != current_user.id and not current_user.is_admin()):
|
||||
return jsonify({'error': 'Compte non trouvé'}), 404
|
||||
|
||||
access_token, error = get_access_token(account)
|
||||
|
|
@ -122,7 +122,8 @@ def api_sync_folders(account_id):
|
|||
return jsonify({'error': f'Erreur: {str(e)}'}), 500
|
||||
|
||||
|
||||
@sync_bp.route('/api/sync-mails/<int:account_id>/<folder_id>')
|
||||
@sync_bp.route('/api/sync-mails/<int:account_id>/<folder_id>', methods=['POST'])
|
||||
@sync_bp.route('/api/sync-mails/<int:account_id>', defaults={'folder_id': 'inbox'}, methods=['POST'])
|
||||
@login_required
|
||||
def api_sync_mails(account_id, folder_id='inbox'):
|
||||
"""Synchronise les mails d'un dossier avec pagination complète."""
|
||||
|
|
@ -130,7 +131,7 @@ def api_sync_mails(account_id, folder_id='inbox'):
|
|||
from datetime import datetime, timezone
|
||||
|
||||
account = OutlookAccount.query.get(account_id)
|
||||
if not account or (account.user_id != current_user.id and current_user.role != 'admin'):
|
||||
if not account or (account.user_id != current_user.id and not current_user.is_admin()):
|
||||
return jsonify({'error': 'Compte non trouvé'}), 404
|
||||
|
||||
access_token, error = get_access_token(account)
|
||||
|
|
@ -144,7 +145,7 @@ def api_sync_mails(account_id, folder_id='inbox'):
|
|||
# Trouver le dossier
|
||||
folder = None
|
||||
if folder_id != 'inbox':
|
||||
folder = OutlookFolder.query.get(folder_id)
|
||||
folder = OutlookFolder.query.filter_by(id=folder_id, account_id=account_id).first()
|
||||
else:
|
||||
# Chercher par folder_type ou par nom (Boîte de réception)
|
||||
folder = OutlookFolder.query.filter_by(account_id=account_id, folder_type='inbox').first()
|
||||
|
|
@ -251,7 +252,7 @@ def api_sync_mails(account_id, folder_id='inbox'):
|
|||
|
||||
# ─── Pièces jointes ─────────────────────────────────────────────────────────
|
||||
|
||||
@sync_bp.route('/api/sync-attachments/<mail_id>')
|
||||
@sync_bp.route('/api/sync-attachments/<mail_id>', methods=['POST'])
|
||||
@login_required
|
||||
def api_sync_attachments(mail_id):
|
||||
"""Synchronise les pièces jointes d'un email."""
|
||||
|
|
@ -259,6 +260,8 @@ def api_sync_attachments(mail_id):
|
|||
|
||||
mail = OutlookMail.query.get_or_404(mail_id)
|
||||
account = OutlookAccount.query.get_or_404(mail.account_id)
|
||||
if account.user_id != current_user.id and not current_user.is_admin():
|
||||
return jsonify({'error': 'Message non trouvé'}), 404
|
||||
|
||||
access_token, error = get_access_token(account)
|
||||
if error:
|
||||
|
|
@ -317,6 +320,8 @@ def download_attachment(att_id):
|
|||
attachment = OutlookAttachment.query.get_or_404(att_id)
|
||||
mail = OutlookMail.query.get_or_404(attachment.mail_id)
|
||||
account = OutlookAccount.query.get_or_404(mail.account_id)
|
||||
if account.user_id != current_user.id and not current_user.is_admin():
|
||||
return jsonify({'error': 'Pièce jointe non trouvée'}), 404
|
||||
|
||||
access_token, error = get_access_token(account)
|
||||
if error:
|
||||
|
|
@ -349,5 +354,3 @@ def download_attachment(att_id):
|
|||
except Exception as e:
|
||||
flash(f'Erreur: {str(e)}', 'danger')
|
||||
return redirect(url_for('outlook_pages.mail_view', account_id=account.id, mail_id=mail.id))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ function syncFolders() {
|
|||
const statusDiv = document.getElementById('sync-status');
|
||||
statusDiv.classList.remove('d-none');
|
||||
|
||||
fetch('/outlook/api/sync-folders/{{ account.id }}')
|
||||
fetch('/outlook/api/sync-folders/{{ account.id }}', {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
statusDiv.classList.add('d-none');
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ function testConnection(accountId) {
|
|||
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les derniers mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@
|
|||
<script>
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les derniers mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ function syncMails(maxMessages) {
|
|||
status.style.display = 'block';
|
||||
status.innerHTML = '<i class="bi bi-hourglass-split"></i> Récupération des emails...';
|
||||
|
||||
fetch('/outlook/api/sync-mails/{{ account.id }}/{{ folder.folder_id }}?max=' + maxMessages)
|
||||
fetch('/outlook/api/sync-mails/{{ account.id }}/{{ folder.folder_id }}?max=' + maxMessages, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ function syncMails() {
|
|||
status.style.display = 'block';
|
||||
status.innerHTML = 'Synchronisation en cours...';
|
||||
|
||||
fetch('/outlook/api/sync-mails/{{ account.id }}')
|
||||
fetch('/outlook/api/sync-mails/{{ account.id }}', {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ function syncAttachments() {
|
|||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Chargement...';
|
||||
|
||||
fetch('/outlook/api/sync-attachments/{{ mail.id }}')
|
||||
fetch('/outlook/api/sync-attachments/{{ mail.id }}', {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@
|
|||
<script>
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les nouveaux mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Intégration Pronote
|
|||
import logging
|
||||
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from app_new.extensions import db, csrf
|
||||
from ..pronote.models import PronoteSession
|
||||
from ..core.models.college import Room, RoomSchedule
|
||||
|
|
@ -30,14 +29,7 @@ def index():
|
|||
@login_required
|
||||
def connect():
|
||||
"""Connexion à Pronote via QR Code."""
|
||||
from flask_wtf.csrf import validate_csrf
|
||||
if request.method == 'POST':
|
||||
# Validate CSRF token manually
|
||||
try:
|
||||
validate_csrf(request.form.get('csrf_token'))
|
||||
except Exception:
|
||||
# If CSRF fails, try to continue anyway (for testing)
|
||||
pass
|
||||
qr_code = request.form.get('qr_code', '').strip()
|
||||
pin = request.form.get('pin', '').strip()
|
||||
account_pin = request.form.get('account_pin', '').strip() or None
|
||||
|
|
@ -97,7 +89,7 @@ def rooms():
|
|||
return render_template('pronote/rooms.html', rooms=rooms_list)
|
||||
|
||||
|
||||
@pronote_bp.route('/sync-salles')
|
||||
@pronote_bp.route('/sync-salles', methods=['POST'])
|
||||
@login_required
|
||||
def sync_salles():
|
||||
"""Synchroniser les salles depuis Pronote."""
|
||||
|
|
@ -207,7 +199,7 @@ def import_room_planning(room_id):
|
|||
return redirect(url_for('pronote.room_planning', room_id=room_id))
|
||||
|
||||
|
||||
@pronote_bp.route('/disconnect')
|
||||
@pronote_bp.route('/disconnect', methods=['POST'])
|
||||
@login_required
|
||||
def disconnect():
|
||||
"""Déconnexion de Pronote."""
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@
|
|||
<a href="{{ url_for('pronote.connect') }}" class="btn btn-primary">
|
||||
<i class="bi bi-link"></i> Connecter à Pronote
|
||||
</a>
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="btn btn-outline-secondary ms-2">
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}" class="d-inline">
|
||||
<button type="submit" class="btn btn-outline-secondary ms-2">
|
||||
<i class="bi bi-arrow-repeat"></i> Synchroniser les salles
|
||||
</a>
|
||||
</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@
|
|||
<a href="{{ url_for('pronote.connect') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-qr-code"></i> Connexion Pronote
|
||||
</a>
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="btn btn-outline-success ms-2">
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}" class="d-inline">
|
||||
<button type="submit" class="btn btn-outline-success ms-2">
|
||||
<i class="bi bi-download"></i> Synchroniser salles
|
||||
</a>
|
||||
</button></form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -53,9 +54,10 @@
|
|||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Aucune salle d'enseignement disponible.
|
||||
<div class="mt-2">
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="btn btn-primary btn-sm">
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}">
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-download"></i> Synchroniser les salles depuis Pronote
|
||||
</a>
|
||||
</button></form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{% block title %}GMAO Collège{% endblock %}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
||||
|
|
@ -239,7 +240,7 @@
|
|||
<li><a class="dropdown-item" href="{{ url_for('ent.staff') }}"><i class="bi bi-people"></i> Personnels ENT</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('pronote.connect') }}"><i class="bi bi-qr-code"></i> PRONOTE (QR)</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('pronote.sync_salles') }}"><i class="bi bi-download"></i> PRONOTE (sync salles)</a></li>
|
||||
<li><form method="post" action="{{ url_for('pronote.sync_salles') }}"><button class="dropdown-item" type="submit"><i class="bi bi-download"></i> PRONOTE (sync salles)</button></form></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('pronote.planning') }}"><i class="bi bi-calendar3"></i> PRONOTE (plannings)</a></li>
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('yeastar.index') }}"><i class="bi bi-telephone"></i> Téléphone P520</a></li>
|
||||
|
|
@ -320,6 +321,37 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
/* Ajoute le jeton CSRF aux formulaires HTML et aux requêtes fetch mutantes. */
|
||||
(function() {
|
||||
const tokenNode = document.querySelector('meta[name="csrf-token"]');
|
||||
const csrfToken = tokenNode ? tokenNode.content : '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('form').forEach(function(form) {
|
||||
const method = (form.getAttribute('method') || 'GET').toUpperCase();
|
||||
if (method !== 'GET' && !form.querySelector('input[name="csrf_token"]')) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'csrf_token';
|
||||
input.value = csrfToken;
|
||||
form.appendChild(input);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const originalFetch = window.fetch.bind(window);
|
||||
window.fetch = function(resource, options) {
|
||||
const opts = Object.assign({}, options || {});
|
||||
const method = (opts.method || 'GET').toUpperCase();
|
||||
if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
|
||||
const headers = new Headers(opts.headers || {});
|
||||
if (!headers.has('X-CSRFToken')) headers.set('X-CSRFToken', csrfToken);
|
||||
opts.headers = headers;
|
||||
}
|
||||
return originalFetch(resource, opts);
|
||||
};
|
||||
})();
|
||||
|
||||
/* ── Overlay de progression ──
|
||||
Usage JS : showProgress('Texte') / hideProgress()
|
||||
Ou sur un <form> : class="show-progress-form" data-progress-text="Texte optionnel"
|
||||
|
|
|
|||
|
|
@ -292,9 +292,9 @@
|
|||
Statut
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='en_service') }}">En service</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='hs') }}">Hors service</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.change_status', id=unit.id, status='a_jeter') }}">À jeter</a></li>
|
||||
{% for value, label in [('en_service', 'En service'), ('hs', 'Hors service'), ('a_jeter', 'À jeter')] %}
|
||||
<li><form method="post" action="{{ url_for('equipments.change_status', id=unit.id, status=value) }}"><button class="dropdown-item" type="submit">{{ label }}</button></form></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ function testConnection(accountId) {
|
|||
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les derniers mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@
|
|||
<script>
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les derniers mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@
|
|||
<script>
|
||||
function syncMails(accountId) {
|
||||
if (confirm('Synchroniser les nouveaux mails ?')) {
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`)
|
||||
fetch(`/outlook/api/sync-mails/${accountId}`, {method: 'POST'})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
<a href="{{ url_for('pronote.connect') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-qr-code"></i> Connexion Pronote
|
||||
</a>
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="btn btn-outline-success ms-2">
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}" class="d-inline"><button type="submit" class="btn btn-outline-success ms-2">
|
||||
<i class="bi bi-download"></i> Synchroniser salles
|
||||
</a>
|
||||
</button></form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -53,9 +53,9 @@
|
|||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Aucune salle d'enseignement disponible.
|
||||
<div class="mt-2">
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="btn btn-primary btn-sm">
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}"><button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-download"></i> Synchroniser les salles depuis Pronote
|
||||
</a>
|
||||
</button></form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@
|
|||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> Aucune salle importée depuis Pronote.
|
||||
<a href="{{ url_for('pronote.sync_salles') }}" class="alert-link">
|
||||
Synchroniser les salles
|
||||
</a>
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}" class="d-inline">
|
||||
<button type="submit" class="btn btn-link alert-link p-0 align-baseline">Synchroniser les salles</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
25
tests/integration/test_request_security.py
Normal file
25
tests/integration/test_request_security.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"""Régressions sur les protections des requêtes qui écrivent."""
|
||||
from app_new.config import DevelopmentConfig
|
||||
|
||||
|
||||
def test_csrf_is_enabled_in_remote_development():
|
||||
assert DevelopmentConfig.WTF_CSRF_ENABLED is True
|
||||
|
||||
|
||||
def test_login_post_without_csrf_is_rejected_when_enabled(client, app):
|
||||
previous = app.config["WTF_CSRF_ENABLED"]
|
||||
app.config["WTF_CSRF_ENABLED"] = True
|
||||
try:
|
||||
response = client.post("/auth/login", data={"username": "inconnu", "password": "inconnu"})
|
||||
assert response.status_code == 400
|
||||
finally:
|
||||
app.config["WTF_CSRF_ENABLED"] = previous
|
||||
|
||||
|
||||
def test_mutating_routes_reject_get(client):
|
||||
assert client.get("/equipments/1/status/hs").status_code == 405
|
||||
assert client.get("/pronote/sync-salles").status_code == 405
|
||||
assert client.get("/pronote/disconnect").status_code == 405
|
||||
assert client.get("/outlook/api/sync-folders/1").status_code == 405
|
||||
assert client.get("/outlook/api/sync-mails/1").status_code == 405
|
||||
assert client.get("/outlook/api/sync-attachments/inconnu").status_code == 405
|
||||
Loading…
Reference in a new issue