mirror of https://github.com/jumpserver/jumpserver
fit2bot
1 year ago
committed by
GitHub
19 changed files with 371 additions and 64 deletions
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*- |
||||
# |
||||
from rest_framework import generics |
||||
from rest_framework.serializers import Serializer |
||||
|
||||
from common.permissions import IsValidUser |
||||
from common.utils import get_logger |
||||
from .. import serializers |
||||
from ..models import Preference |
||||
|
||||
logger = get_logger(__file__) |
||||
|
||||
|
||||
class PreferenceApi(generics.RetrieveUpdateAPIView): |
||||
permission_classes = (IsValidUser,) |
||||
queryset = Preference.objects.all() |
||||
serializer_class_mapper = { |
||||
'lina': serializers.LinaSerializer, |
||||
'luna': serializers.LunaSerializer, |
||||
'koko': serializers.KokoSerializer, |
||||
} |
||||
|
||||
def check_permissions(self, request): |
||||
if self.category not in self.serializer_class_mapper: |
||||
return self.permission_denied(request, 'category is invalid') |
||||
return super().check_permissions(request) |
||||
|
||||
@property |
||||
def user(self): |
||||
return self.request.user |
||||
|
||||
@property |
||||
def category(self): |
||||
return self.request.query_params.get('category') |
||||
|
||||
def get_serializer_class(self): |
||||
cls = self.serializer_class_mapper.get(self.category) |
||||
return cls |
||||
|
||||
def get_field_defaults(self, serializer): |
||||
field_defaults = {} |
||||
fields = serializer.get_fields() |
||||
for name, field in fields.items(): |
||||
if isinstance(field, Serializer): |
||||
field_defaults[name] = self.get_field_defaults(field) |
||||
continue |
||||
field_defaults[name] = getattr(field, 'default', None) |
||||
return field_defaults |
||||
|
||||
def get_encrypted_fields(self, serializer): |
||||
encrypted_fields = [] |
||||
fields = serializer.get_fields() |
||||
for name, field in fields.items(): |
||||
if isinstance(field, Serializer): |
||||
encrypted_fields += self.get_encrypted_fields(field) |
||||
continue |
||||
if not field.write_only: |
||||
continue |
||||
encrypted_fields.append(name) |
||||
return encrypted_fields |
||||
|
||||
def get_object(self): |
||||
serializer = self.get_serializer_class()() |
||||
field_defaults = self.get_field_defaults(serializer) |
||||
|
||||
qs = self.queryset.filter(user=self.user, category=self.category) |
||||
if not qs.exists(): |
||||
return field_defaults |
||||
|
||||
data = dict(qs.values_list('name', 'value')) |
||||
for k, v in data.items(): |
||||
for d in field_defaults.values(): |
||||
if k in d: |
||||
d[k] = v |
||||
break |
||||
return field_defaults |
||||
|
||||
def perform_update(self, serializer): |
||||
user = self.user |
||||
category = self.category |
||||
model = self.queryset.model |
||||
encrypted_fields = self.get_encrypted_fields(serializer) |
||||
data = serializer.validated_data |
||||
for d in data.values(): |
||||
for name, value in d.items(): |
||||
kwargs = {'name': name, 'user': user} |
||||
defaults = {'category': category} |
||||
if name in encrypted_fields: |
||||
value = model.encrypt(value) |
||||
defaults['encrypted'] = True |
||||
defaults['value'] = value |
||||
defaults.update(kwargs) |
||||
model.objects.update_or_create(defaults, **kwargs) |
@ -0,0 +1,77 @@
|
||||
# Generated by Django 4.1.10 on 2023-09-09 14:16 |
||||
|
||||
import django.db.models.deletion |
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
|
||||
from common.db.utils import Encryptor |
||||
|
||||
|
||||
def migrate_secret_key(apps, *args): |
||||
user_model = apps.get_model('users', 'User') |
||||
preference_model = apps.get_model('users', 'Preference') |
||||
data = user_model.objects.filter( |
||||
secret_key__isnull=False |
||||
).values_list('id', 'secret_key') |
||||
objs = [] |
||||
for user_id, secret_key in data: |
||||
secret_key = Encryptor(secret_key).encrypt() |
||||
objs.append( |
||||
preference_model( |
||||
name='secret_key', category='lina', |
||||
value=secret_key, encrypted=True, user_id=user_id |
||||
) |
||||
) |
||||
preference_model.objects.bulk_create(objs) |
||||
|
||||
|
||||
def migrate_graphical_resolution(apps, *args): |
||||
user_model = apps.get_model('users', 'User') |
||||
setting_model = apps.get_model('settings', 'Setting') |
||||
preference_model = apps.get_model('users', 'Preference') |
||||
s = setting_model.objects.filter(name='TERMINAL_GRAPHICAL_RESOLUTION').first() |
||||
if (s and s.value == 'Auto') or not s: |
||||
return |
||||
|
||||
value = s.value |
||||
objs = [] |
||||
for _id in user_model.objects.values_list('id', flat=True): |
||||
objs.append( |
||||
preference_model( |
||||
name='rdp_resolution', category='luna', |
||||
value=value, user_id=_id |
||||
) |
||||
) |
||||
preference_model.objects.bulk_create(objs) |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
dependencies = [ |
||||
('users', '0042_auto_20230203_1201'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Preference', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('name', models.CharField(max_length=128, verbose_name='Name')), |
||||
('category', models.CharField(max_length=128, verbose_name='Category')), |
||||
('value', models.TextField(blank=True, null=True, verbose_name='Value')), |
||||
('encrypted', models.BooleanField(default=False, verbose_name='Encrypted')), |
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='preferences', |
||||
to=settings.AUTH_USER_MODEL, verbose_name='Users')), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Preference', |
||||
'db_table': 'users_preference', |
||||
'unique_together': {('name', 'user_id')}, |
||||
}, |
||||
), |
||||
migrations.RunPython(migrate_secret_key), |
||||
migrations.RunPython(migrate_graphical_resolution), |
||||
migrations.RemoveField( |
||||
model_name='user', |
||||
name='secret_key', |
||||
), |
||||
] |
@ -0,0 +1,39 @@
|
||||
from django.db import models |
||||
from django.utils.translation import gettext_lazy as _ |
||||
|
||||
from common.db.utils import Encryptor |
||||
from common.utils import get_logger |
||||
|
||||
logger = get_logger(__name__) |
||||
|
||||
|
||||
class Preference(models.Model): |
||||
name = models.CharField(max_length=128, verbose_name=_("Name")) |
||||
category = models.CharField(max_length=128, verbose_name=_('Category')) |
||||
value = models.TextField(verbose_name=_("Value"), null=True, blank=True) |
||||
encrypted = models.BooleanField(default=False, verbose_name=_('Encrypted')) |
||||
user = models.ForeignKey( |
||||
'users.User', verbose_name=_("Users"), related_name='preferences', on_delete=models.CASCADE |
||||
) |
||||
|
||||
def __str__(self): |
||||
return f'{self.name}({self.user.username})' |
||||
|
||||
@classmethod |
||||
def encrypt(cls, value): |
||||
return Encryptor(value).encrypt() |
||||
|
||||
@classmethod |
||||
def decrypt(cls, value): |
||||
return Encryptor(value).decrypt() |
||||
|
||||
@property |
||||
def decrypt_value(self): |
||||
if self.encrypted: |
||||
return self.decrypt(self.value) |
||||
return self.value |
||||
|
||||
class Meta: |
||||
db_table = "users_preference" |
||||
verbose_name = _("Preference") |
||||
unique_together = [('name', 'user_id')] |
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*- |
||||
# |
||||
from .user import * |
||||
from .profile import * |
||||
from .group import * |
||||
from .preference import * |
||||
from .profile import * |
||||
from .realtion import * |
||||
from .user import * |
||||
|
@ -0,0 +1,3 @@
|
||||
from .koko import * |
||||
from .lina import * |
||||
from .luna import * |
@ -0,0 +1,15 @@
|
||||
from django.utils.translation import gettext_lazy as _ |
||||
from rest_framework import serializers |
||||
|
||||
from users.const import FileNameConflictResolution |
||||
|
||||
|
||||
class BasicSerializer(serializers.Serializer): |
||||
file_name_conflict_resolution = serializers.ChoiceField( |
||||
FileNameConflictResolution.choices, default=FileNameConflictResolution.REPLACE, |
||||
required=False, label=_('File name conflict resolution') |
||||
) |
||||
|
||||
|
||||
class KokoSerializer(serializers.Serializer): |
||||
basic = BasicSerializer(required=False, label=_('Basic')) |
@ -0,0 +1,30 @@
|
||||
from django.utils.translation import gettext_lazy as _ |
||||
from rest_framework import serializers |
||||
|
||||
from common.serializers.fields import EncryptedField |
||||
|
||||
|
||||
class BasicSerializer(serializers.Serializer): |
||||
secret_key = EncryptedField( |
||||
required=False, max_length=1024, |
||||
write_only=True, allow_blank=True, label=_('Secret Key') |
||||
) |
||||
secret_key_again = EncryptedField( |
||||
required=False, max_length=1024, |
||||
write_only=True, allow_blank=True, label=_('Secret Key Again') |
||||
) |
||||
|
||||
def validate(self, attrs): |
||||
secret_key = attrs.pop('secret_key', None) |
||||
secret_key_again = attrs.pop('secret_key_again', None) |
||||
|
||||
if (secret_key or secret_key_again) and secret_key != secret_key_again: |
||||
msg = _('The newly set password is inconsistent') |
||||
raise serializers.ValidationError({'secret_key_again': msg}) |
||||
elif secret_key and secret_key_again: |
||||
attrs['secret_key'] = secret_key |
||||
return attrs |
||||
|
||||
|
||||
class LinaSerializer(serializers.Serializer): |
||||
basic = BasicSerializer(required=False, label=_('Basic')) |
@ -0,0 +1,62 @@
|
||||
import json |
||||
|
||||
from django.utils.translation import gettext_lazy as _ |
||||
from rest_framework import serializers |
||||
|
||||
from users.const import RDPResolution, KeyboardLayout, RDPClientOption, RemoteApplicationConnectionMethod |
||||
|
||||
|
||||
class MultipleChoiceField(serializers.MultipleChoiceField): |
||||
|
||||
def to_representation(self, keys): |
||||
if isinstance(keys, str): |
||||
keys = json.loads(keys) |
||||
return keys |
||||
|
||||
def to_internal_value(self, data): |
||||
data = super().to_internal_value(data) |
||||
return json.dumps(list(data)) |
||||
|
||||
|
||||
class BasicSerializer(serializers.Serializer): |
||||
is_async_asset_tree = serializers.BooleanField( |
||||
required=False, default=True, label=_('Async loading of asset tree') |
||||
) |
||||
|
||||
|
||||
class GraphicsSerializer(serializers.Serializer): |
||||
rdp_resolution = serializers.ChoiceField( |
||||
RDPResolution.choices, default=RDPResolution.AUTO, |
||||
required=False, label=_('RDP resolution') |
||||
) |
||||
keyboard_layout = serializers.ChoiceField( |
||||
KeyboardLayout.choices, default=KeyboardLayout.EN_US_QWERTY, |
||||
required=False, label=_('Keyboard layout') |
||||
) |
||||
rdp_client_option = MultipleChoiceField( |
||||
choices=RDPClientOption.choices, default={RDPClientOption.FULL_SCREEN}, |
||||
label=_('RDP client option'), required=False |
||||
) |
||||
remote_application_connection_method = serializers.ChoiceField( |
||||
RemoteApplicationConnectionMethod.choices, default=RemoteApplicationConnectionMethod.WEB, |
||||
required=False, label=_('Remote application connection method') |
||||
) |
||||
|
||||
|
||||
class CommandLineSerializer(serializers.Serializer): |
||||
character_terminal_font_size = serializers.IntegerField( |
||||
default=14, min_value=1, max_value=9999, required=False, |
||||
label=_('Character terminal font size'), |
||||
) |
||||
is_backspace_as_ctrl_h = serializers.BooleanField( |
||||
required=False, default=False, label=_('Backspace as Ctrl+H') |
||||
) |
||||
is_right_click_quickly_paste = serializers.BooleanField( |
||||
required=False, default=False, label=_('Right click quickly paste') |
||||
) |
||||
|
||||
|
||||
class LunaSerializer(serializers.Serializer): |
||||
basic = BasicSerializer(required=False, label=_('Basic')) |
||||
graphics = GraphicsSerializer(required=False, label=_('Graphics')) |
||||
command_line = CommandLineSerializer(required=False, label=_('Command line')) |
Loading…
Reference in new issue