Merge branch 'dev' into pr@dev@license

This commit is contained in:
feng626 2025-03-18 18:56:41 +08:00 committed by GitHub
commit 28d6f2f9ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2838 additions and 2587 deletions

View File

@ -133,11 +133,13 @@ class AccountRiskViewSet(OrgBulkModelViewSet):
s.validated_data, ("asset", "username", "action", "risk") s.validated_data, ("asset", "username", "action", "risk")
) )
handler = RiskHandler(asset=asset, username=username, request=self.request) handler = RiskHandler(asset=asset, username=username, request=self.request)
data = handler.handle(act, risk)
if not data: try:
return Response(data={"message": "Success"}) risk = handler.handle(act, risk)
s = serializers.AccountRiskSerializer(instance=data) s = serializers.AccountRiskSerializer(instance=risk)
return Response(data=s.data) return Response(data=s.data)
except Exception as e:
return Response(status=400, data=str(e))
class CheckAccountEngineViewSet(JMSModelViewSet): class CheckAccountEngineViewSet(JMSModelViewSet):

View File

@ -155,6 +155,19 @@ class AnalyseAccountRisk:
def _update_risk(self, account): def _update_risk(self, account):
return account return account
def lost_accounts(self, asset, lost_users):
if not self.check_risk:
return
for user in lost_users:
self._create_risk(
dict(
asset_id=str(asset.id),
username=user,
risk=RiskChoice.account_deleted,
details=[{"datetime": self.now.isoformat()}],
)
)
def analyse_risk(self, asset, ga, d, sys_found): def analyse_risk(self, asset, ga, d, sys_found):
if not self.check_risk: if not self.check_risk:
return return
@ -289,6 +302,8 @@ class GatherAccountsManager(AccountBasePlaybookManager):
"username": username, "username": username,
} }
) )
risk_analyser = AnalyseAccountRisk(self.check_risk)
risk_analyser.lost_accounts(asset, lost_users)
# 收集的账号 比 账号列表多的, 有可能是账号中删掉了, 但这时候状态已经是 confirm 了 # 收集的账号 比 账号列表多的, 有可能是账号中删掉了, 但这时候状态已经是 confirm 了
# 标识状态为 待处理, 让管理员去确认 # 标识状态为 待处理, 让管理员去确认

View File

@ -139,6 +139,7 @@ class Migration(migrations.Migration):
choices=[ choices=[
("long_time_no_login", "Long time no login"), ("long_time_no_login", "Long time no login"),
("new_found", "New found"), ("new_found", "New found"),
("account_deleted", "Account deleted"),
("groups_changed", "Groups change"), ("groups_changed", "Groups change"),
("sudoers_changed", "Sudo changed"), ("sudoers_changed", "Sudo changed"),
("authorized_keys_changed", "Authorized keys changed"), ("authorized_keys_changed", "Authorized keys changed"),

View File

@ -1,8 +1,9 @@
from itertools import islice
from django.db import models from django.db import models
from django.db.models import TextChoices from django.db.models import TextChoices
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from itertools import islice
from common.const import ConfirmOrIgnore from common.const import ConfirmOrIgnore
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
@ -41,6 +42,7 @@ class RiskChoice(TextChoices):
# 依赖自动发现的 # 依赖自动发现的
long_time_no_login = 'long_time_no_login', _('Long time no login') # 好久没登录的账号, 禁用、删除 long_time_no_login = 'long_time_no_login', _('Long time no login') # 好久没登录的账号, 禁用、删除
new_found = 'new_found', _('New found') # 未被纳管的账号, 纳管, 删除, 禁用 new_found = 'new_found', _('New found') # 未被纳管的账号, 纳管, 删除, 禁用
account_deleted = 'account_deleted', _('Account deleted') # 账号被删除, 纳管, 删除, 禁用
group_changed = 'groups_changed', _('Groups change') # 组变更, 确认 group_changed = 'groups_changed', _('Groups change') # 组变更, 确认
sudo_changed = 'sudoers_changed', _('Sudo changed') # sudo 变更, 确认 sudo_changed = 'sudoers_changed', _('Sudo changed') # sudo 变更, 确认
authorized_keys_changed = 'authorized_keys_changed', _('Authorized keys changed') # authorized_keys 变更, 确认 authorized_keys_changed = 'authorized_keys_changed', _('Authorized keys changed') # authorized_keys 变更, 确认

View File

@ -8,7 +8,7 @@ from accounts.models import (
AccountRisk, AccountRisk,
SecretType, SecretType,
AutomationExecution, AutomationExecution,
RiskChoice RiskChoice, Account
) )
from common.const import ConfirmOrIgnore from common.const import ConfirmOrIgnore
from common.utils import random_string from common.utils import random_string
@ -19,10 +19,11 @@ TYPE_CHOICES = [
("close", _("Close")), ("close", _("Close")),
("disable_remote", _("Disable remote")), ("disable_remote", _("Disable remote")),
("delete_remote", _("Delete remote")), ("delete_remote", _("Delete remote")),
("delete_account", _("Delete account")),
("delete_both", _("Delete remote")), ("delete_both", _("Delete remote")),
("add_account", _("Add account")), ("add_account", _("Add account")),
("change_password_add", _("Change password and Add")), ("change_password_add", _("Change password and Add")),
("change_password", _("Change password")) ("change_password", _("Change password")),
] ]
@ -73,6 +74,10 @@ class RiskHandler:
def handle_reopen(self): def handle_reopen(self):
pass pass
def handle_delete_account(self):
Account.objects.filter(asset=self.asset, username=self.username).delete()
GatheredAccount.objects.filter(asset=self.asset, username=self.username).delete()
def handle_close(self): def handle_close(self):
pass pass
@ -102,7 +107,7 @@ class RiskHandler:
present=True, status=ConfirmOrIgnore.confirmed present=True, status=ConfirmOrIgnore.confirmed
) )
self.risk = RiskChoice.new_found self.risk = RiskChoice.new_found
risk = self.get_risk() risk = self.get_risk()
risk.account = account risk.account = account
risk.save() risk.save()
@ -113,6 +118,15 @@ class RiskHandler:
def handle_delete_remote(self): def handle_delete_remote(self):
self._handle_delete(delete="remote") self._handle_delete(delete="remote")
@staticmethod
def start_execution(execution):
execution.save()
execution.start()
if execution.status != "success":
msg = _("Execution failed: {}").format(execution.status)
raise ValidationError(msg)
def _handle_delete(self, delete="both"): def _handle_delete(self, delete="both"):
asset = self.asset asset = self.asset
execution = AutomationExecution() execution = AutomationExecution()
@ -124,9 +138,7 @@ class RiskHandler:
"delete": delete, "delete": delete,
"risk": self.risk "risk": self.risk
} }
execution.save() self.start_execution(execution)
execution.start()
return execution.summary
def handle_delete_both(self): def handle_delete_both(self):
self._handle_delete(delete="both") self._handle_delete(delete="both")
@ -134,7 +146,11 @@ class RiskHandler:
def handle_change_password(self): def handle_change_password(self):
asset = self.asset asset = self.asset
execution = AutomationExecution() execution = AutomationExecution()
account = self.asset.accounts.get(username=self.username) account = self.asset.accounts.filter(username=self.username, secret_type=SecretType.PASSWORD).first()
if not account:
raise ValidationError("Account not found")
execution.snapshot = { execution.snapshot = {
"assets": [str(asset.id)], "assets": [str(asset.id)],
"accounts": [str(account.id)], "accounts": [str(account.id)],
@ -143,9 +159,7 @@ class RiskHandler:
"secret_strategy": "random", "secret_strategy": "random",
"name": "Change account password: {}@{}".format(self.username, asset.name), "name": "Change account password: {}@{}".format(self.username, asset.name),
} }
execution.save() self.start_execution(execution)
execution.start()
return execution.summary
def handle_change_password_add(self): def handle_change_password_add(self):
asset = self.asset asset = self.asset
@ -174,10 +188,10 @@ class RiskHandler:
'check_conn_after_change': True, 'check_conn_after_change': True,
"name": "Push account password: {}@{}".format(self.username, asset.name), "name": "Push account password: {}@{}".format(self.username, asset.name),
} }
execution.save() self.start_execution(execution)
execution.start()
GatheredAccount.objects.filter(asset=self.asset, username=self.username).update( (
present=True GatheredAccount.objects
.filter(asset=self.asset, username=self.username)
.update(present=True)
) )
return execution.summary

View File

@ -106,11 +106,9 @@ class CommandFilterACL(UserAssetAccountBaseACL):
return self.name return self.name
def create_command_review_ticket(self, run_command, session, cmd_filter_acl, org_id): def create_command_review_ticket(self, run_command, session, cmd_filter_acl, org_id):
from tickets.const import TicketType
from tickets.models import ApplyCommandTicket from tickets.models import ApplyCommandTicket
data = { data = {
'title': _('Command confirm') + ' ({})'.format(session.user), 'title': _('Command confirm') + ' ({})'.format(session.user),
'type': TicketType.command_confirm,
'applicant': session.user_obj, 'applicant': session.user_obj,
'apply_run_user_id': session.user_id, 'apply_run_user_id': session.user_id,
'apply_run_asset': str(session.asset), 'apply_run_asset': str(session.asset),

View File

@ -11,6 +11,11 @@
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}?_=9"></script> <script src="{% static "js/jumpserver.js" %}?_=9"></script>
<style>
.btn-sm i {
margin-right: 6px;
}
</style>
</head> </head>
@ -20,19 +25,18 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="ibox-content"> <div class="ibox-content">
<div> <div>
<img src="{{ INTERFACE.logo_logout }}" style="margin: auto" width="82" height="82"> <img src="{{ INTERFACE.logo_logout }}" style="margin: auto" width="62" height="62" alt="logo">
<h2 style="display: inline"> <h2 style="display: inline">
{{ INTERFACE.login_title }} {{ INTERFACE.login_title }}
</h2> </h2>
</div> </div>
<p></p> <div class="alert alert-success info-messages">
<div class="alert alert-success info-messages" >
{{ msg|safe }} {{ msg|safe }}
</div> </div>
<div class="alert alert-danger error-messages" style="display: none"> <div class="alert alert-danger error-messages" style="display: none"></div>
</div>
<div class="progress progress-bar-default progress-striped active"> <div class="progress progress-bar-default progress-striped active">
<div aria-valuemax="3600" aria-valuemin="0" aria-valuenow="43" role="progressbar" class="progress-bar"> <div aria-valuemax="3600" aria-valuemin="0" aria-valuenow="43" role="progressbar"
class="progress-bar">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -66,107 +70,111 @@
{% include '_foot_js.html' %} {% include '_foot_js.html' %}
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script> <script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
<script> <script>
var errorMsgShow = false; var errorMsgShow = false;
var errorMsgRef = $(".error-messages"); var errorMsgRef = $(".error-messages");
var infoMsgRef = $(".info-messages"); var infoMsgRef = $(".info-messages");
var timestamp = '{{ timestamp }}'; var timestamp = '{{ timestamp }}';
var progressBarRef = $(".progress-bar"); var progressBarRef = $(".progress-bar");
var interval, checkInterval; var interval, checkInterval;
var url = "{% url 'api-auth:login-confirm-ticket-status' %}"; var url = "{% url 'api-auth:login-confirm-ticket-status' %}";
var successUrl = "{% url 'authentication:login-guard' %}"; var successUrl = "{% url 'authentication:login-guard' %}";
function doRequestAuth() { function doRequestAuth() {
requestApi({ requestApi({
url: url, url: url,
method: "GET", method: "GET",
headers: { headers: {
"X-JMS-LOGIN-TYPE": "W" "X-JMS-LOGIN-TYPE": "W"
}, },
success: function (data) { success: function (data) {
if (!data.error && data.msg === 'ok') { if (!data.error && data.msg === 'ok') {
window.onbeforeunload = function(){}; window.onbeforeunload = function () {
window.location = "{% url 'authentication:login-guard' %}" };
} else if (data.error !== "login_confirm_wait") { window.location = "{% url 'authentication:login-guard' %}"
if (!errorMsgShow) { } else if (data.error !== "login_confirm_wait") {
infoMsgRef.hide(); if (!errorMsgShow) {
errorMsgRef.show(); infoMsgRef.hide();
progressBarRef.addClass('progress-bar-danger'); errorMsgRef.show();
errorMsgShow = true; progressBarRef.addClass('progress-bar-danger');
errorMsgShow = true;
}
clearInterval(interval);
clearInterval(checkInterval);
cancelTicket();
$(".copy-btn").attr('disabled', 'disabled');
errorMsgRef.html(data.msg)
} }
clearInterval(interval); },
clearInterval(checkInterval); error: function (text, data) {
cancelTicket(); },
$(".copy-btn").attr('disabled', 'disabled'); flash_message: false, // 是否显示flash消息
errorMsgRef.html(data.msg) })
}
function initClipboard() {
var clipboard = new Clipboard('.btn-copy', {
text: function (trigger) {
var origin = window.location.origin;
var link = origin + $(".btn-copy").data('link');
return link
} }
}, });
error: function (text, data) { clipboard.on("success", function (e) {
}, toastr.success("{% trans "Copy success" %}")
flash_message: false, // 是否显示flash消息 })
}) }
}
function initClipboard() { function handleProgressBar() {
var clipboard = new Clipboard('.btn-copy', { var now = new Date().getTime() / 1000;
text: function (trigger) { var offset = now - timestamp;
var origin = window.location.origin; var percent = offset / 3600 * 100;
var link = origin + $(".btn-copy").data('link'); if (percent > 100) {
return link percent = 100
} }
}); progressBarRef.css("width", percent + '%');
clipboard.on("success", function (e) { progressBarRef.attr('aria-valuenow', offset);
toastr.success("{% trans "Copy success" %}")
})
}
function handleProgressBar() {
var now = new Date().getTime() / 1000;
var offset = now - timestamp;
var percent = offset / 3600 * 100;
if (percent > 100) {
percent = 100
} }
progressBarRef.css("width", percent + '%');
progressBarRef.attr('aria-valuenow', offset);
}
function cancelTicket() { function cancelTicket() {
requestApi({ requestApi({
url: url, url: url,
method: "DELETE", method: "DELETE",
flash_message: false flash_message: false
}) })
} }
function cancelCloseConfirm() { function cancelCloseConfirm() {
window.onbeforeunload = function() {}; window.onbeforeunload = function () {
window.onunload = function(){}; };
} window.onunload = function () {
};
}
function setCloseConfirm() { function setCloseConfirm() {
window.onbeforeunload = function (e) { window.onbeforeunload = function (e) {
return 'Confirm'; return 'Confirm';
}; };
window.onunload = function (e) { window.onunload = function (e) {
cancelTicket();
}
}
$(document).ready(function () {
interval = setInterval(handleProgressBar, 1000);
checkInterval = setInterval(doRequestAuth, 5000);
doRequestAuth();
initClipboard();
setCloseConfirm();
}).on('click', '.btn-refresh', function () {
cancelCloseConfirm();
window.location.reload();
}).on('click', '.btn-return', function () {
cancelTicket(); cancelTicket();
} cancelCloseConfirm();
} clearInterval(interval);
clearInterval(checkInterval);
$(document).ready(function () { window.location = "{% url 'authentication:logout' %}"
interval = setInterval(handleProgressBar, 1000); })
checkInterval = setInterval(doRequestAuth, 5000);
doRequestAuth();
initClipboard();
setCloseConfirm();
}).on('click', '.btn-refresh', function () {
cancelCloseConfirm();
window.location.reload();
}).on('click', '.btn-return', function () {
cancelTicket();
cancelCloseConfirm();
clearInterval(interval);
clearInterval(checkInterval);
window.location = "{% url 'authentication:logout' %}"
})
</script> </script>
</html> </html>

View File

@ -12,15 +12,10 @@ class CeleryBaseService(BaseService):
@property @property
def cmd(self): def cmd(self):
print('\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize())) print('\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize()))
ansible_config_path = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'ansible.cfg') os.environ.setdefault('PYTHONPATH', settings.APPS_DIR)
ansible_modules_path = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'modules')
os.environ.setdefault('LC_ALL', 'en_US.UTF-8') os.environ.setdefault('LC_ALL', 'en_US.UTF-8')
os.environ.setdefault('LANG', 'en_US.UTF-8') os.environ.setdefault('LANG', 'en_US.UTF-8')
os.environ.setdefault('PYTHONOPTIMIZE', '1') os.environ.setdefault('PYTHONOPTIMIZE', '1')
os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True')
os.environ.setdefault('ANSIBLE_CONFIG', ansible_config_path)
os.environ.setdefault('ANSIBLE_LIBRARY', ansible_modules_path)
os.environ.setdefault('PYTHONPATH', settings.APPS_DIR)
if os.getuid() == 0: if os.getuid() == 0:
os.environ.setdefault('C_FORCE_ROOT', '1') os.environ.setdefault('C_FORCE_ROOT', '1')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -295,7 +295,7 @@
"ClearSuccessMsg": "Clear successful", "ClearSuccessMsg": "Clear successful",
"ClickCopy": "Click to copy", "ClickCopy": "Click to copy",
"ClientCertificate": "Client certificate", "ClientCertificate": "Client certificate",
"Clipboard ": "Clipboard", "Clipboard": "Clipboard",
"ClipboardCopyPaste": "Clipboard copy and paste", "ClipboardCopyPaste": "Clipboard copy and paste",
"Clone": "Clone", "Clone": "Clone",
"Close": "Close", "Close": "Close",
@ -1523,5 +1523,7 @@
"IpGroup": "IP group", "IpGroup": "IP group",
"PublicIP": "Public IP", "PublicIP": "Public IP",
"PrivateIP": "Private IP", "PrivateIP": "Private IP",
"ExecuteAfterSaving": "Execute after saving" "ExecuteAfterSaving": "Execute after saving",
"PleaseEnterReason": "Please enter a reason",
"Processing": "Processing"
} }

View File

@ -280,7 +280,7 @@
"ClearSelection": "Limpiar selección", "ClearSelection": "Limpiar selección",
"ClickCopy": "Hacer clic para copiar", "ClickCopy": "Hacer clic para copiar",
"ClientCertificate": "Certificado de cliente", "ClientCertificate": "Certificado de cliente",
"Clipboard ": "Portapapeles", "Clipboard": "Portapapeles",
"ClipboardCopyPaste": "Copiar y pegar del portapapeles.", "ClipboardCopyPaste": "Copiar y pegar del portapapeles.",
"Clone": "Clonar", "Clone": "Clonar",
"Close": "cerrar", "Close": "cerrar",

View File

@ -298,7 +298,7 @@
"ClearSuccessMsg": "クリアに成功", "ClearSuccessMsg": "クリアに成功",
"ClickCopy": "クリックでコピー", "ClickCopy": "クリックでコピー",
"ClientCertificate": "クライアント証明書", "ClientCertificate": "クライアント証明書",
"Clipboard ": "クリップボード", "Clipboard": "クリップボード",
"ClipboardCopyPaste": "クリップボードのコピーペースト", "ClipboardCopyPaste": "クリップボードのコピーペースト",
"Clone": "クローン", "Clone": "クローン",
"Close": "閉じる", "Close": "閉じる",

View File

@ -295,7 +295,7 @@
"ClearSuccessMsg": "Limpeza bem-sucedida", "ClearSuccessMsg": "Limpeza bem-sucedida",
"ClickCopy": "Clicar para copiar", "ClickCopy": "Clicar para copiar",
"ClientCertificate": "Certificado do cliente", "ClientCertificate": "Certificado do cliente",
"Clipboard ": "Área de transferência", "Clipboard": "Área de transferência",
"ClipboardCopyPaste": "Copiar e colar da área de transferência", "ClipboardCopyPaste": "Copiar e colar da área de transferência",
"Clone": "Clonar", "Clone": "Clonar",
"Close": "Desativar", "Close": "Desativar",

View File

@ -288,7 +288,7 @@
"ClearSuccessMsg": "Очистка выполнена успешно", "ClearSuccessMsg": "Очистка выполнена успешно",
"ClickCopy": "Нажмите для копирования", "ClickCopy": "Нажмите для копирования",
"ClientCertificate": "Сертификат клиента", "ClientCertificate": "Сертификат клиента",
"Clipboard ": "Буфер обмена", "Clipboard": "Буфер обмена",
"ClipboardCopyPaste": "Копирование и вставка в буфер обмена", "ClipboardCopyPaste": "Копирование и вставка в буфер обмена",
"Clone": "Клонировать", "Clone": "Клонировать",
"Close": "закрыть", "Close": "закрыть",

View File

@ -294,7 +294,7 @@
"ClearSuccessMsg": "清除成功", "ClearSuccessMsg": "清除成功",
"ClickCopy": "点击复制", "ClickCopy": "点击复制",
"ClientCertificate": "客户端证书", "ClientCertificate": "客户端证书",
"Clipboard ": "剪贴板", "Clipboard": "剪贴板",
"ClipboardCopyPaste": "剪贴板复制粘贴", "ClipboardCopyPaste": "剪贴板复制粘贴",
"Clone": "克隆", "Clone": "克隆",
"Close": "关闭", "Close": "关闭",
@ -914,7 +914,7 @@
"PasswordAndSSHKey": "认证设置", "PasswordAndSSHKey": "认证设置",
"PasswordChangeLog": "改密日志", "PasswordChangeLog": "改密日志",
"PasswordError": "密码错误", "PasswordError": "密码错误",
"PasswordExpired": "密码过期", "PasswordExpired": "密码过期",
"PasswordPlaceholder": "请输入密码", "PasswordPlaceholder": "请输入密码",
"PasswordRecord": "密码记录", "PasswordRecord": "密码记录",
"PasswordRule": "密码规则", "PasswordRule": "密码规则",
@ -1525,5 +1525,7 @@
"IpGroup": "IP 组", "IpGroup": "IP 组",
"PublicIP": "公有 IP", "PublicIP": "公有 IP",
"PrivateIP": "私有 IP", "PrivateIP": "私有 IP",
"ExecuteAfterSaving": "保存后执行" "ExecuteAfterSaving": "保存后执行",
"PleaseEnterReason": "请输入原因",
"Processing": "处理中"
} }

View File

@ -298,7 +298,7 @@
"ClearSuccessMsg": "清除成功", "ClearSuccessMsg": "清除成功",
"ClickCopy": "點擊複製", "ClickCopy": "點擊複製",
"ClientCertificate": "用戶端證書", "ClientCertificate": "用戶端證書",
"Clipboard ": "剪貼簿", "Clipboard": "剪貼簿",
"ClipboardCopyPaste": "剪貼簿複製貼上", "ClipboardCopyPaste": "剪貼簿複製貼上",
"Clone": "複製", "Clone": "複製",
"Close": "關閉", "Close": "關閉",

View File

@ -1,11 +1,23 @@
from ops.ansible.cleaner import WorkPostRunCleaner, cleanup_post_run import os
from django.conf import settings
from ops.ansible.cleaner import WorkPostRunCleaner
class BaseRunner(WorkPostRunCleaner): class BaseRunner(WorkPostRunCleaner):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.runner_params = kwargs self.runner_params = kwargs
self.clean_workspace = kwargs.pop("clean_workspace", True) self.clean_workspace = kwargs.pop("clean_workspace", True)
self.setup_env()
@staticmethod
def setup_env():
ansible_config_path = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'ansible.cfg')
ansible_modules_path = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'modules')
os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True')
os.environ.setdefault('ANSIBLE_CONFIG', ansible_config_path)
os.environ.setdefault('ANSIBLE_LIBRARY', ansible_modules_path)
@classmethod @classmethod
def kill_precess(cls, pid): def kill_precess(cls, pid):

View File

@ -8,9 +8,6 @@ __all__ = ['AnsibleNativeRunner']
class AnsibleNativeRunner(BaseRunner): class AnsibleNativeRunner(BaseRunner):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@classmethod @classmethod
def kill_precess(cls, pid): def kill_precess(cls, pid):
return kill_ansible_ssh_process(pid) return kill_ansible_ssh_process(pid)

View File

@ -2,6 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from perms.const import ActionChoices from perms.const import ActionChoices
from tickets.const import TicketType
from .general import Ticket from .general import Ticket
__all__ = ['ApplyAssetTicket'] __all__ = ['ApplyAssetTicket']
@ -19,6 +20,8 @@ class ApplyAssetTicket(Ticket):
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
TICKET_TYPE = TicketType.apply_asset
def get_apply_actions_display(self): def get_apply_actions_display(self):
return ActionChoices.display(self.apply_actions) return ActionChoices.display(self.apply_actions)

View File

@ -2,6 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .general import Ticket from .general import Ticket
from ...const import TicketType
class ApplyCommandTicket(Ticket): class ApplyCommandTicket(Ticket):
@ -19,5 +20,7 @@ class ApplyCommandTicket(Ticket):
null=True, verbose_name=_('Command filter acl') null=True, verbose_name=_('Command filter acl')
) )
TICKET_TYPE = TicketType.command_confirm
class Meta: class Meta:
verbose_name = _('Apply Command Ticket') verbose_name = _('Apply Command Ticket')

View File

@ -303,6 +303,8 @@ class Ticket(StatusMixin, JMSBaseModel):
max_length=36, blank=True, default='', verbose_name=_('Organization'), db_index=True max_length=36, blank=True, default='', verbose_name=_('Organization'), db_index=True
) )
TICKET_TYPE = TicketType.general
class Meta: class Meta:
ordering = ('-date_created',) ordering = ('-date_created',)
verbose_name = _('Ticket') verbose_name = _('Ticket')
@ -313,11 +315,23 @@ class Ticket(StatusMixin, JMSBaseModel):
def __str__(self): def __str__(self):
return '{}({})'.format(self.title, self.applicant) return '{}({})'.format(self.title, self.applicant)
def save(self, *args, **kwargs):
self.type = self.TICKET_TYPE
super().save(*args, **kwargs)
@property @property
def spec_ticket(self): def spec_ticket(self):
attr = self.type.replace('_', '') + 'ticket' attr = self.type.replace('_', '') + 'ticket'
return getattr(self, attr) return getattr(self, attr)
@property
def name(self):
return self.title
@name.setter
def name(self, value):
self.title = value
# TODO 先单独处理一下 # TODO 先单独处理一下
@property @property
def org_name(self): def org_name(self):

View File

@ -5,6 +5,8 @@ from .general import Ticket
__all__ = ['ApplyLoginAssetTicket'] __all__ = ['ApplyLoginAssetTicket']
from ...const import TicketType
class ApplyLoginAssetTicket(Ticket): class ApplyLoginAssetTicket(Ticket):
apply_login_user = models.ForeignKey( apply_login_user = models.ForeignKey(
@ -17,6 +19,8 @@ class ApplyLoginAssetTicket(Ticket):
max_length=128, default='', verbose_name=_('Login account') max_length=128, default='', verbose_name=_('Login account')
) )
TICKET_TYPE = TicketType.login_asset_confirm
def activate_connection_token_if_need(self): def activate_connection_token_if_need(self):
if not self.connection_token: if not self.connection_token:
return return

View File

@ -5,11 +5,15 @@ from .general import Ticket
__all__ = ['ApplyLoginTicket'] __all__ = ['ApplyLoginTicket']
from ...const import TicketType
class ApplyLoginTicket(Ticket): class ApplyLoginTicket(Ticket):
apply_login_ip = models.GenericIPAddressField(verbose_name=_('Login IP'), null=True) apply_login_ip = models.GenericIPAddressField(verbose_name=_('Login IP'), null=True)
apply_login_city = models.CharField(max_length=64, verbose_name=_('Login city'), null=True) apply_login_city = models.CharField(max_length=64, verbose_name=_('Login city'), null=True)
apply_login_datetime = models.DateTimeField(verbose_name=_('Login Date'), null=True) apply_login_datetime = models.DateTimeField(verbose_name=_('Login Date'), null=True)
TICKET_TYPE = TicketType.login_confirm
class Meta: class Meta:
verbose_name = _('Apply Login Ticket') verbose_name = _('Apply Login Ticket')

View File

@ -25,8 +25,9 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
fields_mini = ['id', 'title'] fields_mini = ['id', 'title']
fields_small = fields_mini + ['org_id', 'comment'] fields_small = fields_mini + ['org_id', 'comment']
read_only_fields = [ read_only_fields = [
'serial_num', 'process_map', 'approval_step', 'type', 'state', 'applicant', 'serial_num', 'process_map', 'approval_step', 'type',
'status', 'date_created', 'date_updated', 'org_name', 'rel_snapshot' 'state', 'applicant', 'status', 'date_created',
'date_updated', 'org_name', 'rel_snapshot'
] ]
fields = fields_small + read_only_fields fields = fields_small + read_only_fields
extra_kwargs = {} extra_kwargs = {}

View File

@ -137,8 +137,8 @@ class AuthMixin:
if self.can_update_ssh_key(): if self.can_update_ssh_key():
from authentication.models import SSHKey from authentication.models import SSHKey
SSHKey.objects.create( SSHKey.objects.create(
public_key=public_key, private_key=private_key, user=self, name=kwargs.get('name'), public_key=public_key, private_key=private_key, user=self, name=kwargs.get('name', ''),
comment=kwargs.get('comment'), is_active=kwargs.get('is_active') comment=kwargs.get('comment', ''), is_active=kwargs.get('is_active')
) )
post_user_change_password.send(self.__class__, user=self) post_user_change_password.send(self.__class__, user=self)