perf: 优化用户 access key 的使用和创建 (#11776)

* perf: 优化用户 access key 的使用和创建

* perf: 优化 access key api

---------

Co-authored-by: ibuler <ibuler@qq.com>
pull/11784/head^2
fit2bot 2023-10-10 17:52:52 +08:00 committed by GitHub
parent 30b19d31eb
commit 333746e7c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 417 additions and 330 deletions

View File

@ -19,7 +19,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='operatelog', model_name='operatelog',
name='action', name='action',
field=models.CharField(choices=[('view', 'View'), ('update', 'Update'), ('delete', 'Delete'), ('create', 'Create'), ('download', 'Download'), ('connect', 'Connect'), ('login', 'Login'), ('change_password', 'Change password')], max_length=16, verbose_name='Action'), field=models.CharField(choices=[('view', 'View'), ('update', 'Update'), ('delete', 'Delete'), ('create', 'Create'), ('download', 'Download'), ('connect', 'Connect'), ('login', 'Login'), ('change_password', 'Change password'), ('reject', 'Reject'), ('accept', 'Accept'), ('review', 'Review')], max_length=16, verbose_name='Action'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='userloginlog', model_name='userloginlog',

View File

@ -1,20 +1,44 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework.viewsets import ModelViewSet from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.response import Response
from common.api import JMSModelViewSet
from common.permissions import UserConfirmation
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from ..const import ConfirmType
from ..serializers import AccessKeySerializer from ..serializers import AccessKeySerializer
class AccessKeyViewSet(ModelViewSet): class AccessKeyViewSet(JMSModelViewSet):
serializer_class = AccessKeySerializer serializer_class = AccessKeySerializer
search_fields = ['^id', '^secret'] search_fields = ['^id']
permission_classes = [RBACPermission] permission_classes = [RBACPermission]
def get_queryset(self): def get_queryset(self):
return self.request.user.access_keys.all() return self.request.user.access_keys.all()
def get_permissions(self):
if self.is_swagger_request():
return super().get_permissions()
if self.action == 'create':
self.permission_classes = [
RBACPermission, UserConfirmation.require(ConfirmType.PASSWORD)
]
return super().get_permissions()
def perform_create(self, serializer): def perform_create(self, serializer):
user = self.request.user user = self.request.user
user.create_access_key() if user.access_keys.count() >= 10:
raise serializers.ValidationError(_('Access keys can be created at most 10'))
key = user.create_access_key()
return key
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
key = self.perform_create(serializer)
return Response({'secret': key.secret, 'id': key.id}, status=201)

View File

@ -13,7 +13,7 @@ from ..serializers import ConfirmSerializer
class ConfirmBindORUNBindOAuth(RetrieveAPIView): class ConfirmBindORUNBindOAuth(RetrieveAPIView):
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),) permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.RELOGIN),)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
return Response('ok') return Response('ok')
@ -24,7 +24,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
serializer_class = ConfirmSerializer serializer_class = ConfirmSerializer
def get_confirm_backend(self, confirm_type): def get_confirm_backend(self, confirm_type):
backend_classes = ConfirmType.get_can_confirm_backend_classes(confirm_type) backend_classes = ConfirmType.get_prop_backends(confirm_type)
if not backend_classes: if not backend_classes:
return return
for backend_cls in backend_classes: for backend_cls in backend_classes:
@ -34,7 +34,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
return backend return backend
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
confirm_type = request.query_params.get('confirm_type') confirm_type = request.query_params.get('confirm_type', 'password')
backend = self.get_confirm_backend(confirm_type) backend = self.get_confirm_backend(confirm_type)
if backend is None: if backend is None:
msg = _('This action require verify your MFA') msg = _('This action require verify your MFA')
@ -51,7 +51,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data validated_data = serializer.validated_data
confirm_type = validated_data.get('confirm_type') confirm_type = validated_data.get('confirm_type', 'password')
mfa_type = validated_data.get('mfa_type') mfa_type = validated_data.get('mfa_type')
secret_key = validated_data.get('secret_key') secret_key = validated_data.get('secret_key')

View File

@ -27,7 +27,7 @@ class DingTalkQRUnBindBase(APIView):
class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase): class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase):
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),) permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.RELOGIN),)
class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase): class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase):

View File

@ -27,7 +27,7 @@ class FeiShuQRUnBindBase(APIView):
class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase): class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase):
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),) permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.RELOGIN),)
class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase): class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase):

View File

@ -27,7 +27,7 @@ class WeComQRUnBindBase(APIView):
class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase): class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase):
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),) permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.RELOGIN),)
class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase): class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase):

View File

@ -1,119 +1,33 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import time
import uuid
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.cache import cache from django.core.cache import cache
from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import authentication, exceptions from rest_framework import authentication, exceptions
from six import text_type
from common.auth import signature from common.auth import signature
from common.utils import get_object_or_none, make_signature, http_to_unixtime from common.utils import get_object_or_none
from .base import JMSBaseAuthBackend
from ..models import AccessKey, PrivateToken from ..models import AccessKey, PrivateToken
UserModel = get_user_model()
def date_more_than(d, seconds):
return d is None or (timezone.now() - d).seconds > seconds
def get_request_date_header(request): def after_authenticate_update_date(user, token=None):
date = request.META.get('HTTP_DATE', b'') if date_more_than(user.date_api_key_last_used, 60):
if isinstance(date, text_type): user.date_api_key_last_used = timezone.now()
# Work around django test client oddness user.save(update_fields=['date_api_key_last_used'])
date = date.encode(HTTP_HEADER_ENCODING)
return date
if token and hasattr(token, 'date_last_used') and date_more_than(token.date_last_used, 60):
class AccessKeyAuthentication(authentication.BaseAuthentication): token.date_last_used = timezone.now()
"""App使用Access key进行签名认证, 目前签名算法比较简单, token.save(update_fields=['date_last_used'])
app注册或者手动建立后,会生成 access_key_id access_key_secret,
然后使用 如下算法生成签名:
Signature = md5(access_key_secret + '\n' + Date)
example: Signature = md5('d32d2b8b-9a10-4b8d-85bb-1a66976f6fdc' + '\n' +
'Thu, 12 Jan 2017 08:19:41 GMT')
请求时设置请求header
header['Authorization'] = 'Sign access_key_id:Signature' :
header['Authorization'] =
'Sign d32d2b8b-9a10-4b8d-85bb-1a66976f6fdc:OKOlmdxgYPZ9+SddnUUDbQ=='
验证时根据相同算法进行验证, 取到access_key_id对应的access_key_id, 从request
headers取到Date, 然后进行md5, 判断得到的结果是否相同, 如果是认证通过, 否则 认证
失败
"""
keyword = 'Sign'
def authenticate(self, request):
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
msg = _('Invalid signature header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid signature header. Signature '
'string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
sign = auth[1].decode().split(':')
if len(sign) != 2:
msg = _('Invalid signature header. '
'Format like AccessKeyId:Signature')
raise exceptions.AuthenticationFailed(msg)
except UnicodeError:
msg = _('Invalid signature header. '
'Signature string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
access_key_id = sign[0]
try:
uuid.UUID(access_key_id)
except ValueError:
raise exceptions.AuthenticationFailed('Access key id invalid')
request_signature = sign[1]
return self.authenticate_credentials(
request, access_key_id, request_signature
)
@staticmethod
def authenticate_credentials(request, access_key_id, request_signature):
access_key = get_object_or_none(AccessKey, id=access_key_id)
request_date = get_request_date_header(request)
if access_key is None or not access_key.user:
raise exceptions.AuthenticationFailed(_('Invalid signature.'))
access_key_secret = access_key.secret
try:
request_unix_time = http_to_unixtime(request_date)
except ValueError:
raise exceptions.AuthenticationFailed(
_('HTTP header: Date not provide '
'or not %a, %d %b %Y %H:%M:%S GMT'))
if int(time.time()) - request_unix_time > 15 * 60:
raise exceptions.AuthenticationFailed(
_('Expired, more than 15 minutes'))
signature = make_signature(access_key_secret, request_date)
if not signature == request_signature:
raise exceptions.AuthenticationFailed(_('Invalid signature.'))
if not access_key.user.is_active:
raise exceptions.AuthenticationFailed(_('User disabled.'))
return access_key.user, None
def authenticate_header(self, request):
return 'Sign access_key_id:Signature'
class AccessTokenAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Bearer' keyword = 'Bearer'
# expiration = settings.TOKEN_EXPIRATION or 3600
model = get_user_model() model = get_user_model()
def authenticate(self, request): def authenticate(self, request):
@ -125,19 +39,20 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
msg = _('Invalid token header. No credentials provided.') msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2: elif len(auth) > 2:
msg = _('Invalid token header. Sign string ' msg = _('Invalid token header. Sign string should not contain spaces.')
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
try: try:
token = auth[1].decode() token = auth[1].decode()
except UnicodeError: except UnicodeError:
msg = _('Invalid token header. Sign string ' msg = _('Invalid token header. Sign string should not contain invalid characters.')
'should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token) user, header = self.authenticate_credentials(token)
after_authenticate_update_date(user)
return user, header
def authenticate_credentials(self, token): @staticmethod
def authenticate_credentials(token):
model = get_user_model() model = get_user_model()
user_id = cache.get(token) user_id = cache.get(token)
user = get_object_or_none(model, id=user_id) user = get_object_or_none(model, id=user_id)
@ -151,15 +66,23 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
return self.keyword return self.keyword
class PrivateTokenAuthentication(JMSBaseAuthBackend, authentication.TokenAuthentication): class PrivateTokenAuthentication(authentication.TokenAuthentication):
model = PrivateToken model = PrivateToken
def authenticate(self, request):
user_token = super().authenticate(request)
if not user_token:
return
user, token = user_token
after_authenticate_update_date(user, token)
return user, token
class SessionAuthentication(authentication.SessionAuthentication): class SessionAuthentication(authentication.SessionAuthentication):
def authenticate(self, request): def authenticate(self, request):
""" """
Returns a `User` if the request session currently has a logged in user. Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`. Otherwise, returns `None`.
""" """
# Get the session-based user from the underlying HttpRequest object # Get the session-based user from the underlying HttpRequest object
@ -195,6 +118,7 @@ class SignatureAuthentication(signature.SignatureAuthentication):
if not key.is_active: if not key.is_active:
return None, None return None, None
user, secret = key.user, str(key.secret) user, secret = key.user, str(key.secret)
after_authenticate_update_date(user, key)
return user, secret return user, secret
except (AccessKey.DoesNotExist, exceptions.ValidationError): except (AccessKey.DoesNotExist, exceptions.ValidationError):
return None, None return None, None

View File

@ -2,7 +2,6 @@ import abc
class BaseConfirm(abc.ABC): class BaseConfirm(abc.ABC):
def __init__(self, user, request): def __init__(self, user, request):
self.user = user self.user = user
self.request = request self.request = request
@ -23,7 +22,7 @@ class BaseConfirm(abc.ABC):
@property @property
def content(self): def content(self):
return '' return []
@abc.abstractmethod @abc.abstractmethod
def authenticate(self, secret_key, mfa_type) -> tuple: def authenticate(self, secret_key, mfa_type) -> tuple:

View File

@ -15,3 +15,14 @@ class ConfirmPassword(BaseConfirm):
ok = authenticate(self.request, username=self.user.username, password=secret_key) ok = authenticate(self.request, username=self.user.username, password=secret_key)
msg = '' if ok else _('Authentication failed password incorrect') msg = '' if ok else _('Authentication failed password incorrect')
return ok, msg return ok, msg
@property
def content(self):
return [
{
'name': 'password',
'display_name': _('Password'),
'disabled': False,
'placeholder': _('Password'),
}
]

View File

@ -11,7 +11,7 @@ CONFIRM_BACKEND_MAP = {backend.name: backend for backend in CONFIRM_BACKENDS}
class ConfirmType(TextChoices): class ConfirmType(TextChoices):
ReLogin = ConfirmReLogin.name, ConfirmReLogin.display_name RELOGIN = ConfirmReLogin.name, ConfirmReLogin.display_name
PASSWORD = ConfirmPassword.name, ConfirmPassword.display_name PASSWORD = ConfirmPassword.name, ConfirmPassword.display_name
MFA = ConfirmMFA.name, ConfirmMFA.display_name MFA = ConfirmMFA.name, ConfirmMFA.display_name
@ -23,10 +23,11 @@ class ConfirmType(TextChoices):
return types return types
@classmethod @classmethod
def get_can_confirm_backend_classes(cls, confirm_type): def get_prop_backends(cls, confirm_type):
types = cls.get_can_confirm_types(confirm_type) types = cls.get_can_confirm_types(confirm_type)
backend_classes = [ backend_classes = [
CONFIRM_BACKEND_MAP[tp] for tp in types if tp in CONFIRM_BACKEND_MAP CONFIRM_BACKEND_MAP[tp]
for tp in types if tp in CONFIRM_BACKEND_MAP
] ]
return backend_classes return backend_classes

View File

@ -0,0 +1,57 @@
# Generated by Django 4.1.10 on 2023-10-10 02:47
import uuid
import authentication.models.access_key
from django.db import migrations, models
def migrate_access_key_secret(apps, schema_editor):
access_key_model = apps.get_model('authentication', 'AccessKey')
db_alias = schema_editor.connection.alias
batch_size = 100
count = 0
while True:
access_keys = access_key_model.objects.using(db_alias).all()[count:count + batch_size]
if not access_keys:
break
count += len(access_keys)
access_keys_updated = []
for access_key in access_keys:
s = access_key.secret
if len(s) != 32 or not s.islower():
continue
try:
access_key.secret = '%s-%s-%s-%s-%s' % (s[:8], s[8:12], s[12:16], s[16:20], s[20:])
access_keys_updated.append(access_key)
except (ValueError, IndexError):
pass
access_key_model.objects.bulk_update(access_keys_updated, fields=['secret'])
class Migration(migrations.Migration):
dependencies = [
('authentication', '0022_passkey'),
]
operations = [
migrations.AddField(
model_name='accesskey',
name='date_last_used',
field=models.DateTimeField(blank=True, null=True, verbose_name='Date last used'),
),
migrations.AddField(
model_name='privatetoken',
name='date_last_used',
field=models.DateTimeField(blank=True, null=True, verbose_name='Date last used'),
),
migrations.AlterField(
model_name='accesskey',
name='secret',
field=models.CharField(default=authentication.models.access_key.default_secret, max_length=36, verbose_name='AccessKeySecret'),
),
migrations.RunPython(migrate_access_key_secret),
]

View File

@ -5,16 +5,20 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import common.db.models import common.db.models
from common.utils.random import random_string
def default_secret():
return random_string(36)
class AccessKey(models.Model): class AccessKey(models.Model):
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, default=uuid.uuid4, editable=False)
default=uuid.uuid4, editable=False) secret = models.CharField(verbose_name='AccessKeySecret', default=default_secret, max_length=36)
secret = models.UUIDField(verbose_name='AccessKeySecret',
default=uuid.uuid4, editable=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='access_keys') on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='access_keys')
is_active = models.BooleanField(default=True, verbose_name=_('Active')) is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True)
def get_id(self): def get_id(self):

View File

@ -1,9 +1,11 @@
from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
class PrivateToken(Token): class PrivateToken(Token):
"""Inherit from auth token, otherwise migration is boring""" """Inherit from auth token, otherwise migration is boring"""
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
class Meta: class Meta:
verbose_name = _('Private Token') verbose_name = _('Private Token')

View File

@ -10,7 +10,7 @@ from users.serializers import UserProfileSerializer
from ..models import AccessKey, TempToken from ..models import AccessKey, TempToken
__all__ = [ __all__ = [
'AccessKeySerializer', 'BearerTokenSerializer', 'AccessKeySerializer', 'BearerTokenSerializer',
'SSOTokenSerializer', 'TempTokenSerializer', 'SSOTokenSerializer', 'TempTokenSerializer',
] ]
@ -18,8 +18,8 @@ __all__ = [
class AccessKeySerializer(serializers.ModelSerializer): class AccessKeySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AccessKey model = AccessKey
fields = ['id', 'secret', 'is_active', 'date_created'] fields = ['id', 'is_active', 'date_created', 'date_last_used']
read_only_fields = ['id', 'secret', 'date_created'] read_only_fields = ['id', 'date_created', 'date_last_used']
class BearerTokenSerializer(serializers.Serializer): class BearerTokenSerializer(serializers.Serializer):
@ -37,7 +37,8 @@ class BearerTokenSerializer(serializers.Serializer):
def get_keyword(obj): def get_keyword(obj):
return 'Bearer' return 'Bearer'
def update_last_login(self, user): @staticmethod
def update_last_login(user):
user.last_login = timezone.now() user.last_login = timezone.now()
user.save(update_fields=['last_login']) user.save(update_fields=['last_login'])
@ -96,7 +97,7 @@ class TempTokenSerializer(serializers.ModelSerializer):
username = request.user.username username = request.user.username
kwargs = { kwargs = {
'username': username, 'secret': secret, 'username': username, 'secret': secret,
'date_expired': timezone.now() + timezone.timedelta(seconds=5*60), 'date_expired': timezone.now() + timezone.timedelta(seconds=5 * 60),
} }
token = TempToken(**kwargs) token = TempToken(**kwargs)
token.save() token.save()

View File

View File

@ -0,0 +1,34 @@
# Python 示例
# pip install requests drf-httpsig
import datetime
import json
import requests
from httpsig.requests_auth import HTTPSignatureAuth
def get_auth(KeyID, SecretID):
signature_headers = ['(request-target)', 'accept', 'date']
auth = HTTPSignatureAuth(key_id=KeyID, secret=SecretID, algorithm='hmac-sha256', headers=signature_headers)
return auth
def get_user_info(jms_url, auth):
url = jms_url + '/api/v1/users/users/?limit=1'
gmt_form = '%a, %d %b %Y %H:%M:%S GMT'
headers = {
'Accept': 'application/json',
'X-JMS-ORG': '00000000-0000-0000-0000-000000000002',
'Date': datetime.datetime.utcnow().strftime(gmt_form)
}
response = requests.get(url, auth=auth, headers=headers)
print(json.loads(response.text))
if __name__ == '__main__':
jms_url = 'http://localhost:8080'
KeyID = '0753098d-810c-45fb-b42c-b27077147933'
SecretID = 'a58d2530-d7ee-4390-a204-3492e44dde84'
auth = get_auth(KeyID, SecretID)
get_user_info(jms_url, auth)

View File

@ -99,7 +99,7 @@ class DingTalkOAuthMixin(DingTalkBaseMixin, View):
class DingTalkQRBindView(DingTalkQRMixin, View): class DingTalkQRBindView(DingTalkQRMixin, View):
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin)) permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.RELOGIN))
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
user = request.user user = request.user

View File

@ -69,7 +69,7 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMe
class FeiShuQRBindView(FeiShuQRMixin, View): class FeiShuQRBindView(FeiShuQRMixin, View):
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin)) permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.RELOGIN))
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') redirect_url = request.GET.get('redirect_url')

View File

@ -100,7 +100,7 @@ class WeComOAuthMixin(WeComBaseMixin, View):
class WeComQRBindView(WeComQRMixin, View): class WeComQRBindView(WeComQRMixin, View):
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin)) permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.RELOGIN))
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
user = request.user user = request.user

View File

@ -13,7 +13,7 @@ from common.drf.filters import (
IDSpmFilterBackend, CustomFilterBackend, IDInFilterBackend, IDSpmFilterBackend, CustomFilterBackend, IDInFilterBackend,
IDNotFilterBackend, NotOrRelFilterBackend IDNotFilterBackend, NotOrRelFilterBackend
) )
from common.utils import get_logger from common.utils import get_logger, lazyproperty
from .action import RenderToJsonMixin from .action import RenderToJsonMixin
from .serializer import SerializerMixin from .serializer import SerializerMixin
@ -150,9 +150,9 @@ class OrderingFielderFieldsMixin:
ordering_fields = None ordering_fields = None
extra_ordering_fields = [] extra_ordering_fields = []
def __init__(self, *args, **kwargs): @lazyproperty
super().__init__(*args, **kwargs) def ordering_fields(self):
self.ordering_fields = self._get_ordering_fields() return self._get_ordering_fields()
def _get_ordering_fields(self): def _get_ordering_fields(self):
if isinstance(self.__class__.ordering_fields, (list, tuple)): if isinstance(self.__class__.ordering_fields, (list, tuple)):
@ -179,7 +179,10 @@ class OrderingFielderFieldsMixin:
model = self.queryset.model model = self.queryset.model
else: else:
queryset = self.get_queryset() queryset = self.get_queryset()
model = queryset.model if isinstance(queryset, list):
model = None
else:
model = queryset.model
if not model: if not model:
return [] return []
@ -201,4 +204,6 @@ class CommonApiMixin(
SerializerMixin, ExtraFilterFieldsMixin, OrderingFielderFieldsMixin, SerializerMixin, ExtraFilterFieldsMixin, OrderingFielderFieldsMixin,
QuerySetMixin, RenderToJsonMixin, PaginatedResponseMixin QuerySetMixin, RenderToJsonMixin, PaginatedResponseMixin
): ):
pass def is_swagger_request(self):
return getattr(self, 'swagger_fake_view', False) or \
getattr(self, 'raw_action', '') == 'metadata'

View File

@ -59,7 +59,7 @@ class WithBootstrapToken(permissions.BasePermission):
class UserConfirmation(permissions.BasePermission): class UserConfirmation(permissions.BasePermission):
ttl = 60 * 5 ttl = 60 * 5
min_level = 1 min_level = 1
confirm_type = ConfirmType.ReLogin confirm_type = ConfirmType.RELOGIN
def has_permission(self, request, view): def has_permission(self, request, view):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA: if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
@ -82,7 +82,7 @@ class UserConfirmation(permissions.BasePermission):
return ttl return ttl
@classmethod @classmethod
def require(cls, confirm_type=ConfirmType.ReLogin, ttl=60 * 5): def require(cls, confirm_type=ConfirmType.RELOGIN, ttl=60 * 5):
min_level = ConfirmType.values.index(confirm_type) + 1 min_level = ConfirmType.values.index(confirm_type) + 1
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl) name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type}) return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56db214d34b91e9f6d712cef124890264144b35b99e50d6833b4af0e935778b0 oid sha256:38bd8a6653f3f4dc63552b1c86379f82067f9f9daac227bacb3af3f9f62134f9
size 162655 size 161704

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-09 18:32+0800\n" "POT-Creation-Date: 2023-10-10 11:13+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -25,10 +25,11 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。"
#: accounts/const/account.py:6 #: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:32 #: accounts/serializers/automations/change_secret.py:32
#: assets/models/_user.py:24 audits/signal_handlers/login_log.py:37 #: assets/models/_user.py:24 audits/signal_handlers/login_log.py:37
#: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/confirm/password.py:9 authentication/confirm/password.py:24
#: authentication/confirm/password.py:26 authentication/forms.py:32
#: authentication/templates/authentication/login.html:324 #: authentication/templates/authentication/login.html:324
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28 #: xpack/plugins/cloud/serializers/account_attrs.py:28
@ -40,7 +41,7 @@ msgstr "パスワード"
msgid "SSH key" msgid "SSH key"
msgstr "SSH キー" msgstr "SSH キー"
#: accounts/const/account.py:8 authentication/models/access_key.py:33 #: accounts/const/account.py:8 authentication/models/access_key.py:37
msgid "Access key" msgid "Access key"
msgstr "アクセスキー" msgstr "アクセスキー"
@ -251,7 +252,7 @@ msgid "Version"
msgstr "バージョン" msgstr "バージョン"
#: accounts/models/account.py:56 accounts/serializers/account/account.py:211 #: accounts/models/account.py:56 accounts/serializers/account/account.py:211
#: users/models/user.py:840 #: users/models/user.py:837
msgid "Source" msgid "Source"
msgstr "ソース" msgstr "ソース"
@ -552,7 +553,7 @@ msgstr "特権アカウント"
#: assets/models/label.py:22 #: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114 #: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:40 #: terminal/models/applet/applet.py:40
#: terminal/models/component/endpoint.py:105 users/serializers/user.py:170 #: terminal/models/component/endpoint.py:105 users/serializers/user.py:167
msgid "Is active" msgid "Is active"
msgstr "アクティブです。" msgstr "アクティブです。"
@ -711,7 +712,7 @@ msgstr "編集済み"
#: acls/templates/acls/asset_login_reminder.html:6 #: acls/templates/acls/asset_login_reminder.html:6
#: assets/models/automations/base.py:19 #: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 #: assets/serializers/automations/base.py:20
#: authentication/api/connection_token.py:381 ops/models/base.py:17 #: authentication/api/connection_token.py:386 ops/models/base.py:17
#: ops/models/job.py:139 ops/serializers/job.py:21 #: ops/models/job.py:139 ops/serializers/job.py:21
#: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: terminal/templates/terminal/_msg_command_execute_alert.html:16
msgid "Assets" msgid "Assets"
@ -751,8 +752,8 @@ msgstr "ID"
#: terminal/notifications.py:205 terminal/serializers/command.py:16 #: terminal/notifications.py:205 terminal/serializers/command.py:16
#: terminal/templates/terminal/_msg_command_warning.html:6 #: terminal/templates/terminal/_msg_command_warning.html:6
#: terminal/templates/terminal/_msg_session_sharing.html:6 #: terminal/templates/terminal/_msg_session_sharing.html:6
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:990 #: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:987
#: users/models/user.py:1026 users/serializers/group.py:18 #: users/models/user.py:1023 users/serializers/group.py:18
msgid "User" msgid "User"
msgstr "ユーザー" msgstr "ユーザー"
@ -1006,7 +1007,7 @@ msgstr "1-100、低い値は最初に一致します"
msgid "Reviewers" msgid "Reviewers"
msgstr "レビュー担当者" msgstr "レビュー担当者"
#: acls/models/base.py:43 authentication/models/access_key.py:17 #: acls/models/base.py:43 authentication/models/access_key.py:20
#: authentication/models/connection_token.py:53 #: authentication/models/connection_token.py:53
#: authentication/templates/authentication/_access_key_modal.html:32 #: authentication/templates/authentication/_access_key_modal.html:32
#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29 #: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29
@ -1312,7 +1313,7 @@ msgid "Disabled"
msgstr "無効" msgstr "無効"
#: assets/const/base.py:34 settings/serializers/basic.py:6 #: assets/const/base.py:34 settings/serializers/basic.py:6
#: users/serializers/preference/koko.py:15 #: users/serializers/preference/koko.py:19
#: users/serializers/preference/lina.py:39 #: users/serializers/preference/lina.py:39
#: users/serializers/preference/luna.py:60 #: users/serializers/preference/luna.py:60
msgid "Basic" msgid "Basic"
@ -1527,12 +1528,12 @@ msgstr "SSHパブリックキー"
#: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/_user.py:28 assets/models/automations/base.py:114
#: assets/models/cmd_filter.py:41 assets/models/group.py:19 #: assets/models/cmd_filter.py:41 assets/models/group.py:19
#: audits/models.py:261 common/db/models.py:34 ops/models/base.py:54 #: audits/models.py:261 common/db/models.py:34 ops/models/base.py:54
#: ops/models/job.py:227 users/models/user.py:1027 #: ops/models/job.py:227 users/models/user.py:1024
msgid "Date created" msgid "Date created"
msgstr "作成された日付" msgstr "作成された日付"
#: assets/models/_user.py:29 assets/models/cmd_filter.py:42 #: assets/models/_user.py:29 assets/models/cmd_filter.py:42
#: common/db/models.py:35 users/models/user.py:849 #: common/db/models.py:35 users/models/user.py:846
msgid "Date updated" msgid "Date updated"
msgstr "更新日" msgstr "更新日"
@ -1782,7 +1783,7 @@ msgstr "デフォルト"
msgid "Default asset group" msgid "Default asset group"
msgstr "デフォルトアセットグループ" msgstr "デフォルトアセットグループ"
#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1012 #: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1009
msgid "System" msgid "System"
msgstr "システム" msgstr "システム"
@ -2493,7 +2494,7 @@ msgstr "認証トークン"
#: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73 #: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73
#: authentication/views/login.py:77 authentication/views/wecom.py:159 #: authentication/views/login.py:77 authentication/views/wecom.py:159
#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
#: users/models/user.py:745 users/models/user.py:850 #: users/models/user.py:745 users/models/user.py:847
msgid "WeCom" msgid "WeCom"
msgstr "企業微信" msgstr "企業微信"
@ -2501,14 +2502,14 @@ msgstr "企業微信"
#: authentication/views/login.py:89 notifications/backends/__init__.py:14 #: authentication/views/login.py:89 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10 #: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:747 #: settings/serializers/auth/feishu.py:13 users/models/user.py:747
#: users/models/user.py:852 #: users/models/user.py:849
msgid "FeiShu" msgid "FeiShu"
msgstr "本を飛ばす" msgstr "本を飛ばす"
#: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159 #: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159
#: authentication/views/login.py:83 notifications/backends/__init__.py:12 #: authentication/views/login.py:83 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746
#: users/models/user.py:851 #: users/models/user.py:848
msgid "DingTalk" msgid "DingTalk"
msgstr "DingTalk" msgstr "DingTalk"
@ -2540,23 +2541,23 @@ msgstr ""
"再使用可能な接続トークンの使用は許可されていません。グローバル設定は有効に" "再使用可能な接続トークンの使用は許可されていません。グローバル設定は有効に"
"なっていません" "なっていません"
#: authentication/api/connection_token.py:352 #: authentication/api/connection_token.py:357
msgid "Anonymous account is not supported for this asset" msgid "Anonymous account is not supported for this asset"
msgstr "匿名アカウントはこのプロパティではサポートされていません" msgstr "匿名アカウントはこのプロパティではサポートされていません"
#: authentication/api/connection_token.py:371 #: authentication/api/connection_token.py:376
msgid "Account not found" msgid "Account not found"
msgstr "アカウントが見つかりません" msgstr "アカウントが見つかりません"
#: authentication/api/connection_token.py:374 #: authentication/api/connection_token.py:379
msgid "Permission expired" msgid "Permission expired"
msgstr "承認の有効期限が切れています" msgstr "承認の有効期限が切れています"
#: authentication/api/connection_token.py:401 #: authentication/api/connection_token.py:406
msgid "ACL action is reject: {}({})" msgid "ACL action is reject: {}({})"
msgstr "ACL アクションは拒否です: {}({})" msgstr "ACL アクションは拒否です: {}({})"
#: authentication/api/connection_token.py:405 #: authentication/api/connection_token.py:410
msgid "ACL action is review" msgid "ACL action is review"
msgstr "ACL アクションはレビューです" msgstr "ACL アクションはレビューです"
@ -2599,56 +2600,21 @@ msgstr "認証"
msgid "User invalid, disabled or expired" msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです" msgstr "ユーザーが無効、無効、または期限切れです"
#: authentication/backends/drf.py:54 #: authentication/backends/drf.py:39
msgid "Invalid signature header. No credentials provided."
msgstr "署名ヘッダーが無効です。資格情報は提供されていません。"
#: authentication/backends/drf.py:57
msgid "Invalid signature header. Signature string should not contain spaces."
msgstr "署名ヘッダーが無効です。署名文字列にはスペースを含まないでください。"
#: authentication/backends/drf.py:64
msgid "Invalid signature header. Format like AccessKeyId:Signature"
msgstr "署名ヘッダーが無効です。AccessKeyIdのような形式: Signature"
#: authentication/backends/drf.py:68
msgid ""
"Invalid signature header. Signature string should not contain invalid "
"characters."
msgstr ""
"署名ヘッダーが無効です。署名文字列に無効な文字を含めることはできません。"
#: authentication/backends/drf.py:88 authentication/backends/drf.py:104
msgid "Invalid signature."
msgstr "署名が無効です。"
#: authentication/backends/drf.py:95
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
msgstr "HTTP header: Date not provide or not"
#: authentication/backends/drf.py:100
msgid "Expired, more than 15 minutes"
msgstr "期限切れ、15分以上"
#: authentication/backends/drf.py:107
msgid "User disabled."
msgstr "ユーザーが無効になりました。"
#: authentication/backends/drf.py:125
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "無効なトークンヘッダー。資格情報は提供されていません。" msgstr "無効なトークンヘッダー。資格情報は提供されていません。"
#: authentication/backends/drf.py:128 #: authentication/backends/drf.py:42
msgid "Invalid token header. Sign string should not contain spaces." msgid "Invalid token header. Sign string should not contain spaces."
msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。" msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。"
#: authentication/backends/drf.py:135 #: authentication/backends/drf.py:48
msgid "" msgid ""
"Invalid token header. Sign string should not contain invalid characters." "Invalid token header. Sign string should not contain invalid characters."
msgstr "" msgstr ""
"無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。" "無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。"
#: authentication/backends/drf.py:146 #: authentication/backends/drf.py:61
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "無効なトークンまたはキャッシュの更新。" msgstr "無効なトークンまたはキャッシュの更新。"
@ -2665,6 +2631,8 @@ msgid "Added on"
msgstr "に追加" msgstr "に追加"
#: authentication/backends/passkey/models.py:14 #: authentication/backends/passkey/models.py:14
#: authentication/models/access_key.py:21
#: authentication/models/private_token.py:8
msgid "Date last used" msgid "Date last used"
msgstr "最後に使用した日付" msgstr "最後に使用した日付"
@ -3041,7 +3009,7 @@ msgstr "スーパー接続トークンのシークレットを表示できます
msgid "Super connection token" msgid "Super connection token"
msgstr "スーパー接続トークン" msgstr "スーパー接続トークン"
#: authentication/models/private_token.py:9 #: authentication/models/private_token.py:11
msgid "Private Token" msgid "Private Token"
msgstr "プライベートトークン" msgstr "プライベートトークン"
@ -3099,7 +3067,7 @@ msgstr "アクション"
#: authentication/serializers/connection_token.py:42 #: authentication/serializers/connection_token.py:42
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:174 #: users/serializers/user.py:97 users/serializers/user.py:171
msgid "Is expired" msgid "Is expired"
msgstr "期限切れです" msgstr "期限切れです"
@ -3118,9 +3086,9 @@ msgstr "メール"
msgid "The {} cannot be empty" msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない" msgstr "{} 空にしてはならない"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 #: authentication/serializers/token.py:80 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98 #: perms/serializers/permission.py:58 users/serializers/user.py:98
#: users/serializers/user.py:171 #: users/serializers/user.py:168
msgid "Is valid" msgid "Is valid"
msgstr "有効です" msgstr "有効です"
@ -6260,7 +6228,7 @@ msgstr "一括作成非サポート"
msgid "Storage is invalid" msgid "Storage is invalid"
msgstr "ストレージが無効です" msgstr "ストレージが無効です"
#: terminal/models/applet/applet.py:30 xpack/plugins/license/models.py:86 #: terminal/models/applet/applet.py:30 xpack/plugins/license/models.py:88
msgid "Community edition" msgid "Community edition"
msgstr "コミュニティ版" msgstr "コミュニティ版"
@ -7394,7 +7362,7 @@ msgstr "ユーザー設定"
msgid "Force enable" msgid "Force enable"
msgstr "強制有効" msgstr "強制有効"
#: users/models/user.py:804 users/serializers/user.py:172 #: users/models/user.py:804 users/serializers/user.py:169
msgid "Is service account" msgid "Is service account"
msgstr "サービスアカウントです" msgstr "サービスアカウントです"
@ -7406,7 +7374,7 @@ msgstr "アバター"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:812 users/serializers/user.py:109 #: users/models/user.py:812 users/serializers/user.py:106
msgid "Phone" msgid "Phone"
msgstr "電話" msgstr "電話"
@ -7420,43 +7388,47 @@ msgid "Private key"
msgstr "ssh秘密鍵" msgstr "ssh秘密鍵"
#: users/models/user.py:830 users/serializers/profile.py:125 #: users/models/user.py:830 users/serializers/profile.py:125
#: users/serializers/user.py:169 #: users/serializers/user.py:166
msgid "Is first login" msgid "Is first login"
msgstr "最初のログインです" msgstr "最初のログインです"
#: users/models/user.py:844 #: users/models/user.py:840
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最終更新日パスワード" msgstr "最終更新日パスワード"
#: users/models/user.py:847 #: users/models/user.py:843
msgid "Need update password" msgid "Need update password"
msgstr "更新パスワードが必要" msgstr "更新パスワードが必要"
#: users/models/user.py:971 #: users/models/user.py:845
msgid "Date api key used"
msgstr "Api key 最後に使用した日付"
#: users/models/user.py:968
msgid "Can not delete admin user" msgid "Can not delete admin user"
msgstr "管理者ユーザーを削除できませんでした" msgstr "管理者ユーザーを削除できませんでした"
#: users/models/user.py:997 #: users/models/user.py:994
msgid "Can invite user" msgid "Can invite user"
msgstr "ユーザーを招待できます" msgstr "ユーザーを招待できます"
#: users/models/user.py:998 #: users/models/user.py:995
msgid "Can remove user" msgid "Can remove user"
msgstr "ユーザーを削除できます" msgstr "ユーザーを削除できます"
#: users/models/user.py:999 #: users/models/user.py:996
msgid "Can match user" msgid "Can match user"
msgstr "ユーザーに一致できます" msgstr "ユーザーに一致できます"
#: users/models/user.py:1008 #: users/models/user.py:1005
msgid "Administrator" msgid "Administrator"
msgstr "管理者" msgstr "管理者"
#: users/models/user.py:1011 #: users/models/user.py:1008
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "管理者はシステムのスーパーユーザーです" msgstr "管理者はシステムのスーパーユーザーです"
#: users/models/user.py:1036 #: users/models/user.py:1033
msgid "User password history" msgid "User password history"
msgstr "ユーザーパスワード履歴" msgstr "ユーザーパスワード履歴"
@ -7495,6 +7467,12 @@ msgstr "MFAのリセット"
msgid "File name conflict resolution" msgid "File name conflict resolution"
msgstr "ファイル名競合ソリューション" msgstr "ファイル名競合ソリューション"
#: users/serializers/preference/koko.py:14
#, fuzzy
#| msgid "Terminal setting"
msgid "Terminal theme name"
msgstr "ターミナル設定"
#: users/serializers/preference/lina.py:13 #: users/serializers/preference/lina.py:13
msgid "New file encryption password" msgid "New file encryption password"
msgstr "新しいファイルの暗号化パスワード" msgstr "新しいファイルの暗号化パスワード"
@ -7583,7 +7561,7 @@ msgstr "MFAフォース有効化"
msgid "Login blocked" msgid "Login blocked"
msgstr "ログインがロックされました" msgstr "ログインがロックされました"
#: users/serializers/user.py:99 users/serializers/user.py:178 #: users/serializers/user.py:99 users/serializers/user.py:175
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "仮想MFAがバインドされているか" msgstr "仮想MFAがバインドされているか"
@ -7591,27 +7569,27 @@ msgstr "仮想MFAがバインドされているか"
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "公開鍵認証が可能" msgstr "公開鍵認証が可能"
#: users/serializers/user.py:173 #: users/serializers/user.py:170
msgid "Is org admin" msgid "Is org admin"
msgstr "組織管理者です" msgstr "組織管理者です"
#: users/serializers/user.py:175 #: users/serializers/user.py:172
msgid "Avatar url" msgid "Avatar url"
msgstr "アバターURL" msgstr "アバターURL"
#: users/serializers/user.py:179 #: users/serializers/user.py:176
msgid "MFA level" msgid "MFA level"
msgstr "MFA レベル" msgstr "MFA レベル"
#: users/serializers/user.py:285 #: users/serializers/user.py:282
msgid "Select users" msgid "Select users"
msgstr "ユーザーの選択" msgstr "ユーザーの選択"
#: users/serializers/user.py:286 #: users/serializers/user.py:283
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします" msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:319 #: users/serializers/user.py:316
msgid "name not unique" msgid "name not unique"
msgstr "名前が一意ではない" msgstr "名前が一意ではない"
@ -8536,7 +8514,7 @@ msgstr "ライセンスのインポートに成功"
msgid "License is invalid" msgid "License is invalid"
msgstr "ライセンスが無効です" msgstr "ライセンスが無効です"
#: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:138 #: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:140
msgid "License" msgid "License"
msgstr "ライセンス" msgstr "ライセンス"
@ -8556,6 +8534,35 @@ msgstr "エンタープライズプロフェッショナル版"
msgid "Ultimate edition" msgid "Ultimate edition"
msgstr "エンタープライズ・フラッグシップ・エディション" msgstr "エンタープライズ・フラッグシップ・エディション"
#~ msgid "Invalid signature header. No credentials provided."
#~ msgstr "署名ヘッダーが無効です。資格情報は提供されていません。"
#~ msgid ""
#~ "Invalid signature header. Signature string should not contain spaces."
#~ msgstr ""
#~ "署名ヘッダーが無効です。署名文字列にはスペースを含まないでください。"
#~ msgid "Invalid signature header. Format like AccessKeyId:Signature"
#~ msgstr "署名ヘッダーが無効です。AccessKeyIdのような形式: Signature"
#~ msgid ""
#~ "Invalid signature header. Signature string should not contain invalid "
#~ "characters."
#~ msgstr ""
#~ "署名ヘッダーが無効です。署名文字列に無効な文字を含めることはできません。"
#~ msgid "Invalid signature."
#~ msgstr "署名が無効です。"
#~ msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
#~ msgstr "HTTP header: Date not provide or not"
#~ msgid "Expired, more than 15 minutes"
#~ msgstr "期限切れ、15分以上"
#~ msgid "User disabled."
#~ msgstr "ユーザーが無効になりました。"
#~ msgid "Random" #~ msgid "Random"
#~ msgstr "ランダム" #~ msgstr "ランダム"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:06ff4e3474944be5a7f95106ecdeaa88d66be345fa69e1e62ec5d8c27be580ea oid sha256:25c1d449875189c84c0d586792424d70651a1f86f55b93332287dfef44db2f2f
size 132912 size 132289

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-09 18:33+0800\n" "POT-Creation-Date: 2023-10-10 11:13+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -24,10 +24,11 @@ msgstr "参数 'action' 必须是 [{}]"
#: accounts/const/account.py:6 #: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:32 #: accounts/serializers/automations/change_secret.py:32
#: assets/models/_user.py:24 audits/signal_handlers/login_log.py:37 #: assets/models/_user.py:24 audits/signal_handlers/login_log.py:37
#: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/confirm/password.py:9 authentication/confirm/password.py:24
#: authentication/confirm/password.py:26 authentication/forms.py:32
#: authentication/templates/authentication/login.html:324 #: authentication/templates/authentication/login.html:324
#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47
#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/forms/profile.py:22 users/serializers/user.py:104
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28 #: xpack/plugins/cloud/serializers/account_attrs.py:28
@ -39,7 +40,7 @@ msgstr "密码"
msgid "SSH key" msgid "SSH key"
msgstr "SSH 密钥" msgstr "SSH 密钥"
#: accounts/const/account.py:8 authentication/models/access_key.py:33 #: accounts/const/account.py:8 authentication/models/access_key.py:37
msgid "Access key" msgid "Access key"
msgstr "Access key" msgstr "Access key"
@ -250,7 +251,7 @@ msgid "Version"
msgstr "版本" msgstr "版本"
#: accounts/models/account.py:56 accounts/serializers/account/account.py:211 #: accounts/models/account.py:56 accounts/serializers/account/account.py:211
#: users/models/user.py:840 #: users/models/user.py:837
msgid "Source" msgid "Source"
msgstr "来源" msgstr "来源"
@ -551,7 +552,7 @@ msgstr "特权账号"
#: assets/models/label.py:22 #: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114 #: authentication/serializers/connect_token_secret.py:114
#: terminal/models/applet/applet.py:40 #: terminal/models/applet/applet.py:40
#: terminal/models/component/endpoint.py:105 users/serializers/user.py:170 #: terminal/models/component/endpoint.py:105 users/serializers/user.py:167
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
@ -709,7 +710,7 @@ msgstr "已修改"
#: acls/templates/acls/asset_login_reminder.html:6 #: acls/templates/acls/asset_login_reminder.html:6
#: assets/models/automations/base.py:19 #: assets/models/automations/base.py:19
#: assets/serializers/automations/base.py:20 #: assets/serializers/automations/base.py:20
#: authentication/api/connection_token.py:381 ops/models/base.py:17 #: authentication/api/connection_token.py:386 ops/models/base.py:17
#: ops/models/job.py:139 ops/serializers/job.py:21 #: ops/models/job.py:139 ops/serializers/job.py:21
#: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: terminal/templates/terminal/_msg_command_execute_alert.html:16
msgid "Assets" msgid "Assets"
@ -749,8 +750,8 @@ msgstr "ID"
#: terminal/notifications.py:205 terminal/serializers/command.py:16 #: terminal/notifications.py:205 terminal/serializers/command.py:16
#: terminal/templates/terminal/_msg_command_warning.html:6 #: terminal/templates/terminal/_msg_command_warning.html:6
#: terminal/templates/terminal/_msg_session_sharing.html:6 #: terminal/templates/terminal/_msg_session_sharing.html:6
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:990 #: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:987
#: users/models/user.py:1026 users/serializers/group.py:18 #: users/models/user.py:1023 users/serializers/group.py:18
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
@ -1003,7 +1004,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)"
msgid "Reviewers" msgid "Reviewers"
msgstr "审批人" msgstr "审批人"
#: acls/models/base.py:43 authentication/models/access_key.py:17 #: acls/models/base.py:43 authentication/models/access_key.py:20
#: authentication/models/connection_token.py:53 #: authentication/models/connection_token.py:53
#: authentication/templates/authentication/_access_key_modal.html:32 #: authentication/templates/authentication/_access_key_modal.html:32
#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29 #: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29
@ -1304,7 +1305,7 @@ msgid "Disabled"
msgstr "禁用" msgstr "禁用"
#: assets/const/base.py:34 settings/serializers/basic.py:6 #: assets/const/base.py:34 settings/serializers/basic.py:6
#: users/serializers/preference/koko.py:15 #: users/serializers/preference/koko.py:19
#: users/serializers/preference/lina.py:39 #: users/serializers/preference/lina.py:39
#: users/serializers/preference/luna.py:60 #: users/serializers/preference/luna.py:60
msgid "Basic" msgid "Basic"
@ -1519,12 +1520,12 @@ msgstr "SSH公钥"
#: assets/models/_user.py:28 assets/models/automations/base.py:114 #: assets/models/_user.py:28 assets/models/automations/base.py:114
#: assets/models/cmd_filter.py:41 assets/models/group.py:19 #: assets/models/cmd_filter.py:41 assets/models/group.py:19
#: audits/models.py:261 common/db/models.py:34 ops/models/base.py:54 #: audits/models.py:261 common/db/models.py:34 ops/models/base.py:54
#: ops/models/job.py:227 users/models/user.py:1027 #: ops/models/job.py:227 users/models/user.py:1024
msgid "Date created" msgid "Date created"
msgstr "创建日期" msgstr "创建日期"
#: assets/models/_user.py:29 assets/models/cmd_filter.py:42 #: assets/models/_user.py:29 assets/models/cmd_filter.py:42
#: common/db/models.py:35 users/models/user.py:849 #: common/db/models.py:35 users/models/user.py:846
msgid "Date updated" msgid "Date updated"
msgstr "更新日期" msgstr "更新日期"
@ -1774,7 +1775,7 @@ msgstr "默认"
msgid "Default asset group" msgid "Default asset group"
msgstr "默认资产组" msgstr "默认资产组"
#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1012 #: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:1009
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
@ -2476,7 +2477,7 @@ msgstr "认证令牌"
#: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73 #: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73
#: authentication/views/login.py:77 authentication/views/wecom.py:159 #: authentication/views/login.py:77 authentication/views/wecom.py:159
#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
#: users/models/user.py:745 users/models/user.py:850 #: users/models/user.py:745 users/models/user.py:847
msgid "WeCom" msgid "WeCom"
msgstr "企业微信" msgstr "企业微信"
@ -2484,14 +2485,14 @@ msgstr "企业微信"
#: authentication/views/login.py:89 notifications/backends/__init__.py:14 #: authentication/views/login.py:89 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10 #: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:747 #: settings/serializers/auth/feishu.py:13 users/models/user.py:747
#: users/models/user.py:852 #: users/models/user.py:849
msgid "FeiShu" msgid "FeiShu"
msgstr "飞书" msgstr "飞书"
#: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159 #: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159
#: authentication/views/login.py:83 notifications/backends/__init__.py:12 #: authentication/views/login.py:83 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746
#: users/models/user.py:851 #: users/models/user.py:848
msgid "DingTalk" msgid "DingTalk"
msgstr "钉钉" msgstr "钉钉"
@ -2521,23 +2522,23 @@ msgstr "该操作需要验证您的 MFA, 请先开启并配置"
msgid "Reusable connection token is not allowed, global setting not enabled" msgid "Reusable connection token is not allowed, global setting not enabled"
msgstr "不允许使用可重复使用的连接令牌,未启用全局设置" msgstr "不允许使用可重复使用的连接令牌,未启用全局设置"
#: authentication/api/connection_token.py:352 #: authentication/api/connection_token.py:357
msgid "Anonymous account is not supported for this asset" msgid "Anonymous account is not supported for this asset"
msgstr "匿名账号不支持当前资产" msgstr "匿名账号不支持当前资产"
#: authentication/api/connection_token.py:371 #: authentication/api/connection_token.py:376
msgid "Account not found" msgid "Account not found"
msgstr "账号未找到" msgstr "账号未找到"
#: authentication/api/connection_token.py:374 #: authentication/api/connection_token.py:379
msgid "Permission expired" msgid "Permission expired"
msgstr "授权已过期" msgstr "授权已过期"
#: authentication/api/connection_token.py:401 #: authentication/api/connection_token.py:406
msgid "ACL action is reject: {}({})" msgid "ACL action is reject: {}({})"
msgstr "ACL 动作是拒绝: {}({})" msgstr "ACL 动作是拒绝: {}({})"
#: authentication/api/connection_token.py:405 #: authentication/api/connection_token.py:410
msgid "ACL action is review" msgid "ACL action is review"
msgstr "ACL 动作是复核" msgstr "ACL 动作是复核"
@ -2578,54 +2579,20 @@ msgstr "认证"
msgid "User invalid, disabled or expired" msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期" msgstr "用户无效,已禁用或已过期"
#: authentication/backends/drf.py:54 #: authentication/backends/drf.py:39
msgid "Invalid signature header. No credentials provided."
msgstr "不合法的签名头"
#: authentication/backends/drf.py:57
msgid "Invalid signature header. Signature string should not contain spaces."
msgstr "不合法的签名头"
#: authentication/backends/drf.py:64
msgid "Invalid signature header. Format like AccessKeyId:Signature"
msgstr "不合法的签名头"
#: authentication/backends/drf.py:68
msgid ""
"Invalid signature header. Signature string should not contain invalid "
"characters."
msgstr "不合法的签名头"
#: authentication/backends/drf.py:88 authentication/backends/drf.py:104
msgid "Invalid signature."
msgstr "签名无效"
#: authentication/backends/drf.py:95
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
msgstr "HTTP header not valid"
#: authentication/backends/drf.py:100
msgid "Expired, more than 15 minutes"
msgstr "已过期超过15分钟"
#: authentication/backends/drf.py:107
msgid "User disabled."
msgstr "用户已禁用"
#: authentication/backends/drf.py:125
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "无效的令牌头。没有提供任何凭据。" msgstr "无效的令牌头。没有提供任何凭据。"
#: authentication/backends/drf.py:128 #: authentication/backends/drf.py:42
msgid "Invalid token header. Sign string should not contain spaces." msgid "Invalid token header. Sign string should not contain spaces."
msgstr "无效的令牌头。符号字符串不应包含空格。" msgstr "无效的令牌头。符号字符串不应包含空格。"
#: authentication/backends/drf.py:135 #: authentication/backends/drf.py:48
msgid "" msgid ""
"Invalid token header. Sign string should not contain invalid characters." "Invalid token header. Sign string should not contain invalid characters."
msgstr "无效的令牌头。符号字符串不应包含无效字符。" msgstr "无效的令牌头。符号字符串不应包含无效字符。"
#: authentication/backends/drf.py:146 #: authentication/backends/drf.py:61
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "刷新的令牌或缓存无效。" msgstr "刷新的令牌或缓存无效。"
@ -2642,6 +2609,8 @@ msgid "Added on"
msgstr "附加" msgstr "附加"
#: authentication/backends/passkey/models.py:14 #: authentication/backends/passkey/models.py:14
#: authentication/models/access_key.py:21
#: authentication/models/private_token.py:8
msgid "Date last used" msgid "Date last used"
msgstr "最后使用日期" msgstr "最后使用日期"
@ -3008,7 +2977,7 @@ msgstr "可以查看超级连接令牌密文"
msgid "Super connection token" msgid "Super connection token"
msgstr "超级连接令牌" msgstr "超级连接令牌"
#: authentication/models/private_token.py:9 #: authentication/models/private_token.py:11
msgid "Private Token" msgid "Private Token"
msgstr "私有令牌" msgstr "私有令牌"
@ -3066,7 +3035,7 @@ msgstr "动作"
#: authentication/serializers/connection_token.py:42 #: authentication/serializers/connection_token.py:42
#: perms/serializers/permission.py:38 perms/serializers/permission.py:57 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57
#: users/serializers/user.py:97 users/serializers/user.py:174 #: users/serializers/user.py:97 users/serializers/user.py:171
msgid "Is expired" msgid "Is expired"
msgstr "已过期" msgstr "已过期"
@ -3085,9 +3054,9 @@ msgstr "邮箱"
msgid "The {} cannot be empty" msgid "The {} cannot be empty"
msgstr "{} 不能为空" msgstr "{} 不能为空"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 #: authentication/serializers/token.py:80 perms/serializers/permission.py:37
#: perms/serializers/permission.py:58 users/serializers/user.py:98 #: perms/serializers/permission.py:58 users/serializers/user.py:98
#: users/serializers/user.py:171 #: users/serializers/user.py:168
msgid "Is valid" msgid "Is valid"
msgstr "是否有效" msgstr "是否有效"
@ -6168,7 +6137,7 @@ msgstr "不支持批量创建"
msgid "Storage is invalid" msgid "Storage is invalid"
msgstr "存储无效" msgstr "存储无效"
#: terminal/models/applet/applet.py:30 xpack/plugins/license/models.py:86 #: terminal/models/applet/applet.py:30 xpack/plugins/license/models.py:88
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
@ -7290,7 +7259,7 @@ msgstr "用户设置"
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:804 users/serializers/user.py:172 #: users/models/user.py:804 users/serializers/user.py:169
msgid "Is service account" msgid "Is service account"
msgstr "服务账号" msgstr "服务账号"
@ -7302,7 +7271,7 @@ msgstr "头像"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:812 users/serializers/user.py:109 #: users/models/user.py:812 users/serializers/user.py:106
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@ -7316,43 +7285,47 @@ msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
#: users/models/user.py:830 users/serializers/profile.py:125 #: users/models/user.py:830 users/serializers/profile.py:125
#: users/serializers/user.py:169 #: users/serializers/user.py:166
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
#: users/models/user.py:844 #: users/models/user.py:840
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:847 #: users/models/user.py:843
msgid "Need update password" msgid "Need update password"
msgstr "需要更新密码" msgstr "需要更新密码"
#: users/models/user.py:971 #: users/models/user.py:845
msgid "Date api key used"
msgstr "Api key 最后使用日期"
#: users/models/user.py:968
msgid "Can not delete admin user" msgid "Can not delete admin user"
msgstr "无法删除管理员用户" msgstr "无法删除管理员用户"
#: users/models/user.py:997 #: users/models/user.py:994
msgid "Can invite user" msgid "Can invite user"
msgstr "可以邀请用户" msgstr "可以邀请用户"
#: users/models/user.py:998 #: users/models/user.py:995
msgid "Can remove user" msgid "Can remove user"
msgstr "可以移除用户" msgstr "可以移除用户"
#: users/models/user.py:999 #: users/models/user.py:996
msgid "Can match user" msgid "Can match user"
msgstr "可以匹配用户" msgstr "可以匹配用户"
#: users/models/user.py:1008 #: users/models/user.py:1005
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:1011 #: users/models/user.py:1008
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/models/user.py:1036 #: users/models/user.py:1033
msgid "User password history" msgid "User password history"
msgstr "用户密码历史" msgstr "用户密码历史"
@ -7391,6 +7364,12 @@ msgstr "重置 MFA"
msgid "File name conflict resolution" msgid "File name conflict resolution"
msgstr "文件名冲突解决方案" msgstr "文件名冲突解决方案"
#: users/serializers/preference/koko.py:14
#, fuzzy
#| msgid "Terminal setting"
msgid "Terminal theme name"
msgstr "终端设置"
#: users/serializers/preference/lina.py:13 #: users/serializers/preference/lina.py:13
msgid "New file encryption password" msgid "New file encryption password"
msgstr "文件加密密码" msgstr "文件加密密码"
@ -7479,7 +7458,7 @@ msgstr "强制 MFA"
msgid "Login blocked" msgid "Login blocked"
msgstr "登录被锁定" msgstr "登录被锁定"
#: users/serializers/user.py:99 users/serializers/user.py:178 #: users/serializers/user.py:99 users/serializers/user.py:175
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "是否绑定了虚拟 MFA" msgstr "是否绑定了虚拟 MFA"
@ -7487,27 +7466,27 @@ msgstr "是否绑定了虚拟 MFA"
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "可以使用公钥认证" msgstr "可以使用公钥认证"
#: users/serializers/user.py:173 #: users/serializers/user.py:170
msgid "Is org admin" msgid "Is org admin"
msgstr "组织管理员" msgstr "组织管理员"
#: users/serializers/user.py:175 #: users/serializers/user.py:172
msgid "Avatar url" msgid "Avatar url"
msgstr "头像路径" msgstr "头像路径"
#: users/serializers/user.py:179 #: users/serializers/user.py:176
msgid "MFA level" msgid "MFA level"
msgstr "MFA 级别" msgstr "MFA 级别"
#: users/serializers/user.py:285 #: users/serializers/user.py:282
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
#: users/serializers/user.py:286 #: users/serializers/user.py:283
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户" msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:319 #: users/serializers/user.py:316
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"
@ -8416,7 +8395,7 @@ msgstr "许可证导入成功"
msgid "License is invalid" msgid "License is invalid"
msgstr "无效的许可证" msgstr "无效的许可证"
#: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:138 #: xpack/plugins/license/meta.py:10 xpack/plugins/license/models.py:140
msgid "License" msgid "License"
msgstr "许可证" msgstr "许可证"
@ -8436,5 +8415,32 @@ msgstr "企业专业版"
msgid "Ultimate edition" msgid "Ultimate edition"
msgstr "企业旗舰版" msgstr "企业旗舰版"
#~ msgid "Invalid signature header. No credentials provided."
#~ msgstr "不合法的签名头"
#~ msgid ""
#~ "Invalid signature header. Signature string should not contain spaces."
#~ msgstr "不合法的签名头"
#~ msgid "Invalid signature header. Format like AccessKeyId:Signature"
#~ msgstr "不合法的签名头"
#~ msgid ""
#~ "Invalid signature header. Signature string should not contain invalid "
#~ "characters."
#~ msgstr "不合法的签名头"
#~ msgid "Invalid signature."
#~ msgstr "签名无效"
#~ msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
#~ msgstr "HTTP header not valid"
#~ msgid "Expired, more than 15 minutes"
#~ msgstr "已过期超过15分钟"
#~ msgid "User disabled."
#~ msgstr "用户已禁用"
#~ msgid "Random" #~ msgid "Random"
#~ msgstr "随机" #~ msgstr "随机"

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2023-10-08 04:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0046_auto_20230927_1456'),
]
operations = [
migrations.AddField(
model_name='user',
name='date_api_key_last_used',
field=models.DateTimeField(blank=True, null=True, verbose_name='Date api key used'),
),
]

View File

@ -834,11 +834,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract
) )
created_by = models.CharField(max_length=30, default='', blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=30, default='', blank=True, verbose_name=_('Created by'))
updated_by = models.CharField(max_length=30, default='', blank=True, verbose_name=_('Updated by')) updated_by = models.CharField(max_length=30, default='', blank=True, verbose_name=_('Updated by'))
source = models.CharField( source = models.CharField(max_length=30, default=Source.local, choices=Source.choices, verbose_name=_('Source'))
max_length=30, default=Source.local,
choices=Source.choices,
verbose_name=_('Source')
)
date_password_last_updated = models.DateTimeField( date_password_last_updated = models.DateTimeField(
auto_now_add=True, blank=True, null=True, auto_now_add=True, blank=True, null=True,
verbose_name=_('Date password last updated') verbose_name=_('Date password last updated')
@ -846,6 +842,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract
need_update_password = models.BooleanField( need_update_password = models.BooleanField(
default=False, verbose_name=_('Need update password') default=False, verbose_name=_('Need update password')
) )
date_api_key_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date api key used'))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated'))
wecom_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('WeCom')) wecom_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('WeCom'))
dingtalk_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('DingTalk')) dingtalk_id = models.CharField(null=True, default=None, max_length=128, verbose_name=_('DingTalk'))

View File

@ -101,10 +101,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
source="can_use_ssh_key_login", label=_("Can public key authentication"), source="can_use_ssh_key_login", label=_("Can public key authentication"),
read_only=True read_only=True
) )
password = EncryptedField( password = EncryptedField(label=_("Password"), required=False, allow_blank=True, allow_null=True, max_length=1024, )
label=_("Password"), required=False, allow_blank=True,
allow_null=True, max_length=1024,
)
phone = PhoneField( phone = PhoneField(
validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_("Phone") validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_("Phone")
) )
@ -128,8 +125,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
"created_by", "updated_by", "comment", # 通用字段 "created_by", "updated_by", "comment", # 通用字段
] ]
fields_date = [ fields_date = [
"date_expired", "date_joined", "date_expired", "date_joined", "last_login",
"last_login", "date_updated" # 日期字段 "date_updated", "date_api_key_last_used",
] ]
fields_bool = [ fields_bool = [
"is_superuser", "is_org_admin", "is_superuser", "is_org_admin",
@ -155,7 +152,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
read_only_fields = [ read_only_fields = [
"date_joined", "last_login", "created_by", "date_joined", "last_login", "created_by",
"is_first_login", "wecom_id", "dingtalk_id", "is_first_login", "wecom_id", "dingtalk_id",
"feishu_id", "feishu_id", "date_api_key_last_used",
] ]
disallow_self_update_fields = ["is_active", "system_roles", "org_roles"] disallow_self_update_fields = ["is_active", "system_roles", "org_roles"]
extra_kwargs = { extra_kwargs = {