Merge branch 'release/0.2.2' into production

* release/0.2.2:
  set version number
  document homedir creation feature
  create directories for new users
  add stub tasks for file system operations
  make user and group management more robust
This commit is contained in:
Jan Dittberner 2014-12-26 15:16:22 +01:00
commit 3e3db481d2
7 changed files with 145 additions and 203 deletions

View file

@ -1,6 +1,9 @@
Changelog
=========
* :release:`0.2.2 <2014-12-26>`
* :feature:`-` home and mail base directory creation
* :release:`0.2.1 <2014-12-17>`
* :support:`-` update Django to 1.7.1, update other dependencies, drop South
* :bug:`-` wrap :py:meth:`ousers.models.UserManager.create_user` in

View file

@ -55,9 +55,9 @@ copyright = u'2014, Jan Dittberner'
# built documents.
#
# The short X.Y version.
version = '0.2.1'
version = '0.2.2'
# The full version, including alpha/beta/rc tags.
release = '0.2.1'
release = '0.2.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -280,10 +280,11 @@ CELERY_RESULT_PERSISTENT = True
CELERY_TASK_RESULT_EXPIRES = None
CELERY_ROUTES = (
'osusers.tasks.LdapRouter',
'osusers.tasks.FileRouter',
)
CELERY_ACCEPT_CONTENT = ['yaml']
CELERY_TASK_SERIALIZER = 'yaml'
CELERY_RESULT_SERIALIZER = 'yaml'
CELERY_ACCEPT_CONTENT = ['pickle', 'yaml', 'json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
########## END CELERY CONFIGURATION

View file

@ -4,12 +4,9 @@ from django.contrib import admin
from .models import (
AdditionalGroup,
DeleteTaskResult,
Group,
GroupTaskResult,
Shadow,
User,
UserTaskResult,
)
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
@ -25,30 +22,6 @@ class ShadowInline(admin.TabularInline):
can_delete = False
class TaskResultInline(admin.TabularInline):
can_delete = False
extra = 0
readonly_fields = ['task_uuid', 'task_name', 'is_finished', 'is_success',
'state', 'result_body']
def get_queryset(self, request):
qs = super(TaskResultInline, self).get_queryset(request)
for entry in qs:
entry.update_taskstatus()
return qs
def has_add_permission(self, request, obj=None):
return False
class UserTaskResultInline(TaskResultInline):
model = UserTaskResult
class GroupTaskResultInline(TaskResultInline):
model = GroupTaskResult
class UserCreationForm(forms.ModelForm):
"""
A form for creating system users.
@ -91,9 +64,10 @@ class UserCreationForm(forms.ModelForm):
class UserAdmin(admin.ModelAdmin):
inlines = [AdditionalGroupInline, ShadowInline, UserTaskResultInline]
actions = ['perform_delete_selected']
readonly_fields = ['uid']
add_form = UserCreationForm
inlines = [AdditionalGroupInline, ShadowInline]
add_fieldsets = (
(None, {
@ -115,37 +89,42 @@ class UserAdmin(admin.ModelAdmin):
defaults.update(kwargs)
return super(UserAdmin, self).get_form(request, obj, **defaults)
def get_inline_instances(self, request, obj=None):
if obj is None:
return []
return super(UserAdmin, self).get_inline_instances(request, obj)
def get_readonly_fields(self, request, obj=None):
if obj:
return ['uid']
return []
def perform_delete_selected(self, request, queryset):
for user in queryset.all():
user.delete()
perform_delete_selected.short_description = _('Delete selected users')
def get_actions(self, request):
actions = super(UserAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
class GroupAdmin(admin.ModelAdmin):
inlines = [GroupTaskResultInline]
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):
for group in queryset.all():
group.delete()
perform_delete_selected.short_description = _('Delete selected groups')
class DeleteTaskResultAdmin(admin.ModelAdmin):
readonly_fields = ['task_uuid', 'task_name', 'modeltype', 'modelname',
'is_finished', 'is_success', 'state', 'result_body']
list_display = ('task_uuid', 'task_name', 'modeltype', 'modelname',
'is_finished', 'state')
def has_add_permission(self, request, obj=None):
return False
def get_queryset(self, request):
qs = super(DeleteTaskResultAdmin, self).get_queryset(request)
for entry in qs:
entry.update_taskstatus()
return qs
def get_actions(self, request):
actions = super(GroupAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)
admin.site.register(DeleteTaskResult, DeleteTaskResultAdmin)

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('osusers', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='DeleteTaskResult',
),
migrations.RemoveField(
model_name='grouptaskresult',
name='group',
),
migrations.DeleteModel(
name='GroupTaskResult',
),
migrations.RemoveField(
model_name='usertaskresult',
name='user',
),
migrations.DeleteModel(
name='UserTaskResult',
),
]

View file

@ -1,4 +1,7 @@
from __future__ import unicode_literals
from datetime import date
import logging
import os
from django.db import models, transaction
@ -19,43 +22,23 @@ from .tasks import (
add_ldap_user_to_group,
create_ldap_group,
create_ldap_user,
delete_file_mail_userdir,
delete_file_sftp_userdir,
delete_ldap_group_if_empty,
delete_ldap_user,
remove_ldap_user_from_group,
setup_file_mail_userdir,
setup_file_sftp_userdir,
)
logger = logging.getLogger(__name__)
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
"You can not use a user's primary group.")
class TaskResult(TimeStampedModel, models.Model):
task_uuid = models.CharField(primary_key=True, max_length=64, blank=False)
task_name = models.CharField(max_length=255, blank=False, db_index=True)
is_finished = models.BooleanField(default=False)
is_success = models.BooleanField(default=False)
state = models.CharField(max_length=10)
result_body = models.TextField(blank=True)
class Meta:
abstract = True
def _set_result_fields(self, asyncresult):
if asyncresult.ready():
self.is_finished = True
self.is_success = asyncresult.state == 'SUCCESS'
self.result_body = str(asyncresult.result)
self.state = asyncresult.state
asyncresult.get(no_ack=False)
def update_taskstatus(self):
if not self.is_finished:
asyncresult = AsyncResult(self.task_uuid)
self._set_result_fields(asyncresult)
self.save()
class GroupManager(models.Manager):
def get_next_gid(self):
@ -84,74 +67,20 @@ class Group(TimeStampedModel, models.Model):
def __str__(self):
return '{0} ({1})'.format(self.groupname, self.gid)
@transaction.atomic
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
GroupTaskResult.objects.create_grouptaskresult(
self,
create_ldap_group.delay(self.groupname, self.gid, self.descr),
'create_ldap_group'
)
dn = create_ldap_group.delay(
self.groupname, self.gid, self.descr).get()
logger.info("created LDAP group with dn %s", dn)
return self
@transaction.atomic
def delete(self, *args, **kwargs):
DeleteTaskResult.objects.create_deletetaskresult(
'group', self.groupname,
delete_ldap_group_if_empty.delay(self.groupname),
'delete_ldap_group_if_empty'
)
delete_ldap_group_if_empty.delay(self.groupname).get()
super(Group, self).delete(*args, **kwargs)
class TaskResultManager(models.Manager):
def create(self, asyncresult, task_name):
result = self.model(
task_uuid=asyncresult.task_id, task_name=task_name
)
result._set_result_fields(asyncresult)
return result
class DeleteTaskResultManager(TaskResultManager):
def create_deletetaskresult(
self, modeltype, modelname, asyncresult, task_name
):
taskresult = super(DeleteTaskResultManager, self).create(
asyncresult, task_name)
taskresult.modeltype = modeltype
taskresult.modelname = modelname
taskresult.save()
return taskresult
class DeleteTaskResult(TaskResult):
modeltype = models.CharField(max_length=20, db_index=True)
modelname = models.CharField(max_length=255)
objects = DeleteTaskResultManager()
class GroupTaskResultManager(TaskResultManager):
def create_grouptaskresult(
self, group, asyncresult, task_name, commit=False
):
taskresult = super(GroupTaskResultManager, self).create(
asyncresult, task_name)
taskresult.group = group
taskresult.save()
return taskresult
class GroupTaskResult(TaskResult):
group = models.ForeignKey(Group)
objects = GroupTaskResultManager()
class UserManager(models.Manager):
def get_next_uid(self):
@ -215,6 +144,7 @@ class User(TimeStampedModel, models.Model):
def __str__(self):
return '{0} ({1})'.format(self.username, self.uid)
@transaction.atomic
def set_password(self, password):
if hasattr(self, 'shadow'):
self.shadow.set_password(password)
@ -222,67 +152,42 @@ class User(TimeStampedModel, models.Model):
self.shadow = Shadow.objects.create_shadow(
user=self, password=password
)
UserTaskResult.objects.create_usertaskresult(
self,
create_ldap_user.delay(
self.username, self.uid, self.group.gid, self.gecos,
self.homedir, self.shell, password
),
'create_ldap_user',
commit=True
)
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)
@transaction.atomic
def save(self, *args, **kwargs):
UserTaskResult.objects.create_usertaskresult(
self,
create_ldap_user.delay(
self.username, self.uid, self.group.gid, self.gecos,
self.homedir, self.shell, password=None
),
'create_ldap_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(
"created user %(user)s with LDAP dn %(dn)s, home directory "
"%(homedir)s and mail base directory %(maildir)s.", {
'user': self, 'dn': dn,
'homedir': sftp_dir, 'maildir': mail_dir
})
return super(User, self).save(*args, **kwargs)
@transaction.atomic
def delete(self, *args, **kwargs):
for group in [
ag.group for ag in AdditionalGroup.objects.filter(user=self)
]:
DeleteTaskResult.objects.create_deletetaskresult(
'usergroup',
'{0} in {1}'.format(self.username, group.groupname),
remove_ldap_user_from_group.delay(
self.username, group.groupname),
'remove_ldap_user_from_group',
)
DeleteTaskResult.objects.create_deletetaskresult(
'user', self.username,
delete_ldap_user.delay(self.username),
'delete_ldap_user'
)
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()]:
remove_ldap_user_from_group.delay(
self.username, group.groupname).get()
delete_ldap_user.delay(self.username).get()
self.group.delete()
super(User, self).delete(*args, **kwargs)
class UserTaskResultManager(TaskResultManager):
def create_usertaskresult(
self, user, asyncresult, task_name, commit=False
):
taskresult = self.create(asyncresult, task_name)
taskresult.user = user
taskresult.save()
return taskresult
class UserTaskResult(TaskResult):
user = models.ForeignKey(User)
objects = UserTaskResultManager()
class ShadowManager(models.Manager):
@transaction.atomic
def create_shadow(self, user, password):
changedays = (timezone.now().date() - date(1970, 1, 1)).days
shadow = self.create(
@ -356,23 +261,16 @@ class AdditionalGroup(TimeStampedModel, models.Model):
if self.user.group == self.group:
raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL)
@transaction.atomic
def save(self, *args, **kwargs):
GroupTaskResult.objects.create_grouptaskresult(
self.group,
add_ldap_user_to_group.delay(
self.user.username, self.group.groupname),
'add_ldap_user_to_group'
)
add_ldap_user_to_group.delay(
self.user.username, self.group.groupname).get()
super(AdditionalGroup, self).save(*args, **kwargs)
@transaction.atomic
def delete(self, *args, **kwargs):
DeleteTaskResult.objects.create_deletetaskresult(
'usergroup',
str(self),
remove_ldap_user_from_group.delay(
self.user.username, self.group.groupname),
'remove_ldap_user_from_group'
)
remove_ldap_user_from_group.delay(
self.user.username, self.group.groupname).get()
super(AdditionalGroup, self).delete(*args, **kwargs)
def __str__(self):

View file

@ -13,6 +13,16 @@ class LdapRouter(object):
return None
class FileRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
if 'file' in task:
return {'exchange': 'file',
'exchange_type': 'direct',
'queue': 'file'}
return None
@shared_task
def create_ldap_group(groupname, gid, descr):
pass
@ -41,3 +51,23 @@ def delete_ldap_user(username):
@shared_task
def delete_ldap_group_if_empty(groupname):
pass
@shared_task
def setup_file_sftp_userdir(username):
pass
@shared_task
def delete_file_sftp_userdir(username):
pass
@shared_task
def setup_file_mail_userdir(username):
pass
@shared_task
def delete_file_mail_userdir(username):
pass