diff --git a/apps/audits/migrations/0011_userloginlog_backend.py b/apps/audits/migrations/0011_userloginlog_backend.py new file mode 100644 index 000000000..5a708b198 --- /dev/null +++ b/apps/audits/migrations/0011_userloginlog_backend.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-12-09 03:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0010_auto_20200811_1122'), + ] + + operations = [ + migrations.AddField( + model_name='userloginlog', + name='backend', + field=models.CharField(default='', max_length=32, verbose_name='Login backend'), + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index c959bc35c..2b28943bb 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -105,6 +105,7 @@ class UserLoginLog(models.Model): reason = models.CharField(default='', max_length=128, blank=True, 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')) + backend = models.CharField(max_length=32, default='', verbose_name=_('Login backend')) @classmethod def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None): diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 0815226a4..74c8f598c 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -31,7 +31,8 @@ class UserLoginLogSerializer(serializers.ModelSerializer): model = models.UserLoginLog fields = ( 'id', 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', - 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display' + 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display', + 'backend' ) extra_kwargs = { "user_agent": {'label': _('User agent')} diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py index b95f6fbdf..e25bd9be9 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -5,6 +5,8 @@ from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.db import transaction from django.utils import timezone +from django.contrib.auth import BACKEND_SESSION_KEY +from django.utils.translation import ugettext_lazy as _ from rest_framework.renderers import JSONRenderer from rest_framework.request import Request @@ -32,6 +34,19 @@ MODELS_NEED_RECORD = ( ) +LOGIN_BACKEND = { + 'PublicKeyAuthBackend': _('SSH Key'), + 'RadiusBackend': User.Source.radius.label, + 'RadiusRealmBackend': User.Source.radius.label, + 'LDAPAuthorizationBackend': User.Source.ldap.label, + 'ModelBackend': _('Password'), + 'SSOAuthentication': _('SSO'), + 'CASBackend': User.Source.cas.label, + 'OIDCAuthCodeBackend': User.Source.openid.label, + 'OIDCAuthPasswordBackend': User.Source.openid.label, +} + + def create_operate_log(action, sender, resource): user = current_request.user if current_request else None if not user or not user.is_authenticated: @@ -109,6 +124,12 @@ def on_audits_log_create(sender, instance=None, **kwargs): sys_logger.info(msg) +def get_login_backend(request): + backend = request.session.get(BACKEND_SESSION_KEY, '') + backend = backend.rsplit('.', maxsplit=1)[-1] + return LOGIN_BACKEND.get(backend, '') + + def generate_data(username, request): user_agent = request.META.get('HTTP_USER_AGENT', '') login_ip = get_request_ip(request) or '0.0.0.0' @@ -122,7 +143,8 @@ def generate_data(username, request): 'ip': login_ip, 'type': login_type, 'user_agent': user_agent, - 'datetime': timezone.now() + 'datetime': timezone.now(), + 'backend': get_login_backend(request) } return data diff --git a/apps/authentication/backends/radius.py b/apps/authentication/backends/radius.py index 4301e17bf..6798e72f2 100644 --- a/apps/authentication/backends/radius.py +++ b/apps/authentication/backends/radius.py @@ -23,7 +23,7 @@ class CreateUserMixin: email_suffix = settings.EMAIL_SUFFIX email = '{}@{}'.format(username, email_suffix) user = User(username=username, name=username, email=email) - user.source = user.SOURCE_RADIUS + user.source = user.Source.radius.value user.save() return user diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 592a43674..a205a5190 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,11 +1,9 @@ import uuid -from functools import partial from django.utils import timezone from django.utils.translation import ugettext_lazy as _, ugettext as __ from rest_framework.authtoken.models import Token from django.conf import settings -from django.utils.crypto import get_random_string from common.db import models from common.mixins.models import CommonModelMixin diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index 3193a0a73..5ca455380 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -333,7 +333,7 @@ class LDAPImportUtil(object): def update_or_create(self, user): user['email'] = self.get_user_email(user) if user['username'] not in ['admin']: - user['source'] = User.SOURCE_LDAP + user['source'] = User.Source.ldap.value obj, created = User.objects.update_or_create( username=user['username'], defaults=user ) diff --git a/apps/users/forms/user.py b/apps/users/forms/user.py index f3852c0dd..299862b5e 100644 --- a/apps/users/forms/user.py +++ b/apps/users/forms/user.py @@ -28,7 +28,7 @@ class UserCreateUpdateFormMixin(OrgModelForm): ) source = forms.ChoiceField( choices=get_source_choices, required=True, - initial=User.SOURCE_LOCAL, label=_("Source") + initial=User.Source.local.value, label=_("Source") ) public_key = forms.CharField( label=_('ssh public key'), max_length=5000, required=False, diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 56c979738..2dcbd452d 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -7,10 +7,10 @@ import string import random from django.conf import settings -from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser from django.core.cache import cache from django.db import models +from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -481,18 +481,12 @@ class MFAMixin: class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): - SOURCE_LOCAL = 'local' - SOURCE_LDAP = 'ldap' - SOURCE_OPENID = 'openid' - SOURCE_RADIUS = 'radius' - SOURCE_CAS = 'cas' - SOURCE_CHOICES = ( - (SOURCE_LOCAL, _('Local')), - (SOURCE_LDAP, 'LDAP/AD'), - (SOURCE_OPENID, 'OpenID'), - (SOURCE_RADIUS, 'Radius'), - (SOURCE_CAS, 'CAS'), - ) + class Source(TextChoices): + local = 'local', _('Local') + ldap = 'ldap', 'LDAP/AD' + openid = 'openid', 'OpenID' + radius = 'radius', 'Radius' + cas = 'cas', 'CAS' id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField( @@ -542,7 +536,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): max_length=30, default='', blank=True, verbose_name=_('Created by') ) source = models.CharField( - max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, + max_length=30, default=Source.local.value, choices=Source.choices, verbose_name=_('Source') ) date_password_last_updated = models.DateTimeField( @@ -593,7 +587,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): @property def is_local(self): - return self.source == self.SOURCE_LOCAL + return self.source == self.Source.local.value def set_unprovide_attr_if_need(self): if not self.name: @@ -663,6 +657,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): user.groups.add(UserGroup.initial()) def can_send_created_mail(self): - if self.email and self.source == self.SOURCE_LOCAL: + if self.email and self.source == self.Source.local.value: return True return False diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index a25d4ea20..09320a2e1 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -28,7 +28,7 @@ def on_user_create(sender, user=None, **kwargs): @receiver(cas_user_authenticated) def on_cas_user_authenticated(sender, user, created, **kwargs): if created: - user.source = user.SOURCE_CAS + user.source = user.Source.cas.value user.save() @@ -37,7 +37,7 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs): if user and user.username not in ['admin']: exists = User.objects.filter(username=user.username).exists() if not exists: - user.source = user.SOURCE_LDAP + user.source = user.Source.ldap.value user.save() @@ -46,9 +46,9 @@ def on_openid_create_or_update_user(sender, request, user, created, name, userna if created: logger.debug( "Receive OpenID user created signal: {}, " - "Set user source is: {}".format(user, User.SOURCE_OPENID) + "Set user source is: {}".format(user, User.Source.openid.value) ) - user.source = User.SOURCE_OPENID + user.source = User.Source.openid.value user.save() elif not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER: logger.debug( diff --git a/apps/users/tasks.py b/apps/users/tasks.py index f575a3afc..fc938499b 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -22,7 +22,7 @@ logger = get_logger(__file__) @shared_task def check_password_expired(): - users = User.objects.filter(source=User.SOURCE_LOCAL).exclude(role=User.ROLE.APP) + users = User.objects.filter(source=User.Source.local.value).exclude(role=User.ROLE.APP) for user in users: if not user.is_valid: continue diff --git a/apps/users/utils.py b/apps/users/utils.py index be33f25ba..94669c03a 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -362,18 +362,17 @@ def get_current_org_members(exclude=()): def get_source_choices(): from .models import User - choices_all = dict(User.SOURCE_CHOICES) choices = [ - (User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]), + (User.Source.local.value, User.Source.local.label), ] if settings.AUTH_LDAP: - choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP])) + choices.append((User.Source.ldap.value, User.Source.ldap.label)) if settings.AUTH_OPENID: - choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID])) + choices.append((User.Source.openid.value, User.Source.openid.label)) if settings.AUTH_RADIUS: - choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS])) + choices.append((User.Source.radius.value, User.Source.radius.label)) if settings.AUTH_CAS: - choices.append((User.SOURCE_CAS, choices_all[User.SOURCE_CAS])) + choices.append((User.Source.cas.value, User.Source.cas.label)) return choices