mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-05-19 18:01:30 +00:00
Fix download modal: use per-backup modals for CSP/Turbo compatibility
- Replace shared modal + inline JS with per-backup modals that have filename pre-set in hidden fields (no JavaScript needed) - Add data-turbo="false" to download forms for native browser handling - Add data-bs-dismiss="modal" to submit button to auto-close modal - Add hidden username field for Chrome accessibility best practice - Fix test: GET on POST-only route returns 404 not 405
This commit is contained in:
parent
dd8698840d
commit
877e3005bc
2 changed files with 46 additions and 53 deletions
|
|
@ -418,10 +418,9 @@
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
{% if not backup_download_disabled and is_granted('@system.manage_updates') %}
|
{% if not backup_download_disabled and is_granted('@system.manage_updates') %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-outline-secondary btn-download-backup"
|
class="btn btn-outline-secondary"
|
||||||
data-filename="{{ backup.file }}"
|
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#downloadBackupModal"
|
data-bs-target="#downloadBackupModal-{{ loop.index }}"
|
||||||
title="{% trans %}update_manager.backup.download{% endtrans %}">
|
title="{% trans %}update_manager.backup.download{% endtrans %}">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -457,6 +456,48 @@
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if not backup_download_disabled and is_granted('@system.manage_updates') %}
|
||||||
|
{# Per-backup download modal - no inline JS needed, CSP compatible with Turbo #}
|
||||||
|
<div class="modal fade" id="downloadBackupModal-{{ loop.index }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form action="{{ path('admin_update_manager_backup_download') }}" method="post" data-turbo="false">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-download me-2"></i>{% trans %}update_manager.backup.download{% endtrans %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-exclamation-triangle me-1"></i>
|
||||||
|
{% trans %}update_manager.backup.download.security_warning{% endtrans %}
|
||||||
|
</div>
|
||||||
|
<p class="text-muted small mb-3">{{ backup.file }}</p>
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('update_manager_download') }}">
|
||||||
|
<input type="hidden" name="filename" value="{{ backup.file }}">
|
||||||
|
<input type="hidden" name="username" autocomplete="username">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="downloadPassword-{{ loop.index }}" class="form-label">
|
||||||
|
{% trans %}update_manager.backup.download.password_label{% endtrans %}
|
||||||
|
</label>
|
||||||
|
<input type="password" class="form-control" id="downloadPassword-{{ loop.index }}"
|
||||||
|
name="password" required autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
{% trans %}cancel{% endtrans %}
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-download me-1"></i>{% trans %}update_manager.backup.download{% endtrans %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -477,52 +518,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Password confirmation modal for backup download #}
|
|
||||||
{% if not backup_download_disabled and is_granted('@system.manage_updates') %}
|
|
||||||
<div class="modal fade" id="downloadBackupModal" tabindex="-1" aria-labelledby="downloadBackupModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form action="{{ path('admin_update_manager_backup_download') }}" method="post" id="downloadBackupForm">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="downloadBackupModalLabel">
|
|
||||||
<i class="fas fa-download me-2"></i>{% trans %}update_manager.backup.download{% endtrans %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<i class="fas fa-exclamation-triangle me-1"></i>
|
|
||||||
{% trans %}update_manager.backup.download.security_warning{% endtrans %}
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token('update_manager_download') }}">
|
|
||||||
<input type="hidden" name="filename" id="downloadBackupFilename" value="">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="downloadBackupPassword" class="form-label">
|
|
||||||
{% trans %}update_manager.backup.download.password_label{% endtrans %}
|
|
||||||
</label>
|
|
||||||
<input type="password" class="form-control" id="downloadBackupPassword"
|
|
||||||
name="password" required autocomplete="current-password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans %}cancel{% endtrans %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
<i class="fas fa-download me-1"></i>{% trans %}update_manager.backup.download{% endtrans %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script nonce="{{ csp_nonce('script') }}">
|
|
||||||
document.getElementById('downloadBackupModal').addEventListener('show.bs.modal', function (event) {
|
|
||||||
var button = event.relatedTarget;
|
|
||||||
var filename = button.getAttribute('data-filename');
|
|
||||||
document.getElementById('downloadBackupFilename').value = filename;
|
|
||||||
document.getElementById('downloadBackupPassword').value = '';
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -251,10 +251,10 @@ final class UpdateManagerControllerTest extends WebTestCase
|
||||||
$client = static::createClient();
|
$client = static::createClient();
|
||||||
$this->loginAsAdmin($client);
|
$this->loginAsAdmin($client);
|
||||||
|
|
||||||
// GET should return 405 Method Not Allowed
|
// GET returns 404 since no GET route exists for this path
|
||||||
$client->request('GET', '/en/system/update-manager/backup/download');
|
$client->request('GET', '/en/system/update-manager/backup/download');
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(405);
|
$this->assertResponseStatusCodeSame(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDownloadBackupRequiresAuth(): void
|
public function testDownloadBackupRequiresAuth(): void
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue