merge: with remote

pull/9048/head
ibuler 2022-11-11 15:11:10 +08:00
commit 1cd551e692
10 changed files with 445 additions and 322 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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