mirror of https://github.com/jumpserver/jumpserver
feat: data masking
parent
7cd1e4d3a0
commit
b16304c48a
|
|
@ -3,3 +3,4 @@ from .connect_method import *
|
|||
from .login_acl import *
|
||||
from .login_asset_acl import *
|
||||
from .login_asset_check import *
|
||||
from .data_masking import *
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
from common.api import JMSBulkModelViewSet
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from .common import ACLUserFilterMixin
|
||||
from ..models import DataMaskingRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['DataMaskingRuleViewSet']
|
||||
|
||||
|
||||
class DataMaskingRuleFilter(ACLUserFilterMixin):
|
||||
class Meta:
|
||||
model = DataMaskingRule
|
||||
fields = ('name', 'action')
|
||||
|
||||
|
||||
class DataMaskingRuleViewSet(JMSBulkModelViewSet):
|
||||
queryset = DataMaskingRule.objects.all()
|
||||
filterset_class = DataMaskingRuleFilter
|
||||
search_fields = ('name',)
|
||||
serializer_class = serializers.DataMaskingRuleSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
with tmp_to_root_org():
|
||||
return super().filter_queryset(queryset)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 4.1.13 on 2025-10-07 16:16
|
||||
|
||||
import common.db.fields
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('acls', '0002_auto_20210926_1047'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DataMaskingRule',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||
('action', models.CharField(default='reject', max_length=64, verbose_name='Action')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||
('users', common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users')),
|
||||
('assets', common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets')),
|
||||
('accounts', models.JSONField(default=list, verbose_name='Accounts')),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('fields_pattern', models.CharField(default='password', max_length=128, verbose_name='Fields pattern')),
|
||||
('masking_method', models.CharField(choices=[('fixed_char', 'Fixed Character Replacement'), ('hide_middle', 'Hide Middle Characters'), ('keep_prefix', 'Keep Prefix Only'), ('keep_suffix', 'Keep Suffix Only')], default='fixed_char', max_length=32, verbose_name='Masking Method')),
|
||||
('mask_pattern', models.CharField(blank=True, default='######', max_length=128, null=True, verbose_name='Mask Pattern')),
|
||||
('reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Data Masking Rule',
|
||||
'unique_together': {('org_id', 'name')},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -2,3 +2,4 @@ from .command_acl import *
|
|||
from .connect_method import *
|
||||
from .login_acl import *
|
||||
from .login_asset_acl import *
|
||||
from .data_masking import *
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
from django.db import models
|
||||
|
||||
from acls.models import UserAssetAccountBaseACL
|
||||
from common.utils import get_logger
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = ['MaskingMethod', 'DataMaskingRule']
|
||||
|
||||
|
||||
class MaskingMethod(models.TextChoices):
|
||||
fixed_char = "fixed_char", _("Fixed Character Replacement") # 固定字符替换
|
||||
hide_middle = "hide_middle", _("Hide Middle Characters") # 隐藏中间几位
|
||||
keep_prefix = "keep_prefix", _("Keep Prefix Only") # 只保留前缀
|
||||
keep_suffix = "keep_suffix", _("Keep Suffix Only") # 只保留后缀
|
||||
|
||||
|
||||
class DataMaskingRule(UserAssetAccountBaseACL):
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
fields_pattern = models.CharField(max_length=128, default='password', verbose_name=_("Fields pattern"))
|
||||
|
||||
masking_method = models.CharField(
|
||||
max_length=32,
|
||||
choices=MaskingMethod.choices,
|
||||
default=MaskingMethod.fixed_char,
|
||||
verbose_name=_("Masking Method"),
|
||||
)
|
||||
mask_pattern = models.CharField(
|
||||
max_length=128,
|
||||
verbose_name=_("Mask Pattern"),
|
||||
default="######",
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name')]
|
||||
verbose_name = _("Data Masking Rule")
|
||||
|
|
@ -3,3 +3,4 @@ from .connect_method import *
|
|||
from .login_acl import *
|
||||
from .login_asset_acl import *
|
||||
from .login_asset_check import *
|
||||
from .data_masking import *
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
from common.serializers.fields import LabeledChoiceField
|
||||
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
|
||||
from common.serializers.mixin import CommonBulkModelSerializer
|
||||
from ..models import DataMaskingRule
|
||||
|
||||
__all__ = ['DataMaskingRuleSerializer']
|
||||
|
||||
from ..models.data_masking import MaskingMethod
|
||||
|
||||
|
||||
class DataMaskingRuleSerializer(BaseSerializer, CommonBulkModelSerializer):
|
||||
masking_method = LabeledChoiceField(
|
||||
choices=MaskingMethod, default=MaskingMethod.fixed_char, label='Masking Method'
|
||||
)
|
||||
|
||||
class Meta(BaseSerializer.Meta):
|
||||
model = DataMaskingRule
|
||||
fields = BaseSerializer.Meta.fields + ['fields_pattern', 'masking_method', 'mask_pattern']
|
||||
|
|
@ -11,6 +11,7 @@ router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl'
|
|||
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
|
||||
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
|
||||
router.register(r'connect-method-acls', api.ConnectMethodACLViewSet, 'connect-method-acl')
|
||||
router.register(r'data-masking-rules', api.DataMaskingRuleViewSet, 'data-masking-rule')
|
||||
|
||||
urlpatterns = [
|
||||
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
||||
|
|
|
|||
|
|
@ -338,6 +338,18 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||
acls = CommandFilterACL.filter_queryset(**kwargs).valid()
|
||||
return acls
|
||||
|
||||
@lazyproperty
|
||||
def data_masking_rules(self):
|
||||
from acls.models import DataMaskingRule
|
||||
kwargs = {
|
||||
'user': self.user,
|
||||
'asset': self.asset,
|
||||
'account': self.account_object,
|
||||
}
|
||||
with tmp_to_org(self.asset.org_id):
|
||||
rules = DataMaskingRule.filter_queryset(**kwargs).valid()
|
||||
return rules
|
||||
|
||||
|
||||
class SuperConnectionToken(ConnectionToken):
|
||||
_type = ConnectionTokenType.SUPER
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from rest_framework import serializers
|
|||
|
||||
from accounts.const import SecretType
|
||||
from accounts.models import Account
|
||||
from acls.models import CommandGroup, CommandFilterACL
|
||||
from acls.models import CommandGroup, CommandFilterACL, DataMaskingRule
|
||||
from assets.models import Asset, Platform, Gateway, Zone
|
||||
from assets.serializers.asset import AssetProtocolsSerializer
|
||||
from assets.serializers.platform import PlatformSerializer
|
||||
|
|
@ -83,6 +83,14 @@ class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class _ConnectionTokenDataMaskingRuleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DataMaskingRule
|
||||
fields = ['id', 'name', 'fields_pattern',
|
||||
'masking_method', 'mask_pattern',
|
||||
'is_active', 'priority']
|
||||
|
||||
|
||||
class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer):
|
||||
command_groups = ObjectRelatedField(
|
||||
many=True, required=False, queryset=CommandGroup.objects,
|
||||
|
|
@ -105,7 +113,7 @@ class _ConnectionTokenPlatformSerializer(PlatformSerializer):
|
|||
class Meta(PlatformSerializer.Meta):
|
||||
model = Platform
|
||||
fields = [field for field in PlatformSerializer.Meta.fields
|
||||
if field not in PlatformSerializer.Meta.fields_m2m]
|
||||
if field not in PlatformSerializer.Meta.fields_m2m]
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
names = super().get_field_names(declared_fields, info)
|
||||
|
|
@ -139,6 +147,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
|||
platform = _ConnectionTokenPlatformSerializer(read_only=True)
|
||||
zone = ObjectRelatedField(queryset=Zone.objects, required=False, label=_('Domain'))
|
||||
command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True)
|
||||
data_masking_rules = _ConnectionTokenDataMaskingRuleSerializer(read_only=True, many=True)
|
||||
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
|
||||
connect_method = _ConnectTokenConnectMethodSerializer(read_only=True, source='connect_method_object')
|
||||
connect_options = serializers.JSONField(read_only=True)
|
||||
|
|
@ -149,7 +158,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
|
|||
model = ConnectionToken
|
||||
fields = [
|
||||
'id', 'value', 'user', 'asset', 'account',
|
||||
'platform', 'command_filter_acls', 'protocol',
|
||||
'platform', 'command_filter_acls', 'data_masking_rules', 'protocol',
|
||||
'zone', 'gateway', 'actions', 'expire_at',
|
||||
'from_ticket', 'expire_now', 'connect_method',
|
||||
'connect_options', 'face_monitor_token'
|
||||
|
|
|
|||
Loading…
Reference in New Issue