mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-01-05 07:34:05 +00:00
Compare commits
11 Commits
v3.10.16-l
...
v3.10.17
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de90a72e6 | ||
|
|
2e7bd076f4 | ||
|
|
11f6fe0bf9 | ||
|
|
ae94648e80 | ||
|
|
94e08f3d96 | ||
|
|
8bedef92f0 | ||
|
|
e5bb28231a | ||
|
|
b5aeb24ae9 | ||
|
|
674ea7142f | ||
|
|
5ab7b99b9d | ||
|
|
9cd163c99d |
@@ -62,7 +62,7 @@ def create_accounts_activities(account, action='create'):
|
||||
|
||||
@receiver(post_save, sender=Account)
|
||||
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
||||
if not created or instance.source != Source.TEMPLATE:
|
||||
if not created:
|
||||
return
|
||||
push_accounts_if_need.delay(accounts=(instance,))
|
||||
create_accounts_activities(instance, action='create')
|
||||
|
||||
@@ -189,9 +189,13 @@ class ResourceActivityAPIView(generics.ListAPIView):
|
||||
'id', 'datetime', 'r_detail', 'r_detail_id',
|
||||
'r_user', 'r_action', 'r_type'
|
||||
)
|
||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||
if resource_id:
|
||||
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
||||
|
||||
org_q = Q()
|
||||
if not current_org.is_root():
|
||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||
if resource_id:
|
||||
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
||||
|
||||
with tmp_to_root_org():
|
||||
qs1 = self.get_operate_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||
qs2 = self.get_activity_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||
|
||||
@@ -14,7 +14,7 @@ from audits.handler import (
|
||||
create_or_update_operate_log, get_instance_dict_from_cache
|
||||
)
|
||||
from audits.utils import model_to_dict_for_operate_log as model_to_dict
|
||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, SKIP_SIGNAL
|
||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, OP_LOG_SKIP_SIGNAL
|
||||
from common.signals import django_ready
|
||||
from jumpserver.utils import current_request
|
||||
from ..const import MODELS_NEED_RECORD, ActionChoices
|
||||
@@ -77,7 +77,7 @@ def signal_of_operate_log_whether_continue(
|
||||
condition = True
|
||||
if not instance:
|
||||
condition = False
|
||||
if instance and getattr(instance, SKIP_SIGNAL, False):
|
||||
if instance and getattr(instance, OP_LOG_SKIP_SIGNAL, False):
|
||||
condition = False
|
||||
# 不记录组件的操作日志
|
||||
user = current_request.user if current_request else None
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import base64
|
||||
import requests
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -67,14 +68,6 @@ class OAuth2Backend(JMSModelBackend):
|
||||
response_data = response_data['data']
|
||||
return response_data
|
||||
|
||||
@staticmethod
|
||||
def get_query_dict(response_data, query_dict):
|
||||
query_dict.update({
|
||||
'uid': response_data.get('uid', ''),
|
||||
'access_token': response_data.get('access_token', '')
|
||||
})
|
||||
return query_dict
|
||||
|
||||
def authenticate(self, request, code=None, **kwargs):
|
||||
log_prompt = "Process authenticate [OAuth2Backend]: {}"
|
||||
logger.debug(log_prompt.format('Start'))
|
||||
@@ -83,29 +76,31 @@ class OAuth2Backend(JMSModelBackend):
|
||||
return None
|
||||
|
||||
query_dict = {
|
||||
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
||||
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'grant_type': 'authorization_code', 'code': code,
|
||||
'redirect_uri': build_absolute_uri(
|
||||
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
|
||||
)
|
||||
}
|
||||
if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
|
||||
separator = '&'
|
||||
else:
|
||||
separator = '?'
|
||||
separator = '&' if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT else '?'
|
||||
access_token_url = '{url}{separator}{query}'.format(
|
||||
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict)
|
||||
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT,
|
||||
separator=separator, query=urlencode(query_dict)
|
||||
)
|
||||
# token_method -> get, post(post_data), post_json
|
||||
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
|
||||
logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method))
|
||||
encoded_credentials = base64.b64encode(
|
||||
f"{settings.AUTH_OAUTH2_CLIENT_ID}:{settings.AUTH_OAUTH2_CLIENT_SECRET}".encode()
|
||||
).decode()
|
||||
headers = {
|
||||
'Accept': 'application/json'
|
||||
'Accept': 'application/json', 'Authorization': f'Basic {encoded_credentials}'
|
||||
}
|
||||
if token_method.startswith('post'):
|
||||
body_key = 'json' if token_method.endswith('json') else 'data'
|
||||
query_dict.update({
|
||||
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
||||
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
||||
})
|
||||
access_token_response = requests.post(
|
||||
access_token_url, headers=headers, **{body_key: query_dict}
|
||||
)
|
||||
@@ -121,22 +116,12 @@ class OAuth2Backend(JMSModelBackend):
|
||||
logger.error(log_prompt.format(error))
|
||||
return None
|
||||
|
||||
query_dict = self.get_query_dict(response_data, query_dict)
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {}'.format(response_data.get('access_token', ''))
|
||||
}
|
||||
|
||||
logger.debug(log_prompt.format('Get userinfo endpoint'))
|
||||
if '?' in settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT:
|
||||
separator = '&'
|
||||
else:
|
||||
separator = '?'
|
||||
userinfo_url = '{url}{separator}{query}'.format(
|
||||
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator=separator,
|
||||
query=urlencode(query_dict)
|
||||
)
|
||||
userinfo_url = settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT
|
||||
userinfo_response = requests.get(userinfo_url, headers=headers)
|
||||
try:
|
||||
userinfo_response.raise_for_status()
|
||||
|
||||
@@ -107,7 +107,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
# parameters because we won't be able to get a valid token for the user in that case.
|
||||
if (state is None and settings.AUTH_OPENID_USE_STATE) or code is None:
|
||||
logger.debug(log_prompt.format('Authorization code or state value is missing'))
|
||||
raise SuspiciousOperation('Authorization code or state value is missing')
|
||||
return
|
||||
|
||||
# Prepares the token payload that will be used to request an authentication token to the
|
||||
# token endpoint of the OIDC provider.
|
||||
@@ -165,7 +165,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
error = "Json token response error, token response " \
|
||||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
raise ParseError(error)
|
||||
return
|
||||
|
||||
# Validates the token.
|
||||
logger.debug(log_prompt.format('Validate ID Token'))
|
||||
@@ -206,7 +206,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
error = "Json claims response error, claims response " \
|
||||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
raise ParseError(error)
|
||||
return
|
||||
|
||||
logger.debug(log_prompt.format('Get or create user from claims'))
|
||||
user, created = self.get_or_create_user_from_claims(request, claims)
|
||||
|
||||
@@ -16,4 +16,4 @@ POST_CLEAR = 'post_clear'
|
||||
POST_PREFIX = 'post'
|
||||
PRE_PREFIX = 'pre'
|
||||
|
||||
SKIP_SIGNAL = 'skip_signal'
|
||||
OP_LOG_SKIP_SIGNAL = 'operate_log_skip_signal'
|
||||
|
||||
@@ -17,7 +17,7 @@ from django.db.models import F, ExpressionWrapper, CASCADE
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..const.signals import SKIP_SIGNAL
|
||||
from ..const.signals import OP_LOG_SKIP_SIGNAL
|
||||
|
||||
|
||||
class ChoicesMixin:
|
||||
@@ -82,7 +82,7 @@ def CASCADE_SIGNAL_SKIP(collector, field, sub_objs, using):
|
||||
# 级联删除时,操作日志标记不保存,以免用户混淆
|
||||
try:
|
||||
for obj in sub_objs:
|
||||
setattr(obj, SKIP_SIGNAL, True)
|
||||
setattr(obj, OP_LOG_SKIP_SIGNAL, True)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG']
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
VERSION = '2.0.0'
|
||||
VERSION = 'v3.10.17'
|
||||
CONFIG = ConfigManager.load_user_config()
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.conf import settings
|
||||
from common.const.crontab import CRONTAB_AT_AM_TWO
|
||||
from common.utils import get_logger, get_object_or_none, get_log_keep_day
|
||||
from ops.celery import app
|
||||
from ops.const import Types
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
|
||||
@@ -52,14 +53,13 @@ def _run_ops_job_execution(execution):
|
||||
activity_callback=job_task_activity_callback
|
||||
)
|
||||
def run_ops_job(job_id):
|
||||
if not settings.SECURITY_COMMAND_EXECUTION:
|
||||
return
|
||||
with tmp_to_root_org():
|
||||
job = get_object_or_none(Job, id=job_id)
|
||||
if not job:
|
||||
logger.error("Did not get the execution: {}".format(job_id))
|
||||
return
|
||||
|
||||
if not settings.SECURITY_COMMAND_EXECUTION and job.type != Types.upload_file:
|
||||
return
|
||||
with tmp_to_org(job.org):
|
||||
execution = job.create_execution()
|
||||
execution.creator = job.creator
|
||||
@@ -80,14 +80,14 @@ def job_execution_task_activity_callback(self, execution_id, *args, **kwargs):
|
||||
activity_callback=job_execution_task_activity_callback
|
||||
)
|
||||
def run_ops_job_execution(execution_id, **kwargs):
|
||||
if not settings.SECURITY_COMMAND_EXECUTION:
|
||||
return
|
||||
with tmp_to_root_org():
|
||||
execution = get_object_or_none(JobExecution, id=execution_id)
|
||||
|
||||
if not execution:
|
||||
logger.error("Did not get the execution: {}".format(execution_id))
|
||||
return
|
||||
if not settings.SECURITY_COMMAND_EXECUTION and execution.job.type != Types.upload_file:
|
||||
return
|
||||
_run_ops_job_execution(execution)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class OrgSerializer(ModelSerializer):
|
||||
class CurrentOrgSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = ['id', 'name', 'is_default', 'is_root', 'comment']
|
||||
fields = ['id', 'name', 'is_default', 'is_root', 'is_system', 'comment']
|
||||
|
||||
|
||||
class CurrentOrgDefault:
|
||||
|
||||
@@ -24,13 +24,13 @@ def get_org_from_request(request):
|
||||
# 其次session
|
||||
if not oid:
|
||||
oid = request.session.get("oid")
|
||||
|
||||
|
||||
if oid and oid.lower() == 'default':
|
||||
return Organization.default()
|
||||
|
||||
if oid and oid.lower() == 'root':
|
||||
return Organization.root()
|
||||
|
||||
|
||||
if oid and oid.lower() == 'system':
|
||||
return Organization.system()
|
||||
|
||||
@@ -39,14 +39,14 @@ def get_org_from_request(request):
|
||||
if org and org.internal:
|
||||
# 内置组织直接返回
|
||||
return org
|
||||
|
||||
|
||||
if not settings.XPACK_ENABLED:
|
||||
# 社区版用户只能使用默认组织
|
||||
return Organization.default()
|
||||
|
||||
|
||||
if not org and request.user.is_authenticated:
|
||||
# 企业版用户优先从自己有权限的组织中获取
|
||||
org = request.user.orgs.first()
|
||||
org = request.user.orgs.exclude(id=Organization.SYSTEM_ID).first()
|
||||
|
||||
if not org:
|
||||
org = Organization.default()
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import logging
|
||||
from django.db.models import Q
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_filters import rest_framework as filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView, Response
|
||||
from django_filters import rest_framework as filters
|
||||
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.api import JMSBulkModelViewSet
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.exceptions import JMSException
|
||||
from common.permissions import WithBootstrapToken
|
||||
from common.permissions import WithBootstrapToken, IsServiceAccount
|
||||
from jumpserver.conf import ConfigCrypto
|
||||
from terminal import serializers
|
||||
from terminal.models import Terminal
|
||||
|
||||
__all__ = [
|
||||
'TerminalViewSet', 'TerminalConfig',
|
||||
'TerminalRegistrationApi',
|
||||
'TerminalRegistrationApi', 'EncryptedTerminalConfig'
|
||||
]
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
@@ -89,3 +91,17 @@ class TerminalRegistrationApi(generics.CreateAPIView):
|
||||
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
class EncryptedTerminalConfig(generics.CreateAPIView):
|
||||
serializer_class = serializers.EncryptedConfigSerializer
|
||||
permission_classes = [IsServiceAccount]
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
encrypt_key = serializer.validated_data['secret_encrypt_key']
|
||||
encrypted_value = serializer.validated_data['encrypted_value']
|
||||
config_crypto = ConfigCrypto(encrypt_key)
|
||||
value = config_crypto.decrypt(encrypted_value)
|
||||
return Response(data={'value': value}, status=200)
|
||||
|
||||
@@ -105,6 +105,8 @@ class AppletMethod:
|
||||
if not has_applet_hosts:
|
||||
return methods
|
||||
applets = Applet.objects.filter(is_active=True)
|
||||
if not settings.XPACK_LICENSE_IS_VALID:
|
||||
applets = applets.filter(builtin=True)
|
||||
for applet in applets:
|
||||
for protocol in applet.protocols:
|
||||
methods[protocol].append({
|
||||
@@ -125,6 +127,8 @@ class VirtualAppMethod:
|
||||
methods = defaultdict(list)
|
||||
if not getattr(settings, 'VIRTUAL_APP_ENABLED'):
|
||||
return methods
|
||||
if not settings.XPACK_LICENSE_IS_VALID:
|
||||
return methods
|
||||
virtual_apps = VirtualApp.objects.filter(is_active=True)
|
||||
for virtual_app in virtual_apps:
|
||||
for protocol in virtual_app.protocols:
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.const.signals import SKIP_SIGNAL
|
||||
from common.const.signals import OP_LOG_SKIP_SIGNAL
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from orgs.utils import tmp_to_root_org
|
||||
@@ -152,7 +152,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel):
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.user:
|
||||
setattr(self.user, SKIP_SIGNAL, True)
|
||||
setattr(self.user, OP_LOG_SKIP_SIGNAL, True)
|
||||
self.user.delete()
|
||||
self.name = self.name + '_' + uuid.uuid4().hex[:8]
|
||||
self.user = None
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from assets.models import Asset
|
||||
from common.const import OP_LOG_SKIP_SIGNAL
|
||||
from common.utils import get_object_or_none, lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from terminal.backends import get_multi_command_storage
|
||||
@@ -203,6 +204,7 @@ class Session(OrgModelMixin):
|
||||
if self.need_update_cmd_amount:
|
||||
cmd_amount = self.compute_command_amount()
|
||||
self.cmd_amount = cmd_amount
|
||||
setattr(self, OP_LOG_SKIP_SIGNAL, True)
|
||||
self.save()
|
||||
elif self.need_compute_cmd_amount:
|
||||
cmd_amount = self.compute_command_amount()
|
||||
|
||||
@@ -147,3 +147,8 @@ class ConnectMethodSerializer(serializers.Serializer):
|
||||
type = serializers.CharField(max_length=128)
|
||||
endpoint_protocol = serializers.CharField(max_length=128)
|
||||
component = serializers.CharField(max_length=128)
|
||||
|
||||
|
||||
class EncryptedConfigSerializer(serializers.Serializer):
|
||||
secret_encrypt_key = serializers.CharField(max_length=128)
|
||||
encrypted_value = serializers.CharField(max_length=128)
|
||||
|
||||
@@ -54,6 +54,7 @@ urlpatterns = [
|
||||
# components
|
||||
path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'),
|
||||
path('components/connect-methods/', api.ConnectMethodListApi.as_view(), name='connect-methods'),
|
||||
path('encrypted-config/', api.EncryptedTerminalConfig.as_view(), name='encrypted-terminal-config'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -97,10 +97,10 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
|
||||
def convert_status_metrics(metrics):
|
||||
return {
|
||||
'any': metrics['total'],
|
||||
'normal': metrics['normal'],
|
||||
'high': metrics['high'],
|
||||
'critical': metrics['critical'],
|
||||
'offline': metrics['offline']
|
||||
'normal': len(metrics['normal']),
|
||||
'high': len(metrics['high']),
|
||||
'critical': len(metrics['critical']),
|
||||
'offline': len(metrics['offline'])
|
||||
}
|
||||
|
||||
def get_component_status_metrics(self):
|
||||
@@ -112,8 +112,8 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
|
||||
tp = metric['type']
|
||||
prometheus_metrics.append(f'## 组件: {tp}')
|
||||
status_metrics = self.convert_status_metrics(metric)
|
||||
for status, value in status_metrics.items():
|
||||
metric_text = status_metric_text % (tp, status, value)
|
||||
for status, count in status_metrics.items():
|
||||
metric_text = status_metric_text % (tp, status, count)
|
||||
prometheus_metrics.append(metric_text)
|
||||
return prometheus_metrics
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class UserOrgSerializer(serializers.Serializer):
|
||||
id = serializers.CharField()
|
||||
name = serializers.CharField()
|
||||
is_default = serializers.BooleanField(read_only=True)
|
||||
is_system = serializers.BooleanField(read_only=True)
|
||||
is_root = serializers.BooleanField(read_only=True)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "jumpserver"
|
||||
version = "v3.9"
|
||||
version = "v3.10.17"
|
||||
description = "广受欢迎的开源堡垒机"
|
||||
authors = ["ibuler <ibuler@qq.com>"]
|
||||
license = "GPLv3"
|
||||
|
||||
Reference in New Issue
Block a user