Backups: first UI sceleton for remote / encrypted backups

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Michael Pimmer 2018-11-15 09:56:23 +00:00 committed by James Valleroy
parent 9e4fb5eb59
commit b6d1237c0c
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 143 additions and 6 deletions

View File

@ -23,6 +23,9 @@ from django.core import validators
from django.core.validators import FileExtensionValidator
from django.utils.translation import ugettext, ugettext_lazy as _
from plinth.utils import format_lazy
from plinth import cfg
from . import api
@ -77,3 +80,43 @@ class UploadForm(forms.Form):
validators=[FileExtensionValidator(['gz'],
'Backup files have to be in .tar.gz format')],
help_text=_('Select the backup file you want to upload'))
class CreateRepositoryForm(forms.Form):
repository = forms.CharField(
label=_('Repository path'), strip=True,
help_text=_('Path of the new repository.'))
encryption = forms.ChoiceField(
label=_('Encryption'),
help_text=format_lazy(_('"Key in Repository" means that a '
'password-protected key is stored with the backup. <br />'
'<b>You need this password to restore a backup!</b>')),
choices=[('repokey', 'Key in Repository'), ('none', 'None')]
)
passphrase = forms.CharField(
label=_('Passphrase'),
help_text=_('Passphrase; Only needed when using encryption.'),
widget=forms.PasswordInput()
)
confirm_passphrase = forms.CharField(
label=_('Confirm Passphrase'),
help_text=_('Repeat the passphrase.'),
widget=forms.PasswordInput()
)
store_passphrase = forms.BooleanField(
label=_('Store passphrase on FreedomBox'),
help_text=format_lazy(_('Store the passphrase on your {box_name}.'
'<br />You need to store the passphrase if you want to run '
'recurrent backups.'), box_name=_(cfg.box_name)),
required=False
)
def clean(self):
cleaned_data = super(CreateRepositoryForm, self).clean()
passphrase = cleaned_data.get("passphrase")
confirm_passphrase = cleaned_data.get("confirm_passphrase")
if passphrase != confirm_passphrase:
raise forms.ValidationError(
"passphrase and confirm_passphrase do not match"
)

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% comment %}
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load i18n %}
{% block content %}
<h3>{% trans 'Backup repositories' %}</h3>
<p>
{% for repository in repositories %}
Repository: {{ repository.repository }}<br />
{% endfor %}
</p>
<a title="{% trans 'Create new repository' %}"
role="button" class="btn btn-primary"
href="{% url 'backups:create-repository' %}">
{% trans 'Add Repository' %}
</a>
{% endblock %}

View File

@ -20,9 +20,9 @@ URLs for the backups module.
from django.conf.urls import url
from .views import IndexView, CreateArchiveView, DeleteArchiveView, \
UploadArchiveView, ExportAndDownloadView, RestoreArchiveView, \
RestoreFromUploadView
from .views import IndexView, CreateArchiveView, CreateRepositoryView, \
DeleteArchiveView, UploadArchiveView, ExportAndDownloadView, \
RepositoriesView, RestoreArchiveView, RestoreFromUploadView
urlpatterns = [
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
@ -36,4 +36,8 @@ urlpatterns = [
RestoreArchiveView.as_view(), name='restore-archive'),
url(r'^sys/backups/restore-from-upload/$',
RestoreFromUploadView.as_view(), name='restore-from-upload'),
url(r'^sys/backups/repositories/$',
RepositoriesView.as_view(), name='repositories'),
url(r'^sys/backups/repositories/create/$',
CreateRepositoryView.as_view(), name='create-repository'),
]

View File

@ -20,11 +20,13 @@ Views for the backups app.
from datetime import datetime
import gzip
import json
from io import BytesIO
import logging
import mimetypes
import os
import tempfile
from uuid import uuid1
from urllib.parse import unquote
from django.contrib import messages
@ -37,7 +39,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.generic import View, FormView, TemplateView
from plinth import actions
from plinth import actions, kvstore
from plinth.errors import PlinthError
from plinth.modules import backups, storage
@ -51,10 +53,13 @@ subsubmenu = [{
'text': ugettext_lazy('Backups')
}, {
'url': reverse_lazy('backups:upload'),
'text': ugettext_lazy('Upload backup')
'text': ugettext_lazy('Upload')
}, {
'url': reverse_lazy('backups:create'),
'text': ugettext_lazy('Create backup')
'text': ugettext_lazy('Create')
}, {
'url': reverse_lazy('backups:repositories'),
'text': ugettext_lazy('Repositories')
}]
@ -284,3 +289,49 @@ class ExportAndDownloadView(View):
response['Content-Disposition'] = 'attachment; filename="%s"' % \
filename
return response
class RepositoriesView(TemplateView):
"""View list of repositories."""
template_name = 'backups_repositories.html'
def get_context_data(self, **kwargs):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
context['title'] = 'Backup repositories'
repositories = kvstore.get_default('backups_repositories', [])
context['repositories'] = json.loads(repositories)
context['subsubmenu'] = subsubmenu
return context
class CreateRepositoryView(SuccessMessageMixin, FormView):
"""View to create a new repository."""
form_class = forms.CreateRepositoryForm
prefix = 'backups'
template_name = 'backups_form.html'
success_url = reverse_lazy('backups:repositories')
success_message = _('Created new repository.')
def get_context_data(self, **kwargs):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
context['title'] = _('Create new repository')
context['subsubmenu'] = subsubmenu
return context
def form_valid(self, form):
"""Restore files from the archive on valid form submission."""
repositories = kvstore.get_default('backups_repositories', [])
if repositories:
repositories = json.loads(repositories)
new_repo = {
'uuid': str(uuid1()),
'repository': form.cleaned_data['repository'],
'encryption': form.cleaned_data['encryption'],
}
if form.cleaned_data['store_passphrase']:
new_repo['passphrase'] = form.cleaned_data['passphrase']
repositories.append(new_repo)
kvstore.set('backups_repositories', json.dumps(repositories))
return super().form_valid(form)