mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 16:42:34 +00:00
Compare commits
16 Commits
ibuler-pat
...
v3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f96d9eb8 | ||
|
|
e4f117e753 | ||
|
|
adfe4faf98 | ||
|
|
d8bf45c760 | ||
|
|
2245c13017 | ||
|
|
06a24b1a04 | ||
|
|
339d872d37 | ||
|
|
012da07999 | ||
|
|
d6c22dd485 | ||
|
|
d1e5559a7b | ||
|
|
5fddd0b02b | ||
|
|
11232871a2 | ||
|
|
3f6c3c3dbb | ||
|
|
675516f6da | ||
|
|
234554060a | ||
|
|
afc7232bd9 |
@@ -81,7 +81,7 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
i = data.get('id')
|
i = data.get('id') or data.get('pk')
|
||||||
else:
|
else:
|
||||||
i = data
|
i = data
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ class AssetAccountSerializer(
|
|||||||
template = serializers.BooleanField(
|
template = serializers.BooleanField(
|
||||||
default=False, label=_("Template"), write_only=True
|
default=False, label=_("Template"), write_only=True
|
||||||
)
|
)
|
||||||
name = serializers.CharField(max_length=128, required=True, label=_("Name"))
|
name = serializers.CharField(max_length=128, required=False, label=_("Name"))
|
||||||
|
secret_type = serializers.CharField(max_length=64, default='password', label=_("Secret type"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class BaseFileParser(BaseParser):
|
|||||||
return {'pk': obj_id, 'name': obj_name}
|
return {'pk': obj_id, 'name': obj_name}
|
||||||
|
|
||||||
def parse_value(self, field, value):
|
def parse_value(self, field, value):
|
||||||
if value is '-':
|
if value == '-' and field and field.allow_null:
|
||||||
return None
|
return None
|
||||||
elif hasattr(field, 'to_file_internal_value'):
|
elif hasattr(field, 'to_file_internal_value'):
|
||||||
value = field.to_file_internal_value(value)
|
value = field.to_file_internal_value(value)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import phonenumbers
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@@ -17,6 +18,7 @@ __all__ = [
|
|||||||
"BitChoicesField",
|
"BitChoicesField",
|
||||||
"TreeChoicesField",
|
"TreeChoicesField",
|
||||||
"LabeledMultipleChoiceField",
|
"LabeledMultipleChoiceField",
|
||||||
|
"PhoneField",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -201,3 +203,11 @@ class BitChoicesField(TreeChoicesField):
|
|||||||
value = self.to_internal_value(data)
|
value = self.to_internal_value(data)
|
||||||
self.run_validators(value)
|
self.run_validators(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class PhoneField(serializers.CharField):
|
||||||
|
def to_representation(self, value):
|
||||||
|
if value:
|
||||||
|
phone = phonenumbers.parse(value, 'CN')
|
||||||
|
value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number}
|
||||||
|
return value
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import phonenumbers
|
||||||
|
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.validators import (
|
from rest_framework.validators import (
|
||||||
UniqueTogetherValidator, ValidationError
|
UniqueTogetherValidator, ValidationError
|
||||||
)
|
)
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from phonenumbers.phonenumberutil import NumberParseException
|
||||||
|
|
||||||
from common.utils.strings import no_special_chars
|
from common.utils.strings import no_special_chars
|
||||||
|
|
||||||
@@ -42,9 +45,14 @@ class NoSpecialChars:
|
|||||||
|
|
||||||
|
|
||||||
class PhoneValidator:
|
class PhoneValidator:
|
||||||
pattern = re.compile(r"^1[3456789]\d{9}$")
|
|
||||||
message = _('The mobile phone number format is incorrect')
|
message = _('The mobile phone number format is incorrect')
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if not self.pattern.match(value):
|
try:
|
||||||
|
phone = phonenumbers.parse(value, 'CN')
|
||||||
|
valid = phonenumbers.is_valid_number(phone)
|
||||||
|
except NumberParseException:
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if not valid:
|
||||||
raise serializers.ValidationError(self.message)
|
raise serializers.ValidationError(self.message)
|
||||||
|
|||||||
@@ -139,12 +139,12 @@ class JMSInventory:
|
|||||||
self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway)
|
self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway)
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def get_asset_accounts(self, asset):
|
def get_asset_sorted_accounts(self, asset):
|
||||||
from assets.const import Connectivity
|
accounts = list(asset.accounts.filter(is_active=True))
|
||||||
accounts = asset.accounts.filter(is_active=True).order_by('-privileged', '-date_updated')
|
connectivity_score = {'ok': 2, '-': 1, 'err': 0}
|
||||||
accounts_connectivity_ok = list(accounts.filter(connectivity=Connectivity.OK))
|
sort_key = lambda x: (x.privileged, connectivity_score.get(x.connectivity, 0), x.date_updated)
|
||||||
accounts_connectivity_no = list(accounts.exclude(connectivity=Connectivity.OK))
|
accounts_sorted = sorted(accounts, key=sort_key, reverse=True)
|
||||||
return accounts_connectivity_ok + accounts_connectivity_no
|
return accounts_sorted
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_account_prefer(account_prefer):
|
def get_account_prefer(account_prefer):
|
||||||
@@ -163,28 +163,23 @@ class JMSInventory:
|
|||||||
return account
|
return account
|
||||||
|
|
||||||
def select_account(self, asset):
|
def select_account(self, asset):
|
||||||
accounts = self.get_asset_accounts(asset)
|
accounts = self.get_asset_sorted_accounts(asset)
|
||||||
if not accounts or self.account_policy == 'skip':
|
if not accounts:
|
||||||
return None
|
return None
|
||||||
account_selected = None
|
|
||||||
|
|
||||||
# 首先找到特权账号
|
refer_account = self.get_refer_account(accounts)
|
||||||
privileged_accounts = list(filter(lambda account: account.privileged, accounts))
|
if refer_account:
|
||||||
|
return refer_account
|
||||||
|
|
||||||
# 不同类型的账号选择,优先使用提供的名称
|
account_selected = accounts[0]
|
||||||
refer_privileged_account = self.get_refer_account(privileged_accounts)
|
if self.account_policy == 'skip':
|
||||||
if self.account_policy in ['privileged_only', 'privileged_first']:
|
return None
|
||||||
first_privileged = privileged_accounts[0] if privileged_accounts else None
|
elif self.account_policy == 'privileged_first':
|
||||||
account_selected = refer_privileged_account or first_privileged
|
|
||||||
|
|
||||||
# 此策略不管是否匹配到账号都需强制返回
|
|
||||||
if self.account_policy == 'privileged_only':
|
|
||||||
return account_selected
|
return account_selected
|
||||||
|
elif self.account_policy == 'privileged_only' and account_selected.privileged:
|
||||||
if not account_selected:
|
return account_selected
|
||||||
account_selected = self.get_refer_account(accounts)
|
else:
|
||||||
|
return None
|
||||||
return account_selected or accounts[0]
|
|
||||||
|
|
||||||
def generate(self, path_dir):
|
def generate(self, path_dir):
|
||||||
hosts = []
|
hosts = []
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ class JMSPermedInventory(JMSInventory):
|
|||||||
self.user = user
|
self.user = user
|
||||||
self.assets_accounts_mapper = self.get_assets_accounts_mapper()
|
self.assets_accounts_mapper = self.get_assets_accounts_mapper()
|
||||||
|
|
||||||
def get_asset_accounts(self, asset):
|
def get_asset_sorted_accounts(self, asset):
|
||||||
return self.assets_accounts_mapper.get(asset.id, [])
|
accounts = self.assets_accounts_mapper.get(asset.id, [])
|
||||||
|
return list(accounts)
|
||||||
|
|
||||||
def get_assets_accounts_mapper(self):
|
def get_assets_accounts_mapper(self):
|
||||||
mapper = defaultdict(set)
|
mapper = defaultdict(set)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.db.models import Q
|
||||||
from django.db.models import Q, QuerySet
|
|
||||||
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
|
||||||
|
|
||||||
@@ -119,7 +118,10 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
|
|||||||
return
|
return
|
||||||
assets = self.get_all_assets(nodes, assets)
|
assets = self.get_all_assets(nodes, assets)
|
||||||
accounts = self.create_accounts(assets)
|
accounts = self.create_accounts(assets)
|
||||||
push_accounts_to_assets_task.delay([str(account.id) for account in accounts])
|
account_ids = [str(account.id) for account in accounts]
|
||||||
|
slice_count = 20
|
||||||
|
for i in range(0, len(account_ids), slice_count):
|
||||||
|
push_accounts_to_assets_task.delay(account_ids[i:i + slice_count])
|
||||||
|
|
||||||
def validate_accounts(self, usernames: list[str]):
|
def validate_accounts(self, usernames: list[str]):
|
||||||
template_ids = []
|
template_ids = []
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
#
|
#
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.db import transaction
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ops.celery.decorator import after_app_ready_start
|
from ops.celery.decorator import after_app_ready_start
|
||||||
from ops.celery.utils import (
|
from ops.celery.utils import (
|
||||||
@@ -22,6 +22,7 @@ def sync_ldap_user():
|
|||||||
|
|
||||||
|
|
||||||
@shared_task(verbose_name=_('Import ldap user'))
|
@shared_task(verbose_name=_('Import ldap user'))
|
||||||
|
@transaction.atomic
|
||||||
def import_ldap_user():
|
def import_ldap_user():
|
||||||
logger.info("Start import ldap user task")
|
logger.info("Start import ldap user task")
|
||||||
util_server = LDAPServerUtil()
|
util_server = LDAPServerUtil()
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import phonenumbers
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
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.serializers import CommonBulkSerializerMixin
|
from common.serializers import CommonBulkSerializerMixin
|
||||||
from common.serializers.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField
|
from common.serializers.fields import (
|
||||||
|
EncryptedField, ObjectRelatedField, LabeledChoiceField, PhoneField
|
||||||
|
)
|
||||||
from common.utils import pretty_string, get_logger
|
from common.utils import pretty_string, get_logger
|
||||||
from common.validators import PhoneValidator
|
from common.validators import PhoneValidator
|
||||||
from rbac.builtin import BuiltinRole
|
from rbac.builtin import BuiltinRole
|
||||||
@@ -101,6 +105,9 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
|
|||||||
label=_("Password"), required=False, allow_blank=True,
|
label=_("Password"), required=False, allow_blank=True,
|
||||||
allow_null=True, max_length=1024,
|
allow_null=True, max_length=1024,
|
||||||
)
|
)
|
||||||
|
phone = PhoneField(
|
||||||
|
validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_("Phone")
|
||||||
|
)
|
||||||
custom_m2m_fields = {
|
custom_m2m_fields = {
|
||||||
"system_roles": [BuiltinRole.system_user],
|
"system_roles": [BuiltinRole.system_user],
|
||||||
"org_roles": [BuiltinRole.org_user],
|
"org_roles": [BuiltinRole.org_user],
|
||||||
@@ -167,7 +174,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
|
|||||||
"created_by": {"read_only": True, "allow_blank": True},
|
"created_by": {"read_only": True, "allow_blank": True},
|
||||||
"role": {"default": "User"},
|
"role": {"default": "User"},
|
||||||
"is_otp_secret_key_bound": {"label": _("Is OTP bound")},
|
"is_otp_secret_key_bound": {"label": _("Is OTP bound")},
|
||||||
"phone": {"validators": [PhoneValidator()]},
|
|
||||||
'mfa_level': {'label': _("MFA level")},
|
'mfa_level': {'label': _("MFA level")},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pycparser==2.21
|
|||||||
cryptography==38.0.4
|
cryptography==38.0.4
|
||||||
pycryptodome==3.15.0
|
pycryptodome==3.15.0
|
||||||
pycryptodomex==3.15.0
|
pycryptodomex==3.15.0
|
||||||
|
phonenumbers==8.13.8
|
||||||
gmssl==3.2.1
|
gmssl==3.2.1
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
pyotp==2.6.0
|
pyotp==2.6.0
|
||||||
|
|||||||
Reference in New Issue
Block a user