feat: 添加 临时 password (#8035)

* perf: 添加 template password

* perf: 修改id

* perf: 修改 翻译

* perf: 修改 tmp token

* perf: 修改 token

Co-authored-by: ibuler <ibuler@qq.com>
This commit is contained in:
fit2bot
2022-04-13 20:24:56 +08:00
committed by GitHub
parent 10b033010e
commit b610d71e11
26 changed files with 611 additions and 390 deletions

View File

@@ -11,3 +11,4 @@ from .wecom import *
from .dingtalk import *
from .feishu import *
from .password import *
from .temp_token import *

View File

@@ -0,0 +1,27 @@
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.decorators import action
from common.drf.api import JMSModelViewSet
from common.permissions import IsValidUser
from ..models import TempToken
from ..serializers import TempTokenSerializer
class TempTokenViewSet(JMSModelViewSet):
serializer_class = TempTokenSerializer
permission_classes = [IsValidUser]
http_method_names = ['post', 'get', 'options', 'patch']
def get_queryset(self):
username = self.request.user.username
return TempToken.objects.filter(username=username)
@action(methods=['PATCH'], detail=True, url_path='expire')
def expire(self, *args, **kwargs):
instance = self.get_object()
instance.date_expired = timezone.now()
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)

View File

@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
from django.shortcuts import redirect
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView

View File

@@ -1,10 +1,11 @@
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import User
from common.utils import get_logger
UserModel = get_user_model()
logger = get_logger(__file__)

View File

@@ -53,7 +53,7 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
else:
built = False
return (user, built)
return user, built
def pre_check(self, username, password):
if not settings.AUTH_LDAP:
@@ -75,6 +75,9 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
def authenticate(self, request=None, username=None, password=None, **kwargs):
logger.info('Authentication LDAP backend')
if username is None or password is None:
logger.info('No username or password')
return None
match, msg = self.pre_check(username, password)
if not match:
logger.info('Authenticate failed: {}'.format(msg))

View File

@@ -13,20 +13,23 @@ User = get_user_model()
class CreateUserMixin:
def get_django_user(self, username, password=None, *args, **kwargs):
@staticmethod
def get_django_user(username, password=None, *args, **kwargs):
if isinstance(username, bytes):
username = username.decode()
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
if '@' in username:
email = username
else:
email_suffix = settings.EMAIL_SUFFIX
email = '{}@{}'.format(username, email_suffix)
user = User(username=username, name=username, email=email)
user.source = user.Source.radius.value
user.save()
user = User.objects.filter(username=username).first()
if user:
return user
if '@' in username:
email = username
else:
email_suffix = settings.EMAIL_SUFFIX
email = '{}@{}'.format(username, email_suffix)
user = User(username=username, name=username, email=email)
user.source = user.Source.radius.value
user.save()
return user
def _perform_radius_auth(self, client, packet):

View File

@@ -14,7 +14,7 @@ from ..base import JMSModelBackend
__all__ = ['SAML2Backend']
logger = get_logger(__file__)
logger = get_logger(__name__)
class SAML2Backend(JMSModelBackend):

View File

@@ -0,0 +1,26 @@
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import PermissionDenied
from authentication.models import TempToken
from .base import JMSModelBackend
class TempTokenAuthBackend(JMSModelBackend):
model = TempToken
def authenticate(self, request, username='', password='', *args, **kwargs):
token = self.model.objects.filter(username=username, secret=password).first()
if not token:
return None
if not token.is_valid:
raise PermissionDenied('Token is invalid, expired at {}'.format(token.date_expired))
token.verified = True
token.date_verified = timezone.now()
token.save()
return token.user
@staticmethod
def is_enabled():
return settings.AUTH_TEMP_TOKEN

View File

@@ -0,0 +1,32 @@
# Generated by Django 3.1.14 on 2022-04-08 07:04
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('authentication', '0009_auto_20220310_0616'),
]
operations = [
migrations.CreateModel(
name='TempToken',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=32, 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')),
('username', models.CharField(max_length=128, verbose_name='Username')),
('secret', models.CharField(max_length=64, verbose_name='Secret')),
('verified', models.BooleanField(default=False, verbose_name='Verified')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('date_expired', models.DateTimeField(verbose_name='Date verified')),
],
options={
'verbose_name': 'Temporary token',
},
),
]

View File

@@ -1,8 +1,9 @@
import uuid
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from rest_framework.authtoken.models import Token
from django.conf import settings
from rest_framework.authtoken.models import Token
from common.db import models
@@ -64,6 +65,27 @@ class ConnectionToken(models.JMSBaseModel):
]
class TempToken(models.JMSModel):
username = models.CharField(max_length=128, verbose_name=_("Username"))
secret = models.CharField(max_length=64, verbose_name=_("Secret"))
verified = models.BooleanField(default=False, verbose_name=_("Verified"))
date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified"))
date_expired = models.DateTimeField(verbose_name=_("Date expired"))
class Meta:
verbose_name = _("Temporary token")
@property
def user(self):
from users.models import User
return User.objects.filter(username=self.username).first()
@property
def is_valid(self):
not_expired = self.date_expired and self.date_expired > timezone.now()
return not self.verified and not_expired
class SuperConnectionToken(ConnectionToken):
class Meta:
proxy = True

View File

@@ -0,0 +1,3 @@
from .token import *
from .connect_token import *
from .password_mfa import *

View File

@@ -1,109 +1,22 @@
# -*- coding: utf-8 -*-
#
from django.utils import timezone
from rest_framework import serializers
from common.utils import get_object_or_none
from users.models import User
from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule
from applications.models import Application
from users.serializers import UserProfileSerializer
from assets.serializers import ProtocolsField
from perms.serializers.base import ActionsField
from .models import AccessKey
__all__ = [
'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer',
'MFAChallengeSerializer', 'SSOTokenSerializer',
'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer',
'PasswordVerifySerializer', 'MFASelectTypeSerializer',
'ConnectionTokenSerializer', 'ConnectionTokenApplicationSerializer',
'ConnectionTokenUserSerializer', 'ConnectionTokenFilterRuleSerializer',
'ConnectionTokenAssetSerializer', 'ConnectionTokenSystemUserSerializer',
'ConnectionTokenDomainSerializer', 'ConnectionTokenRemoteAppSerializer',
'ConnectionTokenGatewaySerializer', 'ConnectionTokenSecretSerializer'
]
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ['id', 'secret', 'is_active', 'date_created']
read_only_fields = ['id', 'secret', 'date_created']
class OtpVerifySerializer(serializers.Serializer):
code = serializers.CharField(max_length=6, min_length=6)
class PasswordVerifySerializer(serializers.Serializer):
password = serializers.CharField()
class BearerTokenSerializer(serializers.Serializer):
username = serializers.CharField(allow_null=True, required=False, write_only=True)
password = serializers.CharField(write_only=True, allow_null=True,
required=False, allow_blank=True)
public_key = serializers.CharField(write_only=True, allow_null=True,
allow_blank=True, required=False)
token = serializers.CharField(read_only=True)
keyword = serializers.SerializerMethodField()
date_expired = serializers.DateTimeField(read_only=True)
user = UserProfileSerializer(read_only=True)
@staticmethod
def get_keyword(obj):
return 'Bearer'
def update_last_login(self, user):
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
def get_request_user(self):
request = self.context.get('request')
if request.user and request.user.is_authenticated:
user = request.user
else:
user_id = request.session.get('user_id')
user = get_object_or_none(User, pk=user_id)
if not user:
raise serializers.ValidationError(
"user id {} not exist".format(user_id)
)
return user
def create(self, validated_data):
request = self.context.get('request')
user = self.get_request_user()
token, date_expired = user.create_bearer_token(request)
self.update_last_login(user)
instance = {
"token": token,
"date_expired": date_expired,
"user": user
}
return instance
class MFASelectTypeSerializer(serializers.Serializer):
type = serializers.CharField()
username = serializers.CharField(required=False, allow_blank=True, allow_null=True)
class MFAChallengeSerializer(serializers.Serializer):
type = serializers.CharField(write_only=True, required=False, allow_blank=True)
code = serializers.CharField(write_only=True)
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class SSOTokenSerializer(serializers.Serializer):
username = serializers.CharField(write_only=True)
login_url = serializers.CharField(read_only=True)
next = serializers.CharField(write_only=True, allow_blank=True, required=False, allow_null=True)
class ConnectionTokenSerializer(serializers.Serializer):
user = serializers.CharField(max_length=128, required=False, allow_blank=True)
system_user = serializers.CharField(max_length=128, required=True)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
__all__ = [
'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer',
'PasswordVerifySerializer',
]
class PasswordVerifySerializer(serializers.Serializer):
password = serializers.CharField()
class MFASelectTypeSerializer(serializers.Serializer):
type = serializers.CharField()
username = serializers.CharField(required=False, allow_blank=True, allow_null=True)
class MFAChallengeSerializer(serializers.Serializer):
type = serializers.CharField(write_only=True, required=False, allow_blank=True)
code = serializers.CharField(write_only=True)
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class OtpVerifySerializer(serializers.Serializer):
code = serializers.CharField(max_length=6, min_length=6)

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
#
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.utils import get_object_or_none, random_string
from users.models import User
from users.serializers import UserProfileSerializer
from ..models import AccessKey, TempToken
__all__ = [
'AccessKeySerializer', 'BearerTokenSerializer',
'SSOTokenSerializer', 'TempTokenSerializer',
]
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ['id', 'secret', 'is_active', 'date_created']
read_only_fields = ['id', 'secret', 'date_created']
class BearerTokenSerializer(serializers.Serializer):
username = serializers.CharField(allow_null=True, required=False, write_only=True)
password = serializers.CharField(write_only=True, allow_null=True,
required=False, allow_blank=True)
public_key = serializers.CharField(write_only=True, allow_null=True,
allow_blank=True, required=False)
token = serializers.CharField(read_only=True)
keyword = serializers.SerializerMethodField()
date_expired = serializers.DateTimeField(read_only=True)
user = UserProfileSerializer(read_only=True)
@staticmethod
def get_keyword(obj):
return 'Bearer'
def update_last_login(self, user):
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
def get_request_user(self):
request = self.context.get('request')
if request.user and request.user.is_authenticated:
user = request.user
else:
user_id = request.session.get('user_id')
user = get_object_or_none(User, pk=user_id)
if not user:
raise serializers.ValidationError(
"user id {} not exist".format(user_id)
)
return user
def create(self, validated_data):
request = self.context.get('request')
user = self.get_request_user()
token, date_expired = user.create_bearer_token(request)
self.update_last_login(user)
instance = {
"token": token,
"date_expired": date_expired,
"user": user
}
return instance
class SSOTokenSerializer(serializers.Serializer):
username = serializers.CharField(write_only=True)
login_url = serializers.CharField(read_only=True)
next = serializers.CharField(write_only=True, allow_blank=True, required=False, allow_null=True)
class TempTokenSerializer(serializers.ModelSerializer):
is_valid = serializers.BooleanField(label=_("Is valid"), read_only=True)
class Meta:
model = TempToken
fields = [
'id', 'username', 'secret', 'verified', 'is_valid',
'date_created', 'date_updated', 'date_verified',
'date_expired',
]
read_only_fields = fields
def create(self, validated_data):
request = self.context.get('request')
if not request or not request.user:
raise PermissionError()
secret = random_string(36)
username = request.user.username
kwargs = {
'username': username, 'secret': secret,
'date_expired': timezone.now() + timezone.timedelta(seconds=5*60),
}
token = TempToken(**kwargs)
token.save()
return token

View File

@@ -9,6 +9,7 @@ app_name = 'authentication'
router = DefaultRouter()
router.register('access-keys', api.AccessKeyViewSet, 'access-key')
router.register('sso', api.SSOViewSet, 'sso')
router.register('temp-tokens', api.TempTokenViewSet, 'temp-token')
router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token')