perf: password 等使用 rsa 加密传输 (#8188)

* perf: 修改 model fields 路径

* stash it

* pref: 统一加密方式,密码字段采用 rsa 加密

* pref: 临时密码使用 rsa

* perf: 去掉 debug msg

* perf: 去掉 Debug

* perf: 去掉 debug

* perf: 抽出来

Co-authored-by: ibuler <ibuler@qq.com>
pull/8199/head
fit2bot 2022-05-07 16:20:12 +08:00 committed by GitHub
parent 3f856e68f0
commit 031077c298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 291 additions and 245 deletions

View File

@ -1,6 +1,6 @@
# Generated by Django 2.1.7 on 2019-05-20 11:04 # Generated by Django 2.1.7 on 2019-05-20 11:04
import common.fields.model import common.db.fields
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import uuid import uuid
@ -23,7 +23,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
('path', models.CharField(max_length=128, verbose_name='App path')), ('path', models.CharField(max_length=128, verbose_name='App path')),
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), ('params', common.db.fields.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),

View File

@ -1,7 +1,7 @@
# Generated by Django 3.1.12 on 2021-08-26 09:07 # Generated by Django 3.1.12 on 2021-08-26 09:07
import assets.models.base import assets.models.base
import common.fields.model import common.db.fields
from django.conf import settings from django.conf import settings
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
@ -26,9 +26,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')),
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
@ -56,9 +56,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),

View File

@ -4,7 +4,6 @@ from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.utils.crypto import get_aes_crypto
from common.permissions import IsValidUser from common.permissions import IsValidUser
from common.mixins.api import SuggestionMixin from common.mixins.api import SuggestionMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
@ -102,27 +101,17 @@ class SystemUserTempAuthInfoApi(generics.CreateAPIView):
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
serializer_class = SystemUserTempAuthSerializer serializer_class = SystemUserTempAuthSerializer
def decrypt_data_if_need(self, data):
csrf_token = self.request.META.get('CSRF_COOKIE')
aes = get_aes_crypto(csrf_token, 'ECB')
password = data.get('password', '')
try:
data['password'] = aes.decrypt(password)
except:
pass
return data
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
serializer = super().get_serializer(data=request.data) serializer = super().get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
pk = kwargs.get('pk') pk = kwargs.get('pk')
data = self.decrypt_data_if_need(serializer.validated_data) data = serializer.validated_data
instance_id = data.get('instance_id') asset_or_app_id = data.get('instance_id')
with tmp_to_root_org(): with tmp_to_root_org():
instance = get_object_or_404(SystemUser, pk=pk) instance = get_object_or_404(SystemUser, pk=pk)
instance.set_temp_auth(instance_id, self.request.user.id, data) instance.set_temp_auth(asset_or_app_id, self.request.user.id, data)
return Response(serializer.data, status=201) return Response(serializer.data, status=201)

View File

@ -1,7 +1,7 @@
# Generated by Django 2.1.7 on 2019-06-24 13:08 # Generated by Django 2.1.7 on 2019-06-24 13:08
import assets.models.utils import assets.models.utils
import common.fields.model import common.db.fields
from django.db import migrations from django.db import migrations
@ -15,61 +15,61 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adminuser', model_name='adminuser',
name='_password', name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='adminuser', model_name='adminuser',
name='_private_key', name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='adminuser', model_name='adminuser',
name='_public_key', name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='authbook', model_name='authbook',
name='_password', name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='authbook', model_name='authbook',
name='_private_key', name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='authbook', model_name='authbook',
name='_public_key', name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gateway', model_name='gateway',
name='_password', name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gateway', model_name='gateway',
name='_private_key', name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gateway', model_name='gateway',
name='_public_key', name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='_password', name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='_private_key', name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='_public_key', name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
), ),
] ]

View File

@ -1,6 +1,6 @@
# Generated by Django 2.1.7 on 2019-07-11 12:18 # Generated by Django 2.1.7 on 2019-07-11 12:18
import common.fields.model import common.db.fields
from django.db import migrations from django.db import migrations
@ -14,21 +14,21 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adminuser', model_name='adminuser',
name='private_key', name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='authbook', model_name='authbook',
name='private_key', name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gateway', model_name='gateway',
name='private_key', name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='private_key', name='private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
), ),
] ]

View File

@ -1,6 +1,6 @@
# Generated by Django 2.2.7 on 2019-12-06 07:26 # Generated by Django 2.2.7 on 2019-12-06 07:26
import common.fields.model import common.db.fields
from django.db import migrations, models from django.db import migrations, models
@ -36,7 +36,7 @@ class Migration(migrations.Migration):
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')), ('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')), ('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')), ('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')), ('meta', common.db.fields.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
('internal', models.BooleanField(default=False, verbose_name='Internal')), ('internal', models.BooleanField(default=False, verbose_name='Internal')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
], ],

View File

@ -1,6 +1,6 @@
# Generated by Django 3.1.6 on 2021-06-05 16:10 # Generated by Django 3.1.6 on 2021-06-05 16:10
import common.fields.model import common.db.fields
from django.conf import settings from django.conf import settings
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
@ -58,9 +58,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')),
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),

View File

@ -11,7 +11,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from common.fields.model import JsonDictTextField from common.db.fields import JsonDictTextField
from common.utils import lazyproperty from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager from orgs.mixins.models import OrgModelMixin, OrgManager

View File

@ -19,7 +19,7 @@ from common.utils import (
) )
from common.utils.encode import ssh_pubkey_gen from common.utils.encode import ssh_pubkey_gen
from common.validators import alphanumeric from common.validators import alphanumeric
from common import fields from common.db import fields
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin

View File

@ -6,12 +6,13 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
from common.drf.fields import EncryptedField
from assets.models import Type from assets.models import Type
class AuthSerializer(serializers.ModelSerializer): class AuthSerializer(serializers.ModelSerializer):
password = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=1024) password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024)
private_key = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=4096) private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096)
def gen_keys(self, private_key=None, password=None): def gen_keys(self, private_key=None, password=None):
if private_key is None: if private_key is None:
@ -31,6 +32,8 @@ class AuthSerializer(serializers.ModelSerializer):
class AuthSerializerMixin(serializers.ModelSerializer): class AuthSerializerMixin(serializers.ModelSerializer):
password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024)
private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096)
passphrase = serializers.CharField( passphrase = serializers.CharField(
allow_blank=True, allow_null=True, required=False, max_length=512, allow_blank=True, allow_null=True, required=False, max_length=512,
write_only=True, label=_('Key password') write_only=True, label=_('Key password')

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2022-05-05 11:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audits', '0013_auto_20211130_1037'),
]
operations = [
migrations.AlterField(
model_name='operatelog',
name='action',
field=models.CharField(choices=[('create', 'Create'), ('view', 'View'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action'),
),
]

View File

@ -27,8 +27,10 @@ class TokenCreateApi(AuthMixin, CreateAPIView):
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
self.create_session_if_need() self.create_session_if_need()
# 如果认证没有过,检查账号密码 # 如果认证没有过,检查账号密码
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try: try:
user = self.check_user_auth_if_need() user = self.get_user_or_auth(serializer.validated_data)
self.check_user_mfa_if_need(user) self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user) self.check_user_login_confirm_if_need(user)
self.send_auth_signal(success=True, user=user) self.send_auth_signal(success=True, user=user)

View File

@ -1,15 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from captcha.fields import CaptchaField, CaptchaTextInput from captcha.fields import CaptchaField, CaptchaTextInput
from common.utils import get_logger, rsa_decrypt_by_session_pkey
logger = get_logger(__name__)
class EncryptedField(forms.CharField):
def to_python(self, value):
value = super().to_python(value)
return rsa_decrypt_by_session_pkey(value)
class UserLoginForm(forms.Form): class UserLoginForm(forms.Form):
days_auto_login = int(settings.SESSION_COOKIE_AGE / 3600 / 24) days_auto_login = int(settings.SESSION_COOKIE_AGE / 3600 / 24)
disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE or days_auto_login < 1 disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE \
or days_auto_login < 1
username = forms.CharField( username = forms.CharField(
label=_('Username'), max_length=100, label=_('Username'), max_length=100,
@ -18,7 +28,7 @@ class UserLoginForm(forms.Form):
'autofocus': 'autofocus' 'autofocus': 'autofocus'
}) })
) )
password = forms.CharField( password = EncryptedField(
label=_('Password'), widget=forms.PasswordInput, label=_('Password'), widget=forms.PasswordInput,
max_length=1024, strip=False max_length=1024, strip=False
) )

View File

@ -1,8 +1,12 @@
import base64
from django.shortcuts import redirect, reverse from django.shortcuts import redirect, reverse
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from common.utils import gen_key_pair
class MFAMiddleware: class MFAMiddleware:
""" """
@ -48,3 +52,28 @@ class SessionCookieMiddleware(MiddlewareMixin):
return response return response
response.set_cookie(key, value) response.set_cookie(key, value)
return response return response
class EncryptedMiddleware:
def __init__(self, get_response):
self.get_response = get_response
@staticmethod
def check_key_pair(request, response):
pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
public_key = request.session.get(pub_key_name)
cookie_key = request.COOKIES.get(pub_key_name)
if public_key and public_key == cookie_key:
return
pri_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
private_key, public_key = gen_key_pair()
public_key_decode = base64.b64encode(public_key.encode()).decode()
request.session[pub_key_name] = public_key_decode
request.session[pri_key_name] = private_key
response.set_cookie(pub_key_name, public_key_decode)
def __call__(self, request):
response = self.get_response(request)
self.check_key_pair(request, response)
return response

View File

@ -23,9 +23,7 @@ from acls.models import LoginACL
from users.models import User from users.models import User
from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil
from . import errors from . import errors
from .utils import rsa_decrypt, gen_key_pair
from .signals import post_auth_success, post_auth_failed from .signals import post_auth_success, post_auth_failed
from .const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY
logger = get_logger(__name__) logger = get_logger(__name__)
@ -91,46 +89,8 @@ def authenticate(request=None, **credentials):
auth.authenticate = authenticate auth.authenticate = authenticate
class PasswordEncryptionViewMixin: class CommonMixin:
request = None request: Request
def get_decrypted_password(self, password=None, username=None):
request = self.request
if hasattr(request, 'data'):
data = request.data
else:
data = request.POST
username = username or data.get('username')
password = password or data.get('password')
password = self.decrypt_passwd(password)
if not password:
self.raise_password_decrypt_failed(username=username)
return password
def raise_password_decrypt_failed(self, username):
ip = self.get_request_ip()
raise errors.CredentialError(
error=errors.reason_password_decrypt_failed,
username=username, ip=ip, request=self.request
)
def decrypt_passwd(self, raw_passwd):
# 获取解密密钥,对密码进行解密
rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY)
if rsa_private_key is None:
return raw_passwd
try:
return rsa_decrypt(raw_passwd, rsa_private_key)
except Exception as e:
logger.error(e, exc_info=True)
logger.error(
f'Decrypt password failed: password[{raw_passwd}] '
f'rsa_private_key[{rsa_private_key}]'
)
return None
def get_request_ip(self): def get_request_ip(self):
ip = '' ip = ''
@ -139,26 +99,6 @@ class PasswordEncryptionViewMixin:
ip = ip or get_request_ip(self.request) ip = ip or get_request_ip(self.request)
return ip return ip
def get_context_data(self, **kwargs):
# 生成加解密密钥对public_key传递给前端private_key存入session中供解密使用
rsa_public_key = self.request.session.get(RSA_PUBLIC_KEY)
rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY)
if not all([rsa_private_key, rsa_public_key]):
rsa_private_key, rsa_public_key = gen_key_pair()
rsa_public_key = rsa_public_key.replace('\n', '\\n')
self.request.session[RSA_PRIVATE_KEY] = rsa_private_key
self.request.session[RSA_PUBLIC_KEY] = rsa_public_key
kwargs.update({
'rsa_public_key': rsa_public_key,
})
return super().get_context_data(**kwargs)
class CommonMixin(PasswordEncryptionViewMixin):
request: Request
get_request_ip: Callable
def raise_credential_error(self, error): def raise_credential_error(self, error):
raise self.partial_credential_error(error=error) raise self.partial_credential_error(error=error)
@ -193,20 +133,13 @@ class CommonMixin(PasswordEncryptionViewMixin):
user.backend = self.request.session.get("auth_backend") user.backend = self.request.session.get("auth_backend")
return user return user
def get_auth_data(self, decrypt_passwd=False): def get_auth_data(self, data):
request = self.request request = self.request
if hasattr(request, 'data'):
data = request.data
else:
data = request.POST
items = ['username', 'password', 'challenge', 'public_key', 'auto_login'] items = ['username', 'password', 'challenge', 'public_key', 'auto_login']
username, password, challenge, public_key, auto_login = bulk_get(data, items, default='') username, password, challenge, public_key, auto_login = bulk_get(data, items, default='')
ip = self.get_request_ip() ip = self.get_request_ip()
self._set_partial_credential_error(username=username, ip=ip, request=request) self._set_partial_credential_error(username=username, ip=ip, request=request)
if decrypt_passwd:
password = self.get_decrypted_password()
password = password + challenge.strip() password = password + challenge.strip()
return username, password, public_key, ip, auto_login return username, password, public_key, ip, auto_login
@ -482,10 +415,10 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
need = cache.get(self.key_prefix_captcha.format(ip)) need = cache.get(self.key_prefix_captcha.format(ip))
return need return need
def check_user_auth(self, decrypt_passwd=False): def check_user_auth(self, valid_data=None):
# pre check # pre check
self.check_is_block() self.check_is_block()
username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd) username, password, public_key, ip, auto_login = self.get_auth_data(valid_data)
self._check_only_allow_exists_user_auth(username) self._check_only_allow_exists_user_auth(username)
# check auth # check auth
@ -537,11 +470,12 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
self.mark_password_ok(user, False) self.mark_password_ok(user, False)
return user return user
def check_user_auth_if_need(self, decrypt_passwd=False): def get_user_or_auth(self, valid_data):
request = self.request request = self.request
if not request.session.get('auth_password'): if request.session.get('auth_password'):
return self.check_user_auth(decrypt_passwd=decrypt_passwd)
return self.get_user_from_session() return self.get_user_from_session()
else:
return self.check_user_auth(valid_data)
def clear_auth_mark(self): def clear_auth_mark(self):
keys = ['auth_password', 'user_id', 'auth_confirm', 'auth_ticket_id'] keys = ['auth_password', 'user_id', 'auth_confirm', 'auth_ticket_id']

View File

@ -2,6 +2,8 @@
# #
from rest_framework import serializers from rest_framework import serializers
from common.drf.fields import EncryptedField
__all__ = [ __all__ = [
'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer', 'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer',
@ -10,7 +12,7 @@ __all__ = [
class PasswordVerifySerializer(serializers.Serializer): class PasswordVerifySerializer(serializers.Serializer):
password = serializers.CharField() password = EncryptedField()
class MFASelectTypeSerializer(serializers.Serializer): class MFASelectTypeSerializer(serializers.Serializer):

View File

@ -161,6 +161,7 @@
<span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span> <span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
</div> </div>
<div class="contact-form col-md-10 col-md-offset-1"> <div class="contact-form col-md-10 col-md-offset-1">
<form id="login-form" action="" method="post" role="form" novalidate="novalidate"> <form id="login-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %} {% csrf_token %}
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;"> <div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
@ -241,20 +242,10 @@
{% include '_foot_js.html' %} {% include '_foot_js.html' %}
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script> <script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
<script> <script>
function encryptLoginPassword(password, rsaPublicKey) {
if (!password) {
return ''
}
var jsencrypt = new JSEncrypt(); //加密对象
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
return jsencrypt.encrypt(password); //加密
}
function doLogin() { function doLogin() {
//公钥加密 //公钥加密
var rsaPublicKey = "{{ rsa_public_key }}"
var password = $('#password').val(); //明文密码 var password = $('#password').val(); //明文密码
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey) var passwordEncrypted = encryptPassword(password)
$('#password-hidden').val(passwordEncrypted); //返回给密码输入input $('#password-hidden').val(passwordEncrypted); //返回给密码输入input
$('#login-form').submit(); //post提交 $('#login-form').submit(); //post提交
} }

View File

@ -1,62 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import base64
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5
from Cryptodome import Random
from django.conf import settings from django.conf import settings
from .notifications import DifferentCityLoginMessage
from audits.models import UserLoginLog
from audits.const import DEFAULT_CITY
from common.utils import validate_ip, get_ip_city, get_request_ip from common.utils import validate_ip, get_ip_city, get_request_ip
from common.utils import get_logger from common.utils import get_logger
from audits.models import UserLoginLog
from audits.const import DEFAULT_CITY
from .notifications import DifferentCityLoginMessage
logger = get_logger(__file__) logger = get_logger(__file__)
def gen_key_pair():
""" 生成加密key
用于登录页面提交用户名/密码时对密码进行加密前端/解密后端
"""
random_generator = Random.new().read
rsa = RSA.generate(1024, random_generator)
rsa_private_key = rsa.exportKey().decode()
rsa_public_key = rsa.publickey().exportKey().decode()
return rsa_private_key, rsa_public_key
def rsa_encrypt(message, rsa_public_key):
""" 加密登录密码 """
key = RSA.importKey(rsa_public_key)
cipher = PKCS1_v1_5.new(key)
cipher_text = base64.b64encode(cipher.encrypt(message.encode())).decode()
return cipher_text
def rsa_decrypt(cipher_text, rsa_private_key=None):
""" 解密登录密码 """
if rsa_private_key is None:
# rsa_private_key 为 None可以能是API请求认证不需要解密
return cipher_text
key = RSA.importKey(rsa_private_key)
cipher = PKCS1_v1_5.new(key)
cipher_decoded = base64.b64decode(cipher_text.encode())
# Todo: 弄明白为何要以下这么写https://xbuba.com/questions/57035263
if len(cipher_decoded) == 127:
hex_fixed = '00' + cipher_decoded.hex()
cipher_decoded = base64.b16decode(hex_fixed.upper())
message = cipher.decrypt(cipher_decoded, b'error').decode()
return message
def check_different_city_login_if_need(user, request): def check_different_city_login_if_need(user, request):
if not settings.SECURITY_CHECK_DIFFERENT_CITY_LOGIN: if not settings.SECURITY_CHECK_DIFFERENT_CITY_LOGIN:
return return
ip = get_request_ip(request) or '0.0.0.0' ip = get_request_ip(request) or '0.0.0.0'
if not (ip and validate_ip(ip)): if not (ip and validate_ip(ip)):
city = DEFAULT_CITY city = DEFAULT_CITY
else: else:

View File

@ -96,7 +96,7 @@ class UserLoginView(mixins.AuthMixin, FormView):
self.request.session.delete_test_cookie() self.request.session.delete_test_cookie()
try: try:
self.check_user_auth(decrypt_passwd=True) self.check_user_auth(form.cleaned_data)
except errors.AuthFailedError as e: except errors.AuthFailedError as e:
form.add_error(None, e.msg) form.add_error(None, e.msg)
self.set_login_failed_mark() self.set_login_failed_mark()
@ -219,7 +219,7 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
try: try:
user = self.check_user_auth_if_need() user = self.get_user_from_session()
self.check_user_mfa_if_need(user) self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user) self.check_user_login_confirm_if_need(user)
except (errors.CredentialError, errors.SessionEmptyError) as e: except (errors.CredentialError, errors.SessionEmptyError) as e:

View File

@ -5,7 +5,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from ..utils import signer, crypto from common.utils import signer, crypto
__all__ = [ __all__ = [

View File

@ -3,9 +3,10 @@
from rest_framework import serializers from rest_framework import serializers
from common.utils import rsa_decrypt_by_session_pkey
__all__ = [ __all__ = [
'ReadableHiddenField', 'ReadableHiddenField', 'EncryptedField'
] ]
@ -23,3 +24,9 @@ class ReadableHiddenField(serializers.HiddenField):
if hasattr(value, 'id'): if hasattr(value, 'id'):
return getattr(value, 'id') return getattr(value, 'id')
return value return value
class EncryptedField(serializers.CharField):
def to_internal_value(self, value):
value = super().to_internal_value(value)
return rsa_decrypt_by_session_pkey(value)

View File

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
#
from .model import *

View File

@ -1,7 +1,10 @@
import base64 import base64
from Cryptodome.Cipher import AES import logging
from Cryptodome.Cipher import AES, PKCS1_v1_5
from Cryptodome.Util.Padding import pad from Cryptodome.Util.Padding import pad
from Cryptodome.Random import get_random_bytes from Cryptodome.Random import get_random_bytes
from Cryptodome.PublicKey import RSA
from Cryptodome import Random
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
from django.conf import settings from django.conf import settings
@ -193,4 +196,55 @@ class Crypto:
continue continue
def gen_key_pair(length=1024):
""" 生成加密key
用于登录页面提交用户名/密码时对密码进行加密前端/解密后端
"""
random_generator = Random.new().read
rsa = RSA.generate(length, random_generator)
rsa_private_key = rsa.exportKey().decode()
rsa_public_key = rsa.publickey().exportKey().decode()
return rsa_private_key, rsa_public_key
def rsa_encrypt(message, rsa_public_key):
""" 加密登录密码 """
key = RSA.importKey(rsa_public_key)
cipher = PKCS1_v1_5.new(key)
cipher_text = base64.b64encode(cipher.encrypt(message.encode())).decode()
return cipher_text
def rsa_decrypt(cipher_text, rsa_private_key=None):
""" 解密登录密码 """
if rsa_private_key is None:
# rsa_private_key 为 None可以能是API请求认证不需要解密
return cipher_text
key = RSA.importKey(rsa_private_key)
cipher = PKCS1_v1_5.new(key)
cipher_decoded = base64.b64decode(cipher_text.encode())
# Todo: 弄明白为何要以下这么写https://xbuba.com/questions/57035263
if len(cipher_decoded) == 127:
hex_fixed = '00' + cipher_decoded.hex()
cipher_decoded = base64.b16decode(hex_fixed.upper())
message = cipher.decrypt(cipher_decoded, b'error').decode()
return message
def rsa_decrypt_by_session_pkey(value):
from jumpserver.utils import current_request
private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
private_key = current_request.session.get(private_key_name)
if not private_key or not value:
return value
try:
value= rsa_decrypt(value, private_key)
except Exception as e:
logging.error('Decrypt field error: {}'.format(e))
return value
crypto = Crypto() crypto = Crypto()

View File

@ -95,6 +95,7 @@ MIDDLEWARE = [
'authentication.backends.cas.middleware.CASMiddleware', 'authentication.backends.cas.middleware.CASMiddleware',
'authentication.middleware.MFAMiddleware', 'authentication.middleware.MFAMiddleware',
'authentication.middleware.SessionCookieMiddleware', 'authentication.middleware.SessionCookieMiddleware',
'authentication.middleware.EncryptedMiddleware',
'simple_history.middleware.HistoryRequestMiddleware', 'simple_history.middleware.HistoryRequestMiddleware',
] ]

View File

@ -169,3 +169,6 @@ ANNOUNCEMENT = CONFIG.ANNOUNCEMENT
# help # help
HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL
HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL
SESSION_RSA_PRIVATE_KEY_NAME = 'jms_private_key'
SESSION_RSA_PUBLIC_KEY_NAME = 'jms_public_key'

View File

@ -1,6 +1,6 @@
# Generated by Django 2.2.7 on 2019-12-17 09:58 # Generated by Django 2.2.7 on 2019-12-17 09:58
import common.fields.model import common.db.fields
from django.db import migrations from django.db import migrations
@ -18,17 +18,17 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adhoc', model_name='adhoc',
name='_become', name='_become',
field=common.fields.model.EncryptJsonDictCharField(blank=True, null=True, default='', max_length=1024, verbose_name='Become'), field=common.db.fields.EncryptJsonDictCharField(blank=True, null=True, default='', max_length=1024, verbose_name='Become'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='adhoc', model_name='adhoc',
name='_options', name='_options',
field=common.fields.model.JsonDictCharField(default='', max_length=1024, verbose_name='Options'), field=common.db.fields.JsonDictCharField(default='', max_length=1024, verbose_name='Options'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='adhoc', model_name='adhoc',
name='_tasks', name='_tasks',
field=common.fields.model.JsonListTextField(verbose_name='Tasks'), field=common.db.fields.JsonListTextField(verbose_name='Tasks'),
), ),
migrations.RenameField( migrations.RenameField(
model_name='adhoc', model_name='adhoc',
@ -48,12 +48,12 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adhocrunhistory', model_name='adhocrunhistory',
name='_result', name='_result',
field=common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc raw result'), field=common.db.fields.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc raw result'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='adhocrunhistory', model_name='adhocrunhistory',
name='_summary', name='_summary',
field=common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc result summary'), field=common.db.fields.JsonDictTextField(blank=True, null=True, verbose_name='Adhoc result summary'),
), ),
migrations.RenameField( migrations.RenameField(
model_name='adhocrunhistory', model_name='adhocrunhistory',

View File

@ -1,6 +1,6 @@
# Generated by Django 2.2.7 on 2020-01-06 07:34 # Generated by Django 2.2.7 on 2020-01-06 07:34
import common.fields.model import common.db.fields
from django.db import migrations from django.db import migrations
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adhoc', model_name='adhoc',
name='become', name='become',
field=common.fields.model.EncryptJsonDictCharField(blank=True, default='', max_length=1024, null=True, verbose_name='Become'), field=common.db.fields.EncryptJsonDictCharField(blank=True, default='', max_length=1024, null=True, verbose_name='Become'),
), ),
] ]

View File

@ -9,11 +9,11 @@ from celery import current_task
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, gettext from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from common.utils.translate import translate_value from common.utils.translate import translate_value
from common.fields.model import ( from common.db.fields import (
JsonListTextField, JsonDictCharField, EncryptJsonDictCharField, JsonListTextField, JsonDictCharField, EncryptJsonDictCharField,
JsonDictTextField, JsonDictTextField,
) )

View File

@ -1,6 +1,8 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.drf.fields import EncryptedField
__all__ = [ __all__ = [
'LDAPTestConfigSerializer', 'LDAPUserSerializer', 'LDAPTestLoginSerializer', 'LDAPTestConfigSerializer', 'LDAPUserSerializer', 'LDAPTestLoginSerializer',
'LDAPSettingSerializer', 'LDAPSettingSerializer',
@ -20,7 +22,7 @@ class LDAPTestConfigSerializer(serializers.Serializer):
class LDAPTestLoginSerializer(serializers.Serializer): class LDAPTestLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=1024, required=True) username = serializers.CharField(max_length=1024, required=True)
password = serializers.CharField(max_length=2014, required=True) password = EncryptedField(max_length=2014, required=True)
class LDAPUserSerializer(serializers.Serializer): class LDAPUserSerializer(serializers.Serializer):

View File

@ -1501,3 +1501,19 @@ function getStatusIcon(status, mapping, title) {
} }
return icon; return icon;
} }
function encryptPassword(password) {
if (!password) {
return ''
}
var rsaPublicKeyText = getCookie('jms_public_key')
.replaceAll('"', '')
var rsaPublicKey = atob(rsaPublicKeyText)
var jsencrypt = new JSEncrypt(); //加密对象
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
var value = jsencrypt.encrypt(password); //加密
return value
}
window.encryptPassword = encryptPassword

View File

@ -1,6 +1,6 @@
# Generated by Django 2.2.5 on 2019-11-22 10:07 # Generated by Django 2.2.5 on 2019-11-22 10:07
import common.fields.model import common.db.fields
from django.db import migrations, models from django.db import migrations, models
import uuid import uuid
@ -21,7 +21,7 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=32, unique=True, verbose_name='Name')), ('name', models.CharField(max_length=32, unique=True, verbose_name='Name')),
('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('es', 'Elasticsearch')], default='server', max_length=16, verbose_name='Type')), ('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('es', 'Elasticsearch')], default='server', max_length=16, verbose_name='Type')),
('meta', common.fields.model.EncryptJsonDictTextField(default={})), ('meta', common.db.fields.EncryptJsonDictTextField(default={})),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
], ],
options={ options={
@ -37,7 +37,7 @@ class Migration(migrations.Migration):
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('name', models.CharField(max_length=32, unique=True, verbose_name='Name')), ('name', models.CharField(max_length=32, unique=True, verbose_name='Name')),
('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type')), ('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type')),
('meta', common.fields.model.EncryptJsonDictTextField(default={})), ('meta', common.db.fields.EncryptJsonDictTextField(default={})),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
], ],
options={ options={

View File

@ -1,7 +1,6 @@
# Generated by Django 3.1.14 on 2022-04-12 07:39 # Generated by Django 3.1.14 on 2022-04-12 07:39
import copy import common.db.fields
import common.fields.model
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -83,13 +82,13 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('host', models.CharField(max_length=256, verbose_name='Host', blank=True)), ('host', models.CharField(max_length=256, verbose_name='Host', blank=True)),
('https_port', common.fields.model.PortField(default=443, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTPS Port')), ('https_port', common.db.fields.PortField(default=443, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTPS Port')),
('http_port', common.fields.model.PortField(default=80, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTP Port')), ('http_port', common.db.fields.PortField(default=80, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTP Port')),
('ssh_port', common.fields.model.PortField(default=2222, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='SSH Port')), ('ssh_port', common.db.fields.PortField(default=2222, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='SSH Port')),
('rdp_port', common.fields.model.PortField(default=3389, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='RDP Port')), ('rdp_port', common.db.fields.PortField(default=3389, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='RDP Port')),
('mysql_port', common.fields.model.PortField(default=33060, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MySQL Port')), ('mysql_port', common.db.fields.PortField(default=33060, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MySQL Port')),
('mariadb_port', common.fields.model.PortField(default=33061, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MariaDB Port')), ('mariadb_port', common.db.fields.PortField(default=33061, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MariaDB Port')),
('postgresql_port', common.fields.model.PortField(default=54320, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='PostgreSQL Port')), ('postgresql_port', common.db.fields.PortField(default=54320, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='PostgreSQL Port')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
], ],
options={ options={

View File

@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from common.db.models import JMSModel from common.db.models import JMSModel
from common.fields.model import PortField from common.db.fields import PortField
from common.utils.ip import contains_ip from common.utils.ip import contains_ip

View File

@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from common.mixins import CommonModelMixin from common.mixins import CommonModelMixin
from common.utils import get_logger from common.utils import get_logger
from common.fields.model import EncryptJsonDictTextField from common.db.fields import EncryptJsonDictTextField
from terminal.backends import TYPE_ENGINE_MAPPING from terminal.backends import TYPE_ENGINE_MAPPING
from .terminal import Terminal from .terminal import Terminal
from .command import Command from .command import Command

View File

@ -1,6 +1,6 @@
# Generated by Django 2.2.5 on 2019-11-15 06:57 # Generated by Django 2.2.5 on 2019-11-15 06:57
import common.fields.model import common.db.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -26,7 +26,7 @@ class Migration(migrations.Migration):
('user_display', models.CharField(max_length=128, verbose_name='User display name')), ('user_display', models.CharField(max_length=128, verbose_name='User display name')),
('title', models.CharField(max_length=256, verbose_name='Title')), ('title', models.CharField(max_length=256, verbose_name='Title')),
('body', models.TextField(verbose_name='Body')), ('body', models.TextField(verbose_name='Body')),
('meta', common.fields.model.JsonDictTextField(default='{}', verbose_name='Meta')), ('meta', common.db.fields.JsonDictTextField(default='{}', verbose_name='Meta')),
('assignee_display', models.CharField(blank=True, max_length=128, null=True, verbose_name='Assignee display name')), ('assignee_display', models.CharField(blank=True, max_length=128, null=True, verbose_name='Assignee display name')),
('assignees_display', models.CharField(blank=True, max_length=128, verbose_name='Assignees display name')), ('assignees_display', models.CharField(blank=True, max_length=128, verbose_name='Assignees display name')),
('type', models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm')], default='general', max_length=16, verbose_name='Type')), ('type', models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm')], default='general', max_length=16, verbose_name='Type')),

View File

@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from authentication.forms import EncryptedField
from ..models import User from ..models import User
@ -17,7 +18,7 @@ __all__ = [
class UserCheckPasswordForm(forms.Form): class UserCheckPasswordForm(forms.Form):
password = forms.CharField( password = EncryptedField(
label=_('Password'), widget=forms.PasswordInput, label=_('Password'), widget=forms.PasswordInput,
max_length=1024, strip=False max_length=1024, strip=False
) )
@ -77,12 +78,12 @@ UserFirstLoginFinishForm.verbose_name = _("Finish")
class UserTokenResetPasswordForm(forms.Form): class UserTokenResetPasswordForm(forms.Form):
new_password = forms.CharField( new_password = EncryptedField(
min_length=5, max_length=128, min_length=5, max_length=128,
widget=forms.PasswordInput, widget=forms.PasswordInput,
label=_("New password") label=_("New password")
) )
confirm_password = forms.CharField( confirm_password = EncryptedField(
min_length=5, max_length=128, min_length=5, max_length=128,
widget=forms.PasswordInput, widget=forms.PasswordInput,
label=_("Confirm password") label=_("Confirm password")
@ -103,7 +104,7 @@ class UserForgotPasswordForm(forms.Form):
class UserPasswordForm(UserTokenResetPasswordForm): class UserPasswordForm(UserTokenResetPasswordForm):
old_password = forms.CharField( old_password = EncryptedField(
max_length=128, widget=forms.PasswordInput, max_length=128, widget=forms.PasswordInput,
label=_("Old password") label=_("Old password")
) )

View File

@ -1,6 +1,6 @@
# Generated by Django 2.1.7 on 2019-06-25 03:04 # Generated by Django 2.1.7 on 2019-06-25 03:04
import common.fields.model import common.db.fields
from django.db import migrations, models from django.db import migrations, models
@ -14,17 +14,17 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='_otp_secret_key', name='_otp_secret_key',
field=common.fields.model.EncryptCharField(blank=True, max_length=128, null=True), field=common.db.fields.EncryptCharField(blank=True, max_length=128, null=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='_private_key', name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='Private key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Private key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',
name='_public_key', name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='Public key'), field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Public key'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name='user',

View File

@ -1,6 +1,6 @@
# Generated by Django 3.1.13 on 2021-12-07 08:23 # Generated by Django 3.1.13 on 2021-12-07 08:23
import common.fields.model import common.db.fields
from django.db import migrations from django.db import migrations
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',
name='secret_key', name='secret_key',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Secret key'), field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Secret key'),
), ),
] ]

View File

@ -20,7 +20,7 @@ from django.shortcuts import reverse
from orgs.utils import current_org from orgs.utils import current_org
from orgs.models import Organization from orgs.models import Organization
from rbac.const import Scope from rbac.const import Scope
from common import fields from common.db import fields
from common.utils import ( from common.utils import (
date_expired_default, get_logger, lazyproperty, random_string, bulk_create_with_signal date_expired_default, get_logger, lazyproperty, random_string, bulk_create_with_signal
) )

View File

@ -3,6 +3,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from common.drf.fields import EncryptedField
from ..models import User from ..models import User
from .user import UserSerializer from .user import UserSerializer
@ -16,9 +17,9 @@ class UserOrgSerializer(serializers.Serializer):
class UserUpdatePasswordSerializer(serializers.ModelSerializer): class UserUpdatePasswordSerializer(serializers.ModelSerializer):
old_password = serializers.CharField(required=True, max_length=128, write_only=True) old_password = EncryptedField(required=True, max_length=128, write_only=True)
new_password = serializers.CharField(required=True, max_length=128, write_only=True) new_password = EncryptedField(required=True, max_length=128, write_only=True)
new_password_again = serializers.CharField(required=True, max_length=128, write_only=True) new_password_again = EncryptedField(required=True, max_length=128, write_only=True)
class Meta: class Meta:
model = User model = User
@ -41,11 +42,13 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer):
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return value return value
def validate_new_password_again(self, value): def validate(self, values):
if value != self.initial_data.get('new_password', ''): new_password = values.get('new_password', '')
new_password_again = values.get('new_password_again', '')
if new_password != new_password_again:
msg = _('The newly set password is inconsistent') msg = _('The newly set password is inconsistent')
raise serializers.ValidationError(msg) raise serializers.ValidationError({'new_password_again': msg})
return value return values
def update(self, instance, validated_data): def update(self, instance, validated_data):
new_password = self.validated_data.get('new_password') new_password = self.validated_data.get('new_password')

View File

@ -75,6 +75,19 @@ $(document).ready(function () {
var password = idPassword.val(); var password = idPassword.val();
checkPasswordRules(password, minLength); checkPasswordRules(password, minLength);
}) })
$("form").submit(function(){
// Let's find the input to check
var ids = ['id_new_password', 'id_confirm_password']
for (id of ids) {
var passwordRef = $('#' + id)
var value = passwordRef.val()
if (value) {
value = encryptPassword(value)
passwordRef.val(value)
}
}
});
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@ -15,10 +15,23 @@
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<div class="form-input form-group"> <div class="form-input form-group">
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required=""> <input type="password" id="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
</div> </div>
<button type="submit" class="btn btn-primary">{% trans 'Confirm' %}</button> <button type="submit" class="btn btn-primary">{% trans 'Confirm' %}</button>
</form> </form>
<script>
$("form").submit(function(){
// Let's find the input to check
var passwordRef = $('#password')
var value = passwordRef.val()
if (value) {
// Value is falsey (i.e. null), lets set a new one
value = encryptPassword(value)
passwordRef.val(value)
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -7,7 +7,7 @@ from django.shortcuts import redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from authentication.mixins import PasswordEncryptionViewMixin, AuthMixin from authentication.mixins import AuthMixin
from authentication import errors from authentication import errors
from common.utils import get_logger from common.utils import get_logger
@ -31,7 +31,7 @@ class UserVerifyPasswordView(AuthMixin, FormView):
return redirect('authentication:login') return redirect('authentication:login')
try: try:
password = self.get_decrypted_password(username=user.username) password = form.cleaned_data['password']
except errors.AuthFailedError as e: except errors.AuthFailedError as e:
form.add_error("password", _(f"Password invalid") + f'({e.msg})') form.add_error("password", _(f"Password invalid") + f'({e.msg})')
return self.form_invalid(form) return self.form_invalid(form)