Update authenthication code to support multitenants. Replace all remaining instances of hardcoded User model.

This commit is contained in:
Roberto Rosario
2016-03-07 03:08:11 -04:00
parent 6492908c59
commit 8a5a26c0b4
13 changed files with 124 additions and 15 deletions

View File

@@ -13,7 +13,7 @@ class EmailAuthBackend(ModelBackend):
def authenticate(self, email=None, password=None): def authenticate(self, email=None, password=None):
UserModel = get_user_model() UserModel = get_user_model()
try: try:
user = UserModel.objects.get(email=email) user = UserModel.on_organization.get(email=email)
if user.check_password(password): if user.check_password(password):
return user return user
except UserModel.DoesNotExist: except UserModel.DoesNotExist:

View File

@@ -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)

View File

@@ -2,7 +2,7 @@ from __future__ import unicode_literals
import logging import logging
from django.contrib.auth.models import User from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import IntegrityError, models from django.db import IntegrityError, models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@@ -186,7 +186,7 @@ class WorkflowInstanceLogEntry(models.Model):
transition = models.ForeignKey( transition = models.ForeignKey(
WorkflowTransition, verbose_name=_('Transition') 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')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self): def __str__(self):

View File

@@ -5,7 +5,7 @@ import hashlib
import logging import logging
import uuid import uuid
from django.contrib.auth.models import User from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.files import File from django.core.files import File
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@@ -806,7 +806,7 @@ class RecentDocument(models.Model):
a given user a given user
""" """
user = models.ForeignKey( 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 = models.ForeignKey(
Document, editable=False, verbose_name=_('Document') Document, editable=False, verbose_name=_('Document')
@@ -822,7 +822,7 @@ class RecentDocument(models.Model):
def natural_key(self): def natural_key(self):
return self.document.natural_key() + self.user.natural_key() 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: class Meta:
ordering = ('-datetime_accessed',) ordering = ('-datetime_accessed',)

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
import urllib import urllib
import urlparse import urlparse
from django.contrib.auth.models import User from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.utils.encoding import ( 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 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')) query = models.TextField(editable=False, verbose_name=_('Query'))
datetime_created = models.DateTimeField( datetime_created = models.DateTimeField(
auto_now=True, db_index=True, verbose_name=_('Datetime created') auto_now=True, db_index=True, verbose_name=_('Datetime created')

View File

@@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@@ -26,7 +27,7 @@ class Folder(models.Model):
label = models.CharField( label = models.CharField(
db_index=True, max_length=128, verbose_name=_('Label') 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( datetime_created = models.DateTimeField(
auto_now_add=True, verbose_name=_('Datetime created') auto_now_add=True, verbose_name=_('Datetime created')
) )

View File

@@ -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']

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals 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 from rest_framework import generics
@@ -83,7 +84,7 @@ class APIUserListView(generics.ListCreateAPIView):
mayan_object_permissions = {'GET': (permission_user_view,)} mayan_object_permissions = {'GET': (permission_user_view,)}
mayan_view_permissions = {'POST': (permission_user_create,)} mayan_view_permissions = {'POST': (permission_user_create,)}
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = User.objects.all() queryset = get_user_model().objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
@@ -108,7 +109,7 @@ class APIUserView(generics.RetrieveUpdateDestroyAPIView):
'DELETE': (permission_user_delete,) 'DELETE': (permission_user_delete,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = User.objects.all() queryset = get_user_model().objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):

View File

@@ -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,),
),
]

View File

@@ -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()

View File

@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model 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.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response from django.shortcuts import get_object_or_404, render_to_response
@@ -86,7 +86,7 @@ class GroupMembersView(AssignRemoveView):
def left_list(self): def left_list(self):
return AssignRemoveView.generate_choices( return AssignRemoveView.generate_choices(
User.objects.exclude( get_user_model().objects.exclude(
groups=self.get_object() groups=self.get_object()
).exclude(is_staff=True).exclude(is_superuser=True) ).exclude(is_staff=True).exclude(is_superuser=True)
) )
@@ -131,7 +131,7 @@ class UserGroupsView(AssignRemoveView):
} }
def get_object(self): 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): def left_list(self):
return AssignRemoveView.generate_choices( return AssignRemoveView.generate_choices(

View File

@@ -288,3 +288,8 @@ TIMEZONE_COOKIE_NAME = 'django_timezone'
TIMEZONE_SESSION_KEY = 'django_timezone' TIMEZONE_SESSION_KEY = 'django_timezone'
# ------ Organization ------- # ------ Organization -------
ORGANIZATION_ID = 1 ORGANIZATION_ID = 1
# ------ User model --------
AUTH_USER_MODEL = 'user_management.MayanUser'
# ------- Authentication -------
AUTHENTICATION_BACKENDS = ('authentication.auth.model_auth_backend.UsernameModelBackend',)