[Update] 移动model

pull/2461/head
ibuler 2019-02-28 17:58:53 +08:00
parent 1969fb79fe
commit 34040fcd59
24 changed files with 258 additions and 220 deletions

2
apps/audits/hands.py Normal file
View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
#

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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__ = [

View File

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

View File

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

View File

@ -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=[

View File

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

View File

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

View File

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

View File

@ -4,5 +4,4 @@
from .user import *
from .group import *
from .authentication import *
from .utils import *

View File

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

View File

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

View File

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