252 lines
9.0 KiB
Python
252 lines
9.0 KiB
Python
import pathlib
|
|
import logging
|
|
|
|
from allauth.account.forms import LoginForm, SignupForm, ResetPasswordForm
|
|
from django.conf import settings
|
|
from django.contrib.auth import login
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib.auth.models import User
|
|
from django.db.utils import OperationalError
|
|
from django.forms import TextInput, ModelForm
|
|
from django.http import Http404, HttpResponseForbidden, HttpResponse, JsonResponse
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils import formats, translation
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.views.generic import TemplateView, ListView
|
|
from django.views.static import serve
|
|
from modeltranslation import settings as modeltranslation_settings
|
|
|
|
from repomaker import DEFAULT_USER_NAME
|
|
from repomaker.models import RemoteRepository, Repository, RemoteApp
|
|
from repomaker.storage import USER_RE, REMOTE_REPO_RE
|
|
from repomaker.utils import clean
|
|
|
|
|
|
# TODO remove when not needed anymore for testing
|
|
def update(request, repo_id):
|
|
repo = get_object_or_404(Repository, pk=repo_id)
|
|
if repo.user != request.user:
|
|
return HttpResponseForbidden()
|
|
|
|
repo.update_async()
|
|
return HttpResponse("Updated")
|
|
|
|
|
|
# TODO remove when not needed anymore for testing
|
|
def publish(request, repo_id):
|
|
repo = get_object_or_404(Repository, pk=repo_id)
|
|
if repo.user != request.user:
|
|
return HttpResponseForbidden()
|
|
|
|
repo.publish()
|
|
return HttpResponse("Published")
|
|
|
|
|
|
# TODO remove when not needed anymore for testing
|
|
def remote_update(request, remote_repo_id):
|
|
remote_repo = get_object_or_404(RemoteRepository, pk=remote_repo_id)
|
|
if request.user not in remote_repo.users.all():
|
|
return HttpResponseForbidden()
|
|
|
|
remote_repo.update_index()
|
|
return HttpResponse("Remote Repo Updated")
|
|
|
|
|
|
def media_serve(request, path, document_root=None, show_indexes=False):
|
|
"""
|
|
A wrapper around django.views.static.serve with added authentication checks.
|
|
"""
|
|
if not request.user.is_authenticated:
|
|
return HttpResponseForbidden() # only authenticated access to media
|
|
|
|
path_parts = pathlib.Path(path).parts
|
|
if len(path_parts) == 0:
|
|
return HttpResponseForbidden() # don't allow access to the root folder
|
|
|
|
user_id = request.user.pk
|
|
path_start = path_parts[0]
|
|
if user_media_access(user_id, path_start) and \
|
|
remote_repo_media_access(user_id, path_start):
|
|
return serve(request, path, document_root, show_indexes) # finally serve media file
|
|
else:
|
|
return HttpResponseForbidden() # did not pass access tests
|
|
|
|
|
|
def user_media_access(user_id, path):
|
|
"""
|
|
Returns true if the user with the given user_id is allowed to access this path
|
|
or if this path does not point to a user's media files.
|
|
Returns false otherwise.
|
|
"""
|
|
match = USER_RE.match(path)
|
|
if not match:
|
|
return True # not a path to user media
|
|
if int(match.group(1)) == user_id:
|
|
return True # user is allowed to access
|
|
return False # user is not allowed to access
|
|
|
|
|
|
def remote_repo_media_access(user_id, path):
|
|
"""
|
|
Returns true if the user with the given user_id is allowed to access this path
|
|
or if this path does not point to a remote repository's media files.
|
|
Returns false otherwise.
|
|
"""
|
|
match = REMOTE_REPO_RE.match(path)
|
|
if not match:
|
|
return True # not a path to a remote repo's media
|
|
repo = get_object_or_404(RemoteRepository, pk=int(match.group(1)))
|
|
if repo.pre_installed or repo.users.filter(pk=user_id).exists:
|
|
return True # repo is pre-installed or user added it
|
|
return False # user is not allowed to access this repo's files
|
|
|
|
|
|
class AppScrollListView(ListView):
|
|
paginate_by = 15
|
|
object_list = None
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if request.is_ajax():
|
|
self.object_list = self.get_queryset()
|
|
apps = self.get_context_data(**kwargs)['apps']
|
|
apps_json = []
|
|
for app in apps:
|
|
for lang in app.get_available_languages():
|
|
with translation.override(lang):
|
|
app_json = {'id': app.id, 'name': app.name, 'icon': app.icon_url,
|
|
'summary': app.summary, 'description': app.description,
|
|
'lang': lang}
|
|
|
|
app_latest_version = app.get_latest_version()
|
|
if app_latest_version is not None:
|
|
version = app_latest_version.version_name
|
|
date = formats.date_format(app_latest_version.added_date, 'DATE_FORMAT')
|
|
app_json['updated'] = \
|
|
_('Version %(version)s (%(date)s)') % {'version': version,
|
|
'date': date}
|
|
|
|
if self.model == RemoteApp:
|
|
app_json['repo_id'] = app.repo.pk
|
|
app_json['added'] = app.is_in_repo(self.get_repo())
|
|
app_json['categories'] = list(app.category.all().values('name'))
|
|
|
|
apps_json.append(app_json)
|
|
return JsonResponse(apps_json, safe=False)
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_repo(self):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class ErrorView(TemplateView):
|
|
request = None
|
|
template_name = "repomaker/error.html"
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
return self.get(request, *args, **kwargs)
|
|
|
|
|
|
class DatabaseLockedView(ErrorView):
|
|
template_name = 'repomaker/db_locked.html'
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
context = self.get_context_data(**kwargs)
|
|
return self.render_to_response(context, status=408)
|
|
|
|
|
|
class RmLoginForm(LoginForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(RmLoginForm, self).__init__(*args, **kwargs)
|
|
# remove placeholders from form widgets
|
|
fields = ['login', 'password']
|
|
for field in fields:
|
|
if 'placeholder' in self.fields[field].widget.attrs:
|
|
del self.fields[field].widget.attrs['placeholder']
|
|
|
|
|
|
class RmResetPasswordForm(ResetPasswordForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(RmResetPasswordForm, self).__init__(*args, **kwargs)
|
|
# Replace email placeholder
|
|
self.fields['email'].label = _('Enter email')
|
|
self.fields['email'].help_text = \
|
|
_("Enter your email address and we'll send a link to reset it. "
|
|
"If you did not sign up with an email, "
|
|
"we cannot help you securely reset your password.")
|
|
if 'placeholder' in self.fields['email'].widget.attrs:
|
|
del self.fields['email'].widget.attrs['placeholder']
|
|
|
|
|
|
class RmSignupForm(SignupForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(RmSignupForm, self).__init__(*args, **kwargs)
|
|
# remove placeholders from form widgets
|
|
fields = ['username', 'email', 'password1', 'password2']
|
|
for field in fields:
|
|
if 'placeholder' in self.fields[field].widget.attrs:
|
|
del self.fields[field].widget.attrs['placeholder']
|
|
|
|
|
|
class BaseModelForm(ModelForm):
|
|
|
|
pass
|
|
|
|
|
|
class DataListTextInput(TextInput):
|
|
|
|
def __init__(self, data_list, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self._list = data_list
|
|
|
|
def render(self, name, value, attrs=None, renderer=None):
|
|
self.attrs.update({'list': 'list__%s' % name})
|
|
text_html = super().render(name, value, attrs, renderer)
|
|
data_list = '<datalist id="list__%s">' % name
|
|
for item in self._list:
|
|
value = clean(str(item[0]))
|
|
name = clean(str(item[1]))
|
|
data_list += '<option value="%s">%s</option>' % (value, name)
|
|
data_list += '</datalist>'
|
|
return text_html + data_list
|
|
|
|
|
|
class LoginOrSingleUserRequiredMixin(LoginRequiredMixin):
|
|
"""
|
|
Mixin that should be used for all class-based views within this project.
|
|
It verifies that the current user is authenticated
|
|
or logs in the default user in single-user mode.
|
|
"""
|
|
request = None
|
|
kwargs = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
if settings.SINGLE_USER_MODE and not request.user.is_authenticated:
|
|
user = User.objects.get(username=DEFAULT_USER_NAME)
|
|
login(request, user)
|
|
return super().dispatch(request, *args, **kwargs)
|
|
except OperationalError as e:
|
|
if str(e) == 'database is locked':
|
|
return DatabaseLockedView().dispatch(request, *args, **kwargs)
|
|
raise e
|
|
|
|
|
|
class LanguageMixin:
|
|
kwargs = None
|
|
|
|
def get_language(self):
|
|
if 'lang' in self.kwargs:
|
|
return self.kwargs['lang']
|
|
return None
|
|
|
|
def activate_language(self):
|
|
language = self.get_language()
|
|
if language:
|
|
if language in modeltranslation_settings.AVAILABLE_LANGUAGES:
|
|
translation.activate(language)
|
|
else:
|
|
raise Http404()
|