diff --git a/docs/changelog.rst b/docs/changelog.rst index 99e4d05..d457f95 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* :feature:`-` add frontend functionality to set an os users' sftp password + (needs gvaldap >= 0.4.0 on the LDAP side) * :support:`-` remove unused dashboard.views.LogoutView and the corresponding URL in dashboard.urls * :feature:`-` add new task stub to set an ldap user's password diff --git a/docs/code/gvacommon.rst b/docs/code/gvacommon.rst index 4dbc502..4a16cb2 100644 --- a/docs/code/gvacommon.rst +++ b/docs/code/gvacommon.rst @@ -13,3 +13,11 @@ provides some functionality that is common to all gnuviechadmin subprojects. .. automodule:: gvacommon.celeryrouters :members: :undoc-members: + + +:py:mod:`viewmixins ` +------------------------------------------- + +.. automodule:: gvacommon.viewmixins + :members: + :undoc-members: diff --git a/docs/code/hostingpackages.rst b/docs/code/hostingpackages.rst index 2d5d077..ad021dc 100644 --- a/docs/code/hostingpackages.rst +++ b/docs/code/hostingpackages.rst @@ -22,3 +22,17 @@ .. automodule:: hostingpackages.models :members: + + +:py:mod:`views ` +--------------------------------------- + +.. automodule:: hostingpackages.views + :members: + + +:py:mod:`urls ` +------------------------------------- + +.. automodule:: hostingpackages.urls + :members: diff --git a/docs/code/osusers.rst b/docs/code/osusers.rst index 46d72cb..3353239 100644 --- a/docs/code/osusers.rst +++ b/docs/code/osusers.rst @@ -18,8 +18,28 @@ :members: +:py:mod:`forms ` +------------------------------- + +.. automodule:: osusers.forms + :members: + + :py:mod:`models ` --------------------------------- .. automodule:: osusers.models :members: + + +:py:mod:`urls ` +----------------------------- + +.. automodule:: osusers.urls + + +:py:mod:`views ` +------------------------------- + +.. automodule:: osusers.views + :members: diff --git a/gnuviechadmin/dashboard/views.py b/gnuviechadmin/dashboard/views.py index d378b1d..1542e45 100644 --- a/gnuviechadmin/dashboard/views.py +++ b/gnuviechadmin/dashboard/views.py @@ -4,15 +4,13 @@ This module defines the views for the gnuviechadmin customer dashboard. """ from __future__ import unicode_literals -from django.http import HttpResponseForbidden from django.views.generic import ( DetailView, TemplateView, ) -from django.utils.translation import ugettext as _ from django.contrib.auth import get_user_model -from braces.views import LoginRequiredMixin +from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin from hostingpackages.models import CustomerHostingPackage @@ -25,7 +23,7 @@ class IndexView(TemplateView): template_name = 'dashboard/index.html' -class UserDashboardView(LoginRequiredMixin, DetailView): +class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView): """ This is the user dashboard view. @@ -35,18 +33,16 @@ class UserDashboardView(LoginRequiredMixin, DetailView): slug_field = 'username' template_name = 'dashboard/user_dashboard.html' - def dispatch(self, request, *args, **kwargs): - if (request.user.is_staff or request.user == self.get_object()): - return super(UserDashboardView, self).dispatch( - request, *args, **kwargs - ) - return HttpResponseForbidden( - _('You are not allowed to view this page.') - ) - def get_context_data(self, **kwargs): context = super(UserDashboardView, self).get_context_data(**kwargs) context['hosting_packages'] = CustomerHostingPackage.objects.filter( customer=self.object ) return context + + def get_customer_object(self): + """ + Returns the customer object. + + """ + return self.get_object() diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py index 3237cb2..6d52afb 100644 --- a/gnuviechadmin/gnuviechadmin/settings/base.py +++ b/gnuviechadmin/gnuviechadmin/settings/base.py @@ -352,5 +352,8 @@ OSUSER_MINGID = int(get_env_variable('GVA_MIN_OS_GID')) OSUSER_USERNAME_PREFIX = get_env_variable('GVA_OSUSER_PREFIX') OSUSER_HOME_BASEPATH = get_env_variable('GVA_OSUSER_HOME_BASEPATH') OSUSER_DEFAULT_SHELL = get_env_variable('GVA_OSUSER_DEFAULT_SHELL') -OSUSER_DEFAULT_GROUPS = ['sftponly'] +OSUSER_SFTP_GROUP = 'sftponly' +OSUSER_SSH_GROUP = 'sshusers' +OSUSER_DEFAULT_GROUPS = [OSUSER_SFTP_GROUP] +OSUSER_UPLOAD_SERVER = get_env_variable('GVA_OSUSER_UPLOADSERVER') ########## END CUSTOM APP CONFIGURATION diff --git a/gnuviechadmin/gnuviechadmin/urls.py b/gnuviechadmin/gnuviechadmin/urls.py index 974ba1f..d3356b0 100644 --- a/gnuviechadmin/gnuviechadmin/urls.py +++ b/gnuviechadmin/gnuviechadmin/urls.py @@ -11,6 +11,7 @@ urlpatterns = patterns( url(r'', include('dashboard.urls')), url(r'^accounts/', include('allauth.urls')), url(r'^hosting/', include('hostingpackages.urls')), + url(r'^osuser/', include('osusers.urls')), url(r'^admin/', include(admin.site.urls)), ) diff --git a/gnuviechadmin/gvacommon/viewmixins.py b/gnuviechadmin/gvacommon/viewmixins.py new file mode 100644 index 0000000..fc7f106 --- /dev/null +++ b/gnuviechadmin/gvacommon/viewmixins.py @@ -0,0 +1,42 @@ +""" +This module defines mixins for gnuviechadmin views. + +""" +from __future__ import unicode_literals + +from django.http import HttpResponseForbidden +from django.utils.translation import ugettext as _ + +from braces.views import LoginRequiredMixin + + +class StaffOrSelfLoginRequiredMixin(LoginRequiredMixin): + """ + Mixin that makes sure that a user is logged in and matches the current + customer or is a staff user. + + """ + + def dispatch(self, request, *args, **kwargs): + if ( + request.user.is_staff or + request.user == self.get_customer_object() + ): + return super(StaffOrSelfLoginRequiredMixin, self).dispatch( + request, *args, **kwargs + ) + return HttpResponseForbidden( + _('You are not allowed to view this page.') + ) + + def get_customer_object(self): + """ + Views based on this mixin have to implement this method to return + the customer that must be an object of the same class as the + django.contrib.auth user type. + + :return: customer + :rtype: settings.AUTH_USER_MODEL + + """ + raise NotImplemented("subclass has to implement get_customer_object") diff --git a/gnuviechadmin/hostingpackages/models.py b/gnuviechadmin/hostingpackages/models.py index b665162..021eb95 100644 --- a/gnuviechadmin/hostingpackages/models.py +++ b/gnuviechadmin/hostingpackages/models.py @@ -5,6 +5,7 @@ This module contains the hosting package models. from __future__ import absolute_import, unicode_literals from django.conf import settings +from django.core.urlresolvers import reverse from django.db import transaction from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -218,6 +219,12 @@ class CustomerHostingPackage(HostingPackageBase): name=self.name, customer=self.customer ) + def get_absolute_url(self): + return reverse('hosting_package_details', kwargs={ + 'user': self.customer.username, + 'pk': self.id, + }) + def copy_template_attributes(self): """ Copy the attributes of the hosting package's template to the package. diff --git a/gnuviechadmin/hostingpackages/urls.py b/gnuviechadmin/hostingpackages/urls.py index 9e7f00d..0280eca 100644 --- a/gnuviechadmin/hostingpackages/urls.py +++ b/gnuviechadmin/hostingpackages/urls.py @@ -1,12 +1,22 @@ +""" +This module defines the URL patterns for hosting package related views. + +""" from __future__ import absolute_import, unicode_literals from django.conf.urls import patterns, url -from .views import CreateHostingPackage +from .views import ( + CreateHostingPackage, + CustomerHostingPackageDetails, +) urlpatterns = patterns( '', url(r'^(?P[\w0-9@.+-_]+)/create$', CreateHostingPackage.as_view(), name='create_hosting_package'), + url(r'^(?P[\w0-9@.+-_]+)/hostingpackage/(?P\d+)/$', + CustomerHostingPackageDetails.as_view(), + name='hosting_package_details'), ) diff --git a/gnuviechadmin/hostingpackages/views.py b/gnuviechadmin/hostingpackages/views.py index a29c098..e368088 100644 --- a/gnuviechadmin/hostingpackages/views.py +++ b/gnuviechadmin/hostingpackages/views.py @@ -4,18 +4,22 @@ This module defines views related to hosting packages. """ from __future__ import absolute_import, unicode_literals +from django.conf import settings from django.core.urlresolvers import reverse from django.shortcuts import redirect from django.utils.translation import ugettext as _ +from django.views.generic import DetailView from django.views.generic.edit import CreateView -from django.contrib.auth import get_user_model from django.contrib import messages +from django.contrib.auth import get_user_model from braces.views import ( LoginRequiredMixin, StaffuserRequiredMixin, ) +from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin + from .forms import CreateHostingPackageForm from .models import CustomerHostingPackage @@ -37,13 +41,12 @@ class CreateHostingPackage( kwargs.update(self.kwargs) return kwargs - def _get_customer(self): + def get_customer_object(self): return get_user_model().objects.get(username=self.kwargs['user']) - def get_context_data(self, **kwargs): context = super(CreateHostingPackage, self).get_context_data(**kwargs) - context['customer'] = self._get_customer() + context['customer'] = self.get_customer_object() return context def get_success_url(self): @@ -52,7 +55,7 @@ class CreateHostingPackage( def form_valid(self, form): hostingpackage = form.save(commit=False) - hostingpackage.customer = self._get_customer() + hostingpackage.customer = self.get_customer_object() hostingpackage.save() messages.success( self.request, @@ -60,3 +63,24 @@ class CreateHostingPackage( name=hostingpackage.name) ) return redirect(self.get_success_url()) + + +class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView): + """ + This view is for showing details of a customer hosting package. + + """ + model = CustomerHostingPackage + context_object_name = 'hostingpackage' + + def get_customer_object(self): + return get_user_model().objects.get(username=self.kwargs['user']) + + def get_context_data(self, **kwargs): + context = super(CustomerHostingPackageDetails, self).get_context_data( + **kwargs) + context.update({ + 'customer': self.get_customer_object(), + 'uploadserver': settings.OSUSER_UPLOAD_SERVER, + }) + return context diff --git a/gnuviechadmin/osusers/admin.py b/gnuviechadmin/osusers/admin.py index 3bb3579..7d757be 100644 --- a/gnuviechadmin/osusers/admin.py +++ b/gnuviechadmin/osusers/admin.py @@ -6,6 +6,9 @@ from django import forms from django.utils.translation import ugettext as _ from django.contrib import admin +from .forms import ( + PASSWORD_MISMATCH_ERROR +) from .models import ( AdditionalGroup, Group, @@ -13,11 +16,6 @@ from .models import ( User, ) -PASSWORD_MISMATCH_ERROR = _("Passwords don't match") -""" -Error message for non matching passwords. -""" - class AdditionalGroupInline(admin.TabularInline): """ diff --git a/gnuviechadmin/osusers/forms.py b/gnuviechadmin/osusers/forms.py new file mode 100644 index 0000000..1ed1c82 --- /dev/null +++ b/gnuviechadmin/osusers/forms.py @@ -0,0 +1,71 @@ +""" +This module defines operating system user related forms. + +""" +from __future__ import unicode_literals + +from django import forms +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit + +from .models import User + +PASSWORD_MISMATCH_ERROR = _("Passwords don't match") +""" +Error message for non matching passwords. +""" + + +class ChangeOsUserPasswordForm(forms.ModelForm): + """ + A form for setting an OS user's password. + + """ + password1 = forms.CharField( + label=_('Password'), widget=forms.PasswordInput, + required=False, + ) + password2 = forms.CharField( + label=_('Password (again)'), widget=forms.PasswordInput, + required=False, + ) + + class Meta: + model = User + fields = [] + + def __init__(self, *args, **kwargs): + self.helper = FormHelper() + super(ChangeOsUserPasswordForm, self).__init__(*args, **kwargs) + self.helper.form_action = reverse( + 'set_osuser_password', kwargs={'slug': self.instance.username}) + self.helper.add_input(Submit('submit', _('Set password'))) + + def clean_password2(self): + """ + Check that the two password entries match. + + :return: the validated password + :rtype: str or None + + """ + password1 = self.cleaned_data.get('password1') + password2 = self.cleaned_data.get('password2') + if password1 and password2 and password1 != password2: + raise forms.ValidationError(PASSWORD_MISMATCH_ERROR) + return password2 + + def save(self, commit=True): + """ + Save the provided password in hashed format. + + :param boolean commit: whether to save the created user + :return: user instance + :rtype: :py:class:`osusers.models.User` + + """ + self.instance.set_password(self.cleaned_data['password1']) + return super(ChangeOsUserPasswordForm, self).save(commit=commit) diff --git a/gnuviechadmin/osusers/models.py b/gnuviechadmin/osusers/models.py index c5c25a6..5f64a12 100644 --- a/gnuviechadmin/osusers/models.py +++ b/gnuviechadmin/osusers/models.py @@ -29,6 +29,7 @@ from ldaptasks.tasks import ( delete_ldap_group, delete_ldap_user, remove_ldap_user_from_group, + set_ldap_user_password, ) from fileservertasks.tasks import ( @@ -245,15 +246,26 @@ class User(TimeStampedModel, models.Model): """ if hasattr(self, 'shadow'): self.shadow.set_password(password) + success = set_ldap_user_password.delay( + self.username, password).get() + if success: + _LOGGER.info( + "successfully set LDAP password for %s", self.username) + else: + _LOGGER.error( + "setting the LDAP password for %s failed", self.username) + return success else: self.shadow = Shadow.objects.create_shadow( user=self, password=password ) - dn = create_ldap_user.delay( - self.username, self.uid, self.group.gid, self.gecos, - self.homedir, self.shell, password - ).get() - logging.info("set LDAP password for %s", dn) + dn = create_ldap_user.delay( + self.username, self.uid, self.group.gid, self.gecos, + self.homedir, self.shell, password + ).get() + _LOGGER.info("set LDAP password for %s", dn) + return True + @transaction.atomic def save(self, *args, **kwargs): diff --git a/gnuviechadmin/osusers/urls.py b/gnuviechadmin/osusers/urls.py new file mode 100644 index 0000000..7b62056 --- /dev/null +++ b/gnuviechadmin/osusers/urls.py @@ -0,0 +1,16 @@ +""" +This module defines the URL patterns for operating system user related views. + +""" +from __future__ import absolute_import, unicode_literals + +from django.conf.urls import patterns, url + +from .views import SetOsUserPassword + + +urlpatterns = patterns( + '', + url(r'^(?P[\w0-9@.+-_]+)/setpassword$', SetOsUserPassword.as_view(), + name='set_osuser_password'), +) diff --git a/gnuviechadmin/osusers/views.py b/gnuviechadmin/osusers/views.py new file mode 100644 index 0000000..a78067a --- /dev/null +++ b/gnuviechadmin/osusers/views.py @@ -0,0 +1,45 @@ +""" +This module defines the views for gnuviechadmin operating system user handling. + +""" +from __future__ import unicode_literals, absolute_import + +from django.shortcuts import redirect +from django.views.generic import UpdateView +from django.utils.translation import ugettext as _ +from django.contrib import messages + +from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin + +from .forms import ChangeOsUserPasswordForm +from .models import User + + +class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView): + """ + This view is used for setting a new operating system user password. + + """ + model = User + slug_field = 'username' + template_name_suffix = '_setpassword' + context_object_name = 'osuser' + form_class = ChangeOsUserPasswordForm + + def get_customer_object(self): + return self.get_object().customer + + def get_context_data(self, *args, **kwargs): + context = super(SetOsUserPassword, self).get_context_data( + *args, **kwargs) + context['customer'] = self.get_customer_object() + return context + + def form_valid(self, form): + osuser = form.save() + messages.success( + self.request, + _("New password for {username} has been set successfully.").format( + username=osuser.username + )) + return redirect(osuser.customerhostingpackage) diff --git a/gnuviechadmin/templates/dashboard/user_dashboard.html b/gnuviechadmin/templates/dashboard/user_dashboard.html index fc2509a..929a088 100644 --- a/gnuviechadmin/templates/dashboard/user_dashboard.html +++ b/gnuviechadmin/templates/dashboard/user_dashboard.html @@ -22,7 +22,7 @@ {% for package in hosting_packages %} - {{ package.name }} + {{ package.name }} {% with diskspace=package.get_disk_space %} {{ diskspace|filesizeformat }} diff --git a/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html new file mode 100644 index 0000000..daa06b5 --- /dev/null +++ b/gnuviechadmin/templates/hostingpackages/customerhostingpackage_detail.html @@ -0,0 +1,48 @@ +{% extends "hostingpackages/base.html" %} +{% load i18n %} + +{% block title %}{{ block.super }} - {% spaceless %} +{% if user == customer %} + {% blocktrans with package=hostingpackage.name %}Details for your Hosting Package {{ package }}{% endblocktrans %} +{% else %} + {% blocktrans with package=hostingpackage.name full_name=customer.get_full_name %}Details for Hosting Package {{ package }} of {{ full_name }}{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock title %} + +{% block page_title %}{% blocktrans with package=hostingpackage.name %}Details of Hosting Package {{ package }}{% endblocktrans %}{% endblock page_title %} + +{% block content %} +
+
+
+
+ {% trans "Hosting Package Information" %}
+
+
+
{% trans "Name" %}
+
{{ hostingpackage.name }}
+
{% trans "Description" %}
+
{{ hostingpackage.description|default:"-" }}
+
{% trans "Disk space" %}
+ {% with diskspace=hostingpackage.get_disk_space %} +
{{ diskspace|filesizeformat }}
+ {% endwith %} +
{% trans "Mailboxes" %}
+
{% blocktrans with num=hostingpackage.get_used_mailboxes total=hostingpackage.get_mailboxes %}{{ num }} of {{ total }} in use{% endblocktrans %}
+
{% if hostingpackage.osuser.is_sftp_user %}{% trans "SFTP username" %}{% else %}{% trans "SSH/SFTP username" %}{% endif %}
+
{{ hostingpackage.osuser.username }}
+
{% trans "Upload server" %}
+
{{ uploadserver }}
+
+
+
+ +
+{% endblock content %} diff --git a/gnuviechadmin/templates/osusers/base.html b/gnuviechadmin/templates/osusers/base.html new file mode 100644 index 0000000..94d9808 --- /dev/null +++ b/gnuviechadmin/templates/osusers/base.html @@ -0,0 +1 @@ +{% extends "base.html" %} diff --git a/gnuviechadmin/templates/osusers/user_setpassword.html b/gnuviechadmin/templates/osusers/user_setpassword.html new file mode 100644 index 0000000..86fa844 --- /dev/null +++ b/gnuviechadmin/templates/osusers/user_setpassword.html @@ -0,0 +1,30 @@ +{% extends "osusers/base.html" %} +{% load i18n crispy_forms_tags %} +{% block title %}{{ block.super }} - {% spaceless %} +{% if customer == user %} + {% blocktrans with osuser=osuser.username %}Set new password for user {{ osuser }}{% endblocktrans %} +{% else %} + {% blocktrans with osuser=osuser.username full_name=customer.get_full_name %}Set new password for user {{ osuser }} of customer {{ full_name }}{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock title %} + +{% block page_title %}{% spaceless %} +{% if customer == user %} + {% blocktrans with osuser=osuser.username %}Set new password for user {{ osuser }}{% endblocktrans %} +{% else %} + {% blocktrans with osuser=osuser.username full_name=customer.get_full_name %}Set new password for user {{ osuser }} of customer {{ full_name }}{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock page_title %} + +{% block content %} +{% crispy form %} +{% endblock content %} + +{% block extra_js %} + +{% endblock extra_js %}