mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-22 20:09:54 +00:00
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:
@@ -11,3 +11,4 @@ from .wecom import *
|
||||
from .dingtalk import *
|
||||
from .feishu import *
|
||||
from .password import *
|
||||
from .temp_token import *
|
||||
|
27
apps/authentication/api/temp_token.py
Normal file
27
apps/authentication/api/temp_token.py
Normal 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)
|
||||
|
@@ -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
|
||||
|
@@ -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__)
|
||||
|
||||
|
||||
|
@@ -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))
|
||||
|
@@ -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):
|
||||
|
@@ -14,7 +14,7 @@ from ..base import JMSModelBackend
|
||||
|
||||
__all__ = ['SAML2Backend']
|
||||
|
||||
logger = get_logger(__file__)
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SAML2Backend(JMSModelBackend):
|
||||
|
26
apps/authentication/backends/token.py
Normal file
26
apps/authentication/backends/token.py
Normal 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
|
32
apps/authentication/migrations/0010_temptoken.py
Normal file
32
apps/authentication/migrations/0010_temptoken.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
@@ -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
|
||||
|
3
apps/authentication/serializers/__init__.py
Normal file
3
apps/authentication/serializers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .token import *
|
||||
from .connect_token import *
|
||||
from .password_mfa import *
|
@@ -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)
|
33
apps/authentication/serializers/password_mfa.py
Normal file
33
apps/authentication/serializers/password_mfa.py
Normal 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)
|
103
apps/authentication/serializers/token.py
Normal file
103
apps/authentication/serializers/token.py
Normal 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
|
@@ -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')
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user