diff --git a/mayan/apps/authentication/auth/email_auth_backend.py b/mayan/apps/authentication/auth/email_auth_backend.py index 3caf5f0804..043126df4a 100644 --- a/mayan/apps/authentication/auth/email_auth_backend.py +++ b/mayan/apps/authentication/auth/email_auth_backend.py @@ -13,7 +13,7 @@ class EmailAuthBackend(ModelBackend): def authenticate(self, email=None, password=None): UserModel = get_user_model() try: - user = UserModel.objects.get(email=email) + user = UserModel.on_organization.get(email=email) if user.check_password(password): return user except UserModel.DoesNotExist: diff --git a/mayan/apps/authentication/auth/model_auth_backend.py b/mayan/apps/authentication/auth/model_auth_backend.py new file mode 100644 index 0000000000..575c65c6ea --- /dev/null +++ b/mayan/apps/authentication/auth/model_auth_backend.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + + +class UsernameModelBackend(ModelBackend): + def authenticate(self, username=None, password=None, **kwargs): + UserModel = get_user_model() + if username is None: + username = kwargs.get(UserModel.USERNAME_FIELD) + try: + user = UserModel.on_organization.get(username=username) + if user.check_password(password): + return user + except UserModel.DoesNotExist: + # Run the default password hasher once to reduce the timing + # difference between an existing and a non-existing user (#20760). + UserModel().set_password(password) diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py index f4035ed846..09fc7cf345 100644 --- a/mayan/apps/document_states/models.py +++ b/mayan/apps/document_states/models.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import logging -from django.contrib.auth.models import User +from django.conf import settings from django.core.urlresolvers import reverse from django.db import IntegrityError, models from django.utils.encoding import python_2_unicode_compatible @@ -186,7 +186,7 @@ class WorkflowInstanceLogEntry(models.Model): transition = models.ForeignKey( WorkflowTransition, verbose_name=_('Transition') ) - user = models.ForeignKey(User, verbose_name=_('User')) + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User')) comment = models.TextField(blank=True, verbose_name=_('Comment')) def __str__(self): diff --git a/mayan/apps/documents/models.py b/mayan/apps/documents/models.py index 0142628e36..712c74b08e 100644 --- a/mayan/apps/documents/models.py +++ b/mayan/apps/documents/models.py @@ -5,7 +5,7 @@ import hashlib import logging import uuid -from django.contrib.auth.models import User +from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.files import File from django.core.urlresolvers import reverse @@ -806,7 +806,7 @@ class RecentDocument(models.Model): a given user """ user = models.ForeignKey( - User, db_index=True, editable=False, verbose_name=_('User') + settings.AUTH_USER_MODEL, db_index=True, editable=False, verbose_name=_('User') ) document = models.ForeignKey( Document, editable=False, verbose_name=_('Document') @@ -822,7 +822,7 @@ class RecentDocument(models.Model): def natural_key(self): return self.document.natural_key() + self.user.natural_key() - natural_key.dependencies = ['documents.Document', 'auth.User'] + natural_key.dependencies = ['documents.Document', 'user_management.User'] class Meta: ordering = ('-datetime_accessed',) diff --git a/mayan/apps/dynamic_search/models.py b/mayan/apps/dynamic_search/models.py index 9afc0c97dc..b3ee98b156 100644 --- a/mayan/apps/dynamic_search/models.py +++ b/mayan/apps/dynamic_search/models.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import urllib import urlparse -from django.contrib.auth.models import User +from django.conf import settings from django.core.urlresolvers import reverse from django.db import models from django.utils.encoding import ( @@ -20,7 +20,9 @@ class RecentSearch(models.Model): Keeps a list of the [n] most recent search keywords for a given user """ - user = models.ForeignKey(User, editable=False, verbose_name=_('User')) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, editable=False, verbose_name=_('User') + ) query = models.TextField(editable=False, verbose_name=_('Query')) datetime_created = models.DateTimeField( auto_now=True, db_index=True, verbose_name=_('Datetime created') diff --git a/mayan/apps/folders/models.py b/mayan/apps/folders/models.py index 4db0235042..8d8dca0135 100644 --- a/mayan/apps/folders/models.py +++ b/mayan/apps/folders/models.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse @@ -26,7 +27,7 @@ class Folder(models.Model): label = models.CharField( db_index=True, max_length=128, verbose_name=_('Label') ) - user = models.ForeignKey(User, verbose_name=_('User')) + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User')) datetime_created = models.DateTimeField( auto_now_add=True, verbose_name=_('Datetime created') ) diff --git a/mayan/apps/user_management/admin.py b/mayan/apps/user_management/admin.py new file mode 100644 index 0000000000..989fd6e179 --- /dev/null +++ b/mayan/apps/user_management/admin.py @@ -0,0 +1,17 @@ +from django import forms +from django.contrib import admin +from django.contrib.auth.models import Group +from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField + +from user_management.models import MayanUser + + +@admin.register(MayanUser) +class MayanUserAdmin(UserAdmin): + list_display = ('organization',) + UserAdmin.list_display + list_display_links = ('username',) + list_filter = UserAdmin.list_filter + ('organization',) + ordering = ('organization',) + UserAdmin.ordering + fieldsets = UserAdmin.fieldsets + fieldsets[1][1]['fields'] = ('organization',) + fieldsets[1][1]['fields'] diff --git a/mayan/apps/user_management/api_views.py b/mayan/apps/user_management/api_views.py index d4cb4e8fce..945267fc5a 100644 --- a/mayan/apps/user_management/api_views.py +++ b/mayan/apps/user_management/api_views.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals -from django.contrib.auth.models import Group, User +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from rest_framework import generics @@ -83,7 +84,7 @@ class APIUserListView(generics.ListCreateAPIView): mayan_object_permissions = {'GET': (permission_user_view,)} mayan_view_permissions = {'POST': (permission_user_create,)} permission_classes = (MayanPermission,) - queryset = User.objects.all() + queryset = get_user_model().objects.all() serializer_class = UserSerializer def get(self, *args, **kwargs): @@ -108,7 +109,7 @@ class APIUserView(generics.RetrieveUpdateDestroyAPIView): 'DELETE': (permission_user_delete,) } permission_classes = (MayanPermission,) - queryset = User.objects.all() + queryset = get_user_model().objects.all() serializer_class = UserSerializer def delete(self, *args, **kwargs): diff --git a/mayan/apps/user_management/migrations/0001_initial.py b/mayan/apps/user_management/migrations/0001_initial.py new file mode 100644 index 0000000000..41ab1b5f3c --- /dev/null +++ b/mayan/apps/user_management/migrations/0001_initial.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.utils.timezone +import django.core.validators +import organizations.shortcuts + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + ('organizations', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='MayanUser', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])), + ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), + ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), + ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')), + ('organization', models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization')), + ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + }, + bases=(models.Model,), + ), + ] diff --git a/mayan/apps/user_management/migrations/__init__.py b/mayan/apps/user_management/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mayan/apps/user_management/models.py b/mayan/apps/user_management/models.py new file mode 100644 index 0000000000..6c3b77ff9c --- /dev/null +++ b/mayan/apps/user_management/models.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +from django.contrib.auth.models import AbstractUser, UserManager +from django.core.urlresolvers import reverse +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ + +from organizations.models import Organization +from organizations.managers import CurrentOrganizationManager +from organizations.shortcuts import get_current_organization + + +class MayanUser(AbstractUser): + organization = models.ForeignKey( + Organization, default=get_current_organization + ) + + objects = UserManager() + on_organization = CurrentOrganizationManager() diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py index 5ac4cf73cf..d90cad59ac 100644 --- a/mayan/apps/user_management/views.py +++ b/mayan/apps/user_management/views.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals from django.conf import settings from django.contrib import messages from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group, User +from django.contrib.auth.models import Group from django.core.urlresolvers import reverse, reverse_lazy from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render_to_response @@ -86,7 +86,7 @@ class GroupMembersView(AssignRemoveView): def left_list(self): return AssignRemoveView.generate_choices( - User.objects.exclude( + get_user_model().objects.exclude( groups=self.get_object() ).exclude(is_staff=True).exclude(is_superuser=True) ) @@ -131,7 +131,7 @@ class UserGroupsView(AssignRemoveView): } def get_object(self): - return get_object_or_404(User, pk=self.kwargs['pk']) + return get_object_or_404(get_user_model(), pk=self.kwargs['pk']) def left_list(self): return AssignRemoveView.generate_choices( diff --git a/mayan/settings/base.py b/mayan/settings/base.py index f5fc541218..b839d7a0b6 100644 --- a/mayan/settings/base.py +++ b/mayan/settings/base.py @@ -288,3 +288,8 @@ TIMEZONE_COOKIE_NAME = 'django_timezone' TIMEZONE_SESSION_KEY = 'django_timezone' # ------ Organization ------- ORGANIZATION_ID = 1 +# ------ User model -------- +AUTH_USER_MODEL = 'user_management.MayanUser' +# ------- Authentication ------- +AUTHENTICATION_BACKENDS = ('authentication.auth.model_auth_backend.UsernameModelBackend',) +