Merge branch 'pam' into pr@pam@translate

pull/14774/head
feng626 2025-01-07 14:18:44 +08:00 committed by GitHub
commit 84cbe1c644
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 211 additions and 129 deletions

View File

@ -7,35 +7,43 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node
from common.drf.filters import BaseFilterSet
from common.utils.timezone import local_zero_hour, local_now
from .models import Account, GatheredAccount, ChangeSecretRecord
from .models import Account, GatheredAccount, ChangeSecretRecord, AccountRisk
class AccountFilterSet(BaseFilterSet):
ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr='exact')
asset = drf_filters.CharFilter(field_name='asset', lookup_expr='exact')
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
nodes = drf_filters.CharFilter(method='filter_nodes')
node_id = drf_filters.CharFilter(method='filter_nodes')
has_secret = drf_filters.BooleanFilter(method='filter_has_secret')
platform = drf_filters.CharFilter(field_name='asset__platform_id', lookup_expr='exact')
category = drf_filters.CharFilter(field_name='asset__platform__category', lookup_expr='exact')
type = drf_filters.CharFilter(field_name='asset__platform__type', lookup_expr='exact')
latest_discovery = drf_filters.BooleanFilter(method='filter_latest')
latest_accessed = drf_filters.BooleanFilter(method='filter_latest')
latest_updated = drf_filters.BooleanFilter(method='filter_latest')
latest_secret_changed = drf_filters.BooleanFilter(method='filter_latest')
latest_secret_change_failed = drf_filters.BooleanFilter(method='filter_latest')
risk = drf_filters.CharFilter(method='filter_risk', )
long_time_no_change_secret = drf_filters.BooleanFilter(method='filter_long_time')
long_time_no_verified = drf_filters.BooleanFilter(method='filter_long_time')
ip = drf_filters.CharFilter(field_name="address", lookup_expr="exact")
hostname = drf_filters.CharFilter(field_name="name", lookup_expr="exact")
username = drf_filters.CharFilter(field_name="username", lookup_expr="exact")
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr="exact")
asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr="exact")
asset = drf_filters.CharFilter(field_name="asset", lookup_expr="exact")
assets = drf_filters.CharFilter(field_name="asset_id", lookup_expr="exact")
nodes = drf_filters.CharFilter(method="filter_nodes")
node_id = drf_filters.CharFilter(method="filter_nodes")
has_secret = drf_filters.BooleanFilter(method="filter_has_secret")
platform = drf_filters.CharFilter(
field_name="asset__platform_id", lookup_expr="exact"
)
category = drf_filters.CharFilter(
field_name="asset__platform__category", lookup_expr="exact"
)
type = drf_filters.CharFilter(
field_name="asset__platform__type", lookup_expr="exact"
)
latest_discovery = drf_filters.BooleanFilter(method="filter_latest")
latest_accessed = drf_filters.BooleanFilter(method="filter_latest")
latest_updated = drf_filters.BooleanFilter(method="filter_latest")
latest_secret_changed = drf_filters.BooleanFilter(method="filter_latest")
latest_secret_change_failed = drf_filters.BooleanFilter(method="filter_latest")
risk = drf_filters.CharFilter(
method="filter_risk",
)
long_time_no_change_secret = drf_filters.BooleanFilter(method="filter_long_time")
long_time_no_verified = drf_filters.BooleanFilter(method="filter_long_time")
@staticmethod
def filter_has_secret(queryset, name, has_secret):
q = Q(_secret__isnull=True) | Q(_secret='')
q = Q(_secret__isnull=True) | Q(_secret="")
if has_secret:
return queryset.exclude(q)
else:
@ -45,15 +53,15 @@ class AccountFilterSet(BaseFilterSet):
def filter_long_time(queryset, name, value):
date = timezone.now() - timezone.timedelta(days=30)
if name == 'long_time_no_change_secret':
field = 'date_change_secret'
confirm_field = 'change_secret_status'
if name == "long_time_no_change_secret":
field = "date_change_secret"
confirm_field = "change_secret_status"
else:
field = 'date_verified'
confirm_field = 'connectivity'
field = "date_verified"
confirm_field = "connectivity"
q = Q(**{f'{field}__lt': date}) | Q(**{f'{field}__isnull': True})
confirm_q = {f'{confirm_field}': 'na'}
q = Q(**{f"{field}__lt": date}) | Q(**{f"{field}__isnull": True})
confirm_q = {f"{confirm_field}": "na"}
queryset = queryset.exclude(**confirm_q).filter(q)
return queryset
@ -62,7 +70,11 @@ class AccountFilterSet(BaseFilterSet):
if not value:
return queryset
return Account.get_risks(queryset, value)
risks = AccountRisk.objects.filter(risk=value)
usernames = risks.values_list('username', flat=True)
assets = risks.values_list('asset', flat=True)
queryset = queryset.filter(username__in=usernames, asset__in=assets)
return queryset
@staticmethod
def filter_latest(queryset, name, value):
@ -72,17 +84,19 @@ class AccountFilterSet(BaseFilterSet):
date = timezone.now() - timezone.timedelta(days=7)
kwargs = {}
if name == 'latest_discovery':
kwargs.update({'date_created__gte': date, 'source': 'collected'})
elif name == 'latest_accessed':
kwargs.update({'date_last_login__gte': date})
elif name == 'latest_updated':
kwargs.update({'date_updated__gte': date})
elif name == 'latest_secret_changed':
kwargs.update({'date_change_secret__gt': date})
if name == "latest_discovery":
kwargs.update({"date_created__gte": date, "source": "collected"})
elif name == "latest_accessed":
kwargs.update({"date_last_login__gte": date})
elif name == "latest_updated":
kwargs.update({"date_updated__gte": date})
elif name == "latest_secret_changed":
kwargs.update({"date_change_secret__gt": date})
if name == 'latest_secret_change_failed':
queryset = queryset.filter(date_change_secret__gt=date).exclude(change_secret_status='ok')
if name == "latest_secret_change_failed":
queryset = queryset.filter(date_change_secret__gt=date).exclude(
change_secret_status="ok"
)
if kwargs:
queryset = queryset.filter(date_last_login__gte=date)
@ -97,19 +111,22 @@ class AccountFilterSet(BaseFilterSet):
node_qs = Node.objects.none()
for node in nodes:
node_qs |= node.get_all_children(with_self=True)
node_ids = list(node_qs.values_list('id', flat=True))
node_ids = list(node_qs.values_list("id", flat=True))
queryset = queryset.filter(asset__nodes__in=node_ids)
return queryset
class Meta:
model = Account
fields = ['id', 'asset', 'source_id', 'secret_type', 'category', 'type']
fields = ["id", "asset", "source_id", "secret_type", "category", "type"]
class GatheredAccountFilterSet(BaseFilterSet):
node_id = drf_filters.CharFilter(method='filter_nodes')
asset_id = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
node_id = drf_filters.CharFilter(method="filter_nodes")
asset_id = drf_filters.CharFilter(field_name="asset_id", lookup_expr="exact")
asset_name = drf_filters.CharFilter(
field_name="asset__name", lookup_expr="icontains"
)
status = drf_filters.CharFilter(field_name="status", lookup_expr="exact")
@staticmethod
def filter_nodes(queryset, name, value):
@ -117,15 +134,20 @@ class GatheredAccountFilterSet(BaseFilterSet):
class Meta:
model = GatheredAccount
fields = ['id', 'username']
fields = ["id", "username"]
class ChangeSecretRecordFilterSet(BaseFilterSet):
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
account_username = drf_filters.CharFilter(field_name='account__username', lookup_expr='icontains')
execution_id = drf_filters.CharFilter(field_name='execution_id', lookup_expr='exact')
days = drf_filters.NumberFilter(method='filter_days')
asset_name = drf_filters.CharFilter(
field_name="asset__name", lookup_expr="icontains"
)
account_username = drf_filters.CharFilter(
field_name="account__username", lookup_expr="icontains"
)
execution_id = drf_filters.CharFilter(
field_name="execution_id", lookup_expr="exact"
)
days = drf_filters.NumberFilter(method="filter_days")
@staticmethod
def filter_days(queryset, name, value):
@ -138,4 +160,4 @@ class ChangeSecretRecordFilterSet(BaseFilterSet):
class Meta:
model = ChangeSecretRecord
fields = ['id', 'status', 'asset_id', 'execution']
fields = ["id", "status", "asset_id", "execution"]

View File

@ -230,7 +230,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
.prefetch_related('platform', 'platform__automation') \
.annotate(category=F("platform__category")) \
.annotate(type=F("platform__type")) \
.annotate(assets_amount=Count('accounts'))
.annotate(accounts_amount=Count('accounts'))
if queryset.model is Asset:
queryset = queryset.prefetch_related('labels__label', 'labels')
else:

View File

@ -6,7 +6,8 @@ from rest_framework.validators import UniqueValidator
from assets.models import Asset
from common.serializers import (
WritableNestedModelSerializer, type_field_map, MethodSerializer,
DictSerializer, create_serializer_class, ResourceLabelsMixin
DictSerializer, create_serializer_class, ResourceLabelsMixin,
CommonSerializerMixin
)
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import lazyproperty
@ -158,7 +159,7 @@ class PlatformCustomField(serializers.Serializer):
choices = serializers.ListField(default=list, label=_("Choices"), required=False)
class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
class PlatformSerializer(ResourceLabelsMixin, CommonSerializerMixin, WritableNestedModelSerializer):
id = serializers.IntegerField(
label='ID', required=False,
validators=[UniqueValidator(queryset=Platform.objects.all())]

View File

@ -402,6 +402,14 @@
</button>
</div>
{% if demo_mode %}
<div>
<p class="red-fonts" style='text-align: center;'>
{% trans 'Username' %}: demo {% trans 'Password' %}: jumpserver
</p>
</div>
{% endif %}
<div class="more-login">
{% if auth_methods %}
<div class="more-methods-title {{ current_lang.code }}">

View File

@ -4,6 +4,7 @@ import base64
import json
import logging
from collections import defaultdict
from django.utils import timezone
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
@ -18,18 +19,26 @@ from rest_framework.filters import OrderingFilter
from common import const
from common.db.fields import RelatedManager
logger = logging.getLogger('jumpserver.common')
logger = logging.getLogger("jumpserver.common")
__all__ = [
"DatetimeRangeFilterBackend", "IDSpmFilterBackend",
'IDInFilterBackend', "CustomFilterBackend",
"BaseFilterSet", 'IDNotFilterBackend',
'NotOrRelFilterBackend', 'LabelFilterBackend',
'RewriteOrderingFilter', 'AttrRulesFilterBackend'
"DatetimeRangeFilterBackend",
"IDSpmFilterBackend",
"IDInFilterBackend",
"CustomFilterBackend",
"BaseFilterSet",
"IDNotFilterBackend",
"NotOrRelFilterBackend",
"LabelFilterBackend",
"RewriteOrderingFilter",
"AttrRulesFilterBackend",
]
class BaseFilterSet(drf_filters.FilterSet):
days = drf_filters.NumberFilter(method="filter_days")
days__lt = drf_filters.NumberFilter(method="filter_days")
def do_nothing(self, queryset, name, value):
return queryset
@ -38,6 +47,22 @@ class BaseFilterSet(drf_filters.FilterSet):
return self.form.cleaned_data[k]
return default
@staticmethod
def filter_days(queryset, name, value):
try:
value = int(value)
except ValueError:
return queryset.none()
if name == 'days':
arg = 'date_created__gte'
else:
arg = 'date_created__lt'
date = timezone.now() - timezone.timedelta(days=value)
kwargs = {arg: date}
return queryset.filter(**kwargs)
class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
@ -50,18 +75,20 @@ class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
for v in date_range_keyword:
ret.append(
coreapi.Field(
name=v, location='query', required=False, type='string',
name=v,
location="query",
required=False,
type="string",
schema=coreschema.String(
title=v,
description='%s %s' % (attr, v)
)
title=v, description="%s %s" % (attr, v)
),
)
)
return ret
def _get_date_range_filter_fields(self, view):
if not hasattr(view, 'date_range_filter_fields'):
if not hasattr(view, "date_range_filter_fields"):
return {}
try:
return dict(view.date_range_filter_fields)
@ -75,7 +102,9 @@ class DatetimeRangeFilterBackend(filters.BaseFilterBackend):
('db column', ('query param date from', 'query param date to'))
]
```
""".format(view.name)
""".format(
view.name
)
logger.error(msg)
raise ImproperlyConfigured(msg)
@ -110,14 +139,17 @@ class IDSpmFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='spm', location='query', required=False,
type='string', example='',
description='Pre post objects id get spm id, then using filter'
name="spm",
location="query",
required=False,
type="string",
example="",
description="Pre post objects id get spm id, then using filter",
)
]
def filter_queryset(self, request, queryset, view):
spm = request.query_params.get('spm')
spm = request.query_params.get("spm")
if not spm:
return queryset
cache_key = const.KEY_CACHE_RESOURCE_IDS.format(spm)
@ -127,7 +159,7 @@ class IDSpmFilterBackend(filters.BaseFilterBackend):
return queryset.none()
if isinstance(resource_ids, str):
resource_ids = [resource_ids]
if hasattr(view, 'filter_spm_queryset'):
if hasattr(view, "filter_spm_queryset"):
queryset = view.filter_spm_queryset(resource_ids, queryset)
else:
queryset = queryset.filter(id__in=resource_ids)
@ -138,17 +170,20 @@ class IDInFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='ids', location='query', required=False,
type='string', example='/api/v1/users/users?ids=1,2,3',
description='Filter by id set'
name="ids",
location="query",
required=False,
type="string",
example="/api/v1/users/users?ids=1,2,3",
description="Filter by id set",
)
]
def filter_queryset(self, request, queryset, view):
ids = request.query_params.get('ids')
ids = request.query_params.get("ids")
if not ids:
return queryset
id_list = [i.strip() for i in ids.split(',')]
id_list = [i.strip() for i in ids.split(",")]
queryset = queryset.filter(id__in=id_list)
return queryset
@ -157,17 +192,20 @@ class IDNotFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='id!', location='query', required=False,
type='string', example='/api/v1/users/users?id!=1,2,3',
description='Exclude by id set'
name="id!",
location="query",
required=False,
type="string",
example="/api/v1/users/users?id!=1,2,3",
description="Exclude by id set",
)
]
def filter_queryset(self, request, queryset, view):
ids = request.query_params.get('id!')
ids = request.query_params.get("id!")
if not ids:
return queryset
id_list = [i.strip() for i in ids.split(',')]
id_list = [i.strip() for i in ids.split(",")]
queryset = queryset.exclude(id__in=id_list)
return queryset
@ -176,26 +214,30 @@ class LabelFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='label', location='query', required=False,
type='string', example='/api/v1/users/users?label=abc',
description='Filter by label'
name="label",
location="query",
required=False,
type="string",
example="/api/v1/users/users?label=abc",
description="Filter by label",
)
]
@staticmethod
def parse_labels(labels_id):
from labels.models import Label
label_ids = [i.strip() for i in labels_id.split(',')]
label_ids = [i.strip() for i in labels_id.split(",")]
cleaned = []
args = []
for label_id in label_ids:
kwargs = {}
if ':' in label_id:
k, v = label_id.split(':', 1)
kwargs['name'] = k.strip()
if v != '*':
kwargs['value'] = v.strip()
if ":" in label_id:
k, v = label_id.split(":", 1)
kwargs["name"] = k.strip()
if v != "*":
kwargs["value"] = v.strip()
args.append(kwargs)
else:
cleaned.append(label_id)
@ -209,14 +251,14 @@ class LabelFilterBackend(filters.BaseFilterBackend):
return cleaned
def filter_queryset(self, request, queryset, view):
labels_id = request.query_params.get('labels')
labels_id = request.query_params.get("labels")
if not labels_id:
return queryset
if not hasattr(queryset, 'model'):
if not hasattr(queryset, "model"):
return queryset
if not hasattr(queryset.model, 'label_model'):
if not hasattr(queryset.model, "label_model"):
return queryset
model = queryset.model.label_model()
@ -225,7 +267,8 @@ class LabelFilterBackend(filters.BaseFilterBackend):
model_name = model._meta.model_name
full_resources = labeled_resource_cls.objects.filter(
res_type__app_label=app_label, res_type__model=model_name,
res_type__app_label=app_label,
res_type__model=model_name,
)
labels = self.parse_labels(labels_id)
grouped = defaultdict(set)
@ -234,8 +277,10 @@ class LabelFilterBackend(filters.BaseFilterBackend):
matched_ids = set()
for name, label_ids in grouped.items():
resources = model.filter_resources_by_labels(full_resources, label_ids, rel='any')
res_ids = resources.values_list('res_id', flat=True)
resources = model.filter_resources_by_labels(
full_resources, label_ids, rel="any"
)
res_ids = resources.values_list("res_id", flat=True)
if not matched_ids:
matched_ids = set(res_ids)
else:
@ -249,16 +294,14 @@ class CustomFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
fields = []
defaults = dict(
location='query', required=False,
type='string', example='',
description=''
location="query", required=False, type="string", example="", description=""
)
if not hasattr(view, 'custom_filter_fields'):
if not hasattr(view, "custom_filter_fields"):
return []
for field in view.custom_filter_fields:
if isinstance(field, str):
defaults['name'] = field
defaults["name"] = field
elif isinstance(field, dict):
defaults.update(field)
else:
@ -270,7 +313,7 @@ class CustomFilterBackend(filters.BaseFilterBackend):
return queryset
def current_user_filter(user_field='user'):
def current_user_filter(user_field="user"):
class CurrentUserFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(**{user_field: request.user})
@ -290,27 +333,30 @@ class AttrRulesFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='attr_rules', location='query', required=False,
type='string', example='/api/v1/users/users?attr_rules=jsonbase64',
description='Filter by json like {"type": "attrs", "attrs": []} to base64'
name="attr_rules",
location="query",
required=False,
type="string",
example="/api/v1/users/users?attr_rules=jsonbase64",
description='Filter by json like {"type": "attrs", "attrs": []} to base64',
)
]
def filter_queryset(self, request, queryset, view):
attr_rules = request.query_params.get('attr_rules')
attr_rules = request.query_params.get("attr_rules")
if not attr_rules:
return queryset
try:
attr_rules = base64.b64decode(attr_rules.encode('utf-8'))
attr_rules = base64.b64decode(attr_rules.encode("utf-8"))
except Exception:
raise ValidationError({'attr_rules': 'attr_rules should be base64'})
raise ValidationError({"attr_rules": "attr_rules should be base64"})
try:
attr_rules = json.loads(attr_rules)
except Exception:
raise ValidationError({'attr_rules': 'attr_rules should be json'})
raise ValidationError({"attr_rules": "attr_rules should be json"})
logger.debug('attr_rules: %s', attr_rules)
logger.debug("attr_rules: %s", attr_rules)
qs = RelatedManager.get_to_filter_qs(attr_rules, queryset.model)
for q in qs:
queryset = queryset.filter(q)
@ -321,33 +367,38 @@ class NotOrRelFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='_rel', location='query', required=False,
type='string', example='/api/v1/users/users?name=abc&username=def&_rel=union',
description='Filter by rel, or not, default is and'
name="_rel",
location="query",
required=False,
type="string",
example="/api/v1/users/users?name=abc&username=def&_rel=union",
description="Filter by rel, or not, default is and",
)
]
def filter_queryset(self, request, queryset, view):
_rel = request.query_params.get('_rel')
if not _rel or _rel not in ('or', 'not'):
_rel = request.query_params.get("_rel")
if not _rel or _rel not in ("or", "not"):
return queryset
if _rel == 'not':
if _rel == "not":
queryset.query.where.negated = True
elif _rel == 'or':
queryset.query.where.connector = 'OR'
elif _rel == "or":
queryset.query.where.connector = "OR"
queryset._result_cache = None
return queryset
class RewriteOrderingFilter(OrderingFilter):
default_ordering_if_has = ('name', )
default_ordering_if_has = ("name",)
def get_default_ordering(self, view):
ordering = super().get_default_ordering(view)
# 如果 view.ordering = [] 表示不排序, 这样可以节约性能 (比如: 用户授权的资产)
if ordering is not None:
return ordering
ordering_fields = getattr(view, 'ordering_fields', self.ordering_fields)
ordering_fields = getattr(view, "ordering_fields", self.ordering_fields)
if ordering_fields:
ordering = tuple([f for f in ordering_fields if f in self.default_ordering_if_has])
ordering = tuple(
[f for f in ordering_fields if f in self.default_ordering_if_has]
)
return ordering

View File

@ -5254,7 +5254,7 @@ msgstr ""
#: ops/models/job.py:148
msgid "Timeout (Seconds)"
msgstr ""
msgstr "Timeout (Sec)"
#: ops/models/job.py:153
msgid "Use Parameter Define"
@ -5334,8 +5334,8 @@ msgid "Next execution time"
msgstr ""
#: ops/serializers/job.py:15
msgid "Execute after saving"
msgstr "Execute after saving"
msgid "Run on save"
msgstr "Run on save"
#: ops/serializers/job.py:72
msgid "Job type"

View File

@ -5527,7 +5527,7 @@ msgid "Next execution time"
msgstr "最後の実行"
#: ops/serializers/job.py:15
msgid "Execute after saving"
msgid "Run on save"
msgstr "保存後に実行"
#: ops/serializers/job.py:72

View File

@ -5478,7 +5478,7 @@ msgid "Next execution time"
msgstr "下次执行时间"
#: ops/serializers/job.py:15
msgid "Execute after saving"
msgid "Run on save"
msgstr "保存后执行"
#: ops/serializers/job.py:72

View File

@ -12,7 +12,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Execute after saving"), default=False, required=False)
run_after_save = serializers.BooleanField(label=_("Run on save"), default=False, required=False)
nodes = serializers.ListField(required=False, child=serializers.CharField())
date_last_run = serializers.DateTimeField(label=_('Date last run'), read_only=True)
name = serializers.CharField(label=_('Name'), max_length=128, allow_blank=True, required=False)