Merge branch 'feature/customer-users'
* feature/customer-users: add german translation add code documentation to docs document osusers code add customer field to osusers.models.User allow generated password for new osusers
This commit is contained in:
commit
42b1b07cc4
13 changed files with 868 additions and 38 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -43,3 +43,4 @@ Desktop.ini
|
|||
htmlcov/
|
||||
tags
|
||||
_build/
|
||||
*.mo
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
* :feature:`-` add a `customer` field to :py:class:`osusers.models.User`
|
||||
* :feature:`-` allow empty password input in
|
||||
:py:class:`osusers.admin.UserCreationForm` to allow generated passwords for
|
||||
new users
|
||||
|
||||
* :release:`0.3.0 <2014-12-27>`
|
||||
* :feature:`-` call create/delete mailbox tasks when saving/deleting mailboxes
|
||||
* :support:`-` use celery routers from gvacommon
|
||||
|
|
150
docs/code.rst
Normal file
150
docs/code.rst
Normal file
|
@ -0,0 +1,150 @@
|
|||
******************
|
||||
Code documentation
|
||||
******************
|
||||
|
||||
.. index:: Django
|
||||
|
||||
gva is implemented as `Django`_ project and provides a frontend for
|
||||
administrators and customers.
|
||||
|
||||
.. _Django: https://www.djangoproject.com/
|
||||
|
||||
|
||||
The project module :py:mod:`gnuviechadmin`
|
||||
==========================================
|
||||
|
||||
.. automodule:: gnuviechadmin
|
||||
|
||||
|
||||
:py:mod:`celery <gnuviechadmin.celery>`
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: gnuviechadmin.celery
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`urls <gnuviechadmin.urls>`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: gnuviechadmin.urls
|
||||
|
||||
|
||||
:py:mod:`wsgi <gnuviechadmin.wsgi>`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: gnuviechadmin.wsgi
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`settings <gnuviechadmin.settings>`
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: gnuviechadmin.settings
|
||||
|
||||
|
||||
:py:mod:`base <gnuviechadmin.settings.base>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.base
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`local <gnuviechadmin.settings.local>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.local
|
||||
|
||||
|
||||
:py:mod:`production <gnuviechadmin.settings.production>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.production
|
||||
|
||||
|
||||
:py:mod:`test <gnuviechadmin.settings.test>`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. automodule:: gnuviechadmin.settings.test
|
||||
|
||||
|
||||
:py:mod:`gvacommon`
|
||||
===================
|
||||
|
||||
This module is imported from a separate git project via git subtree and
|
||||
provides some functionality that is common to all gnuviechadmin subprojects.
|
||||
|
||||
.. automodule:: gvacommon
|
||||
|
||||
|
||||
:py:mod:`celeryrouters <gvacommon.celeryrouters>`
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: gvacommon.celeryrouters
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
:py:mod:`managemails` app
|
||||
=========================
|
||||
|
||||
.. automodule:: managemails
|
||||
|
||||
|
||||
:py:mod:`admin <managemails.admin>`
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: managemails.admin
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`models <managemails.models>`
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: managemails.models
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`osusers` app
|
||||
=====================
|
||||
|
||||
.. automodule:: osusers
|
||||
|
||||
|
||||
:py:mod:`admin <osusers.admin>`
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: osusers.admin
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`apps <osusers.apps>`
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: osusers.apps
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`models <osusers.models>`
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: osusers.models
|
||||
:members:
|
||||
|
||||
|
||||
:py:mod:`tasks <osusers.tasks>`
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: osusers.tasks
|
||||
|
||||
.. autotask:: osusers.tasks.add_ldap_user_to_group
|
||||
.. autotask:: osusers.tasks.create_file_mailbox
|
||||
.. autotask:: osusers.tasks.create_ldap_group
|
||||
.. autotask:: osusers.tasks.create_ldap_user
|
||||
.. autotask:: osusers.tasks.delete_file_mail_userdir
|
||||
.. autotask:: osusers.tasks.delete_file_mailbox
|
||||
.. autotask:: osusers.tasks.delete_file_sftp_userdir
|
||||
.. autotask:: osusers.tasks.delete_ldap_group_if_empty
|
||||
.. autotask:: osusers.tasks.delete_ldap_user
|
||||
.. autotask:: osusers.tasks.remove_ldap_user_from_group
|
||||
.. autotask:: osusers.tasks.setup_file_mail_userdir
|
||||
.. autotask:: osusers.tasks.setup_file_sftp_userdir
|
11
docs/conf.py
11
docs/conf.py
|
@ -13,13 +13,18 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
#import sys
|
||||
#import os
|
||||
import sys
|
||||
import os
|
||||
import django
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join('..', 'gnuviechadmin')))
|
||||
|
||||
os.environ['GVA_SITE_ADMINMAIL'] = 'admin@gva.example.org'
|
||||
|
||||
django.setup()
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ Contents:
|
|||
install
|
||||
deploy
|
||||
tests
|
||||
code
|
||||
changelog
|
||||
|
||||
|
||||
|
|
60
gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po
Normal file
60
gnuviechadmin/managemails/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,60 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: managemails\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-12-27 22:45+0100\n"
|
||||
"PO-Revision-Date: 2014-12-27 22:57+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: de <de@li.org>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: admin.py:14
|
||||
msgid "Passwords don't match"
|
||||
msgstr "Passwörter stimmen nicht überein"
|
||||
|
||||
#: admin.py:21 tests/test_admin.py:37
|
||||
msgid "Hash"
|
||||
msgstr "Hash-Code"
|
||||
|
||||
#: admin.py:44
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: admin.py:46
|
||||
msgid "Password (again)"
|
||||
msgstr "Passwortwiederholung"
|
||||
|
||||
#: admin.py:100
|
||||
msgid "Activate"
|
||||
msgstr "Aktivieren"
|
||||
|
||||
#: admin.py:101
|
||||
msgid "Deactivate"
|
||||
msgstr "Deaktivieren"
|
||||
|
||||
#: models.py:51
|
||||
msgid "Mailbox"
|
||||
msgstr "Postfach"
|
||||
|
||||
#: models.py:52
|
||||
msgid "Mailboxes"
|
||||
msgstr "Postfächer"
|
||||
|
||||
#: models.py:76
|
||||
msgid "Mail address"
|
||||
msgstr "E-Mailadresse"
|
||||
|
||||
#: models.py:77
|
||||
msgid "Mail addresses"
|
||||
msgstr "E-Mailadressen"
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
This app is for managing operating system users and groups.
|
||||
|
||||
"""
|
||||
default_app_config = 'osusers.apps.OsusersAppConfig'
|
|
@ -1,3 +1,7 @@
|
|||
"""
|
||||
This module contains the Django admin classes of the :py:mod:`osusers` app.
|
||||
|
||||
"""
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import admin
|
||||
|
@ -10,13 +14,24 @@ from .models import (
|
|||
)
|
||||
|
||||
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
|
||||
"""
|
||||
Error message for non matching passwords.
|
||||
"""
|
||||
|
||||
|
||||
class AdditionalGroupInline(admin.TabularInline):
|
||||
"""
|
||||
Inline for :py:class:`osusers.models.AdditionalGroup` instances.
|
||||
|
||||
"""
|
||||
model = AdditionalGroup
|
||||
|
||||
|
||||
class ShadowInline(admin.TabularInline):
|
||||
"""
|
||||
Inline for :py:class:`osusers.models.ShadowInline` instances.
|
||||
|
||||
"""
|
||||
model = Shadow
|
||||
readonly_fields = ['passwd']
|
||||
can_delete = False
|
||||
|
@ -24,22 +39,30 @@ class ShadowInline(admin.TabularInline):
|
|||
|
||||
class UserCreationForm(forms.ModelForm):
|
||||
"""
|
||||
A form for creating system users.
|
||||
A form for creating :py:class:`operating system users
|
||||
<osusers.models.User>`.
|
||||
|
||||
"""
|
||||
password1 = forms.CharField(label=_('Password'),
|
||||
widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(label=_('Password (again)'),
|
||||
widget=forms.PasswordInput)
|
||||
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 = []
|
||||
fields = ['customer']
|
||||
|
||||
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')
|
||||
|
@ -51,8 +74,13 @@ class UserCreationForm(forms.ModelForm):
|
|||
"""
|
||||
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`
|
||||
|
||||
"""
|
||||
user = User.objects.create_user(
|
||||
customer=self.cleaned_data['customer'],
|
||||
password=self.cleaned_data['password1'], commit=commit)
|
||||
return user
|
||||
|
||||
|
@ -60,10 +88,16 @@ class UserCreationForm(forms.ModelForm):
|
|||
"""
|
||||
No additional groups are created when this form is saved, so this
|
||||
method just does nothing.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin class for working with :py:class:`operating system users
|
||||
<osusers.models.User>`.
|
||||
|
||||
"""
|
||||
actions = ['perform_delete_selected']
|
||||
add_form = UserCreationForm
|
||||
inlines = [AdditionalGroupInline, ShadowInline]
|
||||
|
@ -71,13 +105,20 @@ class UserAdmin(admin.ModelAdmin):
|
|||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('password1', 'password2')}),
|
||||
'fields': ('customer', 'password1', 'password2')}),
|
||||
)
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Use special form during user creation.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param obj: either a :py:class:`User <osusers.models.User>` instance or
|
||||
None for a new user
|
||||
:param kwargs: keyword arguments to be passed to
|
||||
:py:meth:`django.contrib.admin.ModelAdmin.get_form`
|
||||
:return: form instance
|
||||
|
||||
"""
|
||||
defaults = {}
|
||||
if obj is None:
|
||||
|
@ -89,16 +130,47 @@ class UserAdmin(admin.ModelAdmin):
|
|||
return super(UserAdmin, self).get_form(request, obj, **defaults)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
"""
|
||||
Make sure that uid is not editable for existing users.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param obj: either a :py:class:`User <osusers.models.User>` instance or
|
||||
None for a new user
|
||||
:return: a list of fields
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
if obj:
|
||||
return ['uid']
|
||||
return []
|
||||
|
||||
def perform_delete_selected(self, request, queryset):
|
||||
"""
|
||||
Action to delete a list of selected users.
|
||||
|
||||
This action calls the delete method of each selected user in contrast
|
||||
to the default `delete_selected`.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param queryset: Django ORM queryset representing the selected users
|
||||
|
||||
"""
|
||||
for user in queryset.all():
|
||||
user.delete()
|
||||
perform_delete_selected.short_description = _('Delete selected users')
|
||||
|
||||
def get_actions(self, request):
|
||||
"""
|
||||
Get the available actions for users.
|
||||
|
||||
This overrides the default behavior to remove the default
|
||||
`delete_selected` action.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:return: list of actions
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
actions = super(UserAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
|
@ -106,19 +178,40 @@ class UserAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin class for workint with :py:class:`operating system groups
|
||||
<osusers.models.Group>`.
|
||||
|
||||
"""
|
||||
actions = ['perform_delete_selected']
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
if obj is None:
|
||||
return []
|
||||
return super(GroupAdmin, self).get_inline_instances(request, obj)
|
||||
|
||||
def perform_delete_selected(self, request, queryset):
|
||||
"""
|
||||
Action to delete a list of selected groups.
|
||||
|
||||
This action calls the delete method of each selected group in contrast
|
||||
to the default `delete_selected`.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:param queryset: Django ORM queryset representing the selected groups
|
||||
|
||||
"""
|
||||
for group in queryset.all():
|
||||
group.delete()
|
||||
perform_delete_selected.short_description = _('Delete selected groups')
|
||||
|
||||
def get_actions(self, request):
|
||||
"""
|
||||
Get the available actions for groups.
|
||||
|
||||
This overrides the default behavior to remove the default
|
||||
`delete_selected` action.
|
||||
|
||||
:param request: the current HTTP request
|
||||
:return: list of actions
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
actions = super(GroupAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
|
|
17
gnuviechadmin/osusers/apps.py
Normal file
17
gnuviechadmin/osusers/apps.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
This module contains the :py:class:`django.apps.AppConfig` instance for the
|
||||
:py:module:`osusers` app.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class OsusersAppConfig(AppConfig):
|
||||
"""
|
||||
AppConfig for the :py:mod:`osusers` app.
|
||||
|
||||
"""
|
||||
name = 'osusers'
|
||||
verbose_name = _('Operating System Users and Groups')
|
175
gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po
Normal file
175
gnuviechadmin/osusers/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,175 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: osusers\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-12-27 22:46+0100\n"
|
||||
"PO-Revision-Date: 2014-12-27 22:54+0100\n"
|
||||
"Last-Translator: Jan Dittberner <jan@dittberner.info>\n"
|
||||
"Language-Team: de <de@li.org>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.6.10\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
#: admin.py:16
|
||||
msgid "Passwords don't match"
|
||||
msgstr "Passwörter stimmen nicht überein"
|
||||
|
||||
#: admin.py:47
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: admin.py:51
|
||||
msgid "Password (again)"
|
||||
msgstr "Passwortwiederholung"
|
||||
|
||||
#: admin.py:160
|
||||
msgid "Delete selected users"
|
||||
msgstr "Ausgewählte Nutzer löschen"
|
||||
|
||||
#: admin.py:201
|
||||
msgid "Delete selected groups"
|
||||
msgstr "Ausgewählte Gruppen löschen"
|
||||
|
||||
#: apps.py:17
|
||||
msgid "Operating System Users and Groups"
|
||||
msgstr "Betriebssystemnutzer- und Gruppen"
|
||||
|
||||
#: models.py:41
|
||||
msgid "You can not use a user's primary group."
|
||||
msgstr "Sie können nicht die primäre Gruppe des Nutzers verwenden."
|
||||
|
||||
#: models.py:71
|
||||
msgid "Group name"
|
||||
msgstr "Gruppenname"
|
||||
|
||||
#: models.py:73
|
||||
msgid "Group ID"
|
||||
msgstr "Gruppen-ID"
|
||||
|
||||
#: models.py:74
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: models.py:76
|
||||
msgid "Group password"
|
||||
msgstr "Gruppenpasswort"
|
||||
|
||||
#: models.py:81 models.py:212
|
||||
msgid "Group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: models.py:82
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: models.py:209
|
||||
msgid "User name"
|
||||
msgstr "Nutzername"
|
||||
|
||||
#: models.py:211
|
||||
msgid "User ID"
|
||||
msgstr "Nutzer-ID"
|
||||
|
||||
#: models.py:213
|
||||
msgid "Gecos field"
|
||||
msgstr "GECOS-Feld"
|
||||
|
||||
#: models.py:214
|
||||
msgid "Home directory"
|
||||
msgstr "Home-Verzeichnis"
|
||||
|
||||
#: models.py:215
|
||||
msgid "Login shell"
|
||||
msgstr "Loginshell"
|
||||
|
||||
#: models.py:221 models.py:335
|
||||
msgid "User"
|
||||
msgstr "Nutzer"
|
||||
|
||||
#: models.py:222
|
||||
msgid "Users"
|
||||
msgstr "Nutzer"
|
||||
|
||||
#: models.py:336
|
||||
msgid "Encrypted password"
|
||||
msgstr "Verschlüsseltes Passwort"
|
||||
|
||||
#: models.py:338
|
||||
msgid "Date of last change"
|
||||
msgstr "Datum der letzten Änderung"
|
||||
|
||||
#: models.py:339
|
||||
msgid "This is expressed in days since Jan 1, 1970"
|
||||
msgstr "Ausgedrückt als Tage seit dem 1. Januar 1970"
|
||||
|
||||
#: models.py:342
|
||||
msgid "Minimum age"
|
||||
msgstr "Minimales Alter"
|
||||
|
||||
#: models.py:343
|
||||
msgid "Minimum number of days before the password can be changed"
|
||||
msgstr "Minmale Anzahl von Tagen bevor das Passwort geändert werden kann"
|
||||
|
||||
#: models.py:347
|
||||
msgid "Maximum age"
|
||||
msgstr "Maximales Alter"
|
||||
|
||||
#: models.py:348
|
||||
msgid "Maximum number of days after which the password has to be changed"
|
||||
msgstr ""
|
||||
"Maximale Anzahl von Tagen, nach denen das Passwort geändert werden muss"
|
||||
|
||||
#: models.py:352
|
||||
msgid "Grace period"
|
||||
msgstr "Duldungsperiode"
|
||||
|
||||
#: models.py:353
|
||||
msgid "The number of days before the password is going to expire"
|
||||
msgstr "Anzahl von Tagen nach denen das Passwort verfällt"
|
||||
|
||||
#: models.py:357
|
||||
msgid "Inactivity period"
|
||||
msgstr "Inaktivitätsperiode"
|
||||
|
||||
#: models.py:358
|
||||
msgid ""
|
||||
"The number of days after the password has expired during which the password "
|
||||
"should still be accepted"
|
||||
msgstr ""
|
||||
"Die Anzahl von Tagen für die ein verfallenes Passwort noch akzeptiert werden "
|
||||
"soll"
|
||||
|
||||
#: models.py:362
|
||||
msgid "Account expiration date"
|
||||
msgstr "Kontoverfallsdatum"
|
||||
|
||||
#: models.py:363
|
||||
msgid ""
|
||||
"The date of expiration of the account, expressed as number of days since Jan "
|
||||
"1, 1970"
|
||||
msgstr "Kontoverfallsdatum in Tagen seit dem 1. Januar 1970"
|
||||
|
||||
#: models.py:370
|
||||
msgid "Shadow password"
|
||||
msgstr "Shadow-Passwort"
|
||||
|
||||
#: models.py:371
|
||||
msgid "Shadow passwords"
|
||||
msgstr "Shadow-Passwörter"
|
||||
|
||||
#: models.py:397
|
||||
msgid "Additional group"
|
||||
msgstr "Weitere Gruppe"
|
||||
|
||||
#: models.py:398
|
||||
msgid "Additional groups"
|
||||
msgstr "Weitere Gruppen"
|
22
gnuviechadmin/osusers/migrations/0003_user_customer.py
Normal file
22
gnuviechadmin/osusers/migrations/0003_user_customer.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('osusers', '0002_auto_20141226_1456'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='customer',
|
||||
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -1,3 +1,7 @@
|
|||
"""
|
||||
This module defines the database models of operating system users.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import date
|
||||
|
@ -30,7 +34,7 @@ from .tasks import (
|
|||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
|
||||
|
@ -38,8 +42,19 @@ CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
|
|||
|
||||
|
||||
class GroupManager(models.Manager):
|
||||
"""
|
||||
Manager class for :py:class:`osusers.models.Group`.
|
||||
|
||||
"""
|
||||
|
||||
def get_next_gid(self):
|
||||
"""
|
||||
Get the next available group id.
|
||||
|
||||
:returns: group id
|
||||
:rtype: int
|
||||
|
||||
"""
|
||||
q = self.aggregate(models.Max('gid'))
|
||||
if q['gid__max'] is None:
|
||||
return settings.OSUSER_MINGID
|
||||
|
@ -48,6 +63,10 @@ class GroupManager(models.Manager):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Group(TimeStampedModel, models.Model):
|
||||
"""
|
||||
This entity class corresponds to an operating system group.
|
||||
|
||||
"""
|
||||
groupname = models.CharField(
|
||||
_('Group name'), max_length=16, unique=True)
|
||||
gid = models.PositiveSmallIntegerField(
|
||||
|
@ -67,34 +86,73 @@ class Group(TimeStampedModel, models.Model):
|
|||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save the group to the database and synchronizes group information to
|
||||
LDAP.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:return: self
|
||||
:rtype: :py:class:`osusers.models.Group`
|
||||
|
||||
"""
|
||||
super(Group, self).save(*args, **kwargs)
|
||||
dn = create_ldap_group.delay(
|
||||
self.groupname, self.gid, self.descr).get()
|
||||
logger.info("created LDAP group with dn %s", dn)
|
||||
_LOGGER.info("created LDAP group with dn %s", dn)
|
||||
return self
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Delete the group from LDAP and the database.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
|
||||
"""
|
||||
delete_ldap_group_if_empty.delay(self.groupname).get()
|
||||
super(Group, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
class UserManager(models.Manager):
|
||||
"""
|
||||
Manager class for :py:class:`osusers.models.User`.
|
||||
|
||||
"""
|
||||
|
||||
def get_next_uid(self):
|
||||
"""
|
||||
Get the next available user id.
|
||||
|
||||
:return: user id
|
||||
:rtype: int
|
||||
|
||||
"""
|
||||
q = self.aggregate(models.Max('uid'))
|
||||
if q['uid__max'] is None:
|
||||
return settings.OSUSER_MINUID
|
||||
return max(settings.OSUSER_MINUID, q['uid__max'] + 1)
|
||||
|
||||
def get_next_username(self):
|
||||
"""
|
||||
Get the next available user name.
|
||||
|
||||
:return: user name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
count = 1
|
||||
usernameformat = "{0}{1:02d}"
|
||||
nextuser = usernameformat.format(settings.OSUSER_USERNAME_PREFIX,
|
||||
count)
|
||||
for user in self.values('username').filter(
|
||||
username__startswith=settings.OSUSER_USERNAME_PREFIX).order_by(
|
||||
'username'):
|
||||
username__startswith=settings.OSUSER_USERNAME_PREFIX
|
||||
).order_by('username'):
|
||||
if user['username'] == nextuser:
|
||||
count += 1
|
||||
nextuser = usernameformat.format(
|
||||
|
@ -104,7 +162,26 @@ class UserManager(models.Manager):
|
|||
return nextuser
|
||||
|
||||
@transaction.atomic
|
||||
def create_user(self, username=None, password=None, commit=False):
|
||||
def create_user(
|
||||
self, customer, username=None, password=None, commit=False
|
||||
):
|
||||
"""
|
||||
Create a new user with a primary group named the same as the user and
|
||||
an initial password.
|
||||
|
||||
If username is None the result of :py:meth:`get_next_username` is used.
|
||||
If password is None a new password will be generated using passlib's
|
||||
:py:func:`generate_password`.
|
||||
|
||||
:param customer: Django User instance this user is associated to
|
||||
:param str username: the username or None
|
||||
:param str password: the password or None
|
||||
:param boolean commit: whether to commit the user data to the database
|
||||
or not
|
||||
:return: new user
|
||||
:rtype: :py:class:`osusers.models.User`
|
||||
|
||||
"""
|
||||
uid = self.get_next_uid()
|
||||
gid = Group.objects.get_next_gid()
|
||||
if username is None:
|
||||
|
@ -114,7 +191,7 @@ class UserManager(models.Manager):
|
|||
homedir = os.path.join(settings.OSUSER_HOME_BASEPATH, username)
|
||||
group = Group.objects.create(groupname=username, gid=gid)
|
||||
user = self.create(username=username, group=group, uid=uid,
|
||||
homedir=homedir,
|
||||
homedir=homedir, customer=customer,
|
||||
shell=settings.OSUSER_DEFAULT_SHELL)
|
||||
user.set_password(password)
|
||||
if commit:
|
||||
|
@ -124,6 +201,10 @@ class UserManager(models.Manager):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class User(TimeStampedModel, models.Model):
|
||||
"""
|
||||
This entity class corresponds to an operating system user.
|
||||
|
||||
"""
|
||||
username = models.CharField(
|
||||
_('User name'), max_length=64, unique=True)
|
||||
uid = models.PositiveSmallIntegerField(
|
||||
|
@ -132,6 +213,7 @@ class User(TimeStampedModel, models.Model):
|
|||
gecos = models.CharField(_('Gecos field'), max_length=128, blank=True)
|
||||
homedir = models.CharField(_('Home directory'), max_length=256)
|
||||
shell = models.CharField(_('Login shell'), max_length=64)
|
||||
customer = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
|
@ -144,6 +226,15 @@ class User(TimeStampedModel, models.Model):
|
|||
|
||||
@transaction.atomic
|
||||
def set_password(self, password):
|
||||
"""
|
||||
Set the password of the user.
|
||||
|
||||
The password is set to the user's
|
||||
:py:class:`Shadow <osusers.models.Shadow>` instance and to LDAP.
|
||||
|
||||
:param str password: the new password
|
||||
|
||||
"""
|
||||
if hasattr(self, 'shadow'):
|
||||
self.shadow.set_password(password)
|
||||
else:
|
||||
|
@ -158,12 +249,24 @@ class User(TimeStampedModel, models.Model):
|
|||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save the user to the database, create user directories and synchronize
|
||||
user information to LDAP.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:return: self
|
||||
:rtype: :py:class:`osusers.models.User`
|
||||
|
||||
"""
|
||||
dn = create_ldap_user.delay(
|
||||
self.username, self.uid, self.group.gid, self.gecos,
|
||||
self.homedir, self.shell, password=None).get()
|
||||
sftp_dir = setup_file_sftp_userdir.delay(self.username).get()
|
||||
mail_dir = setup_file_mail_userdir.delay(self.username).get()
|
||||
logger.info(
|
||||
_LOGGER.info(
|
||||
"created user %(user)s with LDAP dn %(dn)s, home directory "
|
||||
"%(homedir)s and mail base directory %(maildir)s.", {
|
||||
'user': self, 'dn': dn,
|
||||
|
@ -173,6 +276,16 @@ class User(TimeStampedModel, models.Model):
|
|||
|
||||
@transaction.atomic
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Delete the user and its groups from LDAP and the database and remove
|
||||
the user's directories.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
|
||||
"""
|
||||
delete_file_mail_userdir.delay(self.username).get()
|
||||
delete_file_sftp_userdir.delay(self.username).get()
|
||||
for group in [ag.group for ag in self.additionalgroup_set.all()]:
|
||||
|
@ -184,9 +297,23 @@ class User(TimeStampedModel, models.Model):
|
|||
|
||||
|
||||
class ShadowManager(models.Manager):
|
||||
"""
|
||||
Manager class for :py:class:`osusers.models.Shadow`.
|
||||
|
||||
"""
|
||||
|
||||
@transaction.atomic
|
||||
def create_shadow(self, user, password):
|
||||
"""
|
||||
Create a new shadow instance with typical Linux settings for the given
|
||||
user with the given password.
|
||||
|
||||
:param user: :py:class:`User <osusers.models.User>` instance
|
||||
:param str password: the password
|
||||
:return: new Shadow instance
|
||||
:rtype: :py:class:`osusers.models.Shadow` instance
|
||||
|
||||
"""
|
||||
changedays = (timezone.now().date() - date(1970, 1, 1)).days
|
||||
shadow = self.create(
|
||||
user=user, changedays=changedays,
|
||||
|
@ -200,6 +327,11 @@ class ShadowManager(models.Manager):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class Shadow(TimeStampedModel, models.Model):
|
||||
"""
|
||||
This entity class corresponds to an operating system user's shadow file
|
||||
entry.
|
||||
|
||||
"""
|
||||
user = models.OneToOneField(User, primary_key=True, verbose_name=_('User'))
|
||||
passwd = models.CharField(_('Encrypted password'), max_length=128)
|
||||
changedays = models.PositiveSmallIntegerField(
|
||||
|
@ -242,11 +374,21 @@ class Shadow(TimeStampedModel, models.Model):
|
|||
return 'for user {0}'.format(self.user)
|
||||
|
||||
def set_password(self, password):
|
||||
"""
|
||||
Set and encrypt the password.
|
||||
|
||||
:param str password: the password
|
||||
"""
|
||||
self.passwd = sha512_crypt.encrypt(password)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class AdditionalGroup(TimeStampedModel, models.Model):
|
||||
"""
|
||||
This entity class corresponds to additional group assignments for an
|
||||
:py:class:`operating system user <osusers.models.User>`.
|
||||
|
||||
"""
|
||||
user = models.ForeignKey(User)
|
||||
group = models.ForeignKey(Group)
|
||||
|
||||
|
@ -255,21 +397,45 @@ class AdditionalGroup(TimeStampedModel, models.Model):
|
|||
verbose_name = _('Additional group')
|
||||
verbose_name_plural = _('Additional groups')
|
||||
|
||||
def __str__(self):
|
||||
return '{0} in {1}'.format(self.user, self.group)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Ensure that the assigned group is different from the user's primary
|
||||
group.
|
||||
|
||||
"""
|
||||
if self.user.group == self.group:
|
||||
raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Persists the group assignment to LDAP and the database.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.save`
|
||||
:return: this instance
|
||||
:rtype: :py:class:`AdditionalGroup <osusers.models.AdditionalGroup>`
|
||||
|
||||
"""
|
||||
add_ldap_user_to_group.delay(
|
||||
self.user.username, self.group.groupname).get()
|
||||
super(AdditionalGroup, self).save(*args, **kwargs)
|
||||
return super(AdditionalGroup, self).save(*args, **kwargs)
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Delete the group assignment from LDAP and the database.
|
||||
|
||||
:param args: positional arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
:param kwargs: keyword arguments to be passed on to
|
||||
:py:meth:`django.db.Model.delete`
|
||||
"""
|
||||
remove_ldap_user_from_group.delay(
|
||||
self.user.username, self.group.groupname).get()
|
||||
super(AdditionalGroup, self).delete(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return '{0} in {1}'.format(self.user, self.group)
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
"""
|
||||
This module defines task stubs for the tasks implemented on the `Celery`_
|
||||
workers.
|
||||
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from celery import shared_task
|
||||
|
@ -5,59 +12,182 @@ from celery import shared_task
|
|||
|
||||
@shared_task
|
||||
def create_ldap_group(groupname, gid, descr):
|
||||
pass
|
||||
"""
|
||||
This task creates an :py:class:`LDAP group <ldapentities.models.LdapGroup>`
|
||||
if it does not exist yet.
|
||||
|
||||
If a group with the given name exists its group id and description
|
||||
attributes are updated.
|
||||
|
||||
:param str groupname: the group name
|
||||
:param int gid: the group id
|
||||
:param str descr: description text for the group
|
||||
:return: the distinguished name of the group
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
|
||||
pass
|
||||
"""
|
||||
This task creates an :py:class:`LDAP user <ldapentities.models.LdapUser>`
|
||||
if it does not exist yet.
|
||||
|
||||
The task is rejected if the primary group of the user is not defined.
|
||||
|
||||
The user's fields are updated if the user already exists.
|
||||
|
||||
:param str username: the user name
|
||||
:param int uid: the user id
|
||||
:param int gid: the user's primary group's id
|
||||
:param str gecos: the text for the GECOS field
|
||||
:param str homedir: the user's home directory
|
||||
:param str shell: the user's login shell
|
||||
:param str or None password: the clear text password, if :py:const:`None`
|
||||
is passed the password is not touched
|
||||
:raises celery.exceptions.Reject: if the specified primary group does not
|
||||
exist
|
||||
:return: the distinguished name of the user
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def add_ldap_user_to_group(username, groupname):
|
||||
pass
|
||||
"""
|
||||
This task adds the specified user to the given group.
|
||||
|
||||
This task does nothing if the user is already member of the group.
|
||||
|
||||
:param str username: the user name
|
||||
:param str groupname: the group name
|
||||
:raises celery.exceptions.Retry: if the user does not exist yet,
|
||||
:py:func:`create_ldap_user` should be called before
|
||||
:return: True if the user has been added to the group otherwise False
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def remove_ldap_user_from_group(username, groupname):
|
||||
pass
|
||||
"""
|
||||
This task removes the given user from the given group.
|
||||
|
||||
:param str username: the user name
|
||||
:param str groupname: the group name
|
||||
:return: True if the user has been removed, False otherwise
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_ldap_user(username):
|
||||
pass
|
||||
"""
|
||||
This task deletes the given user.
|
||||
|
||||
:param str username: the user name
|
||||
:return: True if the user has been deleted, False otherwise
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_ldap_group_if_empty(groupname):
|
||||
pass
|
||||
"""
|
||||
This task deletes the given group.
|
||||
|
||||
:param str groupname: the group name
|
||||
:return: True if the user has been deleted, False otherwise
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def setup_file_sftp_userdir(username):
|
||||
pass
|
||||
"""
|
||||
This task creates the home directory for an SFTP user if it does not exist
|
||||
yet.
|
||||
|
||||
:param str username: the user name
|
||||
:raises Exception: if the SFTP directory of the user cannot be created
|
||||
:return: the created directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_sftp_userdir(username):
|
||||
pass
|
||||
"""
|
||||
This task recursively deletes the home directory of an SFTP user if it
|
||||
does not exist yet.
|
||||
|
||||
:param str username: the user name
|
||||
:raises Exception: if the SFTP directory of the user cannot be removed
|
||||
:return: the removed directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def setup_file_mail_userdir(username):
|
||||
pass
|
||||
"""
|
||||
This task creates the mail base directory for a user if it does not exist
|
||||
yet.
|
||||
|
||||
:param str username: the user name
|
||||
:raises Exception: if the mail base directory for the user cannot be
|
||||
created
|
||||
:return: the created directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mail_userdir(username):
|
||||
pass
|
||||
"""
|
||||
This task recursively deletes the mail base directory for a user if it
|
||||
does not exist yet.
|
||||
|
||||
:param str username: the user name
|
||||
:raises Exception: if the mail base directory of the user cannot be removed
|
||||
:return: the removed directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_file_mailbox(username, mailboxname):
|
||||
pass
|
||||
"""
|
||||
This task creates a new mailbox directory for the given user and mailbox
|
||||
name.
|
||||
|
||||
:param str username: the user name
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises GVAFileException: if the mailbox directory cannot be created
|
||||
:return: the created mailbox directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_file_mailbox(username, mailboxname):
|
||||
pass
|
||||
"""
|
||||
This task deletes the given mailbox of the given user.
|
||||
|
||||
:param str username: the user name
|
||||
:param str mailboxname: the mailbox name
|
||||
:raises GVAFileException: if the mailbox directory cannot be deleted
|
||||
:return: the deleted mailbox directory name
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue