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 ..models import LoginACL
from .rules import RuleSerializer 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): class LoginACLSerializer(BulkModelSerializer):
user = ObjectRelatedField(queryset=User.objects, label=_('User')) user = ObjectRelatedField(queryset=User.objects, label=_("User"))
reviewers = ObjectRelatedField( 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() rules = MethodSerializer()
class Meta: class Meta:
model = LoginACL model = LoginACL
fields_mini = ['id', 'name'] fields_mini = ["id", "name"]
fields_small = fields_mini + [ fields_small = fields_mini + [
'priority', 'rules', 'action', 'action_display', 'is_active', 'user', "priority",
'date_created', 'date_updated', 'reviewers_amount', 'comment', 'created_by', "rules",
"action",
"action_display",
"is_active",
"user",
"date_created",
"date_updated",
"reviewers_amount",
"comment",
"created_by",
] ]
fields_fk = ['user'] fields_fk = ["user"]
fields_m2m = ['reviewers'] fields_m2m = ["reviewers"]
fields = fields_small + fields_fk + fields_m2m fields = fields_small + fields_fk + fields_m2m
extra_kwargs = { extra_kwargs = {
'priority': {'default': 50}, "priority": {"default": 50},
'is_active': {'default': True}, "is_active": {"default": True},
"reviewers": {'allow_null': False, 'required': True}, "reviewers": {"allow_null": False, "required": True},
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -43,7 +60,7 @@ class LoginACLSerializer(BulkModelSerializer):
self.set_action_choices() self.set_action_choices()
def set_action_choices(self): def set_action_choices(self):
action = self.fields.get('action') action = self.fields.get("action")
if not action: if not action:
return return
choices = action._choices choices = action._choices
@ -53,6 +70,3 @@ class LoginACLSerializer(BulkModelSerializer):
def get_rules_serializer(self): def get_rules_serializer(self):
return RuleSerializer() 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.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.models import Organization from orgs.models import Organization
from assets.const import Protocol from common.drf.fields import LabeledChoiceField
from acls import models 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): class LoginAssetACLUsersSerializer(serializers.Serializer):
username_group = serializers.ListField( username_group = serializers.ListField(
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'), default=["*"],
help_text=common_help_text child=serializers.CharField(max_length=128),
label=_("Username"),
help_text=common_help_text,
) )
class LoginAssetACLAssestsSerializer(serializers.Serializer): class LoginAssetACLAssestsSerializer(serializers.Serializer):
ip_group_help_text = _( ip_group_help_text = _(
'Format for comma-delimited string, with * indicating a match all. ' "Format for comma-delimited string, with * indicating a match all. "
'Such as: ' "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 ' "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)' "(Domain name support)"
) )
ip_group = serializers.ListField( ip_group = serializers.ListField(
default=['*'], child=serializers.CharField(max_length=1024), label=_('IP'), default=["*"],
help_text=ip_group_help_text child=serializers.CharField(max_length=1024),
label=_("IP"),
help_text=ip_group_help_text,
) )
hostname_group = serializers.ListField( hostname_group = serializers.ListField(
default=['*'], child=serializers.CharField(max_length=128), label=_('Hostname'), default=["*"],
help_text=common_help_text child=serializers.CharField(max_length=128),
label=_("Hostname"),
help_text=common_help_text,
) )
class LoginAssetACLAccountsSerializer(serializers.Serializer): class LoginAssetACLAccountsSerializer(serializers.Serializer):
protocol_group_help_text = _( protocol_group_help_text = _(
'Format for comma-delimited string, with * indicating a match all. ' "Format for comma-delimited string, with * indicating a match all. "
'Protocol options: {}' "Protocol options: {}"
) )
name_group = serializers.ListField( name_group = serializers.ListField(
default=['*'], child=serializers.CharField(max_length=128), label=_('Name'), default=["*"],
help_text=common_help_text child=serializers.CharField(max_length=128),
label=_("Name"),
help_text=common_help_text,
) )
username_group = serializers.ListField( username_group = serializers.ListField(
default=['*'], child=serializers.CharField(max_length=128), label=_('Username'), default=["*"],
help_text=common_help_text child=serializers.CharField(max_length=128),
label=_("Username"),
help_text=common_help_text,
) )
@ -58,34 +70,48 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer):
users = LoginAssetACLUsersSerializer() users = LoginAssetACLUsersSerializer()
assets = LoginAssetACLAssestsSerializer() assets = LoginAssetACLAssestsSerializer()
accounts = LoginAssetACLAccountsSerializer() accounts = LoginAssetACLAccountsSerializer()
reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') reviewers_amount = serializers.IntegerField(
action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) read_only=True, source="reviewers.count"
)
action = LabeledChoiceField(
choices=models.LoginAssetACL.ActionChoices.choices, label=_("Action")
)
class Meta: class Meta:
model = models.LoginAssetACL model = models.LoginAssetACL
fields_mini = ['id', 'name'] fields_mini = ["id", "name"]
fields_small = fields_mini + [ fields_small = fields_mini + [
'users', 'accounts', 'assets', "users",
'is_active', 'date_created', 'date_updated', "accounts",
'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id' "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 fields = fields_small + fields_m2m
extra_kwargs = { extra_kwargs = {
"reviewers": {'allow_null': False, 'required': True}, "reviewers": {"allow_null": False, "required": True},
'priority': {'default': 50}, "priority": {"default": 50},
'is_active': {'default': True}, "is_active": {"default": True},
} }
def validate_reviewers(self, reviewers): 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) org = Organization.get_instance(org_id)
if not org: 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) raise serializers.ValidationError(error)
users = org.get_members() users = org.get_members()
valid_reviewers = list(set(reviewers) & set(users)) valid_reviewers = list(set(reviewers) & set(users))
if not valid_reviewers: 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) raise serializers.ValidationError(error)
return valid_reviewers 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 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import TextChoices, IntegerChoices
DEFAULT_CITY = _("Unknown") DEFAULT_CITY = _("Unknown")
MODELS_NEED_RECORD = ( MODELS_NEED_RECORD = (
# users # users
'User', 'UserGroup', "User",
"UserGroup",
# acls # acls
'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting', "LoginACL",
"LoginAssetACL",
"LoginConfirmSetting",
# assets # assets
'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', "Asset",
'CommandFilter', 'Platform', 'Account', "Node",
"AdminUser",
"SystemUser",
"Domain",
"Gateway",
"CommandFilterRule",
"CommandFilter",
"Platform",
"Account",
# applications # applications
# orgs # orgs
'Organization', "Organization",
# settings # settings
'Setting', "Setting",
# perms # perms
'AssetPermission', "AssetPermission",
# xpack # 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.mixins.models import OrgModelMixin, Organization
from orgs.utils import current_org from orgs.utils import current_org
from .const import (
OperateChoices,
ActionChoices,
LoginTypeChoices,
MFAChoices,
LoginStatusChoices,
)
__all__ = [ __all__ = [
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog', "FTPLog",
"OperateLog",
"PasswordChangeLog",
"UserLoginLog",
] ]
class FTPLog(OrgModelMixin): 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) 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"))
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) remote_addr = models.CharField(
max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
)
asset = models.CharField(max_length=1024, verbose_name=_("Asset")) asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
system_user = models.CharField(max_length=128, verbose_name=_("System user")) 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")) filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
is_success = models.BooleanField(default=True, verbose_name=_("Success")) 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: class Meta:
verbose_name = _("File transfer log") verbose_name = _("File transfer log")
class OperateLog(OrgModelMixin): 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) 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"))
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action")) action = models.CharField(
max_length=16, choices=ActionChoices.choices, verbose_name=_("Action")
)
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource")) resource = models.CharField(max_length=128, verbose_name=_("Resource"))
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) remote_addr = models.CharField(
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'), db_index=True) 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): def __str__(self):
return "<{}> {} <{}>".format(self.user, self.action, self.resource) return "<{}> {} <{}>".format(self.user, self.action, self.resource)
@ -84,50 +76,48 @@ class OperateLog(OrgModelMixin):
class PasswordChangeLog(models.Model): class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) 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")) 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) remote_addr = models.CharField(
datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime')) max_length=128, verbose_name=_("Remote addr"), blank=True, null=True
)
datetime = models.DateTimeField(auto_now=True, verbose_name=_("Datetime"))
def __str__(self): def __str__(self):
return "{} change {}'s password".format(self.change_by, self.user) return "{} change {}'s password".format(self.change_by, self.user)
class Meta: class Meta:
verbose_name = _('Password change log') verbose_name = _("Password change log")
class UserLoginLog(models.Model): 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) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(max_length=128, verbose_name=_('Username')) username = models.CharField(max_length=128, verbose_name=_("Username"))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) type = models.CharField(
ip = models.GenericIPAddressField(verbose_name=_('Login ip')) choices=LoginTypeChoices.choices, max_length=2, verbose_name=_("Login type")
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')) ip = models.GenericIPAddressField(verbose_name=_("Login ip"))
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) city = models.CharField(
reason = models.CharField(default='', max_length=128, blank=True, verbose_name=_('Reason')) max_length=254, blank=True, null=True, verbose_name=_("Login city")
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) )
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) user_agent = models.CharField(
backend = models.CharField(max_length=32, default='', verbose_name=_('Authentication backend')) 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 @property
def backend_display(self): 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): def get_login_logs(cls, date_from=None, date_to=None, user=None, keyword=None):
login_logs = cls.objects.all() login_logs = cls.objects.all()
if date_from and date_to: if date_from and date_to:
date_from = "{} {}".format(date_from, '00:00:00') date_from = "{} {}".format(date_from, "00:00:00")
date_to = "{} {}".format(date_to, '23:59:59') date_to = "{} {}".format(date_to, "23:59:59")
login_logs = login_logs.filter( login_logs = login_logs.filter(
datetime__gte=date_from, datetime__lte=date_to datetime__gte=date_from, datetime__lte=date_to
) )
@ -146,18 +136,19 @@ class UserLoginLog(models.Model):
login_logs = login_logs.filter(username=user) login_logs = login_logs.filter(username=user)
if keyword: if keyword:
login_logs = login_logs.filter( login_logs = login_logs.filter(
Q(ip__contains=keyword) | Q(ip__contains=keyword)
Q(city__contains=keyword) | | Q(city__contains=keyword)
Q(username__contains=keyword) | Q(username__contains=keyword)
) )
if not current_org.is_root(): 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) login_logs = login_logs.filter(username__in=username_list)
return login_logs return login_logs
@property @property
def reason_display(self): def reason_display(self):
from authentication.errors import reason_choices, old_reason_choices from authentication.errors import reason_choices, old_reason_choices
reason = reason_choices.get(self.reason) reason = reason_choices.get(self.reason)
if reason: if reason:
return reason return reason
@ -165,5 +156,5 @@ class UserLoginLog(models.Model):
return reason return reason
class Meta: class Meta:
ordering = ['-datetime', 'username'] ordering = ["-datetime", "username"]
verbose_name = _('User login log') verbose_name = _("User login log")

View File

@ -3,77 +3,99 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.serializers import BulkSerializerMixin from common.drf.fields import LabeledChoiceField
from terminal.models import Session from terminal.models import Session
from . import models from . import models
from .const import (
ActionChoices,
OperateChoices,
MFAChoices,
LoginStatusChoices,
LoginTypeChoices,
)
class FTPLogSerializer(serializers.ModelSerializer): class FTPLogSerializer(serializers.ModelSerializer):
operate_display = serializers.ReadOnlyField(source='get_operate_display', label=_('Operate display')) operate = LabeledChoiceField(choices=OperateChoices.choices, label=_("Operate"))
class Meta: class Meta:
model = models.FTPLog model = models.FTPLog
fields_mini = ['id'] fields_mini = ["id"]
fields_small = fields_mini + [ fields_small = fields_mini + [
'user', 'remote_addr', 'asset', 'system_user', 'org_id', "user",
'operate', 'filename', 'operate_display', "remote_addr",
'is_success', "asset",
'date_start', "system_user",
"org_id",
"operate",
"filename",
"is_success",
"date_start",
] ]
fields = fields_small fields = fields_small
class UserLoginLogSerializer(serializers.ModelSerializer): class UserLoginLogSerializer(serializers.ModelSerializer):
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) mfa = LabeledChoiceField(choices=MFAChoices.choices, label=_("MFA"))
status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display')) type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_("Type"))
mfa_display = serializers.ReadOnlyField(source='get_mfa_display', label=_('MFA display')) status = LabeledChoiceField(choices=LoginStatusChoices.choices, label=_("Status"))
class Meta: class Meta:
model = models.UserLoginLog model = models.UserLoginLog
fields_mini = ['id'] fields_mini = ["id"]
fields_small = fields_mini + [ fields_small = fields_mini + [
'username', 'type', 'type_display', 'ip', 'city', 'user_agent', "username",
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend', 'backend_display', "type",
'status', 'status_display', "ip",
'datetime', "city",
"user_agent",
"mfa",
"reason",
"reason_display",
"backend",
"backend_display",
"status",
"datetime",
] ]
fields = fields_small fields = fields_small
extra_kwargs = { extra_kwargs = {
"user_agent": {'label': _('User agent')}, "user_agent": {"label": _("User agent")},
"reason_display": {'label': _('Reason display')}, "reason_display": {"label": _("Reason display")},
'backend_display': {'label': _('Authentication backend')} "backend_display": {"label": _("Authentication backend")},
} }
class OperateLogSerializer(serializers.ModelSerializer): class OperateLogSerializer(serializers.ModelSerializer):
action_display = serializers.CharField(source='get_action_display', label=_('Action')) action = LabeledChoiceField(choices=ActionChoices.choices, label=_("Action"))
class Meta: class Meta:
model = models.OperateLog model = models.OperateLog
fields_mini = ['id'] fields_mini = ["id"]
fields_small = fields_mini + [ fields_small = fields_mini + [
'user', 'action', 'action_display', "user",
'resource_type', 'resource_type_display', 'resource', "action",
'remote_addr', 'datetime', 'org_id' "resource_type",
"resource_type_display",
"resource",
"remote_addr",
"datetime",
"org_id",
] ]
fields = fields_small fields = fields_small
extra_kwargs = { extra_kwargs = {"resource_type_display": {"label": _("Resource Type")}}
'resource_type_display': {'label': _('Resource Type')}
}
class PasswordChangeLogSerializer(serializers.ModelSerializer): class PasswordChangeLogSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.PasswordChangeLog model = models.PasswordChangeLog
fields = ( fields = ("id", "user", "change_by", "remote_addr", "datetime")
'id', 'user', 'change_by', 'remote_addr', 'datetime'
)
class SessionAuditSerializer(serializers.ModelSerializer): class SessionAuditSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Session model = Session
fields = '__all__' fields = "__all__"
# #
# class CommandExecutionSerializer(serializers.ModelSerializer): # class CommandExecutionSerializer(serializers.ModelSerializer):

View File

@ -1,38 +1,34 @@
# -*- coding: utf-8 -*- # -*- 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.conf import settings
from django.db import transaction 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.utils.functional import LazyObject
from django.contrib.auth import BACKEND_SESSION_KEY from django.contrib.auth import BACKEND_SESSION_KEY
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import translation from django.db.models.signals import post_save, m2m_changed, pre_delete
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request 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 orgs.utils import current_org
from perms.models import AssetPermission 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.serializers import SessionSerializer
from terminal.backends.command.serializers import SessionCommandSerializer
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR 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 import get_request_ip, get_logger, get_syslogger
from common.utils.encode import data_to_json 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__) logger = get_logger(__name__)
sys_logger = get_syslogger(__name__) sys_logger = get_syslogger(__name__)
@ -46,14 +42,14 @@ class AuthBackendLabelMapping(LazyObject):
for source, backends in User.SOURCE_BACKEND_MAPPING.items(): for source, backends in User.SOURCE_BACKEND_MAPPING.items():
for backend in backends: for backend in backends:
backend_label_mapping[backend] = source.label backend_label_mapping[backend] = source.label
backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _('SSH Key') backend_label_mapping[settings.AUTH_BACKEND_PUBKEY] = _("SSH Key")
backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _('Password') backend_label_mapping[settings.AUTH_BACKEND_MODEL] = _("Password")
backend_label_mapping[settings.AUTH_BACKEND_SSO] = _('SSO') backend_label_mapping[settings.AUTH_BACKEND_SSO] = _("SSO")
backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _('Auth Token') backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _("Auth Token")
backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _('WeCom') backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _("WeCom")
backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _('FeiShu') backend_label_mapping[settings.AUTH_BACKEND_FEISHU] = _("FeiShu")
backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk') backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _("DingTalk")
backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _('Temporary token') backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _("Temporary token")
return backend_label_mapping return backend_label_mapping
def _setup(self): def _setup(self):
@ -65,41 +61,41 @@ AUTH_BACKEND_LABEL_MAPPING = AuthBackendLabelMapping()
M2M_NEED_RECORD = { M2M_NEED_RECORD = {
User.groups.through.__name__: ( User.groups.through.__name__: (
_('User and Group'), _("User and Group"),
_('{User} JOINED {UserGroup}'), _("{User} JOINED {UserGroup}"),
_('{User} LEFT {UserGroup}') _("{User} LEFT {UserGroup}"),
), ),
Asset.nodes.through.__name__: ( Asset.nodes.through.__name__: (
_('Node and Asset'), _("Node and Asset"),
_('{Node} ADD {Asset}'), _("{Node} ADD {Asset}"),
_('{Node} REMOVE {Asset}') _("{Node} REMOVE {Asset}"),
), ),
AssetPermission.users.through.__name__: ( AssetPermission.users.through.__name__: (
_('User asset permissions'), _("User asset permissions"),
_('{AssetPermission} ADD {User}'), _("{AssetPermission} ADD {User}"),
_('{AssetPermission} REMOVE {User}'), _("{AssetPermission} REMOVE {User}"),
), ),
AssetPermission.user_groups.through.__name__: ( AssetPermission.user_groups.through.__name__: (
_('User group asset permissions'), _("User group asset permissions"),
_('{AssetPermission} ADD {UserGroup}'), _("{AssetPermission} ADD {UserGroup}"),
_('{AssetPermission} REMOVE {UserGroup}'), _("{AssetPermission} REMOVE {UserGroup}"),
), ),
AssetPermission.assets.through.__name__: ( AssetPermission.assets.through.__name__: (
_('Asset permission'), _("Asset permission"),
_('{AssetPermission} ADD {Asset}'), _("{AssetPermission} ADD {Asset}"),
_('{AssetPermission} REMOVE {Asset}'), _("{AssetPermission} REMOVE {Asset}"),
), ),
AssetPermission.nodes.through.__name__: ( AssetPermission.nodes.through.__name__: (
_('Node permission'), _("Node permission"),
_('{AssetPermission} ADD {Node}'), _("{AssetPermission} ADD {Node}"),
_('{AssetPermission} REMOVE {Node}'), _("{AssetPermission} REMOVE {Node}"),
), ),
} }
M2M_ACTION_MAPER = { M2M_ACTION_MAPER = {
POST_ADD: OperateLog.ACTION_CREATE, POST_ADD: ActionChoices.create,
POST_REMOVE: OperateLog.ACTION_DELETE, POST_REMOVE: ActionChoices.delete,
POST_CLEAR: OperateLog.ACTION_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 org_id = current_org.id
remote_addr = get_request_ip(current_request) remote_addr = get_request_ip(current_request)
user = str(user) 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] action = M2M_ACTION_MAPER[action]
if action == OperateLog.ACTION_CREATE: if action == ActionChoices.create:
resource_tmpl = resource_tmpl_add resource_tmpl = resource_tmpl_add
elif action == OperateLog.ACTION_DELETE: elif action == ActionChoices.delete:
resource_tmpl = resource_tmpl_remove resource_tmpl = resource_tmpl_remove
else: else:
return return
@ -139,41 +137,53 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs):
print("Instace name: ", instance_name, instance_value) print("Instace name: ", instance_name, instance_value)
for obj in objs: for obj in objs:
resource = resource_tmpl.format(**{ resource = resource_tmpl.format(
instance_name: instance_value, **{instance_name: instance_value, model_name: str(obj)}
model_name: str(obj) )[
})[:128] # `resource` 字段只有 128 个字符长 😔 :128
] # `resource` 字段只有 128 个字符长 😔
to_create.append(OperateLog( to_create.append(
user=user, action=action, resource_type=resource_type, models.OperateLog(
resource=resource, remote_addr=remote_addr, org_id=org_id user=user,
)) action=action,
OperateLog.objects.bulk_create(to_create) resource_type=resource_type,
resource=resource,
remote_addr=remote_addr,
org_id=org_id,
)
)
models.OperateLog.objects.bulk_create(to_create)
@receiver(post_save) @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 改变是最后登录日期, 每次登录都会改变 # last_login 改变是最后登录日期, 每次登录都会改变
if instance._meta.object_name == 'User' and \ if (
update_fields and 'last_login' in update_fields: instance._meta.object_name == "User"
and update_fields
and "last_login" in update_fields
):
return return
if created: if created:
action = models.OperateLog.ACTION_CREATE action = ActionChoices.create
else: else:
action = models.OperateLog.ACTION_UPDATE action = ActionChoices.update
create_operate_log(action, sender, instance) create_operate_log(action, sender, instance)
@receiver(pre_delete) @receiver(pre_delete)
def on_object_delete(sender, instance=None, **kwargs): 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) @receiver(post_user_change_password, sender=User)
def on_user_change_password(sender, user=None, **kwargs): def on_user_change_password(sender, user=None, **kwargs):
if not current_request: if not current_request:
remote_addr = '127.0.0.1' remote_addr = "127.0.0.1"
change_by = 'System' change_by = "System"
else: else:
remote_addr = get_request_ip(current_request) remote_addr = get_request_ip(current_request)
if not current_request.user.is_authenticated: 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) change_by = str(current_request.user)
with transaction.atomic(): with transaction.atomic():
models.PasswordChangeLog.objects.create( models.PasswordChangeLog.objects.create(
user=str(user), change_by=change_by, user=str(user),
change_by=change_by,
remote_addr=remote_addr, remote_addr=remote_addr,
) )
@ -216,51 +227,52 @@ def on_audits_log_create(sender, instance=None, **kwargs):
def get_login_backend(request): def get_login_backend(request):
backend = request.session.get('auth_backend', '') or \ backend = request.session.get("auth_backend", "") or request.session.get(
request.session.get(BACKEND_SESSION_KEY, '') BACKEND_SESSION_KEY, ""
)
backend_label = AUTH_BACKEND_LABEL_MAPPING.get(backend, None) backend_label = AUTH_BACKEND_LABEL_MAPPING.get(backend, None)
if backend_label is None: if backend_label is None:
backend_label = '' backend_label = ""
return backend_label return backend_label
def generate_data(username, request, login_type=None): def generate_data(username, request, login_type=None):
user_agent = request.META.get('HTTP_USER_AGENT', '') user_agent = request.META.get("HTTP_USER_AGENT", "")
login_ip = get_request_ip(request) or '0.0.0.0' login_ip = get_request_ip(request) or "0.0.0.0"
if login_type is None and isinstance(request, Request): 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: 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)) backend = str(get_login_backend(request))
data = { data = {
'username': username, "username": username,
'ip': login_ip, "ip": login_ip,
'type': login_type, "type": login_type,
'user_agent': user_agent[0:254], "user_agent": user_agent[0:254],
'datetime': timezone.now(), "datetime": timezone.now(),
'backend': backend, "backend": backend,
} }
return data return data
@receiver(post_auth_success) @receiver(post_auth_success)
def on_user_auth_success(sender, user, request, login_type=None, **kwargs): 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) check_different_city_login_if_need(user, request)
data = generate_data(user.username, request, login_type=login_type) data = generate_data(user.username, request, login_type=login_type)
request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S") request.session["login_time"] = data["datetime"].strftime("%Y-%m-%d %H:%M:%S")
data.update({'mfa': int(user.mfa_enabled), 'status': True}) data.update({"mfa": int(user.mfa_enabled), "status": True})
write_login_log(**data) write_login_log(**data)
@receiver(post_auth_failed) @receiver(post_auth_failed)
def on_user_auth_failed(sender, username, request, reason='', **kwargs): def on_user_auth_failed(sender, username, request, reason="", **kwargs):
logger.debug('User login failed: {}'.format(username)) logger.debug("User login failed: {}".format(username))
data = generate_data(username, request) data = generate_data(username, request)
data.update({'reason': reason[:128], 'status': False}) data.update({"reason": reason[:128], "status": False})
write_login_log(**data) write_login_log(**data)

View File

@ -9,13 +9,24 @@ from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import signer, crypto from common.utils import signer, crypto
__all__ = [ __all__ = [
'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin', "JsonMixin",
'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', "JsonDictMixin",
'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', "JsonListMixin",
'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', "JsonTypeMixin",
'EncryptJsonDictCharField', 'PortField', 'BitChoices', "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): 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): def from_db_value(self, value, expression, connection, context=None):
if not value: if not value:
@ -131,7 +142,7 @@ class EncryptMixin:
# 可能和Json mix所以要先解密再json # 可能和Json mix所以要先解密再json
sp = super() 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) plain_value = sp.from_db_value(plain_value, expression, connection, context)
return plain_value return plain_value
@ -141,7 +152,7 @@ class EncryptMixin:
# 先 json 再解密 # 先 json 再解密
sp = super() sp = super()
if hasattr(sp, 'get_prep_value'): if hasattr(sp, "get_prep_value"):
value = sp.get_prep_value(value) value = sp.get_prep_value(value)
value = force_text(value) value = force_text(value)
# 替换新的加密方式 # 替换新的加密方式
@ -155,12 +166,12 @@ class EncryptTextField(EncryptMixin, models.TextField):
class EncryptCharField(EncryptMixin, models.CharField): class EncryptCharField(EncryptMixin, models.CharField):
@staticmethod @staticmethod
def change_max_length(kwargs): def change_max_length(kwargs):
kwargs.setdefault('max_length', 1024) kwargs.setdefault("max_length", 1024)
max_length = kwargs.get('max_length') max_length = kwargs.get("max_length")
if max_length < 129: if max_length < 129:
max_length = 128 max_length = 128
max_length = max_length * 2 max_length = max_length * 2
kwargs['max_length'] = max_length kwargs["max_length"] = max_length
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.change_max_length(kwargs) self.change_max_length(kwargs)
@ -168,10 +179,10 @@ class EncryptCharField(EncryptMixin, models.CharField):
def deconstruct(self): def deconstruct(self):
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
max_length = kwargs.pop('max_length') max_length = kwargs.pop("max_length")
if max_length > 255: if max_length > 255:
max_length = max_length // 2 max_length = max_length // 2
kwargs['max_length'] = max_length kwargs["max_length"] = max_length
return name, path, args, kwargs return name, path, args, kwargs
@ -185,11 +196,13 @@ class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField):
class PortField(models.IntegerField): class PortField(models.IntegerField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.update({ kwargs.update(
'blank': False, {
'null': False, "blank": False,
'validators': [MinValueValidator(0), MaxValueValidator(65535)] "null": False,
}) "validators": [MinValueValidator(0), MaxValueValidator(65535)],
}
)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -200,22 +213,22 @@ class BitChoices(models.IntegerChoices):
@classmethod @classmethod
def tree(cls): def tree(cls):
root = [_('All'), cls.branches()] root = [_("All"), cls.branches()]
return cls.render_node(root) return cls.render_node(root)
@classmethod @classmethod
def render_node(cls, node): def render_node(cls, node):
if isinstance(node, BitChoices): if isinstance(node, BitChoices):
return { return {
'id': node.name, "id": node.name,
'label': node.label, "label": node.label,
} }
else: else:
name, children = node name, children = node
return { return {
'id': name, "id": name,
'label': name, "label": name,
'children': [cls.render_node(child) for child in children] "children": [cls.render_node(child) for child in children],
} }
@classmethod @classmethod
@ -224,5 +237,3 @@ class BitChoices(models.IntegerChoices):
for c in cls: for c in cls:
value |= c.value value |= c.value
return value return value

View File

@ -9,14 +9,20 @@ from rest_framework.request import Request
from common.exceptions import UserConfirmRequired from common.exceptions import UserConfirmRequired
from audits.utils import create_operate_log from audits.utils import create_operate_log
from audits.models import OperateLog from audits.models import OperateLog
from audits.const import ActionChoices
__all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"] __all__ = [
"PermissionsMixin",
"RecordViewLogMixin",
"UserConfirmRequiredExceptionMixin",
]
class UserConfirmRequiredExceptionMixin: class UserConfirmRequiredExceptionMixin:
""" """
异常处理 异常处理
""" """
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
try: try:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -40,23 +46,23 @@ class PermissionsMixin(UserPassesTestMixin):
class RecordViewLogMixin: class RecordViewLogMixin:
ACTION = OperateLog.ACTION_VIEW ACTION = ActionChoices.view
@staticmethod @staticmethod
def get_resource_display(request): def get_resource_display(request):
query_params = dict(request.query_params) query_params = dict(request.query_params)
if query_params.get('format'): if query_params.get("format"):
query_params.pop('format') query_params.pop("format")
spm_filter = query_params.pop('spm') if query_params.get('spm') else None spm_filter = query_params.pop("spm") if query_params.get("spm") else None
if not query_params and not spm_filter: if not query_params and not spm_filter:
display_message = _('Export all') display_message = _("Export all")
elif spm_filter: elif spm_filter:
display_message = _('Export only selected items') display_message = _("Export only selected items")
else: else:
query = ','.join( query = ",".join(
['%s=%s' % (key, value) for key, value in query_params.items()] ["%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 return display_message
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):

View File

@ -21,38 +21,40 @@ class DeployAppletHostManager:
@staticmethod @staticmethod
def get_run_dir(): def get_run_dir():
base = os.path.join(settings.ANSIBLE_DIR, 'applet_host_deploy') base = os.path.join(settings.ANSIBLE_DIR, "applet_host_deploy")
now = datetime.datetime.now().strftime('%Y%m%d%H%M%S') now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
return os.path.join(base, now) return os.path.join(base, now)
def generate_playbook(self): 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 base_site_url = settings.BASE_SITE_URL
bootstrap_token = settings.BOOTSTRAP_TOKEN bootstrap_token = settings.BOOTSTRAP_TOKEN
host_id = str(self.deployment.host.id) host_id = str(self.deployment.host.id)
if not base_site_url: if not base_site_url:
base_site_url = "localhost:8080" base_site_url = "http://localhost:8080"
with open(playbook_src) as f: with open(playbook_src) as f:
plays = yaml.safe_load(f) plays = yaml.safe_load(f)
for play in plays: for play in plays:
play['vars'].update(self.deployment.host.deploy_options) play["vars"].update(self.deployment.host.deploy_options)
play['vars']['DownloadHost'] = base_site_url + '/download/' play["vars"]["DownloadHost"] = base_site_url + "/download"
play['vars']['CORE_HOST'] = base_site_url play["vars"]["CORE_HOST"] = base_site_url
play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
play['vars']['HOST_ID'] = host_id play["vars"]["HOST_ID"] = host_id
play['vars']['HOST_NAME'] = self.deployment.host.name play["vars"]["HOST_NAME"] = self.deployment.host.name
playbook_dir = os.path.join(self.run_dir, 'playbook') playbook_dir = os.path.join(self.run_dir, "playbook")
playbook_dst = os.path.join(playbook_dir, 'main.yml') playbook_dst = os.path.join(playbook_dir, "main.yml")
os.makedirs(playbook_dir, exist_ok=True) 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) yaml.safe_dump(plays, f)
return playbook_dst return playbook_dst
def generate_inventory(self): def generate_inventory(self):
inventory = JMSInventory([self.deployment.host], account_policy='privileged_only') inventory = JMSInventory(
inventory_dir = os.path.join(self.run_dir, 'inventory') [self.deployment.host], account_policy="privileged_only"
inventory_path = os.path.join(inventory_dir, 'hosts.yml') )
inventory_dir = os.path.join(self.run_dir, "inventory")
inventory_path = os.path.join(inventory_dir, "hosts.yml")
inventory.write_to_file(inventory_path) inventory.write_to_file(inventory_path)
return inventory_path return inventory_path
@ -71,7 +73,7 @@ class DeployAppletHostManager:
self.deployment.status = cb.status self.deployment.status = cb.status
except Exception as e: except Exception as e:
logger.error("Error: {}".format(e)) logger.error("Error: {}".format(e))
self.deployment.status = 'error' self.deployment.status = "error"
finally: finally:
self.deployment.date_finished = timezone.now() self.deployment.date_finished = timezone.now()
with safe_db_connection(): with safe_db_connection():