Removed old users app

This commit is contained in:
Marc 2014-09-30 14:52:02 +00:00
parent 23d62c2d77
commit 5ed8773acc
33 changed files with 0 additions and 1447 deletions

View file

@ -1,110 +0,0 @@
from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse
from django.contrib import admin
from django.contrib.admin.util import unquote
from django.contrib.auth import admin as auth
from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import wrap_admin_view
from orchestra.apps.accounts.admin import AccountAdminMixin
from .forms import UserCreationForm, UserChangeForm
from .models import User
from .roles.filters import role_list_filter_factory
class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
list_display = ('username', 'display_is_main')
list_filter = ('is_staff', 'is_superuser', 'is_active')
fieldsets = (
(None, {
'fields': ('account', 'username', 'password')
}),
(_("Personal info"), {
'fields': ('first_name', 'last_name', 'email')
}),
(_("Permissions"), {
'fields': ('is_active', 'is_staff', 'is_superuser', 'display_is_main')
}),
(_("Important dates"), {
'fields': ('last_login', 'date_joined')
}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'account'),
}),
)
search_fields = ['username', 'account__user__username']
readonly_fields = ('display_is_main', 'account_link')
change_readonly_fields = ('username',)
filter_horizontal = ()
add_form = UserCreationForm
form = UserChangeForm
roles = []
ordering = ('-id',)
change_form_template = 'admin/users/user/change_form.html'
def display_is_main(self, instance):
return instance.is_main
display_is_main.short_description = _("is main")
display_is_main.boolean = True
def get_urls(self):
""" Returns the additional urls for the change view links """
urls = super(UserAdmin, self).get_urls()
opts = self.model._meta
new_urls = patterns("")
for role in self.roles:
new_urls += patterns("",
url('^(\d+)/%s/$' % role.url_name,
wrap_admin_view(self, role().change_view),
name='%s_%s_%s_change' % (opts.app_label, opts.model_name, role.name)),
url('^(\d+)/%s/delete/$' % role.url_name,
wrap_admin_view(self, role().delete_view),
name='%s_%s_%s_delete' % (opts.app_label, opts.model_name, role.name))
)
return new_urls + urls
def get_fieldsets(self, request, obj=None):
fieldsets = super(UserAdmin, self).get_fieldsets(request, obj=obj)
if obj and obj.account:
fieldsets[0][1]['fields'] = ('account_link',) + fieldsets[0][1]['fields'][1:]
return fieldsets
def get_list_display(self, request):
roles = []
for role in self.roles:
def has_role(user, role_class=role):
role = role_class(user=user)
if role.exists:
return '<img src="/static/admin/img/icon-yes.gif" alt="True">'
url = reverse('admin:users_user_%s_change' % role.name, args=(user.pk,))
false = '<img src="/static/admin/img/icon-no.gif" alt="False">'
return '<a href="%s">%s</a>' % (url, false)
has_role.short_description = _("Has %s") % role.name
has_role.admin_order_field = role.name
has_role.allow_tags = True
roles.append(has_role)
return list(self.list_display) + roles + ['account_link']
def get_list_filter(self, request):
roles = [ role_list_filter_factory(role) for role in self.roles ]
return list(self.list_filter) + roles
def change_view(self, request, object_id, **kwargs):
user = self.get_object(User, unquote(object_id))
extra_context = kwargs.get('extra_context', {})
extra_context['roles'] = [ role(user=user) for role in self.roles ]
kwargs['extra_context'] = extra_context
return super(UserAdmin, self).change_view(request, object_id, **kwargs)
def get_queryset(self, request):
""" Select related for performance """
related = ['account__user'] + [ role.name for role in self.roles ]
return super(UserAdmin, self).get_queryset(request).select_related(*related)
admin.site.register(User, UserAdmin)

View file

@ -1,20 +0,0 @@
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from orchestra.api import router, SetPasswordApiMixin
from orchestra.apps.accounts.api import AccountApiMixin
from .serializers import UserSerializer
class UserViewSet(AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
model = get_user_model()
serializer_class = UserSerializer
def get_queryset(self):
""" select related roles """
qs = super(UserViewSet, self).get_queryset()
return qs.select_related(*self.inserted)
router.register(r'users', UserViewSet)

View file

@ -1,123 +0,0 @@
import textwrap
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor
from . import settings
class SystemUserBackend(ServiceController):
verbose_name = _("System User")
model = 'users.User'
ignore_fields = ['last_login']
def save(self, user):
context = self.get_context(user)
if user.is_main:
self.append(textwrap.dedent("""
if [[ $( id %(username)s ) ]]; then
usermod --password '%(password)s' %(username)s
else
useradd %(username)s --password '%(password)s' \\
--shell /bin/false
fi
mkdir -p %(home)s
chown %(username)s.%(username)s %(home)s""" % context
))
else:
self.delete(user)
def delete(self, user):
context = self.get_context(user)
self.append("{ sleep 2 && killall -u %(username)s -s KILL; } &" % context)
self.append("killall -u %(username)s" % context)
self.append("userdel %(username)s" % context)
def get_context(self, user):
context = {
'username': user.username,
'password': user.password if user.is_active else '*%s' % user.password,
}
context['home'] = settings.USERS_SYSTEMUSER_HOME % context
return context
class SystemUserDisk(ServiceMonitor):
model = 'users.User'
resource = ServiceMonitor.DISK
verbose_name = _('System user disk')
def monitor(self, user):
context = self.get_context(user)
self.append("du -s %(home)s | xargs echo %(object_id)s" % context)
def get_context(self, user):
context = SystemUserBackend().get_context(user)
context['object_id'] = user.pk
return context
class FTPTraffic(ServiceMonitor):
model = 'users.User'
resource = ServiceMonitor.TRAFFIC
verbose_name = _('FTP traffic')
def prepare(self):
current_date = timezone.localtime(self.current_date)
current_date = current_date.strftime("%Y%m%d%H%M%S")
self.append(textwrap.dedent("""
function monitor () {
OBJECT_ID=$1
INI_DATE=$2
USERNAME="$3"
LOG_FILE="$4"
grep "UPLOAD\|DOWNLOAD" "${LOG_FILE}" \\
| grep " \\[${USERNAME}\\] " \\
| awk -v ini="${INI_DATE}" '
BEGIN {
end = "%s"
sum = 0
months["Jan"] = "01"
months["Feb"] = "02"
months["Mar"] = "03"
months["Apr"] = "04"
months["May"] = "05"
months["Jun"] = "06"
months["Jul"] = "07"
months["Aug"] = "08"
months["Sep"] = "09"
months["Oct"] = "10"
months["Nov"] = "11"
months["Dec"] = "12"
} {
# log: Fri Jul 11 13:23:17 2014
split($4, t, ":")
# line_date = year month day hour minute second
line_date = $5 months[$2] $3 t[1] t[2] t[3]
if ( line_date > ini && line_date < end)
split($0, l, "\\", ")
split(l[3], b, " ")
sum += b[1]
} END {
print sum
}
' | xargs echo ${OBJECT_ID}
}""" % current_date))
def monitor(self, user):
context = self.get_context(user)
self.append(
'monitor %(object_id)i %(last_date)s "%(username)s" "%(log_file)s"' % context)
def get_context(self, user):
last_date = timezone.localtime(self.get_last_date(user.pk))
return {
'log_file': settings.USERS_FTP_LOG_PATH,
'last_date': last_date.strftime("%Y%m%d%H%M%S"),
'object_id': user.pk,
'username': user.username,
}

View file

@ -1,49 +0,0 @@
from django import forms
from django.contrib import auth
from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.core.validators import validate_password
from .models import User
class UserCreationForm(auth.forms.UserCreationForm):
class Meta(auth.forms.UserCreationForm.Meta):
model = User
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].validators.append(validate_password)
def clean_username(self):
# Since User.username is unique, this check is redundant,
# but it sets a nicer error message than the ORM. See #13147.
username = self.cleaned_data["username"]
try:
User._default_manager.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(self.error_messages['duplicate_username'])
class UserChangeForm(forms.ModelForm):
password = auth.forms.ReadOnlyPasswordHashField(label=_("Password"),
help_text=_("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using <a href=\"password/\">this form</a>."))
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)
f = self.fields.get('user_permissions', None)
if f is not None:
f.queryset = f.queryset.select_related('content_type')
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]

View file

@ -1,92 +0,0 @@
from django.contrib.auth import models as auth
from django.core import validators
from django.core.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
class User(auth.AbstractBaseUser):
username = models.CharField(_("username"), max_length=64, unique=True,
help_text=_("Required. 30 characters or fewer. Letters, digits and "
"./-/_ only."),
validators=[validators.RegexValidator(r'^[\w.-]+$',
_("Enter a valid username."), 'invalid')])
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='users')
first_name = models.CharField(_("first name"), max_length=30, blank=True)
last_name = models.CharField(_("last name"), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_superuser = models.BooleanField(_("superuser status"), default=False,
help_text=_("Designates that this user has all permissions without "
"explicitly assigning them."))
is_staff = models.BooleanField(_("staff status"), default=False,
help_text=_("Designates whether the user can log into this admin "
"site."))
is_admin = models.BooleanField(_("admin status"), default=False,
help_text=_("Designates whether the user can administrate its account."))
is_active = models.BooleanField(_("active"), default=True,
help_text=_("Designates whether this user should be treated as "
"active. Unselect this instead of deleting accounts."))
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = auth.UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
@property
def is_main(self):
return self.account.user == self
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip() or self.username
def get_short_name(self):
""" Returns the short name for the user """
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
""" Sends an email to this User """
send_mail(subject, message, from_email, [self.email], **kwargs)
def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return auth._user_has_perm(self, perm, obj)
def has_perms(self, perm_list, obj=None):
"""
Returns True if the user has each of the specified permissions. If
object is passed, it checks if the user has all required perms for this
object.
"""
for perm in perm_list:
if not self.has_perm(perm, obj):
return False
return True
def has_module_perms(self, app_label):
"""
Returns True if the user has any permissions in the given app label.
Uses pretty much the same logic as has_perm, above.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return auth._user_has_module_perms(self, app_label)
services.register(User, menu=False)

View file

@ -1,27 +0,0 @@
from ..models import User
class Register(object):
def __init__(self):
self._registry = {}
def __contains__(self, key):
return key in self._registry
def register(self, name, model):
if name in self._registry:
raise KeyError("%s already registered" % name)
def has_role(user, model=model):
try:
getattr(user, name)
except model.DoesNotExist:
return False
return True
setattr(User, 'has_%s' % name, has_role)
self._registry[name] = model
def get(self):
return self._registry
roles = Register()

View file

@ -1,145 +0,0 @@
from django.contrib import messages
from django.contrib.admin.util import unquote, get_deleted_objects
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.db import router
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.shortcuts import redirect
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin.utils import get_modeladmin, change_url
from .forms import role_form_factory
from ..models import User
class RoleAdmin(object):
model = None
name = ''
url_name = ''
form = None
def __init__(self, user=None):
self.user = user
@property
def exists(self):
try:
return getattr(self.user, self.name)
except self.model.DoesNotExist:
return False
def get_user(self, request, object_id):
try:
user = User.objects.get(pk=unquote(object_id))
except User.DoesNotExist:
opts = self.model._meta
raise Http404(
_('%(name)s object with primary key %(key)r does not exist.') %
{'name': force_text(opts.verbose_name), 'key': escape(object_id)}
)
return user
def change_view(self, request, object_id):
modeladmin = get_modeladmin(User)
user = self.get_user(request, object_id)
self.user = user
obj = None
exists = self.exists
if exists:
obj = getattr(user, self.name)
form_class = self.form if self.form else role_form_factory(self)
form = form_class(instance=obj)
opts = User._meta
app_label = opts.app_label
title = _("Add %s for user %s" % (self.name, user))
action = _("Create")
# User has submitted the form
if request.method == 'POST':
form = form_class(request.POST, instance=obj)
form.user = user
if form.is_valid():
obj = form.save()
context = {
'name': obj._meta.verbose_name,
'obj': obj,
'action': _("saved" if exists else "created")
}
modeladmin.log_change(request, request.user, "%s saved" % self.name.capitalize())
msg = _('The role %(name)s for user "%(obj)s" was %(action)s successfully.') % context
modeladmin.message_user(request, msg, messages.SUCCESS)
if not "_continue" in request.POST:
return redirect(change_url(user))
exists = True
if exists:
title = _("Change %s %s settings" % (user, self.name))
action = _("Save")
form = form_class(instance=obj)
context = {
'title': title,
'opts': opts,
'app_label': app_label,
'form': form,
'action': action,
'role': self,
'roles': [ role(user=user) for role in modeladmin.roles ],
'media': modeladmin.media
}
template = 'admin/users/user/role.html'
app = modeladmin.admin_site.name
return TemplateResponse(request, template, context, current_app=app)
def delete_view(self, request, object_id):
"The 'delete' admin view for this model."
opts = self.model._meta
app_label = opts.app_label
modeladmin = get_modeladmin(User)
user = self.get_user(request, object_id)
obj = getattr(user, self.name)
using = router.db_for_write(self.model)
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
(deleted_objects, perms_needed, protected) = get_deleted_objects(
[obj], opts, request.user, modeladmin.admin_site, using)
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
obj_display = force_text(obj)
modeladmin.log_deletion(request, obj, obj_display)
modeladmin.delete_model(request, obj)
post_url = change_url(user)
preserved_filters = modeladmin.get_preserved_filters(request)
post_url = add_preserved_filters(
{'preserved_filters': preserved_filters, 'opts': opts}, post_url
)
return HttpResponseRedirect(post_url)
object_name = force_text(opts.verbose_name)
if perms_needed or protected:
title = _("Cannot delete %(name)s") % {"name": object_name}
else:
title = _("Are you sure?")
context = {
"title": title,
"object_name": object_name,
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
"protected": protected,
"opts": opts,
"app_label": app_label,
'preserved_filters': modeladmin.get_preserved_filters(request),
'role': self,
}
return TemplateResponse(request, 'admin/users/user/delete_role.html',
context, current_app=modeladmin.admin_site.name)

View file

@ -1,23 +0,0 @@
from django.contrib.admin import SimpleListFilter
from django.utils.translation import ugettext_lazy as _
def role_list_filter_factory(role):
class RoleListFilter(SimpleListFilter):
""" Filter Nodes by group according to request.user """
title = _("has %s" % role.name)
parameter_name = role.url_name
def lookups(self, request, model_admin):
return (
('True', _("Yes")),
('False', _("No")),
)
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.filter(**{ '%s__isnull' % role.name: False })
if self.value() == 'False':
return queryset.filter(**{ '%s__isnull' % role.name: True })
return RoleListFilter

View file

@ -1,17 +0,0 @@
from django import forms
class RoleAdminBaseForm(forms.ModelForm):
class Meta:
exclude = ('user', )
def save(self, *args, **kwargs):
self.instance.user = self.user
return super(RoleAdminBaseForm, self).save(*args, **kwargs)
def role_form_factory(role):
class RoleAdminForm(RoleAdminBaseForm):
class Meta(RoleAdminBaseForm.Meta):
model = role.model
return RoleAdminForm

View file

@ -1,15 +0,0 @@
from django.contrib.auth import get_user_model
from orchestra.admin.utils import insertattr
from orchestra.apps.users.roles.admin import RoleAdmin
from .models import Jabber
class JabberRoleAdmin(RoleAdmin):
model = Jabber
name = 'jabber'
url_name = 'jabber'
insertattr(get_user_model(), 'roles', JabberRoleAdmin)

View file

@ -1,15 +0,0 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .. import roles
class Jabber(models.Model):
user = models.OneToOneField('users.User', verbose_name=_("user"),
related_name='jabber')
def __unicode__(self):
return str(self.user)
roles.register('jabber', Jabber)

View file

@ -1,124 +0,0 @@
from django import forms
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import insertattr, admin_link
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
from orchestra.apps.users.roles.admin import RoleAdmin
from .forms import MailRoleAdminForm
from .models import Mailbox, Address, Autoresponse
class AutoresponseInline(admin.StackedInline):
model = Autoresponse
verbose_name_plural = _("autoresponse")
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'subject':
kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
return super(AutoresponseInline, self).formfield_for_dbfield(db_field, **kwargs)
#class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
# list_display = ('email', 'domain_link', 'mailboxes', 'forwards', 'account_link')
# fields = ('account_link', ('name', 'domain'), 'destination')
# inlines = [AutoresponseInline]
# search_fields = ('name', 'domain__name',)
# readonly_fields = ('account_link', 'domain_link', 'email_link')
# filter_by_account_fields = ['domain']
#
# domain_link = link('domain', order='domain__name')
#
# def email_link(self, address):
# link = self.domain_link(address)
# return "%s@%s" % (address.name, link)
# email_link.short_description = _("Email")
# email_link.allow_tags = True
#
# def mailboxes(self, address):
# boxes = []
# for mailbox in address.get_mailboxes():
# user = mailbox.user
# url = reverse('admin:users_user_mailbox_change', args=(user.pk,))
# boxes.append('<a href="%s">%s</a>' % (url, user.username))
# return '<br>'.join(boxes)
# mailboxes.allow_tags = True
#
# def forwards(self, address):
# values = [ dest for dest in address.destination.split() if '@' in dest ]
# return '<br>'.join(values)
# forwards.allow_tags = True
#
# def formfield_for_dbfield(self, db_field, **kwargs):
# if db_field.name == 'destination':
# kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
# return super(AddressAdmin, self).formfield_for_dbfield(db_field, **kwargs)
#
# def queryset(self, request):
# """ Select related for performance """
# qs = super(AddressAdmin, self).queryset(request)
# return qs.select_related('domain')
class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = (
'email', 'domain_link', 'display_mailboxes', 'display_forward', 'account_link'
)
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
inlines = [AutoresponseInline]
search_fields = ('name', 'domain__name',)
readonly_fields = ('account_link', 'domain_link', 'email_link')
filter_by_account_fields = ['domain']
filter_horizontal = ['mailboxes']
domain_link = admin_link('domain', order='domain__name')
def email_link(self, address):
link = self.domain_link(address)
return "%s@%s" % (address.name, link)
email_link.short_description = _("Email")
email_link.allow_tags = True
def display_mailboxes(self, address):
boxes = []
for mailbox in address.mailboxes.all():
user = mailbox.user
url = reverse('admin:users_user_mailbox_change', args=(user.pk,))
boxes.append('<a href="%s">%s</a>' % (url, user.username))
return '<br>'.join(boxes)
display_mailboxes.short_description = _("Mailboxes")
display_mailboxes.allow_tags = True
def display_forward(self, address):
values = [ dest for dest in address.forward.split() ]
return '<br>'.join(values)
display_forward.short_description = _("Forward")
display_forward.allow_tags = True
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'forward':
kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
if db_field.name == 'mailboxes':
mailboxes = db_field.rel.to.objects.select_related('user')
kwargs['queryset'] = mailboxes.filter(user__account=self.account)
return super(AddressAdmin, self).formfield_for_dbfield(db_field, **kwargs)
def get_queryset(self, request):
""" Select related for performance """
qs = super(AddressAdmin, self).get_queryset(request)
return qs.select_related('domain')
class MailRoleAdmin(RoleAdmin):
model = Mailbox
name = 'mailbox'
url_name = 'mailbox'
form = MailRoleAdminForm
admin.site.register(Address, AddressAdmin)
insertattr(get_user_model(), 'roles', MailRoleAdmin)

View file

@ -1,27 +0,0 @@
from rest_framework import viewsets
from orchestra.api import router
from orchestra.apps.accounts.api import AccountApiMixin
from .models import Address, Mailbox
from .serializers import AddressSerializer, MailboxSerializer
class AddressViewSet(AccountApiMixin, viewsets.ModelViewSet):
model = Address
serializer_class = AddressSerializer
class MailboxViewSet(viewsets.ModelViewSet):
model = Mailbox
serializer_class = MailboxSerializer
def get_queryset(self):
qs = super(MailboxViewSet, self).get_queryset()
qs = qs.select_related('user')
return qs.filter(user__account=self.request.user.account_id)
router.register(r'mailboxes', MailboxViewSet)
router.register(r'addresses', AddressViewSet)

View file

@ -1,160 +0,0 @@
import os
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor
from . import settings
from .models import Address
class MailSystemUserBackend(ServiceController):
verbose_name = _("Mail system user")
model = 'mail.Mailbox'
# TODO related_models = ('resources__content_type') ??
DEFAULT_GROUP = 'postfix'
def create_user(self, context):
self.append(
"if [[ $( id %(username)s ) ]]; then \n"
" usermod -p '%(password)s' %(username)s \n"
"else \n"
" useradd %(username)s --password '%(password)s' \\\n"
" --shell /dev/null \n"
"fi" % context
)
self.append("mkdir -p %(home)s" % context)
self.append("chown %(username)s.%(group)s %(home)s" % context)
def generate_filter(self, mailbox, context):
now = timezone.now().strftime("%B %d, %Y, %H:%M")
context['filtering'] = (
"# Sieve Filter\n"
"# Generated by Orchestra %s\n\n" % now
)
if mailbox.use_custom_filtering:
context['filtering'] += mailbox.custom_filtering
else:
context['filtering'] += settings.EMAILS_DEFAUL_FILTERING
context['filter_path'] = os.path.join(context['home'], '.orchestra.sieve')
self.append("echo '%(filtering)s' > %(filter_path)s" % context)
def save(self, mailbox):
context = self.get_context(mailbox)
self.create_user(context)
self.generate_filter(mailbox, context)
def delete(self, mailbox):
context = self.get_context(mailbox)
self.append("{ sleep 2 && killall -u %(username)s -s KILL; } &" % context)
self.append("killall -u %(username)s" % context)
self.append("userdel %(username)s" % context)
self.append("rm -fr %(home)s" % context)
def get_context(self, mailbox):
user = mailbox.user
context = {
'username': user.username,
'password': user.password if user.is_active else '*%s' % user.password,
'group': self.DEFAULT_GROUP
}
context['home'] = settings.EMAILS_HOME % context
return context
class PostfixAddressBackend(ServiceController):
verbose_name = _("Postfix address")
model = 'mail.Address'
def include_virtdomain(self, context):
self.append(
'[[ $(grep "^\s*%(domain)s\s*$" %(virtdomains)s) ]]'
' || { echo "%(domain)s" >> %(virtdomains)s; UPDATED=1; }' % context
)
def exclude_virtdomain(self, context):
domain = context['domain']
if not Address.objects.filter(domain=domain).exists():
self.append('sed -i "s/^%(domain)s//" %(virtdomains)s' % context)
def update_virtusertable(self, context):
self.append(
'LINE="%(email)s\t%(destination)s"\n'
'if [[ ! $(grep "^%(email)s\s" %(virtusertable)s) ]]; then\n'
' echo "$LINE" >> %(virtusertable)s\n'
' UPDATED=1\n'
'else\n'
' if [[ ! $(grep "^${LINE}$" %(virtusertable)s) ]]; then\n'
' sed -i "s/^%(email)s\s.*$/${LINE}/" %(virtusertable)s\n'
' UPDATED=1\n'
' fi\n'
'fi' % context
)
def exclude_virtusertable(self, context):
self.append(
'if [[ $(grep "^%(email)s\s") ]]; then\n'
' sed -i "s/^%(email)s\s.*$//" %(virtusertable)s\n'
' UPDATED=1\n'
'fi'
)
def save(self, address):
context = self.get_context(address)
self.include_virtdomain(context)
self.update_virtusertable(context)
def delete(self, address):
context = self.get_context(address)
self.exclude_virtdomain(context)
self.exclude_virtusertable(context)
def commit(self):
context = self.get_context_files()
self.append('[[ $UPDATED == 1 ]] && { '
'postmap %(virtdomains)s;'
'postmap %(virtusertable)s;'
'}' % context)
def get_context_files(self):
return {
'virtdomains': settings.EMAILS_VIRTDOMAINS_PATH,
'virtusertable': settings.EMAILS_VIRTUSERTABLE_PATH,
}
def get_context(self, address):
context = self.get_context_files()
context.update({
'domain': address.domain,
'email': address.email,
'destination': address.destination,
})
return context
class AutoresponseBackend(ServiceController):
verbose_name = _("Mail autoresponse")
model = 'mail.Autoresponse'
class MaildirDisk(ServiceMonitor):
model = 'email.Mailbox'
resource = ServiceMonitor.DISK
verbose_name = _("Maildir disk usage")
def monitor(self, mailbox):
context = self.get_context(mailbox)
self.append(
"SIZE=$(sed -n '2p' %(maildir_path)s | cut -d' ' -f1)\n"
"echo %(object_id)s ${SIZE:-0}" % context
)
def get_context(self, mailbox):
context = MailSystemUserBackend().get_context(mailbox)
context['home'] = settings.EMAILS_HOME % context
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['object_id'] = mailbox.pk
return context

View file

@ -1,53 +0,0 @@
from django import forms
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.forms.widgets import ReadOnlyWidget
from .models import Mailbox
from ..forms import RoleAdminBaseForm
class MailRoleAdminForm(RoleAdminBaseForm):
class Meta(RoleAdminBaseForm.Meta):
model = Mailbox
def __init__(self, *args, **kwargs):
super(MailRoleAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
widget = ReadOnlyWidget(self.addresses(instance))
self.fields['addresses'] = forms.CharField(widget=widget,
label=_("Addresses"))
# def addresses(self, mailbox):
# account = mailbox.user.account
# addresses = account.addresses.filter(destination__contains=mailbox.user.username)
# add_url = reverse('admin:mail_address_add')
# add_url += '?account=%d&destination=%s' % (account.pk, mailbox.user.username)
# img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
# onclick = 'onclick="return showAddAnotherPopup(this);"'
# add_link = '<a href="%s" %s>%s Add address</a>' % (add_url, onclick, img)
# value = '%s<br><br>' % add_link
# for pk, name, domain in addresses.values_list('pk', 'name', 'domain__name'):
# url = reverse('admin:mail_address_change', args=(pk,))
# name = '%s@%s' % (name, domain)
# value += '<li><a href="%s">%s</a></li>' % (url, name)
# value = '<ul>%s</ul>' % value
# return mark_safe('<div style="padding-left: 100px;">%s</div>' % value)
def addresses(self, mailbox):
account = mailbox.user.account
add_url = reverse('admin:mail_address_add')
add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk)
img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
onclick = 'onclick="return showAddAnotherPopup(this);"'
add_link = '<a href="%s" %s>%s Add address</a>' % (add_url, onclick, img)
value = '%s<br><br>' % add_link
for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'):
url = reverse('admin:mail_address_change', args=(pk,))
name = '%s@%s' % (name, domain)
value += '<li><a href="%s">%s</a></li>' % (url, name)
value = '<ul>%s</ul>' % value
return mark_safe('<div style="padding-left: 100px;">%s</div>' % value)

View file

@ -1,110 +0,0 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
from .. import roles
from . import validators, settings
class Mailbox(models.Model):
user = models.OneToOneField('users.User', verbose_name=_("User"),
related_name='mailbox')
use_custom_filtering = models.BooleanField(_("Use custom filtering"),
default=False)
custom_filtering = models.TextField(_("filtering"), blank=True,
validators=[validators.validate_sieve],
help_text=_("Arbitrary email filtering in sieve language."))
class Meta:
verbose_name_plural = _("mailboxes")
def __unicode__(self):
return self.user.username
# def get_addresses(self):
# regex = r'(^|\s)+%s(\s|$)+' % self.user.username
# return Address.objects.filter(destination__regex=regex)
#
# def delete(self, *args, **kwargs):
# """ Update related addresses """
# regex = re.compile(r'(^|\s)+(\s*%s)(\s|$)+' % self.user.username)
# super(Mailbox, self).delete(*args, **kwargs)
# for address in self.get_addresses():
# address.destination = regex.sub(r'\3', address.destination).strip()
# if not address.destination:
# address.delete()
# else:
# address.save()
#class Address(models.Model):
# name = models.CharField(_("name"), max_length=64,
# validators=[validators.validate_emailname])
# domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
# verbose_name=_("domain"),
# related_name='addresses')
# destination = models.CharField(_("destination"), max_length=256,
# validators=[validators.validate_destination],
# help_text=_("Space separated mailbox names or email addresses"))
# account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
# related_name='addresses')
#
# class Meta:
# verbose_name_plural = _("addresses")
# unique_together = ('name', 'domain')
#
# def __unicode__(self):
# return self.email
#
# @property
# def email(self):
# return "%s@%s" % (self.name, self.domain)
#
# def get_mailboxes(self):
# for dest in self.destination.split():
# if '@' not in dest:
# yield Mailbox.objects.select_related('user').get(user__username=dest)
class Address(models.Model):
name = models.CharField(_("name"), max_length=64,
validators=[validators.validate_emailname])
domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
verbose_name=_("domain"),
related_name='addresses')
mailboxes = models.ManyToManyField('mail.Mailbox',
verbose_name=_("mailboxes"),
related_name='addresses', blank=True)
forward = models.CharField(_("forward"), max_length=256, blank=True,
validators=[validators.validate_forward])
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='addresses')
class Meta:
verbose_name_plural = _("addresses")
unique_together = ('name', 'domain')
def __unicode__(self):
return self.email
@property
def email(self):
return "%s@%s" % (self.name, self.domain)
class Autoresponse(models.Model):
address = models.OneToOneField(Address, verbose_name=_("address"),
related_name='autoresponse')
# TODO initial_date
subject = models.CharField(_("subject"), max_length=256)
message = models.TextField(_("message"))
enabled = models.BooleanField(_("enabled"), default=False)
def __unicode__(self):
return self.address
services.register(Address)
roles.register('mailbox', Mailbox)

View file

@ -1,43 +0,0 @@
from rest_framework import serializers
from orchestra.api import router
from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .models import Address, Mailbox
#class AddressSerializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = Address
# fields = ('url', 'name', 'domain', 'destination')
class NestedMailboxSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Mailbox
fields = ('url', 'use_custom_filtering', 'custom_filtering')
class MailboxSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Mailbox
fields = ('url', 'user', 'use_custom_filtering', 'custom_filtering')
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = Address
fields = ('url', 'name', 'domain', 'mailboxes', 'forward')
def get_fields(self, *args, **kwargs):
fields = super(AddressSerializer, self).get_fields(*args, **kwargs)
account = self.context['view'].request.user.account_id
mailboxes = fields['mailboxes'].queryset.select_related('user')
fields['mailboxes'].queryset = mailboxes.filter(user__account=account)
# TODO do it on permissions or in self.filter_by_account_field ?
domain = fields['domain'].queryset
fields['domain'].queryset = domain .filter(account=account)
return fields
router.insert('users', 'mailbox', NestedMailboxSerializer, required=False)

View file

@ -1,29 +0,0 @@
from django.conf import settings
EMAILS_DOMAIN_MODEL = getattr(settings, 'EMAILS_DOMAIN_MODEL', 'domains.Domain')
EMAILS_HOME = getattr(settings, 'EMAILS_HOME', '/home/%(username)s/')
EMAILS_SIEVETEST_PATH = getattr(settings, 'EMAILS_SIEVETEST_PATH', '/dev/shm')
EMAILS_SIEVETEST_BIN_PATH = getattr(settings, 'EMAILS_SIEVETEST_BIN_PATH',
'%(orchestra_root)s/bin/sieve-test')
EMAILS_VIRTUSERTABLE_PATH = getattr(settings, 'EMAILS_VIRTUSERTABLE_PATH',
'/etc/postfix/virtusertable')
EMAILS_VIRTDOMAINS_PATH = getattr(settings, 'EMAILS_VIRTDOMAINS_PATH',
'/etc/postfix/virtdomains')
EMAILS_DEFAUL_FILTERING = getattr(settings, 'EMAILS_DEFAULT_FILTERING',
'require ["fileinto","regex","envelope","vacation","reject","relational","comparator-i;ascii-numeric"];\n'
'\n'
'if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-Score" "5" {\n'
' fileinto "Junk";\n'
' discard;\n'
'}'
)

View file

@ -1,62 +0,0 @@
import hashlib
import os
import re
from django.core.validators import ValidationError, EmailValidator
from django.utils.translation import ugettext_lazy as _
from orchestra.utils import paths
from orchestra.utils.system import run
from . import settings
def validate_emailname(value):
msg = _("'%s' is not a correct email name" % value)
if '@' in value:
raise ValidationError(msg)
value += '@localhost'
try:
EmailValidator(value)
except ValidationError:
raise ValidationError(msg)
#def validate_destination(value):
# """ space separated mailboxes or emails """
# for destination in value.split():
# msg = _("'%s' is not an existent mailbox" % destination)
# if '@' in destination:
# if not destination[-1].isalpha():
# raise ValidationError(msg)
# EmailValidator(destination)
# else:
# from .models import Mailbox
# if not Mailbox.objects.filter(user__username=destination).exists():
# raise ValidationError(msg)
# validate_emailname(destination)
def validate_forward(value):
""" space separated mailboxes or emails """
for destination in value.split():
EmailValidator(destination)
def validate_sieve(value):
sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest()
path = os.path.join(settings.EMAILS_SIEVETEST_PATH, sieve_name)
with open(path, 'wb') as f:
f.write(value)
context = {
'orchestra_root': paths.get_orchestra_root()
}
sievetest = settings.EMAILS_SIEVETEST_BIN_PATH % context
test = run(' '.join([sievetest, path, '/dev/null']), display=False)
if test.return_code:
errors = []
for line in test.stderr.splitlines():
error = re.match(r'^.*(line\s+[0-9]+:.*)', line)
if error:
errors += error.groups()
raise ValidationError(' '.join(errors))

View file

@ -1,15 +0,0 @@
from django.contrib.auth import get_user_model
from orchestra.admin.utils import insertattr
from orchestra.apps.users.roles.admin import RoleAdmin
from .models import POSIX
class POSIXRoleAdmin(RoleAdmin):
model = POSIX
name = 'posix'
url_name = 'posix'
insertattr(get_user_model(), 'roles', POSIXRoleAdmin)

View file

@ -1,22 +0,0 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .. import roles
from . import settings
class POSIX(models.Model):
user = models.OneToOneField('users.User', verbose_name=_("user"),
related_name='posix')
home = models.CharField(_("home"), max_length=256, blank=True,
help_text=_("Home directory relative to account's ~primary_user"))
shell = models.CharField(_("shell"), max_length=32,
choices=settings.POSIX_SHELLS, default=settings.POSIX_DEFAULT_SHELL)
def __unicode__(self):
return str(self.user)
# TODO groups
roles.register('posix', POSIX)

View file

@ -1,14 +0,0 @@
from rest_framework import serializers
from orchestra.api import router
from .models import POSIX
class POSIXSerializer(serializers.ModelSerializer):
class Meta:
model = POSIX
fields = ('home', 'shell')
router.insert('users', 'posix', POSIXSerializer, required=False)

View file

@ -1,11 +0,0 @@
from django.conf import settings
from django.utils.translation import ugettext, ugettext_lazy as _
POSIX_SHELLS = getattr(settings, 'POSIX_SHELLS', (
('/bin/false', _("FTP/sFTP only")),
('/bin/rsync', _("rsync shell")),
('/bin/bash', "Bash"),
))
POSIX_DEFAULT_SHELL = getattr(settings, 'POSIX_DEFAULT_SHELL', '/bin/false')

View file

@ -1,35 +0,0 @@
from django.contrib.auth import get_user_model
from django.forms import widgets
from django.utils.translation import ugettext, ugettext_lazy as _
from rest_framework import serializers
from orchestra.apps.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password
class UserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput)
class Meta:
model = get_user_model()
fields = (
'url', 'username', 'password', 'first_name', 'last_name', 'email',
'is_admin', 'is_active',
)
def validate_password(self, attrs, source):
""" POST only password """
if self.object.pk:
if 'password' in attrs:
raise serializers.ValidationError(_("Can not set password"))
elif 'password' not in attrs:
raise serializers.ValidationError(_("Password required"))
return attrs
def save_object(self, obj, **kwargs):
# FIXME this method will be called when saving nested serializers :(
if not obj.pk:
obj.set_password(obj.password)
super(UserSerializer, self).save_object(obj, **kwargs)

View file

@ -1,6 +0,0 @@
from django.conf import settings
USERS_SYSTEMUSER_HOME = getattr(settings, 'USERES_SYSTEMUSER_HOME', '/home/%(username)s')
USERS_FTP_LOG_PATH = getattr(settings, 'USERS_FTP_LOG_PATH', '/var/log/vsftpd.log')

View file

@ -1,15 +0,0 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
<li><a href="." class="historylink">{% trans "User" %}</a></li>
{% for item in roles %}
<li><a href="{{ item.url_name }}/" class="{% if item.exists %}historylink{% else %}addlink{% endif %}" title="{{ item.description }}">{% if item.exists %}{{ item.name.capitalize }}{% else %}Add {{ item.name }}{% endif %}</a></li>
{% endfor %}
<li>
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
</li>
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}

View file

@ -1,15 +0,0 @@
{% extends "admin/delete_confirmation.html" %}
{% load i18n admin_urls %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
&rsaquo; <a href="{% url 'admin:users_user_changelist' %}">Users</a>
&rsaquo; <a href="{% url 'admin:users_user_change' object.pk|admin_urlquote %}">{{ user|truncatewords:"18" }}</a>
&rsaquo; <a href="../">{{ role.name|truncatewords:"18" }}</a>
&rsaquo; {% trans 'Delete' %}
</div>
{% endblock %}

View file

@ -1,70 +0,0 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls admin_static admin_modify utils %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
{{ media }}
{% endblock %}
{% block coltype %}colM{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">Home</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'change' user.pk %}">{{ user|capfirst }}</a>
&rsaquo; {{ role.name.capitalize }}
</div>
{% endblock %}
{% block content %}<div id="content-main">
{% block object-tools %}
<ul class="object-tools">
{% block object-tools-items %}
<li><a href=".." class="historylink">{% trans "User" %}</a></li>
{% for item in roles %}
<li><a href="../{{ item.url_name }}/" class="{% if item.exists %}historylink{% else %}addlink{% endif %}" title="{{ item.description }}">{% if item.exists %}{{ item.name.capitalize }}{% else %}Add {{ item.name }}{% endif %}</a></li>
{% endfor %}
<li>
{% url opts|admin_urlname:'history' user.pk|admin_urlquote as history_url %}
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
</li>
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}
</ul>
{% endblock %}
<form action="" method="post">{% csrf_token %}
<fieldset class="module aligned wide">
{% for field in form %}
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field|is_checkbox %}
{{ field }} <label for="{{ field.id_for_label }}" class="vCheckboxLabel">{{ field.label }}</label>
{% else %}
{{ field.label_tag }} {{ field }}
{% endif %}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit" value="{{ action }}" class="default" name="_save" />
{% if role.exists %}<p class="deletelink-box"><a href="delete/" class="deletelink">Delete</a></p>{% endif %}
<input type="submit" value="{{ action }} and continue editing" name="_continue" />
</div>
</form>
{% endblock %}