mirror of https://github.com/jumpserver/jumpserver
				
				
				
			[Update] 移动model
							parent
							
								
									1969fb79fe
								
							
						
					
					
						commit
						34040fcd59
					
				| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
| 
						 | 
				
			
			@ -38,14 +38,4 @@ class Migration(migrations.Migration):
 | 
			
		|||
                ('datetime', models.DateTimeField(auto_now=True)),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='UserLoginLog',
 | 
			
		||||
            fields=[
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'proxy': True,
 | 
			
		||||
                'indexes': [],
 | 
			
		||||
            },
 | 
			
		||||
            bases=('users.loginlog',),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
# Generated by Django 2.1.7 on 2019-02-28 09:15
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models, connection
 | 
			
		||||
import django.utils.timezone
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('audits', '0004_operatelog_passwordchangelog_userloginlog'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='UserLoginLog',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
 | 
			
		||||
                ('username', models.CharField(max_length=128, verbose_name='Username')),
 | 
			
		||||
                ('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')),
 | 
			
		||||
                ('ip', models.GenericIPAddressField(verbose_name='Login ip')),
 | 
			
		||||
                ('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')),
 | 
			
		||||
                ('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')),
 | 
			
		||||
                ('mfa', models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA')),
 | 
			
		||||
                ('reason', models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason')),
 | 
			
		||||
                ('status', models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status')),
 | 
			
		||||
                ('datetime', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date login')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'ordering': ['-datetime', 'username'],
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
    drop_table_sql = "DROP TABLE audits_userloginlog"
 | 
			
		||||
    rename_table_sql = "RENAME TABLE users_loginlog TO audits_userloginlog"
 | 
			
		||||
    table_exist = 'users_loginlog' in connection.introspection.table_names()
 | 
			
		||||
    if table_exist:
 | 
			
		||||
        operations.append(migrations.RunSQL(drop_table_sql))
 | 
			
		||||
        operations.append(migrations.RunSQL(rename_table_sql))
 | 
			
		||||
| 
						 | 
				
			
			@ -2,9 +2,9 @@ import uuid
 | 
			
		|||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from orgs.mixins import OrgModelMixin
 | 
			
		||||
from .hands import LoginLog
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +55,50 @@ class PasswordChangeLog(models.Model):
 | 
			
		|||
        return "{} change {}'s password".format(self.change_by, self.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLoginLog(LoginLog):
 | 
			
		||||
class UserLoginLog(models.Model):
 | 
			
		||||
    LOGIN_TYPE_CHOICE = (
 | 
			
		||||
        ('W', 'Web'),
 | 
			
		||||
        ('T', 'Terminal'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    MFA_DISABLED = 0
 | 
			
		||||
    MFA_ENABLED = 1
 | 
			
		||||
    MFA_UNKNOWN = 2
 | 
			
		||||
 | 
			
		||||
    MFA_CHOICE = (
 | 
			
		||||
        (MFA_DISABLED, _('Disabled')),
 | 
			
		||||
        (MFA_ENABLED, _('Enabled')),
 | 
			
		||||
        (MFA_UNKNOWN, _('-')),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    REASON_NOTHING = 0
 | 
			
		||||
    REASON_PASSWORD = 1
 | 
			
		||||
    REASON_MFA = 2
 | 
			
		||||
    REASON_NOT_EXIST = 3
 | 
			
		||||
    REASON_PASSWORD_EXPIRED = 4
 | 
			
		||||
 | 
			
		||||
    REASON_CHOICE = (
 | 
			
		||||
        (REASON_NOTHING, _('-')),
 | 
			
		||||
        (REASON_PASSWORD, _('Username/password check failed')),
 | 
			
		||||
        (REASON_MFA, _('MFA authentication failed')),
 | 
			
		||||
        (REASON_NOT_EXIST, _("Username does not exist")),
 | 
			
		||||
        (REASON_PASSWORD_EXPIRED, _("Password expired")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    STATUS_CHOICE = (
 | 
			
		||||
        (True, _('Success')),
 | 
			
		||||
        (False, _('Failed'))
 | 
			
		||||
    )
 | 
			
		||||
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 | 
			
		||||
    username = models.CharField(max_length=128, verbose_name=_('Username'))
 | 
			
		||||
    type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
 | 
			
		||||
    ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
 | 
			
		||||
    city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
 | 
			
		||||
    user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
 | 
			
		||||
    mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
 | 
			
		||||
    reason = models.SmallIntegerField(default=0, choices=REASON_CHOICE, verbose_name=_('Reason'))
 | 
			
		||||
    status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
 | 
			
		||||
    datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        proxy = True
 | 
			
		||||
        ordering = ['-datetime', 'username']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ from common.permissions import AdminUserRequiredMixin
 | 
			
		|||
 | 
			
		||||
from orgs.utils import current_org
 | 
			
		||||
from ops.views import CommandExecutionListView as UserCommandExecutionListView
 | 
			
		||||
from users.models import User
 | 
			
		||||
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,9 @@ from common.utils import get_logger, get_request_ip
 | 
			
		|||
from common.permissions import IsOrgAdminOrAppUser
 | 
			
		||||
from orgs.mixins import RootOrgViewMixin
 | 
			
		||||
from users.serializers import UserSerializer
 | 
			
		||||
from users.models import User, LoginLog
 | 
			
		||||
from users.models import User
 | 
			
		||||
from assets.models import Asset, SystemUser
 | 
			
		||||
from audits.models import UserLoginLog as LoginLog
 | 
			
		||||
from users.utils import (
 | 
			
		||||
    check_user_valid, check_otp_code, increase_login_failed_count,
 | 
			
		||||
    is_block_login, clean_failed_count
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,13 +8,13 @@ from django.core.cache import cache
 | 
			
		|||
from django.conf import settings
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
from django.utils.six import text_type
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from rest_framework import HTTP_HEADER_ENCODING
 | 
			
		||||
from rest_framework import authentication, exceptions
 | 
			
		||||
from rest_framework.authentication import CSRFCheck
 | 
			
		||||
 | 
			
		||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
 | 
			
		||||
from users.models import User, AccessKey, PrivateToken
 | 
			
		||||
from ..models import AccessKey, PrivateToken
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_request_date_header(request):
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +42,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
 | 
			
		|||
    失败
 | 
			
		||||
    """
 | 
			
		||||
    keyword = 'Sign'
 | 
			
		||||
    model = AccessKey
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, request):
 | 
			
		||||
        auth = authentication.get_authorization_header(request).split()
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +108,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
 | 
			
		|||
 | 
			
		||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
 | 
			
		||||
    keyword = 'Bearer'
 | 
			
		||||
    model = User
 | 
			
		||||
    model = get_user_model()
 | 
			
		||||
    expiration = settings.TOKEN_EXPIRATION or 3600
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, request):
 | 
			
		||||
| 
						 | 
				
			
			@ -133,10 +132,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
 | 
			
		|||
            raise exceptions.AuthenticationFailed(msg)
 | 
			
		||||
        return self.authenticate_credentials(token)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def authenticate_credentials(token):
 | 
			
		||||
    def authenticate_credentials(self, token):
 | 
			
		||||
        user_id = cache.get(token)
 | 
			
		||||
        user = get_object_or_none(User, id=user_id)
 | 
			
		||||
        user = get_object_or_none(self.model, id=user_id)
 | 
			
		||||
 | 
			
		||||
        if not user:
 | 
			
		||||
            msg = _('Invalid token or cache refreshed.')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.forms import AuthenticationForm
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from captcha.fields import CaptchaField
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLoginForm(AuthenticationForm):
 | 
			
		||||
    username = forms.CharField(label=_('Username'), max_length=100)
 | 
			
		||||
    password = forms.CharField(
 | 
			
		||||
        label=_('Password'), widget=forms.PasswordInput,
 | 
			
		||||
        max_length=128, strip=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def confirm_login_allowed(self, user):
 | 
			
		||||
        if not user.is_staff:
 | 
			
		||||
            raise forms.ValidationError(
 | 
			
		||||
                self.error_messages['inactive'],
 | 
			
		||||
                code='inactive',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLoginCaptchaForm(UserLoginForm):
 | 
			
		||||
    captcha = CaptchaField()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserCheckOtpCodeForm(forms.Form):
 | 
			
		||||
    otp_code = forms.CharField(label=_('MFA code'), max_length=6)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
# Generated by Django 2.1.7 on 2019-02-28 08:07
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    initial = True
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='AccessKey',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id',
 | 
			
		||||
                 models.UUIDField(default=uuid.uuid4, editable=False,
 | 
			
		||||
                                  primary_key=True, serialize=False,
 | 
			
		||||
                                  verbose_name='AccessKeyID')),
 | 
			
		||||
                ('secret',
 | 
			
		||||
                 models.UUIDField(default=uuid.uuid4, editable=False,
 | 
			
		||||
                                  verbose_name='AccessKeySecret')),
 | 
			
		||||
                ('user', models.ForeignKey(
 | 
			
		||||
                    on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                    related_name='access_keys',
 | 
			
		||||
                    to=settings.AUTH_USER_MODEL, verbose_name='User')),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='PrivateToken',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('key',
 | 
			
		||||
                 models.CharField(max_length=40, primary_key=True,
 | 
			
		||||
                                  serialize=False, verbose_name='Key')),
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True,
 | 
			
		||||
                                                 verbose_name='Created')),
 | 
			
		||||
                ('user', models.OneToOneField(
 | 
			
		||||
                    on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                    related_name='auth_token',
 | 
			
		||||
                    to=settings.AUTH_USER_MODEL, verbose_name='User')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Private Token',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import uuid
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from rest_framework.authtoken.models import Token
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccessKey(models.Model):
 | 
			
		||||
    id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True,
 | 
			
		||||
                          default=uuid.uuid4, editable=False)
 | 
			
		||||
    secret = models.UUIDField(verbose_name='AccessKeySecret',
 | 
			
		||||
                              default=uuid.uuid4, editable=False)
 | 
			
		||||
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
 | 
			
		||||
                             on_delete=models.CASCADE, related_name='access_keys')
 | 
			
		||||
 | 
			
		||||
    def get_id(self):
 | 
			
		||||
        return str(self.id)
 | 
			
		||||
 | 
			
		||||
    def get_secret(self):
 | 
			
		||||
        return str(self.secret)
 | 
			
		||||
 | 
			
		||||
    def get_full_value(self):
 | 
			
		||||
        return '{}:{}'.format(self.id, self.secret)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return str(self.id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrivateToken(Token):
 | 
			
		||||
    """Inherit from auth token, otherwise migration is boring"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _('Private Token')
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
 | 
			
		||||
from .models import AccessKey
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['AccessKeySerializer']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccessKeySerializer(serializers.ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = AccessKey
 | 
			
		||||
        fields = ['id', 'secret']
 | 
			
		||||
        read_only_fields = ['id', 'secret']
 | 
			
		||||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ from common.utils import get_ip_city, validate_ip
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def write_login_log(*args, **kwargs):
 | 
			
		||||
    from users.models import LoginLog
 | 
			
		||||
    from audits.models import UserLoginLog
 | 
			
		||||
    default_city = _("Unknown")
 | 
			
		||||
    ip = kwargs.get('ip', '')
 | 
			
		||||
    if not (ip and validate_ip(ip)):
 | 
			
		||||
| 
						 | 
				
			
			@ -14,5 +14,5 @@ def write_login_log(*args, **kwargs):
 | 
			
		|||
    else:
 | 
			
		||||
        city = get_ip_city(ip) or default_city
 | 
			
		||||
    kwargs.update({'ip': ip, 'city': city})
 | 
			
		||||
    LoginLog.objects.create(**kwargs)
 | 
			
		||||
    UserLoginLog.objects.create(**kwargs)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,14 +17,15 @@ from django.views.generic.edit import FormView
 | 
			
		|||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from common.utils import get_request_ip
 | 
			
		||||
from authentication.signals import post_auth_success, post_auth_failed
 | 
			
		||||
from users import forms
 | 
			
		||||
from users.models import User, LoginLog
 | 
			
		||||
from users.models import User
 | 
			
		||||
from audits.models import UserLoginLog as LoginLog
 | 
			
		||||
from users.utils import (
 | 
			
		||||
    check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
 | 
			
		||||
    set_tmp_user_to_cache, increase_login_failed_count,
 | 
			
		||||
    redirect_user_first_login_or_index,
 | 
			
		||||
)
 | 
			
		||||
from ..signals import post_auth_success, post_auth_failed
 | 
			
		||||
from .. import forms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
 | 
			
		|||
        set_current_org(old_org)
 | 
			
		||||
 | 
			
		||||
    if instance and not created:
 | 
			
		||||
        instance.expire_cache()
 | 
			
		||||
        instance.expire_user_cache()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(m2m_changed, sender=Organization.users.through)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,7 @@
 | 
			
		|||
# ~*~ coding: utf-8 ~*~
 | 
			
		||||
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.forms import AuthenticationForm
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from captcha.fields import CaptchaField
 | 
			
		||||
 | 
			
		||||
from common.utils import validate_ssh_public_key
 | 
			
		||||
from orgs.mixins import OrgModelForm
 | 
			
		||||
| 
						 | 
				
			
			@ -11,24 +9,6 @@ from orgs.utils import current_org
 | 
			
		|||
from .models import User, UserGroup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLoginForm(AuthenticationForm):
 | 
			
		||||
    username = forms.CharField(label=_('Username'), max_length=100)
 | 
			
		||||
    password = forms.CharField(
 | 
			
		||||
        label=_('Password'), widget=forms.PasswordInput,
 | 
			
		||||
        max_length=128, strip=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def confirm_login_allowed(self, user):
 | 
			
		||||
        if not user.is_staff:
 | 
			
		||||
            raise forms.ValidationError(
 | 
			
		||||
                self.error_messages['inactive'],
 | 
			
		||||
                code='inactive',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserLoginCaptchaForm(UserLoginForm):
 | 
			
		||||
    captcha = CaptchaField()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserCheckPasswordForm(forms.Form):
 | 
			
		||||
    username = forms.CharField(label=_('Username'), max_length=100)
 | 
			
		||||
    password = forms.CharField(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,10 +4,8 @@ from __future__ import unicode_literals
 | 
			
		|||
 | 
			
		||||
import common.utils
 | 
			
		||||
from django.contrib.auth.hashers import make_password
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
import django.contrib.auth.models
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import django.utils.timezone
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,40 +73,6 @@ class Migration(migrations.Migration):
 | 
			
		|||
                ('objects', django.contrib.auth.models.UserManager()),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='AccessKey',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='AccessKeyID')),
 | 
			
		||||
                ('secret', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='AccessKeySecret')),
 | 
			
		||||
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_key', to=settings.AUTH_USER_MODEL, verbose_name='User')),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='LoginLog',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
 | 
			
		||||
                ('username', models.CharField(max_length=20, verbose_name='Username')),
 | 
			
		||||
                ('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')),
 | 
			
		||||
                ('ip', models.GenericIPAddressField(verbose_name='Login ip')),
 | 
			
		||||
                ('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')),
 | 
			
		||||
                ('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')),
 | 
			
		||||
                ('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date login')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'ordering': ['-datetime', 'username'],
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='PrivateToken',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('key', models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='Key')),
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
 | 
			
		||||
                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', to=settings.AUTH_USER_MODEL, verbose_name='User')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Private Token',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='UserGroup',
 | 
			
		||||
            fields=[
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,19 +12,4 @@ class Migration(migrations.Migration):
 | 
			
		|||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='mfa',
 | 
			
		||||
            field=models.SmallIntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, '-')], default=2, verbose_name='MFA'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='reason',
 | 
			
		||||
            field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed')], default=0, verbose_name='Reason'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='status',
 | 
			
		||||
            field=models.BooleanField(choices=[(True, 'Success'), (False, 'Failed')], default=True, max_length=2, verbose_name='Status'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,14 +10,4 @@ class Migration(migrations.Migration):
 | 
			
		|||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='reason',
 | 
			
		||||
            field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist')], default=0, verbose_name='Reason'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='username',
 | 
			
		||||
            field=models.CharField(max_length=128, verbose_name='Username'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
# Generated by Django 2.1.1 on 2018-11-23 03:13
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
| 
						 | 
				
			
			@ -17,14 +15,4 @@ class Migration(migrations.Migration):
 | 
			
		|||
            name='date_password_last_updated',
 | 
			
		||||
            field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date password last updated'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='accesskey',
 | 
			
		||||
            name='user',
 | 
			
		||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_keys', to=settings.AUTH_USER_MODEL, verbose_name='User'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='loginlog',
 | 
			
		||||
            name='reason',
 | 
			
		||||
            field=models.SmallIntegerField(choices=[(0, '-'), (1, 'Username/password check failed'), (2, 'MFA authentication failed'), (3, 'Username does not exist'), (4, 'Password expired')], default=0, verbose_name='Reason'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,5 +4,4 @@
 | 
			
		|||
 | 
			
		||||
from .user import *
 | 
			
		||||
from .group import *
 | 
			
		||||
from .authentication import *
 | 
			
		||||
from .utils import *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,89 +0,0 @@
 | 
			
		|||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import uuid
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from rest_framework.authtoken.models import Token
 | 
			
		||||
from .user import User
 | 
			
		||||
 | 
			
		||||
__all__ = ['AccessKey', 'PrivateToken', 'LoginLog']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccessKey(models.Model):
 | 
			
		||||
    id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True,
 | 
			
		||||
                          default=uuid.uuid4, editable=False)
 | 
			
		||||
    secret = models.UUIDField(verbose_name='AccessKeySecret',
 | 
			
		||||
                              default=uuid.uuid4, editable=False)
 | 
			
		||||
    user = models.ForeignKey(User, verbose_name='User',
 | 
			
		||||
                             on_delete=models.CASCADE, related_name='access_keys')
 | 
			
		||||
 | 
			
		||||
    def get_id(self):
 | 
			
		||||
        return str(self.id)
 | 
			
		||||
 | 
			
		||||
    def get_secret(self):
 | 
			
		||||
        return str(self.secret)
 | 
			
		||||
 | 
			
		||||
    def get_full_value(self):
 | 
			
		||||
        return '{}:{}'.format(self.id, self.secret)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return str(self.id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrivateToken(Token):
 | 
			
		||||
    """Inherit from auth token, otherwise migration is boring"""
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _('Private Token')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoginLog(models.Model):
 | 
			
		||||
    LOGIN_TYPE_CHOICE = (
 | 
			
		||||
        ('W', 'Web'),
 | 
			
		||||
        ('T', 'Terminal'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    MFA_DISABLED = 0
 | 
			
		||||
    MFA_ENABLED = 1
 | 
			
		||||
    MFA_UNKNOWN = 2
 | 
			
		||||
 | 
			
		||||
    MFA_CHOICE = (
 | 
			
		||||
        (MFA_DISABLED, _('Disabled')),
 | 
			
		||||
        (MFA_ENABLED, _('Enabled')),
 | 
			
		||||
        (MFA_UNKNOWN, _('-')),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    REASON_NOTHING = 0
 | 
			
		||||
    REASON_PASSWORD = 1
 | 
			
		||||
    REASON_MFA = 2
 | 
			
		||||
    REASON_NOT_EXIST = 3
 | 
			
		||||
    REASON_PASSWORD_EXPIRED = 4
 | 
			
		||||
 | 
			
		||||
    REASON_CHOICE = (
 | 
			
		||||
        (REASON_NOTHING, _('-')),
 | 
			
		||||
        (REASON_PASSWORD, _('Username/password check failed')),
 | 
			
		||||
        (REASON_MFA, _('MFA authentication failed')),
 | 
			
		||||
        (REASON_NOT_EXIST, _("Username does not exist")),
 | 
			
		||||
        (REASON_PASSWORD_EXPIRED, _("Password expired")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    STATUS_CHOICE = (
 | 
			
		||||
        (True, _('Success')),
 | 
			
		||||
        (False, _('Failed'))
 | 
			
		||||
    )
 | 
			
		||||
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
 | 
			
		||||
    username = models.CharField(max_length=128, verbose_name=_('Username'))
 | 
			
		||||
    type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
 | 
			
		||||
    ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
 | 
			
		||||
    city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
 | 
			
		||||
    user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
 | 
			
		||||
    mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
 | 
			
		||||
    reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
 | 
			
		||||
    status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
 | 
			
		||||
    datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        ordering = ['-datetime', 'username']
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ from django.utils import timezone
 | 
			
		|||
from django.shortcuts import reverse
 | 
			
		||||
 | 
			
		||||
from common.utils import get_signer, date_expired_default
 | 
			
		||||
from orgs.utils import current_org
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['User']
 | 
			
		||||
| 
						 | 
				
			
			@ -104,6 +103,8 @@ class User(AbstractUser):
 | 
			
		|||
        verbose_name=_('Date password last updated')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    user_cache_key_prefix = '_User_{}'
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return '{0.name}({0.username})'.format(self)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -281,6 +282,7 @@ class User(AbstractUser):
 | 
			
		|||
            self.role = 'Admin'
 | 
			
		||||
            self.is_active = True
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
        self.expire_user_cache()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def private_token(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -422,8 +424,26 @@ class User(AbstractUser):
 | 
			
		|||
    def delete(self, using=None, keep_parents=False):
 | 
			
		||||
        if self.pk == 1 or self.username == 'admin':
 | 
			
		||||
            return
 | 
			
		||||
        self.expire_user_cache()
 | 
			
		||||
        return super(User, self).delete()
 | 
			
		||||
 | 
			
		||||
    def expire_user_cache(self):
 | 
			
		||||
        key = self.user_cache_key_prefix.format(self.id)
 | 
			
		||||
        cache.delete(key)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_user_or_from_cache(cls, uid):
 | 
			
		||||
        key = cls.user_cache_key_prefix.format(uid)
 | 
			
		||||
        user = cache.get(key)
 | 
			
		||||
        if user:
 | 
			
		||||
            return user
 | 
			
		||||
        try:
 | 
			
		||||
            user = cls.objects.get(id=uid)
 | 
			
		||||
            cache.set(key, user, 3600)
 | 
			
		||||
        except cls.DoesNotExist:
 | 
			
		||||
            user = None
 | 
			
		||||
        return user
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        ordering = ['username']
 | 
			
		||||
        verbose_name = _("User")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,15 +2,9 @@
 | 
			
		|||
#
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
from ..models import User, AccessKey
 | 
			
		||||
from ..models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccessKeySerializer(serializers.ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = AccessKey
 | 
			
		||||
        fields = ['id', 'secret']
 | 
			
		||||
        read_only_fields = ['id', 'secret']
 | 
			
		||||
from authentication.serializers import AccessKeySerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServiceAccountSerializer(serializers.ModelSerializer):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue