mirror of https://github.com/jumpserver/jumpserver
merge: with remote
commit
1cd551e692
|
@ -8,34 +8,51 @@ from users.models import User
|
|||
from ..models import LoginACL
|
||||
from .rules import RuleSerializer
|
||||
|
||||
__all__ = ['LoginACLSerializer', ]
|
||||
__all__ = [
|
||||
"LoginACLSerializer",
|
||||
]
|
||||
|
||||
common_help_text = _('Format for comma-delimited string, with * indicating a match all. ')
|
||||
common_help_text = _(
|
||||
"Format for comma-delimited string, with * indicating a match all. "
|
||||
)
|
||||
|
||||
|
||||
class LoginACLSerializer(BulkModelSerializer):
|
||||
user = ObjectRelatedField(queryset=User.objects, label=_('User'))
|
||||
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
|
||||
reviewers = ObjectRelatedField(
|
||||
queryset=User.objects, label=_('Reviewers'), many=True, required=False
|
||||
queryset=User.objects, label=_("Reviewers"), many=True, required=False
|
||||
)
|
||||
action_display = serializers.ReadOnlyField(
|
||||
source="get_action_display", label=_("Action")
|
||||
)
|
||||
reviewers_amount = serializers.IntegerField(
|
||||
read_only=True, source="reviewers.count"
|
||||
)
|
||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count')
|
||||
rules = MethodSerializer()
|
||||
|
||||
class Meta:
|
||||
model = LoginACL
|
||||
fields_mini = ['id', 'name']
|
||||
fields_mini = ["id", "name"]
|
||||
fields_small = fields_mini + [
|
||||
'priority', 'rules', 'action', 'action_display', 'is_active', 'user',
|
||||
'date_created', 'date_updated', 'reviewers_amount', 'comment', 'created_by',
|
||||
"priority",
|
||||
"rules",
|
||||
"action",
|
||||
"action_display",
|
||||
"is_active",
|
||||
"user",
|
||||
"date_created",
|
||||
"date_updated",
|
||||
"reviewers_amount",
|
||||
"comment",
|
||||
"created_by",
|
||||
]
|
||||
fields_fk = ['user']
|
||||
fields_m2m = ['reviewers']
|
||||
fields_fk = ["user"]
|
||||
fields_m2m = ["reviewers"]
|
||||
fields = fields_small + fields_fk + fields_m2m
|
||||
extra_kwargs = {
|
||||
'priority': {'default': 50},
|
||||
'is_active': {'default': True},
|
||||
"reviewers": {'allow_null': False, 'required': True},
|
||||
"priority": {"default": 50},
|
||||
"is_active": {"default": True},
|
||||
"reviewers": {"allow_null": False, "required": True},
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -43,7 +60,7 @@ class LoginACLSerializer(BulkModelSerializer):
|
|||
self.set_action_choices()
|
||||
|
||||
def set_action_choices(self):
|
||||
action = self.fields.get('action')
|
||||
action = self.fields.get("action")
|
||||
if not action:
|
||||
return
|
||||
choices = action._choices
|
||||
|
@ -53,6 +70,3 @@ class LoginACLSerializer(BulkModelSerializer):
|
|||
|
||||
def get_rules_serializer(self):
|
||||
return RuleSerializer()
|
||||
|
||||
def get_reviewers_display(self, obj):
|
||||
return ','.join([str(user) for user in obj.reviewers.all()])
|
||||
|
|
|
@ -3,54 +3,66 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from orgs.models import Organization
|
||||
from assets.const import Protocol
|
||||
from common.drf.fields import LabeledChoiceField
|
||||
from acls import models
|
||||
|
||||
|
||||
__all__ = ['LoginAssetACLSerializer']
|
||||
__all__ = ["LoginAssetACLSerializer"]
|
||||
|
||||
|
||||
common_help_text = _('Format for comma-delimited string, with * indicating a match all. ')
|
||||
common_help_text = _(
|
||||
"Format for comma-delimited string, with * indicating a match all. "
|
||||
)
|
||||
|
||||
|
||||
class LoginAssetACLUsersSerializer(serializers.Serializer):
|
||||
username_group = serializers.ListField(
|
||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'),
|
||||
help_text=common_help_text
|
||||
default=["*"],
|
||||
child=serializers.CharField(max_length=128),
|
||||
label=_("Username"),
|
||||
help_text=common_help_text,
|
||||
)
|
||||
|
||||
|
||||
class LoginAssetACLAssestsSerializer(serializers.Serializer):
|
||||
ip_group_help_text = _(
|
||||
'Format for comma-delimited string, with * indicating a match all. '
|
||||
'Such as: '
|
||||
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 '
|
||||
'(Domain name support)'
|
||||
"Format for comma-delimited string, with * indicating a match all. "
|
||||
"Such as: "
|
||||
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 "
|
||||
"(Domain name support)"
|
||||
)
|
||||
|
||||
ip_group = serializers.ListField(
|
||||
default=['*'], child=serializers.CharField(max_length=1024), label=_('IP'),
|
||||
help_text=ip_group_help_text
|
||||
default=["*"],
|
||||
child=serializers.CharField(max_length=1024),
|
||||
label=_("IP"),
|
||||
help_text=ip_group_help_text,
|
||||
)
|
||||
hostname_group = serializers.ListField(
|
||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Hostname'),
|
||||
help_text=common_help_text
|
||||
default=["*"],
|
||||
child=serializers.CharField(max_length=128),
|
||||
label=_("Hostname"),
|
||||
help_text=common_help_text,
|
||||
)
|
||||
|
||||
|
||||
class LoginAssetACLAccountsSerializer(serializers.Serializer):
|
||||
protocol_group_help_text = _(
|
||||
'Format for comma-delimited string, with * indicating a match all. '
|
||||
'Protocol options: {}'
|
||||
"Format for comma-delimited string, with * indicating a match all. "
|
||||
"Protocol options: {}"
|
||||
)
|
||||
|
||||
name_group = serializers.ListField(
|
||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Name'),
|
||||
help_text=common_help_text
|
||||
default=["*"],
|
||||
child=serializers.CharField(max_length=128),
|
||||
label=_("Name"),
|
||||
help_text=common_help_text,
|
||||
)
|
||||
username_group = serializers.ListField(
|
||||
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'),
|
||||
help_text=common_help_text
|
||||
default=["*"],
|
||||
child=serializers.CharField(max_length=128),
|
||||
label=_("Username"),
|
||||
help_text=common_help_text,
|
||||
)
|
||||
|
||||
|
||||
|
@ -58,34 +70,48 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
|
|||
users = LoginAssetACLUsersSerializer()
|
||||
assets = LoginAssetACLAssestsSerializer()
|
||||
accounts = LoginAssetACLAccountsSerializer()
|
||||
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count')
|
||||
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action'))
|
||||
reviewers_amount = serializers.IntegerField(
|
||||
read_only=True, source="reviewers.count"
|
||||
)
|
||||
action = LabeledChoiceField(
|
||||
choices=models.LoginAssetACL.ActionChoices.choices, label=_("Action")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.LoginAssetACL
|
||||
fields_mini = ['id', 'name']
|
||||
fields_mini = ["id", "name"]
|
||||
fields_small = fields_mini + [
|
||||
'users', 'accounts', 'assets',
|
||||
'is_active', 'date_created', 'date_updated',
|
||||
'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id'
|
||||
"users",
|
||||
"accounts",
|
||||
"assets",
|
||||
"is_active",
|
||||
"date_created",
|
||||
"date_updated",
|
||||
"priority",
|
||||
"action",
|
||||
"comment",
|
||||
"created_by",
|
||||
"org_id",
|
||||
]
|
||||
fields_m2m = ['reviewers', 'reviewers_amount']
|
||||
fields_m2m = ["reviewers", "reviewers_amount"]
|
||||
fields = fields_small + fields_m2m
|
||||
extra_kwargs = {
|
||||
"reviewers": {'allow_null': False, 'required': True},
|
||||
'priority': {'default': 50},
|
||||
'is_active': {'default': True},
|
||||
"reviewers": {"allow_null": False, "required": True},
|
||||
"priority": {"default": 50},
|
||||
"is_active": {"default": True},
|
||||
}
|
||||
|
||||
def validate_reviewers(self, reviewers):
|
||||
org_id = self.fields['org_id'].default()
|
||||
org_id = self.fields["org_id"].default()
|
||||
org = Organization.get_instance(org_id)
|
||||
if not org:
|
||||
error = _('The organization `{}` does not exist'.format(org_id))
|
||||
error = _("The organization `{}` does not exist".format(org_id))
|
||||
raise serializers.ValidationError(error)
|
||||
users = org.get_members()
|
||||
valid_reviewers = list(set(reviewers) & set(users))
|
||||
if not valid_reviewers:
|
||||
error = _('None of the reviewers belong to Organization `{}`'.format(org.name))
|
||||
error = _(
|
||||
"None of the reviewers belong to Organization `{}`".format(org.name)
|
||||
)
|
||||
raise serializers.ValidationError(error)
|
||||
return valid_reviewers
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CategoryDisplayMixin(serializers.Serializer):
|
||||
category_display = serializers.ReadOnlyField(
|
||||
source='get_category_display', label=_("Category display")
|
||||
)
|
||||
type_display = serializers.ReadOnlyField(
|
||||
source='get_type_display', label=_("Type display")
|
||||
)
|
|
@ -1,24 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import TextChoices, IntegerChoices
|
||||
|
||||
DEFAULT_CITY = _("Unknown")
|
||||
|
||||
MODELS_NEED_RECORD = (
|
||||
# users
|
||||
'User', 'UserGroup',
|
||||
"User",
|
||||
"UserGroup",
|
||||
# acls
|
||||
'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting',
|
||||
"LoginACL",
|
||||
"LoginAssetACL",
|
||||
"LoginConfirmSetting",
|
||||
# assets
|
||||
'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule',
|
||||
'CommandFilter', 'Platform', 'Account',
|
||||
"Asset",
|
||||
"Node",
|
||||
"AdminUser",
|
||||
"SystemUser",
|
||||
"Domain",
|
||||
"Gateway",
|
||||
"CommandFilterRule",
|
||||
"CommandFilter",
|
||||
"Platform",
|
||||
"Account",
|
||||
# applications
|
||||
# orgs
|
||||
'Organization',
|
||||
"Organization",
|
||||
# settings
|
||||
'Setting',
|
||||
"Setting",
|
||||
# perms
|
||||
'AssetPermission',
|
||||
"AssetPermission",
|
||||
# xpack
|
||||
'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask',
|
||||
"License",
|
||||
"Account",
|
||||
"SyncInstanceTask",
|
||||
"ChangeAuthPlan",
|
||||
"GatherUserTask",
|
||||
)
|
||||
|
||||
|
||||
class OperateChoices(TextChoices):
|
||||
mkdir = "mkdir", _("Mkdir")
|
||||
rmdir = "rmdir", _("Rmdir")
|
||||
delete = "delete", _("Delete")
|
||||
upload = "upload", _("Upload")
|
||||
rename = "rename", _("Rename")
|
||||
symlink = "symlink", _("Symlink")
|
||||
download = "download", _("Download")
|
||||
|
||||
|
||||
class ActionChoices(TextChoices):
|
||||
view = "view", _("View")
|
||||
update = "update", _("Update")
|
||||
delete = "delete", _("Delete")
|
||||
create = "create", _("Create")
|
||||
|
||||
|
||||
class LoginTypeChoices(TextChoices):
|
||||
web = "W", _("Web")
|
||||
terminal = "T", _("Terminal")
|
||||
unknown = "U", _("Unknown")
|
||||
|
||||
|
||||
class MFAChoices(IntegerChoices):
|
||||
disabled = 0, _("Disabled")
|
||||
enabled = 1, _("Enabled")
|
||||
unknown = 2, _("-")
|
||||
|
||||
|
||||
class LoginStatusChoices(IntegerChoices):
|
||||
success = True, _("Success")
|
||||
failed = False, _("Failed")
|
||||
|
|
|
@ -8,63 +8,55 @@ from common.utils import lazyproperty
|
|||
|
||||
from orgs.mixins.models import OrgModelMixin, Organization
|
||||
from orgs.utils import current_org
|
||||
from .const import (
|
||||
OperateChoices,
|
||||
ActionChoices,
|
||||
LoginTypeChoices,
|
||||
MFAChoices,
|
||||
LoginStatusChoices,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
||||
"FTPLog",
|
||||
"OperateLog",
|
||||
"PasswordChangeLog",
|
||||
"UserLoginLog",
|
||||
]
|
||||
|
||||
|
||||
class FTPLog(OrgModelMixin):
|
||||
OPERATE_DELETE = 'Delete'
|
||||
OPERATE_UPLOAD = 'Upload'
|
||||
OPERATE_DOWNLOAD = 'Download'
|
||||
OPERATE_RMDIR = 'Rmdir'
|
||||
OPERATE_RENAME = 'Rename'
|
||||
OPERATE_MKDIR = 'Mkdir'
|
||||
OPERATE_SYMLINK = 'Symlink'
|
||||
|
||||
OPERATE_CHOICES = (
|
||||
(OPERATE_DELETE, _('Delete')),
|
||||
(OPERATE_UPLOAD, _('Upload')),
|
||||
(OPERATE_DOWNLOAD, _('Download')),
|
||||
(OPERATE_RMDIR, _('Rmdir')),
|
||||
(OPERATE_RENAME, _('Rename')),
|
||||
(OPERATE_MKDIR, _('Mkdir')),
|
||||
(OPERATE_SYMLINK, _('Symlink'))
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_("User"))
|
||||
remote_addr = models.CharField(
|
||||
max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
|
||||
)
|
||||
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
|
||||
system_user = models.CharField(max_length=128, verbose_name=_("System user"))
|
||||
operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OPERATE_CHOICES)
|
||||
operate = models.CharField(
|
||||
max_length=16, verbose_name=_("Operate"), choices=OperateChoices.choices
|
||||
)
|
||||
filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
|
||||
is_success = models.BooleanField(default=True, verbose_name=_("Success"))
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_("Date start"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("File transfer log")
|
||||
|
||||
|
||||
class OperateLog(OrgModelMixin):
|
||||
ACTION_CREATE = 'create'
|
||||
ACTION_VIEW = 'view'
|
||||
ACTION_UPDATE = 'update'
|
||||
ACTION_DELETE = 'delete'
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_CREATE, _("Create")),
|
||||
(ACTION_VIEW, _("View")),
|
||||
(ACTION_UPDATE, _("Update")),
|
||||
(ACTION_DELETE, _("Delete"))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
user = models.CharField(max_length=128, verbose_name=_("User"))
|
||||
action = models.CharField(
|
||||
max_length=16, choices=ActionChoices.choices, verbose_name=_("Action")
|
||||
)
|
||||
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
|
||||
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'), db_index=True)
|
||||
remote_addr = models.CharField(
|
||||
max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
|
||||
)
|
||||
datetime = models.DateTimeField(
|
||||
auto_now=True, verbose_name=_("Datetime"), db_index=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "<{}> {} <{}>".format(self.user, self.action, self.resource)
|
||||
|
@ -84,50 +76,48 @@ class OperateLog(OrgModelMixin):
|
|||
|
||||
class PasswordChangeLog(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
user = models.CharField(max_length=128, verbose_name=_("User"))
|
||||
change_by = models.CharField(max_length=128, verbose_name=_("Change by"))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'))
|
||||
remote_addr = models.CharField(
|
||||
max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
|
||||
)
|
||||
datetime = models.DateTimeField(auto_now=True, verbose_name=_("Datetime"))
|
||||
|
||||
def __str__(self):
|
||||
return "{} change {}'s password".format(self.change_by, self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Password change log')
|
||||
verbose_name = _("Password change log")
|
||||
|
||||
|
||||
class UserLoginLog(models.Model):
|
||||
LOGIN_TYPE_CHOICE = (
|
||||
('W', 'Web'),
|
||||
('T', 'Terminal'),
|
||||
('U', 'Unknown'),
|
||||
)
|
||||
|
||||
MFA_DISABLED = 0
|
||||
MFA_ENABLED = 1
|
||||
MFA_UNKNOWN = 2
|
||||
|
||||
MFA_CHOICE = (
|
||||
(MFA_DISABLED, _('Disabled')),
|
||||
(MFA_ENABLED, _('Enabled')),
|
||||
(MFA_UNKNOWN, _('-')),
|
||||
)
|
||||
|
||||
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.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=_('Authentication backend'))
|
||||
username = models.CharField(max_length=128, verbose_name=_("Username"))
|
||||
type = models.CharField(
|
||||
choices=LoginTypeChoices.choices, 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=MFAChoices.unknown, choices=MFAChoices.choices, verbose_name=_("MFA")
|
||||
)
|
||||
reason = models.CharField(
|
||||
default="", max_length=128, blank=True, verbose_name=_("Reason")
|
||||
)
|
||||
status = models.BooleanField(
|
||||
default=LoginStatusChoices.success,
|
||||
choices=LoginStatusChoices.choices,
|
||||
verbose_name=_("Status"),
|
||||
)
|
||||
datetime = models.DateTimeField(default=timezone.now, verbose_name=_("Date login"))
|
||||
backend = models.CharField(
|
||||
max_length=32, default="", verbose_name=_("Authentication backend")
|
||||
)
|
||||
|
||||
@property
|
||||
def backend_display(self):
|
||||
|
@ -137,8 +127,8 @@ class UserLoginLog(models.Model):
|
|||
def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None):
|
||||
login_logs = cls.objects.all()
|
||||
if date_from and date_to:
|
||||
date_from = "{} {}".format(date_from, '00:00:00')
|
||||
date_to = "{} {}".format(date_to, '23:59:59')
|
||||
date_from = "{} {}".format(date_from, "00:00:00")
|
||||
date_to = "{} {}".format(date_to, "23:59:59")
|
||||
login_logs = login_logs.filter(
|
||||
datetime__gte=date_from, datetime__lte=date_to
|
||||
)
|
||||
|
@ -146,18 +136,19 @@ class UserLoginLog(models.Model):
|
|||
login_logs = login_logs.filter(username=user)
|
||||
if keyword:
|
||||
login_logs = login_logs.filter(
|
||||
Q(ip__contains=keyword) |
|
||||
Q(city__contains=keyword) |
|
||||
Q(username__contains=keyword)
|
||||
Q(ip__contains=keyword)
|
||||
| Q(city__contains=keyword)
|
||||
| Q(username__contains=keyword)
|
||||
)
|
||||
if not current_org.is_root():
|
||||
username_list = current_org.get_members().values_list('username', flat=True)
|
||||
username_list = current_org.get_members().values_list("username", flat=True)
|
||||
login_logs = login_logs.filter(username__in=username_list)
|
||||
return login_logs
|
||||
|
||||
@property
|
||||
def reason_display(self):
|
||||
from authentication.errors import reason_choices, old_reason_choices
|
||||
|
||||
reason = reason_choices.get(self.reason)
|
||||
if reason:
|
||||
return reason
|
||||
|
@ -165,5 +156,5 @@ class UserLoginLog(models.Model):
|
|||
return reason
|
||||
|
||||
class Meta:
|
||||
ordering = ['-datetime', 'username']
|
||||
verbose_name = _('User login log')
|
||||
ordering = ["-datetime", "username"]
|
||||
verbose_name = _("User login log")
|
||||
|
|
|
@ -3,77 +3,99 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.drf.serializers import BulkSerializerMixin
|
||||
from common.drf.fields import LabeledChoiceField
|
||||
from terminal.models import Session
|
||||
from . import models
|
||||
from .const import (
|
||||
ActionChoices,
|
||||
OperateChoices,
|
||||
MFAChoices,
|
||||
LoginStatusChoices,
|
||||
LoginTypeChoices,
|
||||
)
|
||||
|
||||
|
||||
class FTPLogSerializer(serializers.ModelSerializer):
|
||||
operate_display = serializers.ReadOnlyField(source='get_operate_display', label=_('Operate display'))
|
||||
operate = LabeledChoiceField(choices=OperateChoices.choices, label=_("Operate"))
|
||||
|
||||
class Meta:
|
||||
model = models.FTPLog
|
||||
fields_mini = ['id']
|
||||
fields_mini = ["id"]
|
||||
fields_small = fields_mini + [
|
||||
'user', 'remote_addr', 'asset', 'system_user', 'org_id',
|
||||
'operate', 'filename', 'operate_display',
|
||||
'is_success',
|
||||
'date_start',
|
||||
"user",
|
||||
"remote_addr",
|
||||
"asset",
|
||||
"system_user",
|
||||
"org_id",
|
||||
"operate",
|
||||
"filename",
|
||||
"is_success",
|
||||
"date_start",
|
||||
]
|
||||
fields = fields_small
|
||||
|
||||
|
||||
class UserLoginLogSerializer(serializers.ModelSerializer):
|
||||
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
|
||||
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display'))
|
||||
mfa_display = serializers.ReadOnlyField(source='get_mfa_display', label=_('MFA display'))
|
||||
mfa = LabeledChoiceField(choices=MFAChoices.choices, label=_("MFA"))
|
||||
type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_("Type"))
|
||||
status = LabeledChoiceField(choices=LoginStatusChoices.choices, label=_("Status"))
|
||||
|
||||
class Meta:
|
||||
model = models.UserLoginLog
|
||||
fields_mini = ['id']
|
||||
fields_mini = ["id"]
|
||||
fields_small = fields_mini + [
|
||||
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
||||
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend', 'backend_display',
|
||||
'status', 'status_display',
|
||||
'datetime',
|
||||
"username",
|
||||
"type",
|
||||
"ip",
|
||||
"city",
|
||||
"user_agent",
|
||||
"mfa",
|
||||
"reason",
|
||||
"reason_display",
|
||||
"backend",
|
||||
"backend_display",
|
||||
"status",
|
||||
"datetime",
|
||||
]
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
"user_agent": {'label': _('User agent')},
|
||||
"reason_display": {'label': _('Reason display')},
|
||||
'backend_display': {'label': _('Authentication backend')}
|
||||
"user_agent": {"label": _("User agent")},
|
||||
"reason_display": {"label": _("Reason display")},
|
||||
"backend_display": {"label": _("Authentication backend")},
|
||||
}
|
||||
|
||||
|
||||
class OperateLogSerializer(serializers.ModelSerializer):
|
||||
action_display = serializers.CharField(source='get_action_display', label=_('Action'))
|
||||
action = LabeledChoiceField(choices=ActionChoices.choices, label=_("Action"))
|
||||
|
||||
class Meta:
|
||||
model = models.OperateLog
|
||||
fields_mini = ['id']
|
||||
fields_mini = ["id"]
|
||||
fields_small = fields_mini + [
|
||||
'user', 'action', 'action_display',
|
||||
'resource_type', 'resource_type_display', 'resource',
|
||||
'remote_addr', 'datetime', 'org_id'
|
||||
"user",
|
||||
"action",
|
||||
"resource_type",
|
||||
"resource_type_display",
|
||||
"resource",
|
||||
"remote_addr",
|
||||
"datetime",
|
||||
"org_id",
|
||||
]
|
||||
fields = fields_small
|
||||
extra_kwargs = {
|
||||
'resource_type_display': {'label': _('Resource Type')}
|
||||
}
|
||||
extra_kwargs = {"resource_type_display": {"label": _("Resource Type")}}
|
||||
|
||||
|
||||
class PasswordChangeLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PasswordChangeLog
|
||||
fields = (
|
||||
'id', 'user', 'change_by', 'remote_addr', 'datetime'
|
||||
)
|
||||
fields = ("id", "user", "change_by", "remote_addr", "datetime")
|
||||
|
||||
|
||||
class SessionAuditSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Session
|
||||
fields = '__all__'
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
#
|
||||
# class CommandExecutionSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -1,38 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
|
||||
from django.db.models.signals import (
|
||||
post_save, m2m_changed, pre_delete
|
||||
)
|
||||
from django.dispatch import receiver
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone, translation
|
||||
from django.utils.functional import LazyObject
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import translation
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from django.db.models.signals import post_save, m2m_changed, pre_delete
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
|
||||
from assets.models import Asset
|
||||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from authentication.utils import check_different_city_login_if_need
|
||||
from jumpserver.utils import current_request
|
||||
from users.models import User
|
||||
from users.signals import post_user_change_password
|
||||
from terminal.models import Session, Command
|
||||
from .utils import write_login_log, create_operate_log
|
||||
from . import models, serializers
|
||||
from .models import OperateLog
|
||||
from orgs.utils import current_org
|
||||
from perms.models import AssetPermission
|
||||
from terminal.backends.command.serializers import SessionCommandSerializer
|
||||
from users.models import User
|
||||
from users.signals import post_user_change_password
|
||||
from assets.models import Asset
|
||||
from jumpserver.utils import current_request
|
||||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from authentication.utils import check_different_city_login_if_need
|
||||
from terminal.models import Session, Command
|
||||
from terminal.serializers import SessionSerializer
|
||||
from terminal.backends.command.serializers import SessionCommandSerializer
|
||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR
|
||||
from common.utils import get_request_ip, get_logger, get_syslogger
|
||||
from common.utils.encode import data_to_json
|
||||
from . import models, serializers
|
||||
from .const import ActionChoices
|
||||
from .utils import write_login_log, create_operate_log
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
sys_logger = get_syslogger(__name__)
|
||||
|
@ -46,14 +42,14 @@ class AuthBackendLabelMapping(LazyObject):
|
|||
for source, backends in User.SOURCE_BACKEND_MAPPING.items():
|
||||
for backend in backends:
|
||||
backend_label_mapping[backend] = source.label
|
||||
backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _('SSH Key')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _('Password')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_SSO] = _('SSO')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _('Auth Token')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _('WeCom')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _('FeiShu')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _('Temporary token')
|
||||
backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _("SSH Key")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _("Password")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_SSO] = _("SSO")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _("Auth Token")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _("WeCom")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _("FeiShu")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _("DingTalk")
|
||||
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _("Temporary token")
|
||||
return backend_label_mapping
|
||||
|
||||
def _setup(self):
|
||||
|
@ -65,41 +61,41 @@ AUTH_BACKEND_LABEL_MAPPING = AuthBackendLabelMapping()
|
|||
|
||||
M2M_NEED_RECORD = {
|
||||
User.groups.through.__name__: (
|
||||
_('User and Group'),
|
||||
_('{User} JOINED {UserGroup}'),
|
||||
_('{User} LEFT {UserGroup}')
|
||||
_("User and Group"),
|
||||
_("{User} JOINED {UserGroup}"),
|
||||
_("{User} LEFT {UserGroup}"),
|
||||
),
|
||||
Asset.nodes.through.__name__: (
|
||||
_('Node and Asset'),
|
||||
_('{Node} ADD {Asset}'),
|
||||
_('{Node} REMOVE {Asset}')
|
||||
_("Node and Asset"),
|
||||
_("{Node} ADD {Asset}"),
|
||||
_("{Node} REMOVE {Asset}"),
|
||||
),
|
||||
AssetPermission.users.through.__name__: (
|
||||
_('User asset permissions'),
|
||||
_('{AssetPermission} ADD {User}'),
|
||||
_('{AssetPermission} REMOVE {User}'),
|
||||
_("User asset permissions"),
|
||||
_("{AssetPermission} ADD {User}"),
|
||||
_("{AssetPermission} REMOVE {User}"),
|
||||
),
|
||||
AssetPermission.user_groups.through.__name__: (
|
||||
_('User group asset permissions'),
|
||||
_('{AssetPermission} ADD {UserGroup}'),
|
||||
_('{AssetPermission} REMOVE {UserGroup}'),
|
||||
_("User group asset permissions"),
|
||||
_("{AssetPermission} ADD {UserGroup}"),
|
||||
_("{AssetPermission} REMOVE {UserGroup}"),
|
||||
),
|
||||
AssetPermission.assets.through.__name__: (
|
||||
_('Asset permission'),
|
||||
_('{AssetPermission} ADD {Asset}'),
|
||||
_('{AssetPermission} REMOVE {Asset}'),
|
||||
_("Asset permission"),
|
||||
_("{AssetPermission} ADD {Asset}"),
|
||||
_("{AssetPermission} REMOVE {Asset}"),
|
||||
),
|
||||
AssetPermission.nodes.through.__name__: (
|
||||
_('Node permission'),
|
||||
_('{AssetPermission} ADD {Node}'),
|
||||
_('{AssetPermission} REMOVE {Node}'),
|
||||
_("Node permission"),
|
||||
_("{AssetPermission} ADD {Node}"),
|
||||
_("{AssetPermission} REMOVE {Node}"),
|
||||
),
|
||||
}
|
||||
|
||||
M2M_ACTION_MAPER = {
|
||||
POST_ADD: OperateLog.ACTION_CREATE,
|
||||
POST_REMOVE: OperateLog.ACTION_DELETE,
|
||||
POST_CLEAR: OperateLog.ACTION_DELETE,
|
||||
POST_ADD: ActionChoices.create,
|
||||
POST_REMOVE: ActionChoices.delete,
|
||||
POST_CLEAR: ActionChoices.delete,
|
||||
}
|
||||
|
||||
|
||||
|
@ -117,12 +113,14 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs):
|
|||
org_id = current_org.id
|
||||
remote_addr = get_request_ip(current_request)
|
||||
user = str(user)
|
||||
resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name]
|
||||
resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[
|
||||
sender_name
|
||||
]
|
||||
|
||||
action = M2M_ACTION_MAPER[action]
|
||||
if action == OperateLog.ACTION_CREATE:
|
||||
if action == ActionChoices.create:
|
||||
resource_tmpl = resource_tmpl_add
|
||||
elif action == OperateLog.ACTION_DELETE:
|
||||
elif action == ActionChoices.delete:
|
||||
resource_tmpl = resource_tmpl_remove
|
||||
else:
|
||||
return
|
||||
|
@ -139,41 +137,53 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs):
|
|||
|
||||
print("Instace name: ", instance_name, instance_value)
|
||||
for obj in objs:
|
||||
resource = resource_tmpl.format(**{
|
||||
instance_name: instance_value,
|
||||
model_name: str(obj)
|
||||
})[:128] # `resource` 字段只有 128 个字符长 😔
|
||||
resource = resource_tmpl.format(
|
||||
**{instance_name: instance_value, model_name: str(obj)}
|
||||
)[
|
||||
:128
|
||||
] # `resource` 字段只有 128 个字符长 😔
|
||||
|
||||
to_create.append(OperateLog(
|
||||
user=user, action=action, resource_type=resource_type,
|
||||
resource=resource, remote_addr=remote_addr, org_id=org_id
|
||||
))
|
||||
OperateLog.objects.bulk_create(to_create)
|
||||
to_create.append(
|
||||
models.OperateLog(
|
||||
user=user,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
resource=resource,
|
||||
remote_addr=remote_addr,
|
||||
org_id=org_id,
|
||||
)
|
||||
)
|
||||
models.OperateLog.objects.bulk_create(to_create)
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs):
|
||||
def on_object_created_or_update(
|
||||
sender, instance=None, created=False, update_fields=None, **kwargs
|
||||
):
|
||||
# last_login 改变是最后登录日期, 每次登录都会改变
|
||||
if instance._meta.object_name == 'User' and \
|
||||
update_fields and 'last_login' in update_fields:
|
||||
if (
|
||||
instance._meta.object_name == "User"
|
||||
and update_fields
|
||||
and "last_login" in update_fields
|
||||
):
|
||||
return
|
||||
if created:
|
||||
action = models.OperateLog.ACTION_CREATE
|
||||
action = ActionChoices.create
|
||||
else:
|
||||
action = models.OperateLog.ACTION_UPDATE
|
||||
action = ActionChoices.update
|
||||
create_operate_log(action, sender, instance)
|
||||
|
||||
|
||||
@receiver(pre_delete)
|
||||
def on_object_delete(sender, instance=None, **kwargs):
|
||||
create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance)
|
||||
create_operate_log(ActionChoices.delete, sender, instance)
|
||||
|
||||
|
||||
@receiver(post_user_change_password, sender=User)
|
||||
def on_user_change_password(sender, user=None, **kwargs):
|
||||
if not current_request:
|
||||
remote_addr = '127.0.0.1'
|
||||
change_by = 'System'
|
||||
remote_addr = "127.0.0.1"
|
||||
change_by = "System"
|
||||
else:
|
||||
remote_addr = get_request_ip(current_request)
|
||||
if not current_request.user.is_authenticated:
|
||||
|
@ -182,7 +192,8 @@ def on_user_change_password(sender, user=None, **kwargs):
|
|||
change_by = str(current_request.user)
|
||||
with transaction.atomic():
|
||||
models.PasswordChangeLog.objects.create(
|
||||
user=str(user), change_by=change_by,
|
||||
user=str(user),
|
||||
change_by=change_by,
|
||||
remote_addr=remote_addr,
|
||||
)
|
||||
|
||||
|
@ -216,51 +227,52 @@ def on_audits_log_create(sender, instance=None, **kwargs):
|
|||
|
||||
|
||||
def get_login_backend(request):
|
||||
backend = request.session.get('auth_backend', '') or \
|
||||
request.session.get(BACKEND_SESSION_KEY, '')
|
||||
backend = request.session.get("auth_backend", "") or request.session.get(
|
||||
BACKEND_SESSION_KEY, ""
|
||||
)
|
||||
|
||||
backend_label = AUTH_BACKEND_LABEL_MAPPING.get(backend, None)
|
||||
if backend_label is None:
|
||||
backend_label = ''
|
||||
backend_label = ""
|
||||
return backend_label
|
||||
|
||||
|
||||
def generate_data(username, request, login_type=None):
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||
login_ip = get_request_ip(request) or '0.0.0.0'
|
||||
user_agent = request.META.get("HTTP_USER_AGENT", "")
|
||||
login_ip = get_request_ip(request) or "0.0.0.0"
|
||||
|
||||
if login_type is None and isinstance(request, Request):
|
||||
login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U')
|
||||
login_type = request.META.get("HTTP_X_JMS_LOGIN_TYPE", "U")
|
||||
if login_type is None:
|
||||
login_type = 'W'
|
||||
login_type = "W"
|
||||
|
||||
with translation.override('en'):
|
||||
with translation.override("en"):
|
||||
backend = str(get_login_backend(request))
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'ip': login_ip,
|
||||
'type': login_type,
|
||||
'user_agent': user_agent[0:254],
|
||||
'datetime': timezone.now(),
|
||||
'backend': backend,
|
||||
"username": username,
|
||||
"ip": login_ip,
|
||||
"type": login_type,
|
||||
"user_agent": user_agent[0:254],
|
||||
"datetime": timezone.now(),
|
||||
"backend": backend,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@receiver(post_auth_success)
|
||||
def on_user_auth_success(sender, user, request, login_type=None, **kwargs):
|
||||
logger.debug('User login success: {}'.format(user.username))
|
||||
logger.debug("User login success: {}".format(user.username))
|
||||
check_different_city_login_if_need(user, request)
|
||||
data = generate_data(user.username, request, login_type=login_type)
|
||||
request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S")
|
||||
data.update({'mfa': int(user.mfa_enabled), 'status': True})
|
||||
request.session["login_time"] = data["datetime"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
data.update({"mfa": int(user.mfa_enabled), "status": True})
|
||||
write_login_log(**data)
|
||||
|
||||
|
||||
@receiver(post_auth_failed)
|
||||
def on_user_auth_failed(sender, username, request, reason='', **kwargs):
|
||||
logger.debug('User login failed: {}'.format(username))
|
||||
def on_user_auth_failed(sender, username, request, reason="", **kwargs):
|
||||
logger.debug("User login failed: {}".format(username))
|
||||
data = generate_data(username, request)
|
||||
data.update({'reason': reason[:128], 'status': False})
|
||||
data.update({"reason": reason[:128], "status": False})
|
||||
write_login_log(**data)
|
||||
|
|
|
@ -9,13 +9,24 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
|||
|
||||
from common.utils import signer, crypto
|
||||
|
||||
|
||||
__all__ = [
|
||||
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin',
|
||||
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField',
|
||||
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField',
|
||||
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField',
|
||||
'EncryptJsonDictCharField', 'PortField', 'BitChoices',
|
||||
"JsonMixin",
|
||||
"JsonDictMixin",
|
||||
"JsonListMixin",
|
||||
"JsonTypeMixin",
|
||||
"JsonCharField",
|
||||
"JsonTextField",
|
||||
"JsonListCharField",
|
||||
"JsonListTextField",
|
||||
"JsonDictCharField",
|
||||
"JsonDictTextField",
|
||||
"EncryptCharField",
|
||||
"EncryptTextField",
|
||||
"EncryptMixin",
|
||||
"EncryptJsonDictTextField",
|
||||
"EncryptJsonDictCharField",
|
||||
"PortField",
|
||||
"BitChoices",
|
||||
]
|
||||
|
||||
|
||||
|
@ -116,7 +127,7 @@ class EncryptMixin:
|
|||
"""
|
||||
|
||||
def decrypt_from_signer(self, value):
|
||||
return signer.unsign(value) or ''
|
||||
return signer.unsign(value) or ""
|
||||
|
||||
def from_db_value(self, value, expression, connection, context=None):
|
||||
if not value:
|
||||
|
@ -131,7 +142,7 @@ class EncryptMixin:
|
|||
|
||||
# 可能和Json mix,所以要先解密,再json
|
||||
sp = super()
|
||||
if hasattr(sp, 'from_db_value'):
|
||||
if hasattr(sp, "from_db_value"):
|
||||
plain_value = sp.from_db_value(plain_value, expression, connection, context)
|
||||
return plain_value
|
||||
|
||||
|
@ -141,7 +152,7 @@ class EncryptMixin:
|
|||
|
||||
# 先 json 再解密
|
||||
sp = super()
|
||||
if hasattr(sp, 'get_prep_value'):
|
||||
if hasattr(sp, "get_prep_value"):
|
||||
value = sp.get_prep_value(value)
|
||||
value = force_text(value)
|
||||
# 替换新的加密方式
|
||||
|
@ -155,12 +166,12 @@ class EncryptTextField(EncryptMixin, models.TextField):
|
|||
class EncryptCharField(EncryptMixin, models.CharField):
|
||||
@staticmethod
|
||||
def change_max_length(kwargs):
|
||||
kwargs.setdefault('max_length', 1024)
|
||||
max_length = kwargs.get('max_length')
|
||||
kwargs.setdefault("max_length", 1024)
|
||||
max_length = kwargs.get("max_length")
|
||||
if max_length < 129:
|
||||
max_length = 128
|
||||
max_length = max_length * 2
|
||||
kwargs['max_length'] = max_length
|
||||
kwargs["max_length"] = max_length
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.change_max_length(kwargs)
|
||||
|
@ -168,10 +179,10 @@ class EncryptCharField(EncryptMixin, models.CharField):
|
|||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
max_length = kwargs.pop('max_length')
|
||||
max_length = kwargs.pop("max_length")
|
||||
if max_length > 255:
|
||||
max_length = max_length // 2
|
||||
kwargs['max_length'] = max_length
|
||||
kwargs["max_length"] = max_length
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
|
@ -185,11 +196,13 @@ class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField):
|
|||
|
||||
class PortField(models.IntegerField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.update({
|
||||
'blank': False,
|
||||
'null': False,
|
||||
'validators': [MinValueValidator(0), MaxValueValidator(65535)]
|
||||
})
|
||||
kwargs.update(
|
||||
{
|
||||
"blank": False,
|
||||
"null": False,
|
||||
"validators": [MinValueValidator(0), MaxValueValidator(65535)],
|
||||
}
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -200,22 +213,22 @@ class BitChoices(models.IntegerChoices):
|
|||
|
||||
@classmethod
|
||||
def tree(cls):
|
||||
root = [_('All'), cls.branches()]
|
||||
root = [_("All"), cls.branches()]
|
||||
return cls.render_node(root)
|
||||
|
||||
@classmethod
|
||||
def render_node(cls, node):
|
||||
if isinstance(node, BitChoices):
|
||||
return {
|
||||
'id': node.name,
|
||||
'label': node.label,
|
||||
"id": node.name,
|
||||
"label": node.label,
|
||||
}
|
||||
else:
|
||||
name, children = node
|
||||
return {
|
||||
'id': name,
|
||||
'label': name,
|
||||
'children': [cls.render_node(child) for child in children]
|
||||
"id": name,
|
||||
"label": name,
|
||||
"children": [cls.render_node(child) for child in children],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -224,5 +237,3 @@ class BitChoices(models.IntegerChoices):
|
|||
for c in cls:
|
||||
value |= c.value
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -9,14 +9,20 @@ from rest_framework.request import Request
|
|||
from common.exceptions import UserConfirmRequired
|
||||
from audits.utils import create_operate_log
|
||||
from audits.models import OperateLog
|
||||
from audits.const import ActionChoices
|
||||
|
||||
__all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"]
|
||||
__all__ = [
|
||||
"PermissionsMixin",
|
||||
"RecordViewLogMixin",
|
||||
"UserConfirmRequiredExceptionMixin",
|
||||
]
|
||||
|
||||
|
||||
class UserConfirmRequiredExceptionMixin:
|
||||
"""
|
||||
异常处理
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -40,23 +46,23 @@ class PermissionsMixin(UserPassesTestMixin):
|
|||
|
||||
|
||||
class RecordViewLogMixin:
|
||||
ACTION = OperateLog.ACTION_VIEW
|
||||
ACTION = ActionChoices.view
|
||||
|
||||
@staticmethod
|
||||
def get_resource_display(request):
|
||||
query_params = dict(request.query_params)
|
||||
if query_params.get('format'):
|
||||
query_params.pop('format')
|
||||
spm_filter = query_params.pop('spm') if query_params.get('spm') else None
|
||||
if query_params.get("format"):
|
||||
query_params.pop("format")
|
||||
spm_filter = query_params.pop("spm") if query_params.get("spm") else None
|
||||
if not query_params and not spm_filter:
|
||||
display_message = _('Export all')
|
||||
display_message = _("Export all")
|
||||
elif spm_filter:
|
||||
display_message = _('Export only selected items')
|
||||
display_message = _("Export only selected items")
|
||||
else:
|
||||
query = ','.join(
|
||||
['%s=%s' % (key, value) for key, value in query_params.items()]
|
||||
query = ",".join(
|
||||
["%s=%s" % (key, value) for key, value in query_params.items()]
|
||||
)
|
||||
display_message = _('Export filtered: %s') % query
|
||||
display_message = _("Export filtered: %s") % query
|
||||
return display_message
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
|
|
@ -21,38 +21,40 @@ class DeployAppletHostManager:
|
|||
|
||||
@staticmethod
|
||||
def get_run_dir():
|
||||
base = os.path.join(settings.ANSIBLE_DIR, 'applet_host_deploy')
|
||||
now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
base = os.path.join(settings.ANSIBLE_DIR, "applet_host_deploy")
|
||||
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
return os.path.join(base, now)
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml')
|
||||
playbook_src = os.path.join(CURRENT_DIR, "playbook.yml")
|
||||
base_site_url = settings.BASE_SITE_URL
|
||||
bootstrap_token = settings.BOOTSTRAP_TOKEN
|
||||
host_id = str(self.deployment.host.id)
|
||||
if not base_site_url:
|
||||
base_site_url = "localhost:8080"
|
||||
base_site_url = "http://localhost:8080"
|
||||
with open(playbook_src) as f:
|
||||
plays = yaml.safe_load(f)
|
||||
for play in plays:
|
||||
play['vars'].update(self.deployment.host.deploy_options)
|
||||
play['vars']['DownloadHost'] = base_site_url + '/download/'
|
||||
play['vars']['CORE_HOST'] = base_site_url
|
||||
play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token
|
||||
play['vars']['HOST_ID'] = host_id
|
||||
play['vars']['HOST_NAME'] = self.deployment.host.name
|
||||
play["vars"].update(self.deployment.host.deploy_options)
|
||||
play["vars"]["DownloadHost"] = base_site_url + "/download"
|
||||
play["vars"]["CORE_HOST"] = base_site_url
|
||||
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
|
||||
play["vars"]["HOST_ID"] = host_id
|
||||
play["vars"]["HOST_NAME"] = self.deployment.host.name
|
||||
|
||||
playbook_dir = os.path.join(self.run_dir, 'playbook')
|
||||
playbook_dst = os.path.join(playbook_dir, 'main.yml')
|
||||
playbook_dir = os.path.join(self.run_dir, "playbook")
|
||||
playbook_dst = os.path.join(playbook_dir, "main.yml")
|
||||
os.makedirs(playbook_dir, exist_ok=True)
|
||||
with open(playbook_dst, 'w') as f:
|
||||
with open(playbook_dst, "w") as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
return playbook_dst
|
||||
|
||||
def generate_inventory(self):
|
||||
inventory = JMSInventory([self.deployment.host], account_policy='privileged_only')
|
||||
inventory_dir = os.path.join(self.run_dir, 'inventory')
|
||||
inventory_path = os.path.join(inventory_dir, 'hosts.yml')
|
||||
inventory = JMSInventory(
|
||||
[self.deployment.host], account_policy="privileged_only"
|
||||
)
|
||||
inventory_dir = os.path.join(self.run_dir, "inventory")
|
||||
inventory_path = os.path.join(inventory_dir, "hosts.yml")
|
||||
inventory.write_to_file(inventory_path)
|
||||
return inventory_path
|
||||
|
||||
|
@ -71,7 +73,7 @@ class DeployAppletHostManager:
|
|||
self.deployment.status = cb.status
|
||||
except Exception as e:
|
||||
logger.error("Error: {}".format(e))
|
||||
self.deployment.status = 'error'
|
||||
self.deployment.status = "error"
|
||||
finally:
|
||||
self.deployment.date_finished = timezone.now()
|
||||
with safe_db_connection():
|
||||
|
|
Loading…
Reference in New Issue