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

View file

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

View file

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

View file

@ -4,12 +4,9 @@ from django.contrib import admin
from .models import ( from .models import (
AdditionalGroup, AdditionalGroup,
DeleteTaskResult,
Group, Group,
GroupTaskResult,
Shadow, Shadow,
User, User,
UserTaskResult,
) )
PASSWORD_MISMATCH_ERROR = _("Passwords don't match") PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
@ -25,30 +22,6 @@ class ShadowInline(admin.TabularInline):
can_delete = False 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): class UserCreationForm(forms.ModelForm):
""" """
A form for creating system users. A form for creating system users.
@ -91,9 +64,10 @@ class UserCreationForm(forms.ModelForm):
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
inlines = [AdditionalGroupInline, ShadowInline, UserTaskResultInline] actions = ['perform_delete_selected']
readonly_fields = ['uid'] readonly_fields = ['uid']
add_form = UserCreationForm add_form = UserCreationForm
inlines = [AdditionalGroupInline, ShadowInline]
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
@ -115,37 +89,42 @@ class UserAdmin(admin.ModelAdmin):
defaults.update(kwargs) defaults.update(kwargs)
return super(UserAdmin, self).get_form(request, obj, **defaults) return super(UserAdmin, self).get_form(request, obj, **defaults)
def get_inline_instances(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
if obj is None: if obj:
return [] return ['uid']
return super(UserAdmin, self).get_inline_instances(request, obj) 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): class GroupAdmin(admin.ModelAdmin):
inlines = [GroupTaskResultInline] actions = ['perform_delete_selected']
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):
if obj is None: if obj is None:
return [] return []
return super(GroupAdmin, self).get_inline_instances(request, obj) 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): def get_actions(self, request):
readonly_fields = ['task_uuid', 'task_name', 'modeltype', 'modelname', actions = super(GroupAdmin, self).get_actions(request)
'is_finished', 'is_success', 'state', 'result_body'] if 'delete_selected' in actions:
list_display = ('task_uuid', 'task_name', 'modeltype', 'modelname', del actions['delete_selected']
'is_finished', 'state') return actions
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
admin.site.register(Group, GroupAdmin) admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin) 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 from datetime import date
import logging
import os import os
from django.db import models, transaction from django.db import models, transaction
@ -19,43 +22,23 @@ from .tasks import (
add_ldap_user_to_group, add_ldap_user_to_group,
create_ldap_group, create_ldap_group,
create_ldap_user, create_ldap_user,
delete_file_mail_userdir,
delete_file_sftp_userdir,
delete_ldap_group_if_empty, delete_ldap_group_if_empty,
delete_ldap_user, delete_ldap_user,
remove_ldap_user_from_group, remove_ldap_user_from_group,
setup_file_mail_userdir,
setup_file_sftp_userdir,
) )
logger = logging.getLogger(__name__)
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _( CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _(
"You can not use a user's primary group.") "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): class GroupManager(models.Manager):
def get_next_gid(self): def get_next_gid(self):
@ -84,74 +67,20 @@ class Group(TimeStampedModel, models.Model):
def __str__(self): def __str__(self):
return '{0} ({1})'.format(self.groupname, self.gid) return '{0} ({1})'.format(self.groupname, self.gid)
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs) super(Group, self).save(*args, **kwargs)
GroupTaskResult.objects.create_grouptaskresult( dn = create_ldap_group.delay(
self, self.groupname, self.gid, self.descr).get()
create_ldap_group.delay(self.groupname, self.gid, self.descr), logger.info("created LDAP group with dn %s", dn)
'create_ldap_group'
)
return self return self
@transaction.atomic
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
DeleteTaskResult.objects.create_deletetaskresult( delete_ldap_group_if_empty.delay(self.groupname).get()
'group', self.groupname,
delete_ldap_group_if_empty.delay(self.groupname),
'delete_ldap_group_if_empty'
)
super(Group, self).delete(*args, **kwargs) 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): class UserManager(models.Manager):
def get_next_uid(self): def get_next_uid(self):
@ -215,6 +144,7 @@ class User(TimeStampedModel, models.Model):
def __str__(self): def __str__(self):
return '{0} ({1})'.format(self.username, self.uid) return '{0} ({1})'.format(self.username, self.uid)
@transaction.atomic
def set_password(self, password): def set_password(self, password):
if hasattr(self, 'shadow'): if hasattr(self, 'shadow'):
self.shadow.set_password(password) self.shadow.set_password(password)
@ -222,67 +152,42 @@ class User(TimeStampedModel, models.Model):
self.shadow = Shadow.objects.create_shadow( self.shadow = Shadow.objects.create_shadow(
user=self, password=password user=self, password=password
) )
UserTaskResult.objects.create_usertaskresult( dn = create_ldap_user.delay(
self, self.username, self.uid, self.group.gid, self.gecos,
create_ldap_user.delay( self.homedir, self.shell, password
self.username, self.uid, self.group.gid, self.gecos, ).get()
self.homedir, self.shell, password logging.info("set LDAP password for %s", dn)
),
'create_ldap_user',
commit=True
)
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
UserTaskResult.objects.create_usertaskresult( dn = create_ldap_user.delay(
self, self.username, self.uid, self.group.gid, self.gecos,
create_ldap_user.delay( self.homedir, self.shell, password=None).get()
self.username, self.uid, self.group.gid, self.gecos, sftp_dir = setup_file_sftp_userdir.delay(self.username).get()
self.homedir, self.shell, password=None mail_dir = setup_file_mail_userdir.delay(self.username).get()
), logger.info(
'create_ldap_user' "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) return super(User, self).save(*args, **kwargs)
@transaction.atomic
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
for group in [ delete_file_mail_userdir.delay(self.username).get()
ag.group for ag in AdditionalGroup.objects.filter(user=self) delete_file_sftp_userdir.delay(self.username).get()
]: for group in [ag.group for ag in self.additionalgroup_set.all()]:
DeleteTaskResult.objects.create_deletetaskresult( remove_ldap_user_from_group.delay(
'usergroup', self.username, group.groupname).get()
'{0} in {1}'.format(self.username, group.groupname), delete_ldap_user.delay(self.username).get()
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'
)
self.group.delete() self.group.delete()
super(User, self).delete(*args, **kwargs) 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): class ShadowManager(models.Manager):
@transaction.atomic
def create_shadow(self, user, password): def create_shadow(self, user, password):
changedays = (timezone.now().date() - date(1970, 1, 1)).days changedays = (timezone.now().date() - date(1970, 1, 1)).days
shadow = self.create( shadow = self.create(
@ -356,23 +261,16 @@ class AdditionalGroup(TimeStampedModel, models.Model):
if self.user.group == self.group: if self.user.group == self.group:
raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL) raise ValidationError(CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL)
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
GroupTaskResult.objects.create_grouptaskresult( add_ldap_user_to_group.delay(
self.group, self.user.username, self.group.groupname).get()
add_ldap_user_to_group.delay(
self.user.username, self.group.groupname),
'add_ldap_user_to_group'
)
super(AdditionalGroup, self).save(*args, **kwargs) super(AdditionalGroup, self).save(*args, **kwargs)
@transaction.atomic
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
DeleteTaskResult.objects.create_deletetaskresult( remove_ldap_user_from_group.delay(
'usergroup', self.user.username, self.group.groupname).get()
str(self),
remove_ldap_user_from_group.delay(
self.user.username, self.group.groupname),
'remove_ldap_user_from_group'
)
super(AdditionalGroup, self).delete(*args, **kwargs) super(AdditionalGroup, self).delete(*args, **kwargs)
def __str__(self): def __str__(self):

View file

@ -13,6 +13,16 @@ class LdapRouter(object):
return None 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 @shared_task
def create_ldap_group(groupname, gid, descr): def create_ldap_group(groupname, gid, descr):
pass pass
@ -41,3 +51,23 @@ def delete_ldap_user(username):
@shared_task @shared_task
def delete_ldap_group_if_empty(groupname): def delete_ldap_group_if_empty(groupname):
pass 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