mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-16 17:12:53 +00:00
Compare commits
12 Commits
ibuler-pat
...
v3.7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e75da94fc1 | ||
|
|
1ec2cd6087 | ||
|
|
257ef464f7 | ||
|
|
9cd3cc1da0 | ||
|
|
9520a23f4c | ||
|
|
a4ff9dace3 | ||
|
|
7a1214f358 | ||
|
|
2fdc4c613f | ||
|
|
df6525933a | ||
|
|
6aef27c824 | ||
|
|
26c3409d84 | ||
|
|
3c54c82ce9 |
@@ -36,9 +36,11 @@ ARG TOOLS=" \
|
|||||||
curl \
|
curl \
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
default-mysql-client \
|
default-mysql-client \
|
||||||
|
iputils-ping \
|
||||||
locales \
|
locales \
|
||||||
nmap \
|
nmap \
|
||||||
openssh-client \
|
openssh-client \
|
||||||
|
patch \
|
||||||
sshpass \
|
sshpass \
|
||||||
telnet \
|
telnet \
|
||||||
vim \
|
vim \
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='changesecretautomation',
|
model_name='changesecretautomation',
|
||||||
name='secret_strategy',
|
name='secret_strategy',
|
||||||
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='pushaccountautomation',
|
model_name='pushaccountautomation',
|
||||||
name='secret_strategy',
|
name='secret_strategy',
|
||||||
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -29,6 +29,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='accounttemplate',
|
model_name='accounttemplate',
|
||||||
name='secret_strategy',
|
name='secret_strategy',
|
||||||
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class VaultManagerMixin(models.Manager):
|
|||||||
post_save.send(obj.__class__, instance=obj, created=True)
|
post_save.send(obj.__class__, instance=obj, created=True)
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def bulk_update(self, objs, batch_size=None, ignore_conflicts=False):
|
def bulk_update(self, objs, fields, batch_size=None):
|
||||||
objs = super().bulk_update(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts)
|
objs = super().bulk_update(objs, fields, batch_size=batch_size)
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
post_save.send(obj.__class__, instance=obj, created=False)
|
post_save.send(obj.__class__, instance=obj, created=False)
|
||||||
return objs
|
return objs
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
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
|
||||||
|
|
||||||
|
from accounts.const import SecretStrategy, SecretType
|
||||||
from accounts.models import AccountTemplate, Account
|
from accounts.models import AccountTemplate, Account
|
||||||
|
from accounts.utils import SecretGenerator
|
||||||
from common.serializers import SecretReadableMixin
|
from common.serializers import SecretReadableMixin
|
||||||
from common.serializers.fields import ObjectRelatedField
|
from common.serializers.fields import ObjectRelatedField
|
||||||
from .base import BaseAccountSerializer
|
from .base import BaseAccountSerializer
|
||||||
@@ -55,9 +57,20 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
accounts = Account.objects.filter(**query_data)
|
accounts = Account.objects.filter(**query_data)
|
||||||
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
|
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_secret(attrs):
|
||||||
|
secret_type = attrs.get('secret_type', SecretType.PASSWORD)
|
||||||
|
secret_strategy = attrs.get('secret_strategy', SecretStrategy.custom)
|
||||||
|
password_rules = attrs.get('password_rules')
|
||||||
|
if secret_strategy != SecretStrategy.random:
|
||||||
|
return
|
||||||
|
generator = SecretGenerator(secret_strategy, secret_type, password_rules)
|
||||||
|
attrs['secret'] = generator.get_secret()
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
self._is_sync_account = attrs.pop('is_sync_account', None)
|
self._is_sync_account = attrs.pop('is_sync_account', None)
|
||||||
attrs = super().validate(attrs)
|
attrs = super().validate(attrs)
|
||||||
|
self.generate_secret(attrs)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def migrate_asset_accounts(apps, schema_editor):
|
|||||||
count += len(auth_books)
|
count += len(auth_books)
|
||||||
# auth book 和 account 相同的属性
|
# auth book 和 account 相同的属性
|
||||||
same_attrs = [
|
same_attrs = [
|
||||||
'id', 'username', 'comment', 'date_created', 'date_updated',
|
'username', 'comment', 'date_created', 'date_updated',
|
||||||
'created_by', 'asset_id', 'org_id',
|
'created_by', 'asset_id', 'org_id',
|
||||||
]
|
]
|
||||||
# 认证的属性,可能是 auth_book 的,可能是 system_user 的
|
# 认证的属性,可能是 auth_book 的,可能是 system_user 的
|
||||||
|
|||||||
@@ -402,12 +402,7 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin):
|
|||||||
return Asset.objects.filter(q).distinct()
|
return Asset.objects.filter(q).distinct()
|
||||||
|
|
||||||
def get_assets_amount(self):
|
def get_assets_amount(self):
|
||||||
q = Q(node__key__startswith=f'{self.key}:') | Q(node__key=self.key)
|
return self.get_all_assets().count()
|
||||||
return self.assets.through.objects.filter(q).count()
|
|
||||||
|
|
||||||
def get_assets_account_by_children(self):
|
|
||||||
children = self.get_all_children().values_list()
|
|
||||||
return self.assets.through.objects.filter(node_id__in=children).count()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_all_assets_by_key_v2(cls, key):
|
def get_node_all_assets_by_key_v2(cls, key):
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from common.permissions import ServiceAccountSignaturePermission
|
||||||
from .base import JMSBaseAuthBackend
|
from .base import JMSBaseAuthBackend
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
@@ -18,6 +19,10 @@ class PublicKeyAuthBackend(JMSBaseAuthBackend):
|
|||||||
def authenticate(self, request, username=None, public_key=None, **kwargs):
|
def authenticate(self, request, username=None, public_key=None, **kwargs):
|
||||||
if not public_key:
|
if not public_key:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
permission = ServiceAccountSignaturePermission()
|
||||||
|
if not permission.has_permission(request, None):
|
||||||
|
return None
|
||||||
if username is None:
|
if username is None:
|
||||||
username = kwargs.get(UserModel.USERNAME_FIELD)
|
username = kwargs.get(UserModel.USERNAME_FIELD)
|
||||||
try:
|
try:
|
||||||
@@ -26,7 +31,7 @@ class PublicKeyAuthBackend(JMSBaseAuthBackend):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if user.check_public_key(public_key) and \
|
if user.check_public_key(public_key) and \
|
||||||
self.user_can_authenticate(user):
|
self.user_can_authenticate(user):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_user(self, user_id):
|
def get_user(self, user_id):
|
||||||
|
|||||||
@@ -310,12 +310,6 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
|||||||
age = self.request.session.get_expiry_age()
|
age = self.request.session.get_expiry_age()
|
||||||
self.request.session.set_expiry(age)
|
self.request.session.set_expiry(age)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
response = super().get(request, *args, **kwargs)
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
response.set_cookie('jms_username', request.user.username)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
user = self.get_user_from_session()
|
user = self.get_user_from_session()
|
||||||
|
|||||||
@@ -86,3 +86,38 @@ class UserConfirmation(permissions.BasePermission):
|
|||||||
min_level = ConfirmType.values.index(confirm_type) + 1
|
min_level = ConfirmType.values.index(confirm_type) + 1
|
||||||
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
|
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
|
||||||
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
|
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccountSignaturePermission(permissions.BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
from authentication.models import AccessKey
|
||||||
|
from common.utils.crypto import get_aes_crypto
|
||||||
|
signature = request.META.get('HTTP_X_JMS_SVC', '')
|
||||||
|
if not signature or not signature.startswith('Sign'):
|
||||||
|
return False
|
||||||
|
data = signature[4:].strip()
|
||||||
|
if not data or ':' not in data:
|
||||||
|
return False
|
||||||
|
ak_id, time_sign = data.split(':', 1)
|
||||||
|
if not ak_id or not time_sign:
|
||||||
|
return False
|
||||||
|
ak = AccessKey.objects.filter(id=ak_id).first()
|
||||||
|
if not ak or not ak.is_active:
|
||||||
|
return False
|
||||||
|
if not ak.user or not ak.user.is_active or not ak.user.is_service_account:
|
||||||
|
return False
|
||||||
|
aes = get_aes_crypto(str(ak.secret).replace('-', ''), mode='ECB')
|
||||||
|
try:
|
||||||
|
timestamp = aes.decrypt(time_sign)
|
||||||
|
if not timestamp or not timestamp.isdigit():
|
||||||
|
return False
|
||||||
|
timestamp = int(timestamp)
|
||||||
|
interval = abs(int(time.time()) - timestamp)
|
||||||
|
if interval > 30:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
return False
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
self.target = target
|
self.target = target
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.key = key or self.KEY_TMPL.format(target)
|
self.key = key or self.KEY_TMPL.format(target)
|
||||||
|
self.verify_key = self.key + '_verify'
|
||||||
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
|
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
|
||||||
self.other_args = kwargs
|
self.other_args = kwargs
|
||||||
|
|
||||||
@@ -47,6 +48,11 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def verify(self, code):
|
def verify(self, code):
|
||||||
|
times = cache.get(self.verify_key, 0)
|
||||||
|
if times >= 3:
|
||||||
|
self.__clear()
|
||||||
|
raise CodeExpired
|
||||||
|
cache.set(self.verify_key, times + 1, timeout=self.timeout)
|
||||||
right = cache.get(self.key)
|
right = cache.get(self.key)
|
||||||
if not right:
|
if not right:
|
||||||
raise CodeExpired
|
raise CodeExpired
|
||||||
@@ -59,6 +65,7 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
|
|
||||||
def __clear(self):
|
def __clear(self):
|
||||||
cache.delete(self.key)
|
cache.delete(self.key)
|
||||||
|
cache.delete(self.verify_key)
|
||||||
|
|
||||||
def __ttl(self):
|
def __ttl(self):
|
||||||
return cache.ttl(self.key)
|
return cache.ttl(self.key)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='applet',
|
model_name='applet',
|
||||||
name='edition',
|
name='edition',
|
||||||
field=models.CharField(choices=[('community', 'Community'), ('enterprise', 'Enterprise')],
|
field=models.CharField(choices=[('community', 'Community edition'), ('enterprise', 'Enterprise')], default='community', max_length=128, verbose_name='Edition'),
|
||||||
default='community', max_length=128, verbose_name='Edition'),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ class UserForgotPasswordView(FormView):
|
|||||||
target = form.cleaned_data[form_type]
|
target = form.cleaned_data[form_type]
|
||||||
code = form.cleaned_data['code']
|
code = form.cleaned_data['code']
|
||||||
|
|
||||||
|
query_key = form_type
|
||||||
|
if form_type == 'sms':
|
||||||
|
query_key = 'phone'
|
||||||
|
target = target.lstrip('+')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sender_util = SendAndVerifyCodeUtil(target, backend=form_type)
|
sender_util = SendAndVerifyCodeUtil(target, backend=form_type)
|
||||||
sender_util.verify(code)
|
sender_util.verify(code)
|
||||||
@@ -121,7 +126,6 @@ class UserForgotPasswordView(FormView):
|
|||||||
form.add_error('code', str(e))
|
form.add_error('code', str(e))
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
query_key = 'phone' if form_type == 'sms' else form_type
|
|
||||||
user = get_object_or_none(User, **{'username': username, query_key: target})
|
user = get_object_or_none(User, **{'username': username, query_key: target})
|
||||||
if not user:
|
if not user:
|
||||||
form.add_error('code', _('No user matched'))
|
form.add_error('code', _('No user matched'))
|
||||||
|
|||||||
Reference in New Issue
Block a user