mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-04-28 11:25:42 +00:00
* perf: change i18n
* perf: pam
* perf: change translate
* perf: add check account
* perf: add date field
* perf: add account filter
* perf: remove some js
* perf: add account status action
* perf: update pam
* perf: 修改 discover account
* perf: update filter
* perf: update gathered account
* perf: 修改账号同步
* perf: squash migrations
* perf: update pam
* perf: change i18n
* perf: update account risk
* perf: 更新风险发现
* perf: remove css
* perf: Admin connection token
* perf: Add a switch to check connectivity after changing the password, and add a custom ssh command for push tasks
* perf: Modify account migration files
* perf: update pam
* perf: remove to check account dir
* perf: Admin connection token
* perf: update check account
* perf: 优化发送结果
* perf: update pam
* perf: update bulk update create
* perf: prepaire using thread timer for bulk_create_decorator
* perf: update bulk create decorator
* perf: 优化 playbook manager
* perf: 优化收集账号的报表
* perf: Update poetry
* perf: Update Dockerfile with new base image tag
* fix: Account migrate 0012 file
* perf: 修改备份
* perf: update pam
* fix: Expand resource_type filter to include raw type
* feat: PAM Service (#14552)
* feat: PAM Service
* perf: import package name
---------
Co-authored-by: jiangweidong <1053570670@qq.com>
* perf: Change secret dashboard (#14551)
Co-authored-by: feng <1304903146@qq.com>
* perf: update migrations
* perf: 修改支持 pam
* perf: Change secret record table dashboard
* perf: update status
* fix: Automation send report
* perf: Change secret report
* feat: windows accounts gather
* perf: update change status
* perf: Account backup
* perf: Account backup report
* perf: Account migrate
* perf: update service to application
* perf: update migrations
* perf: update logo
* feat: oracle accounts gather (#14571)
* feat: oracle accounts gather
* feat: sqlserver accounts gather
* feat: postgresql accounts gather
* feat: mysql accounts gather
---------
Co-authored-by: wangruidong <940853815@qq.com>
* feat: mongodb accounts gather
* perf: Change secret
* perf: Migrate
* perf: Merge conflicting migration files
* perf: Change secret
* perf: Automation filter org
* perf: Account push
* perf: Random secret string
* perf: Enhance SQL query and update risk handling in accounts
* perf: Ticket filter assignee_id
* perf: 修改 account remote
* perf: 修改一些 adhoc 任务
* perf: Change secret
* perf: Remove push account extra api
* perf: update status
* perf: The entire organization can view activity log
* fix: risk field check
* perf: add account details api
* perf: add demo mode
* perf: Delete gather_account
* perf: Perfect solution to account version problem
* perf: Update status action to handle multiple accounts
* perf: Add GatherAccountDetailField and update serializers
* perf: Display account history in combination with password change records
* perf: Lina translate
* fix: Update mysql_filter to handle nested user info
* perf: Admin connection token validate_permission account
* perf: copy move account
* perf: account filter risk
* perf: account risk filter
* perf: Copy move account failed message
* fix: gather account sync account to asset
* perf: Pam dashboard
* perf: Account dashboard total accounts
* perf: Pam dashboard
* perf: Change secret filter account secret_reset
* perf: 修改 risk filter
* perf: pam translate
* feat: Check for leaked duplicate passwords. (#14711)
* feat: Check for leaked duplicate passwords.
* perf: Use SQLite instead of txt as leak password database
---------
Co-authored-by: jiangweidong <1053570670@qq.com>
Co-authored-by: 老广 <ibuler@qq.com>
* perf: merge with remote
* perf: Add risk change_password_add handle
* perf: Pam dashboard
* perf: check account manager import
* perf: 重构扫描
* perf: 修改 db
* perf: Gather account manager
* perf: update change db lib
* perf: dashboard
* perf: Account gather
* perf: 修改 asset get queryset
* perf: automation report
* perf: Pam account
* perf: Pam dashboard api
* perf: risk add account
* perf: 修改 risk check
* perf: Risk account
* perf: update risk add reopen action
* perf: add pylintrc
* Revert "perf: automation report"
This reverts commit 22aee54207
.
* perf: check account engine
* perf: Perf: Optimism Gather Report Style
* Perf: Remove unuser actions
* Perf: Perf push account
* perf: perf gather account
* perf: Automation report
* perf: Push account recorder
* perf: Push account record
* perf: Pam dashboard
* perf: perf
* perf: update intergration
* perf: integrations application detail add account tab page
* feat: Custom change password supports configuration of interactive items
* perf: Go and Python demo code
* perf: Custom secret change
* perf: add user filter
* perf: translate
* perf: Add demo code docs
* perf: update some i18n
* perf: update some i18n
* perf: Add Java, Node, Go, and cURL demo code
* perf: Translate
* perf: Change secret translate
* perf: Translate
* perf: update some i18n
* perf: translate
* perf: Ansible playbook
* perf: update some choice
* perf: update some choice
* perf: update account serializer remote unused code
* perf: conflict
* perf: update import
---------
Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: jiangweidong <1053570670@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
Co-authored-by: zhaojisen <1301338853@qq.com>
307 lines
9.8 KiB
Python
307 lines
9.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
import phonenumbers
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.db.models import Model
|
|
from django.utils.translation import gettext_lazy as _
|
|
from rest_framework import serializers
|
|
from rest_framework.fields import ChoiceField, empty
|
|
|
|
from common.db.fields import TreeChoices, JSONManyToManyField as ModelJSONManyToManyField
|
|
from common.utils import decrypt_password, is_uuid
|
|
|
|
__all__ = [
|
|
"ReadableHiddenField",
|
|
"EncryptedField",
|
|
"LabeledChoiceField",
|
|
"ObjectRelatedField",
|
|
"BitChoicesField",
|
|
"TreeChoicesField",
|
|
"LabeledMultipleChoiceField",
|
|
"PhoneField",
|
|
"JSONManyToManyField",
|
|
"LabelRelatedField",
|
|
]
|
|
|
|
|
|
# ReadableHiddenField
|
|
# -------------------
|
|
|
|
|
|
class ReadableHiddenField(serializers.HiddenField):
|
|
"""可读的 HiddenField"""
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.write_only = False
|
|
|
|
def to_representation(self, value):
|
|
if hasattr(value, "id"):
|
|
return getattr(value, "id")
|
|
return value
|
|
|
|
|
|
class EncryptedField(serializers.CharField):
|
|
def __init__(self, write_only=None, **kwargs):
|
|
if write_only is None:
|
|
write_only = True
|
|
kwargs["write_only"] = write_only
|
|
super().__init__(**kwargs)
|
|
|
|
def to_internal_value(self, value):
|
|
value = super().to_internal_value(value)
|
|
return decrypt_password(value)
|
|
|
|
|
|
class LabeledChoiceField(ChoiceField):
|
|
def to_representation(self, key):
|
|
if key is None:
|
|
return key
|
|
label = self.choices.get(key, key)
|
|
return {"value": key, "label": label}
|
|
|
|
def to_internal_value(self, data):
|
|
if isinstance(data, dict):
|
|
data = data.get("value")
|
|
|
|
if isinstance(data, str) and "(" in data and data.endswith(")"):
|
|
data = data.strip(")").split('(')[-1]
|
|
return super(LabeledChoiceField, self).to_internal_value(data)
|
|
|
|
|
|
class LabeledMultipleChoiceField(serializers.MultipleChoiceField):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.choice_mapper = {
|
|
key: value for key, value in self.choices.items()
|
|
}
|
|
|
|
def to_representation(self, keys):
|
|
if keys is None:
|
|
return keys
|
|
return [
|
|
{"value": key, "label": self.choice_mapper.get(key)}
|
|
for key in keys
|
|
]
|
|
|
|
def to_internal_value(self, data):
|
|
if not data:
|
|
return data
|
|
|
|
if isinstance(data[0], dict):
|
|
return [item.get("value") for item in data]
|
|
else:
|
|
return data
|
|
|
|
|
|
class LabelRelatedField(serializers.RelatedField):
|
|
def __init__(self, **kwargs):
|
|
queryset = kwargs.pop("queryset", None)
|
|
if queryset is None:
|
|
from labels.models import LabeledResource
|
|
queryset = LabeledResource.objects.all()
|
|
|
|
kwargs = {**kwargs}
|
|
read_only = kwargs.get("read_only", False)
|
|
if not read_only:
|
|
kwargs["queryset"] = queryset
|
|
super().__init__(**kwargs)
|
|
|
|
def to_file_representation(self, value):
|
|
if value is None:
|
|
return value
|
|
return "{}:{}".format(value.get('name'), value.get('value'))
|
|
|
|
def to_representation(self, value):
|
|
if value is None:
|
|
return value
|
|
label = value.label
|
|
if not label:
|
|
return None
|
|
return {'id': label.id, 'name': label.name, 'value': label.value, 'color': label.color}
|
|
|
|
def to_internal_value(self, data):
|
|
from labels.models import LabeledResource, Label
|
|
if data is None:
|
|
return data
|
|
if isinstance(data, dict) and (data.get("id") or data.get("pk")):
|
|
pk = data.get("id") or data.get("pk")
|
|
label = Label.objects.get(pk=pk)
|
|
elif is_uuid(data):
|
|
label = Label.objects.get(pk=data)
|
|
else:
|
|
if isinstance(data, dict):
|
|
k = data.get("name")
|
|
v = data.get("value")
|
|
elif isinstance(data, str) and ":" in data:
|
|
k, v = [x.strip() for x in data.split(":", 1)]
|
|
else:
|
|
raise serializers.ValidationError(_("Invalid data type"))
|
|
label, __ = Label.objects.get_or_create(name=k, value=v, defaults={'name': k, 'value': v})
|
|
return LabeledResource(label=label)
|
|
|
|
|
|
class ObjectRelatedField(serializers.RelatedField):
|
|
default_error_messages = {
|
|
"required": _("This field is required."),
|
|
"does_not_exist": _('Invalid pk "{pk_value}" - object does not exist.'),
|
|
"incorrect_type": _("Incorrect type. Expected pk value, received {data_type}."),
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
self.attrs = kwargs.pop("attrs", None) or ("id", "name")
|
|
self.many = kwargs.get("many", False)
|
|
super().__init__(**kwargs)
|
|
|
|
def to_representation(self, value):
|
|
data = {}
|
|
for attr in self.attrs:
|
|
if not hasattr(value, attr):
|
|
continue
|
|
data[attr] = getattr(value, attr)
|
|
return data
|
|
|
|
def to_internal_value(self, data):
|
|
queryset = self.get_queryset()
|
|
if isinstance(data, Model):
|
|
return queryset.get(pk=data.pk)
|
|
|
|
if not isinstance(data, dict):
|
|
pk = data
|
|
else:
|
|
pk = data.get("id") or data.get("pk") or data.get(self.attrs[0])
|
|
|
|
try:
|
|
if isinstance(data, bool):
|
|
raise TypeError
|
|
return queryset.get(pk=pk)
|
|
except ObjectDoesNotExist:
|
|
self.fail("does_not_exist", pk_value=pk)
|
|
except (TypeError, ValueError):
|
|
self.fail("incorrect_type", data_type=type(pk).__name__)
|
|
|
|
|
|
class TreeChoicesField(serializers.MultipleChoiceField):
|
|
def __init__(self, choice_cls, **kwargs):
|
|
assert issubclass(choice_cls, TreeChoices)
|
|
choices = [(c.name, c.label) for c in choice_cls]
|
|
self.tree = choice_cls.tree()
|
|
self._choice_cls = choice_cls
|
|
super().__init__(choices=choices, **kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
if not data:
|
|
return data
|
|
if isinstance(data[0], dict):
|
|
return [item.get("value") for item in data]
|
|
else:
|
|
return data
|
|
|
|
|
|
class BitChoicesField(TreeChoicesField):
|
|
"""
|
|
位字段
|
|
"""
|
|
|
|
def to_representation(self, value):
|
|
if isinstance(value, list) and len(value) == 1:
|
|
# Swagger 会使用 field.choices.keys() 迭代传递进来
|
|
return [
|
|
{"value": c.name, "label": c.label}
|
|
for c in self._choice_cls
|
|
if c.name == value[0]
|
|
]
|
|
return [
|
|
{"value": c.name, "label": c.label}
|
|
for c in self._choice_cls
|
|
if c.value & value == c.value
|
|
]
|
|
|
|
def to_internal_value(self, data):
|
|
if not isinstance(data, list):
|
|
raise serializers.ValidationError(_("Invalid data type, should be list"))
|
|
value = 0
|
|
if not data:
|
|
return value
|
|
if isinstance(data[0], dict):
|
|
data = [d["value"] for d in data]
|
|
# 所有的
|
|
if "all" in data:
|
|
for c in self._choice_cls:
|
|
value |= c.value
|
|
return value
|
|
|
|
name_value_map = {c.name: c.value for c in self._choice_cls}
|
|
for name in data:
|
|
if name not in name_value_map:
|
|
raise serializers.ValidationError(_("Invalid choice: {}").format(name))
|
|
value |= name_value_map[name]
|
|
return value
|
|
|
|
def run_validation(self, data=empty):
|
|
"""
|
|
备注:
|
|
创建授权规则不包含 actions 字段时, 会使用默认值(AssetPermission 中设置),
|
|
会直接使用 ['connect', '...'] 等字段保存到数据库,导致类型错误
|
|
这里将获取到的值再执行一下 to_internal_value 方法, 转化为内部值
|
|
"""
|
|
data = super().run_validation(data)
|
|
if isinstance(data, int):
|
|
return data
|
|
value = self.to_internal_value(data)
|
|
self.run_validators(value)
|
|
return value
|
|
|
|
|
|
class PhoneField(serializers.CharField):
|
|
|
|
def to_internal_value(self, data):
|
|
if isinstance(data, dict):
|
|
code = data.get('code')
|
|
phone = data.get('phone', '')
|
|
if code and phone:
|
|
code = code.replace('+', '')
|
|
data = '+{}{}'.format(code, phone)
|
|
else:
|
|
data = phone
|
|
if data:
|
|
try:
|
|
phone = phonenumbers.parse(data, 'CN')
|
|
data = '+{}{}'.format(phone.country_code, phone.national_number)
|
|
except phonenumbers.NumberParseException:
|
|
data = '+86{}'.format(data)
|
|
|
|
return super().to_internal_value(data)
|
|
|
|
def to_representation(self, value):
|
|
try:
|
|
phone = phonenumbers.parse(value, 'CN')
|
|
value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number}
|
|
except phonenumbers.NumberParseException:
|
|
value = {'code': '+86', 'phone': value}
|
|
return value
|
|
|
|
|
|
class JSONManyToManyField(serializers.JSONField):
|
|
def to_representation(self, manager):
|
|
if manager is None:
|
|
return manager
|
|
value = manager.value
|
|
if not isinstance(value, dict):
|
|
return {"type": "ids", "ids": []}
|
|
if value.get("type") == "ids":
|
|
valid_ids = manager.all().values_list("id", flat=True)
|
|
valid_ids = [str(i) for i in valid_ids]
|
|
return {"type": "ids", "ids": valid_ids}
|
|
return value
|
|
|
|
def to_internal_value(self, data):
|
|
if not data:
|
|
data = {}
|
|
try:
|
|
data = super().to_internal_value(data)
|
|
ModelJSONManyToManyField.check_value(data)
|
|
except ValueError as e:
|
|
raise serializers.ValidationError(e)
|
|
return super().to_internal_value(data)
|