from datetime import date import os from django.db import models, transaction from django.conf import settings from django.core.exceptions import ValidationError from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ from model_utils.models import TimeStampedModel from passlib.hash import sha512_crypt from passlib.utils import generate_password class GroupManager(models.Manager): def get_next_gid(self): q = self.aggregate(models.Max('gid')) if q['gid__max'] is None: return settings.OSUSER_MINGID return max(settings.OSUSER_MINGID, q['gid__max'] + 1) @python_2_unicode_compatible class Group(TimeStampedModel, models.Model): groupname = models.CharField( _('Group name'), max_length=16, unique=True) gid = models.PositiveSmallIntegerField( _('Group ID'), unique=True, primary_key=True) descr = models.TextField(_('Description'), blank=True) passwd = models.CharField( _('Group password'), max_length=128, blank=True) objects = GroupManager() class Meta: verbose_name = _('Group') verbose_name_plural = _('Groups') def __str__(self): return '{0} ({1})'.format(self.groupname, self.gid) class UserManager(models.Manager): def get_next_uid(self): 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): 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'): if user == nextuser: count += 1 nextuser = usernameformat.format( settings.OSUSER_USERNAME_PREFIX, count) else: break return nextuser def create_user(self, username=None, password=None): uid = self.get_next_uid() gid = Group.objects.get_next_gid() if username is None: username = self.get_next_username() if password is None: password = generate_password() homedir = os.path.join(settings.OSUSER_HOME_BASEPATH, username) autocommit = transaction.get_autocommit() if autocommit: transaction.set_autocommit(False) group = Group.objects.create(groupname=username, gid=gid) user = self.create(username=username, group=group, uid=uid, homedir=homedir, shell=settings.OSUSER_DEFAULT_SHELL) shadow = Shadow.objects.create_shadow(user=user, password=password) user.save() shadow.save() transaction.commit() if autocommit: transaction.set_autocommit(True) return user @python_2_unicode_compatible class User(TimeStampedModel, models.Model): username = models.CharField( _('User name'), max_length=64, unique=True) uid = models.PositiveSmallIntegerField( _('User ID'), unique=True, primary_key=True) group = models.ForeignKey(Group, verbose_name=_('Group')) 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) objects = UserManager() class Meta: verbose_name = _('User') verbose_name_plural = _('Users') def __str__(self): return '{0} ({1})'.format(self.username, self.uid) class ShadowManager(models.Manager): def create_shadow(self, user, password): changedays = (timezone.now().date() - date(1970, 1, 1)).days pwhash = sha512_crypt.encrypt(password) shadow = self.create( user=user, changedays=changedays, minage=0, maxage=None, gracedays=7, inactdays=30, expiredays=None, passwd=pwhash ) shadow.save() return shadow @python_2_unicode_compatible class Shadow(TimeStampedModel, models.Model): user = models.OneToOneField(User, primary_key=True, verbose_name=_('User')) passwd = models.CharField(_('Encrypted password'), max_length=128) changedays = models.PositiveSmallIntegerField( _('Date of last change'), help_text=_('This is expressed in days since Jan 1, 1970'), blank=True, null=True) minage = models.PositiveSmallIntegerField( _('Minimum age'), help_text=_('Minimum number of days before the password can be' ' changed'), blank=True, null=True) maxage = models.PositiveSmallIntegerField( _('Maximum age'), help_text=_('Maximum number of days after which the password has to' ' be changed'), blank=True, null=True) gracedays = models.PositiveSmallIntegerField( _('Grace period'), help_text=_('The number of days before the password is going to' ' expire'), blank=True, null=True) inactdays = models.PositiveSmallIntegerField( _('Inactivity period'), help_text=_('The number of days after the password has expired during' ' which the password should still be accepted'), blank=True, null=True) expiredays = models.PositiveSmallIntegerField( _('Account expiration date'), help_text=_('The date of expiration of the account, expressed as' ' number of days since Jan 1, 1970'), blank=True, null=True, default=None) objects = ShadowManager() class Meta: verbose_name = _('Shadow password') verbose_name_plural = _('Shadow passwords') def __str__(self): return 'for user {0}'.format(self.user) @python_2_unicode_compatible class AdditionalGroup(TimeStampedModel, models.Model): user = models.ForeignKey(User) group = models.ForeignKey(Group) class Meta: unique_together = ('user', 'group') verbose_name = _('Additional group') verbose_name_plural = _('Additional groups') def clean(self): if self.user.group == self.group: raise ValidationError(_( "You can not use a user's primary group.")) def __str__(self): return '{0} in {1}'.format(self.user, self.group)