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)),
|
('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.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from orgs.mixins import OrgModelMixin
|
from orgs.mixins import OrgModelMixin
|
||||||
from .hands import LoginLog
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
||||||
|
@ -55,6 +55,50 @@ class PasswordChangeLog(models.Model):
|
||||||
return "{} change {}'s password".format(self.change_by, self.user)
|
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:
|
class Meta:
|
||||||
proxy = True
|
ordering = ['-datetime', 'username']
|
||||||
|
|
|
@ -8,7 +8,6 @@ from common.permissions import AdminUserRequiredMixin
|
||||||
|
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
||||||
from users.models import User
|
|
||||||
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
|
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 common.permissions import IsOrgAdminOrAppUser
|
||||||
from orgs.mixins import RootOrgViewMixin
|
from orgs.mixins import RootOrgViewMixin
|
||||||
from users.serializers import UserSerializer
|
from users.serializers import UserSerializer
|
||||||
from users.models import User, LoginLog
|
from users.models import User
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, SystemUser
|
||||||
|
from audits.models import UserLoginLog as LoginLog
|
||||||
from users.utils import (
|
from users.utils import (
|
||||||
check_user_valid, check_otp_code, increase_login_failed_count,
|
check_user_valid, check_otp_code, increase_login_failed_count,
|
||||||
is_block_login, clean_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.conf import settings
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.six import text_type
|
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 HTTP_HEADER_ENCODING
|
||||||
from rest_framework import authentication, exceptions
|
from rest_framework import authentication, exceptions
|
||||||
from rest_framework.authentication import CSRFCheck
|
from rest_framework.authentication import CSRFCheck
|
||||||
|
|
||||||
from common.utils import get_object_or_none, make_signature, http_to_unixtime
|
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):
|
def get_request_date_header(request):
|
||||||
|
@ -42,7 +42,6 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
||||||
失败
|
失败
|
||||||
"""
|
"""
|
||||||
keyword = 'Sign'
|
keyword = 'Sign'
|
||||||
model = AccessKey
|
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
auth = authentication.get_authorization_header(request).split()
|
auth = authentication.get_authorization_header(request).split()
|
||||||
|
@ -109,7 +108,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication):
|
||||||
|
|
||||||
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||||
keyword = 'Bearer'
|
keyword = 'Bearer'
|
||||||
model = User
|
model = get_user_model()
|
||||||
expiration = settings.TOKEN_EXPIRATION or 3600
|
expiration = settings.TOKEN_EXPIRATION or 3600
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
|
@ -133,10 +132,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
|
||||||
raise exceptions.AuthenticationFailed(msg)
|
raise exceptions.AuthenticationFailed(msg)
|
||||||
return self.authenticate_credentials(token)
|
return self.authenticate_credentials(token)
|
||||||
|
|
||||||
@staticmethod
|
def authenticate_credentials(self, token):
|
||||||
def authenticate_credentials(token):
|
|
||||||
user_id = cache.get(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:
|
if not user:
|
||||||
msg = _('Invalid token or cache refreshed.')
|
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):
|
def write_login_log(*args, **kwargs):
|
||||||
from users.models import LoginLog
|
from audits.models import UserLoginLog
|
||||||
default_city = _("Unknown")
|
default_city = _("Unknown")
|
||||||
ip = kwargs.get('ip', '')
|
ip = kwargs.get('ip', '')
|
||||||
if not (ip and validate_ip(ip)):
|
if not (ip and validate_ip(ip)):
|
||||||
|
@ -14,5 +14,5 @@ def write_login_log(*args, **kwargs):
|
||||||
else:
|
else:
|
||||||
city = get_ip_city(ip) or default_city
|
city = get_ip_city(ip) or default_city
|
||||||
kwargs.update({'ip': ip, 'city': 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 django.conf import settings
|
||||||
|
|
||||||
from common.utils import get_request_ip
|
from common.utils import get_request_ip
|
||||||
from authentication.signals import post_auth_success, post_auth_failed
|
from users.models import User
|
||||||
from users import forms
|
from audits.models import UserLoginLog as LoginLog
|
||||||
from users.models import User, LoginLog
|
|
||||||
from users.utils import (
|
from users.utils import (
|
||||||
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
|
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
|
||||||
set_tmp_user_to_cache, increase_login_failed_count,
|
set_tmp_user_to_cache, increase_login_failed_count,
|
||||||
redirect_user_first_login_or_index,
|
redirect_user_first_login_or_index,
|
||||||
)
|
)
|
||||||
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
|
from .. import forms
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -23,7 +23,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
set_current_org(old_org)
|
set_current_org(old_org)
|
||||||
|
|
||||||
if instance and not created:
|
if instance and not created:
|
||||||
instance.expire_cache()
|
instance.expire_user_cache()
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Organization.users.through)
|
@receiver(m2m_changed, sender=Organization.users.through)
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from captcha.fields import CaptchaField
|
|
||||||
|
|
||||||
from common.utils import validate_ssh_public_key
|
from common.utils import validate_ssh_public_key
|
||||||
from orgs.mixins import OrgModelForm
|
from orgs.mixins import OrgModelForm
|
||||||
|
@ -11,24 +9,6 @@ from orgs.utils import current_org
|
||||||
from .models import User, UserGroup
|
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):
|
class UserCheckPasswordForm(forms.Form):
|
||||||
username = forms.CharField(label=_('Username'), max_length=100)
|
username = forms.CharField(label=_('Username'), max_length=100)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
|
|
|
@ -4,10 +4,8 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import common.utils
|
import common.utils
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.conf import settings
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
@ -75,40 +73,6 @@ class Migration(migrations.Migration):
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
('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(
|
migrations.CreateModel(
|
||||||
name='UserGroup',
|
name='UserGroup',
|
||||||
fields=[
|
fields=[
|
||||||
|
|
|
@ -12,19 +12,4 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
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 = [
|
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
|
# Generated by Django 2.1.1 on 2018-11-23 03:13
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -17,14 +15,4 @@ class Migration(migrations.Migration):
|
||||||
name='date_password_last_updated',
|
name='date_password_last_updated',
|
||||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_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 .user import *
|
||||||
from .group import *
|
from .group import *
|
||||||
from .authentication import *
|
|
||||||
from .utils 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 django.shortcuts import reverse
|
||||||
|
|
||||||
from common.utils import get_signer, date_expired_default
|
from common.utils import get_signer, date_expired_default
|
||||||
from orgs.utils import current_org
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['User']
|
__all__ = ['User']
|
||||||
|
@ -104,6 +103,8 @@ class User(AbstractUser):
|
||||||
verbose_name=_('Date password last updated')
|
verbose_name=_('Date password last updated')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
user_cache_key_prefix = '_User_{}'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.name}({0.username})'.format(self)
|
return '{0.name}({0.username})'.format(self)
|
||||||
|
|
||||||
|
@ -281,6 +282,7 @@ class User(AbstractUser):
|
||||||
self.role = 'Admin'
|
self.role = 'Admin'
|
||||||
self.is_active = True
|
self.is_active = True
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
self.expire_user_cache()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_token(self):
|
def private_token(self):
|
||||||
|
@ -422,8 +424,26 @@ class User(AbstractUser):
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.pk == 1 or self.username == 'admin':
|
if self.pk == 1 or self.username == 'admin':
|
||||||
return
|
return
|
||||||
|
self.expire_user_cache()
|
||||||
return super(User, self).delete()
|
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:
|
class Meta:
|
||||||
ordering = ['username']
|
ordering = ['username']
|
||||||
verbose_name = _("User")
|
verbose_name = _("User")
|
||||||
|
|
|
@ -2,15 +2,9 @@
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from ..models import User, AccessKey
|
from ..models import User
|
||||||
|
|
||||||
|
from authentication.serializers import AccessKeySerializer
|
||||||
class AccessKeySerializer(serializers.ModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AccessKey
|
|
||||||
fields = ['id', 'secret']
|
|
||||||
read_only_fields = ['id', 'secret']
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceAccountSerializer(serializers.ModelSerializer):
|
class ServiceAccountSerializer(serializers.ModelSerializer):
|
||||||
|
|
Loading…
Reference in New Issue