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(
model_name='operatelog',
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(
model_name='userloginlog',

View File

@ -1,20 +1,44 @@
# -*- 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 ..const import ConfirmType
from ..serializers import AccessKeySerializer
class AccessKeyViewSet(ModelViewSet):
class AccessKeyViewSet(JMSModelViewSet):
serializer_class = AccessKeySerializer
search_fields = ['^id', '^secret']
search_fields = ['^id']
permission_classes = [RBACPermission]
def get_queryset(self):
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):
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):
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.ReLogin),)
permission_classes = (IsValidUser, UserConfirmation.require(ConfirmType.RELOGIN),)
def retrieve(self, request, *args, **kwargs):
return Response('ok')
@ -24,7 +24,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
serializer_class = ConfirmSerializer
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:
return
for backend_cls in backend_classes:
@ -34,7 +34,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
return backend
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)
if backend is None:
msg = _('This action require verify your MFA')
@ -51,7 +51,7 @@ class ConfirmApi(RetrieveAPIView, CreateAPIView):
serializer.is_valid(raise_exception=True)
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')
secret_key = validated_data.get('secret_key')

View File

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

View File

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

View File

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

View File

@ -1,119 +1,33 @@
# -*- coding: utf-8 -*-
#
import time
import uuid
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.utils import timezone
from django.utils.translation import gettext as _
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import authentication, exceptions
from six import text_type
from common.auth import signature
from common.utils import get_object_or_none, make_signature, http_to_unixtime
from .base import JMSBaseAuthBackend
from common.utils import get_object_or_none
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):
date = request.META.get('HTTP_DATE', b'')
if isinstance(date, text_type):
# Work around django test client oddness
date = date.encode(HTTP_HEADER_ENCODING)
return date
def after_authenticate_update_date(user, token=None):
if date_more_than(user.date_api_key_last_used, 60):
user.date_api_key_last_used = timezone.now()
user.save(update_fields=['date_api_key_last_used'])
class AccessKeyAuthentication(authentication.BaseAuthentication):
"""App使用Access key进行签名认证, 目前签名算法比较简单,
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'
if token and hasattr(token, 'date_last_used') and date_more_than(token.date_last_used, 60):
token.date_last_used = timezone.now()
token.save(update_fields=['date_last_used'])
class AccessTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Bearer'
# expiration = settings.TOKEN_EXPIRATION or 3600
model = get_user_model()
def authenticate(self, request):
@ -125,19 +39,20 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Sign string '
'should not contain spaces.')
msg = _('Invalid token header. Sign string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Sign string '
'should not contain invalid characters.')
msg = _('Invalid token header. Sign string should not contain invalid characters.')
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()
user_id = cache.get(token)
user = get_object_or_none(model, id=user_id)
@ -151,15 +66,23 @@ class AccessTokenAuthentication(authentication.BaseAuthentication):
return self.keyword
class PrivateTokenAuthentication(JMSBaseAuthBackend, authentication.TokenAuthentication):
class PrivateTokenAuthentication(authentication.TokenAuthentication):
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):
def authenticate(self, request):
"""
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
@ -195,6 +118,7 @@ class SignatureAuthentication(signature.SignatureAuthentication):
if not key.is_active:
return None, None
user, secret = key.user, str(key.secret)
after_authenticate_update_date(user, key)
return user, secret
except (AccessKey.DoesNotExist, exceptions.ValidationError):
return None, None

View File

@ -2,7 +2,6 @@ import abc
class BaseConfirm(abc.ABC):
def __init__(self, user, request):
self.user = user
self.request = request
@ -23,7 +22,7 @@ class BaseConfirm(abc.ABC):
@property
def content(self):
return ''
return []
@abc.abstractmethod
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)
msg = '' if ok else _('Authentication failed password incorrect')
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):
ReLogin = ConfirmReLogin.name, ConfirmReLogin.display_name
RELOGIN = ConfirmReLogin.name, ConfirmReLogin.display_name
PASSWORD = ConfirmPassword.name, ConfirmPassword.display_name
MFA = ConfirmMFA.name, ConfirmMFA.display_name
@ -23,10 +23,11 @@ class ConfirmType(TextChoices):
return types
@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)
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

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 _
import common.db.models
from common.utils.random import random_string
def default_secret():
return random_string(36)
class AccessKey(models.Model):
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True,
default=uuid.uuid4, editable=False)
secret = models.UUIDField(verbose_name='AccessKeySecret',
default=uuid.uuid4, editable=False)
id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, default=uuid.uuid4, editable=False)
secret = models.CharField(verbose_name='AccessKeySecret', default=default_secret, max_length=36)
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User',
on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='access_keys')
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)
def get_id(self):

View File

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

View File

@ -10,7 +10,7 @@ from users.serializers import UserProfileSerializer
from ..models import AccessKey, TempToken
__all__ = [
'AccessKeySerializer', 'BearerTokenSerializer',
'AccessKeySerializer', 'BearerTokenSerializer',
'SSOTokenSerializer', 'TempTokenSerializer',
]
@ -18,8 +18,8 @@ __all__ = [
class AccessKeySerializer(serializers.ModelSerializer):
class Meta:
model = AccessKey
fields = ['id', 'secret', 'is_active', 'date_created']
read_only_fields = ['id', 'secret', 'date_created']
fields = ['id', 'is_active', 'date_created', 'date_last_used']
read_only_fields = ['id', 'date_created', 'date_last_used']
class BearerTokenSerializer(serializers.Serializer):
@ -37,7 +37,8 @@ class BearerTokenSerializer(serializers.Serializer):
def get_keyword(obj):
return 'Bearer'
def update_last_login(self, user):
@staticmethod
def update_last_login(user):
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
@ -96,7 +97,7 @@ class TempTokenSerializer(serializers.ModelSerializer):
username = request.user.username
kwargs = {
'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.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):
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin))
permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.RELOGIN))
def get(self, request: HttpRequest):
user = request.user

View File

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

View File

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

View File

@ -13,7 +13,7 @@ from common.drf.filters import (
IDSpmFilterBackend, CustomFilterBackend, IDInFilterBackend,
IDNotFilterBackend, NotOrRelFilterBackend
)
from common.utils import get_logger
from common.utils import get_logger, lazyproperty
from .action import RenderToJsonMixin
from .serializer import SerializerMixin
@ -150,9 +150,9 @@ class OrderingFielderFieldsMixin:
ordering_fields = None
extra_ordering_fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ordering_fields = self._get_ordering_fields()
@lazyproperty
def ordering_fields(self):
return self._get_ordering_fields()
def _get_ordering_fields(self):
if isinstance(self.__class__.ordering_fields, (list, tuple)):
@ -179,7 +179,10 @@ class OrderingFielderFieldsMixin:
model = self.queryset.model
else:
queryset = self.get_queryset()
model = queryset.model
if isinstance(queryset, list):
model = None
else:
model = queryset.model
if not model:
return []
@ -201,4 +204,6 @@ class CommonApiMixin(
SerializerMixin, ExtraFilterFieldsMixin, OrderingFielderFieldsMixin,
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):
ttl = 60 * 5
min_level = 1
confirm_type = ConfirmType.ReLogin
confirm_type = ConfirmType.RELOGIN
def has_permission(self, request, view):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
@ -82,7 +82,7 @@ class UserConfirmation(permissions.BasePermission):
return ttl
@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
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
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
oid sha256:56db214d34b91e9f6d712cef124890264144b35b99e50d6833b4af0e935778b0
size 162655
oid sha256:38bd8a6653f3f4dc63552b1c86379f82067f9f9daac227bacb3af3f9f62134f9
size 161704

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -25,10 +25,11 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。"
#: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:32
#: 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
#: 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/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@ -40,7 +41,7 @@ msgstr "パスワード"
msgid "SSH key"
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"
msgstr "アクセスキー"
@ -251,7 +252,7 @@ msgid "Version"
msgstr "バージョン"
#: accounts/models/account.py:56 accounts/serializers/account/account.py:211
#: users/models/user.py:840
#: users/models/user.py:837
msgid "Source"
msgstr "ソース"
@ -552,7 +553,7 @@ msgstr "特権アカウント"
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114
#: 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"
msgstr "アクティブです。"
@ -711,7 +712,7 @@ msgstr "編集済み"
#: acls/templates/acls/asset_login_reminder.html:6
#: assets/models/automations/base.py:19
#: 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
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
msgid "Assets"
@ -751,8 +752,8 @@ msgstr "ID"
#: terminal/notifications.py:205 terminal/serializers/command.py:16
#: terminal/templates/terminal/_msg_command_warning.html:6
#: terminal/templates/terminal/_msg_session_sharing.html:6
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:990
#: users/models/user.py:1026 users/serializers/group.py:18
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:987
#: users/models/user.py:1023 users/serializers/group.py:18
msgid "User"
msgstr "ユーザー"
@ -1006,7 +1007,7 @@ msgstr "1-100、低い値は最初に一致します"
msgid "Reviewers"
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/templates/authentication/_access_key_modal.html:32
#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29
@ -1312,7 +1313,7 @@ msgid "Disabled"
msgstr "無効"
#: 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/luna.py:60
msgid "Basic"
@ -1527,12 +1528,12 @@ msgstr "SSHパブリックキー"
#: assets/models/_user.py:28 assets/models/automations/base.py:114
#: 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
#: ops/models/job.py:227 users/models/user.py:1027
#: ops/models/job.py:227 users/models/user.py:1024
msgid "Date created"
msgstr "作成された日付"
#: 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"
msgstr "更新日"
@ -1782,7 +1783,7 @@ msgstr "デフォルト"
msgid "Default asset group"
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"
msgstr "システム"
@ -2493,7 +2494,7 @@ msgstr "認証トークン"
#: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73
#: authentication/views/login.py:77 authentication/views/wecom.py:159
#: 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"
msgstr "企業微信"
@ -2501,14 +2502,14 @@ msgstr "企業微信"
#: authentication/views/login.py:89 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:747
#: users/models/user.py:852
#: users/models/user.py:849
msgid "FeiShu"
msgstr "本を飛ばす"
#: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159
#: authentication/views/login.py:83 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746
#: users/models/user.py:851
#: users/models/user.py:848
msgid "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"
msgstr "匿名アカウントはこのプロパティではサポートされていません"
#: authentication/api/connection_token.py:371
#: authentication/api/connection_token.py:376
msgid "Account not found"
msgstr "アカウントが見つかりません"
#: authentication/api/connection_token.py:374
#: authentication/api/connection_token.py:379
msgid "Permission expired"
msgstr "承認の有効期限が切れています"
#: authentication/api/connection_token.py:401
#: authentication/api/connection_token.py:406
msgid "ACL action is reject: {}({})"
msgstr "ACL アクションは拒否です: {}({})"
#: authentication/api/connection_token.py:405
#: authentication/api/connection_token.py:410
msgid "ACL action is review"
msgstr "ACL アクションはレビューです"
@ -2599,56 +2600,21 @@ msgstr "認証"
msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです"
#: authentication/backends/drf.py:54
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
#: authentication/backends/drf.py:39
msgid "Invalid token header. No credentials provided."
msgstr "無効なトークンヘッダー。資格情報は提供されていません。"
#: authentication/backends/drf.py:128
#: authentication/backends/drf.py:42
msgid "Invalid token header. Sign string should not contain spaces."
msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。"
#: authentication/backends/drf.py:135
#: authentication/backends/drf.py:48
msgid ""
"Invalid token header. Sign string should not contain invalid characters."
msgstr ""
"無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。"
#: authentication/backends/drf.py:146
#: authentication/backends/drf.py:61
msgid "Invalid token or cache refreshed."
msgstr "無効なトークンまたはキャッシュの更新。"
@ -2665,6 +2631,8 @@ msgid "Added on"
msgstr "に追加"
#: authentication/backends/passkey/models.py:14
#: authentication/models/access_key.py:21
#: authentication/models/private_token.py:8
msgid "Date last used"
msgstr "最後に使用した日付"
@ -3041,7 +3009,7 @@ msgstr "スーパー接続トークンのシークレットを表示できます
msgid "Super connection token"
msgstr "スーパー接続トークン"
#: authentication/models/private_token.py:9
#: authentication/models/private_token.py:11
msgid "Private Token"
msgstr "プライベートトークン"
@ -3099,7 +3067,7 @@ msgstr "アクション"
#: authentication/serializers/connection_token.py:42
#: 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"
msgstr "期限切れです"
@ -3118,9 +3086,9 @@ msgstr "メール"
msgid "The {} cannot be empty"
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
#: users/serializers/user.py:171
#: users/serializers/user.py:168
msgid "Is valid"
msgstr "有効です"
@ -6260,7 +6228,7 @@ msgstr "一括作成非サポート"
msgid "Storage is invalid"
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"
msgstr "コミュニティ版"
@ -7394,7 +7362,7 @@ msgstr "ユーザー設定"
msgid "Force enable"
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"
msgstr "サービスアカウントです"
@ -7406,7 +7374,7 @@ msgstr "アバター"
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:812 users/serializers/user.py:109
#: users/models/user.py:812 users/serializers/user.py:106
msgid "Phone"
msgstr "電話"
@ -7420,43 +7388,47 @@ msgid "Private key"
msgstr "ssh秘密鍵"
#: users/models/user.py:830 users/serializers/profile.py:125
#: users/serializers/user.py:169
#: users/serializers/user.py:166
msgid "Is first login"
msgstr "最初のログインです"
#: users/models/user.py:844
#: users/models/user.py:840
msgid "Date password last updated"
msgstr "最終更新日パスワード"
#: users/models/user.py:847
#: users/models/user.py:843
msgid "Need update password"
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"
msgstr "管理者ユーザーを削除できませんでした"
#: users/models/user.py:997
#: users/models/user.py:994
msgid "Can invite user"
msgstr "ユーザーを招待できます"
#: users/models/user.py:998
#: users/models/user.py:995
msgid "Can remove user"
msgstr "ユーザーを削除できます"
#: users/models/user.py:999
#: users/models/user.py:996
msgid "Can match user"
msgstr "ユーザーに一致できます"
#: users/models/user.py:1008
#: users/models/user.py:1005
msgid "Administrator"
msgstr "管理者"
#: users/models/user.py:1011
#: users/models/user.py:1008
msgid "Administrator is the super user of system"
msgstr "管理者はシステムのスーパーユーザーです"
#: users/models/user.py:1036
#: users/models/user.py:1033
msgid "User password history"
msgstr "ユーザーパスワード履歴"
@ -7495,6 +7467,12 @@ msgstr "MFAのリセット"
msgid "File name conflict resolution"
msgstr "ファイル名競合ソリューション"
#: users/serializers/preference/koko.py:14
#, fuzzy
#| msgid "Terminal setting"
msgid "Terminal theme name"
msgstr "ターミナル設定"
#: users/serializers/preference/lina.py:13
msgid "New file encryption password"
msgstr "新しいファイルの暗号化パスワード"
@ -7583,7 +7561,7 @@ msgstr "MFAフォース有効化"
msgid "Login blocked"
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"
msgstr "仮想MFAがバインドされているか"
@ -7591,27 +7569,27 @@ msgstr "仮想MFAがバインドされているか"
msgid "Can public key authentication"
msgstr "公開鍵認証が可能"
#: users/serializers/user.py:173
#: users/serializers/user.py:170
msgid "Is org admin"
msgstr "組織管理者です"
#: users/serializers/user.py:175
#: users/serializers/user.py:172
msgid "Avatar url"
msgstr "アバターURL"
#: users/serializers/user.py:179
#: users/serializers/user.py:176
msgid "MFA level"
msgstr "MFA レベル"
#: users/serializers/user.py:285
#: users/serializers/user.py:282
msgid "Select users"
msgstr "ユーザーの選択"
#: users/serializers/user.py:286
#: users/serializers/user.py:283
msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:319
#: users/serializers/user.py:316
msgid "name not unique"
msgstr "名前が一意ではない"
@ -8536,7 +8514,7 @@ msgstr "ライセンスのインポートに成功"
msgid "License is invalid"
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"
msgstr "ライセンス"
@ -8556,6 +8534,35 @@ msgstr "エンタープライズプロフェッショナル版"
msgid "Ultimate edition"
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"
#~ msgstr "ランダム"

View File

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

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\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"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -24,10 +24,11 @@ msgstr "参数 'action' 必须是 [{}]"
#: accounts/const/account.py:6
#: accounts/serializers/automations/change_secret.py:32
#: 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
#: 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/user_password_verify.html:18
#: xpack/plugins/cloud/serializers/account_attrs.py:28
@ -39,7 +40,7 @@ msgstr "密码"
msgid "SSH key"
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"
msgstr "Access key"
@ -250,7 +251,7 @@ msgid "Version"
msgstr "版本"
#: accounts/models/account.py:56 accounts/serializers/account/account.py:211
#: users/models/user.py:840
#: users/models/user.py:837
msgid "Source"
msgstr "来源"
@ -551,7 +552,7 @@ msgstr "特权账号"
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:114
#: 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"
msgstr "激活"
@ -709,7 +710,7 @@ msgstr "已修改"
#: acls/templates/acls/asset_login_reminder.html:6
#: assets/models/automations/base.py:19
#: 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
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
msgid "Assets"
@ -749,8 +750,8 @@ msgstr "ID"
#: terminal/notifications.py:205 terminal/serializers/command.py:16
#: terminal/templates/terminal/_msg_command_warning.html:6
#: terminal/templates/terminal/_msg_session_sharing.html:6
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:990
#: users/models/user.py:1026 users/serializers/group.py:18
#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:987
#: users/models/user.py:1023 users/serializers/group.py:18
msgid "User"
msgstr "用户"
@ -1003,7 +1004,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)"
msgid "Reviewers"
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/templates/authentication/_access_key_modal.html:32
#: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:29
@ -1304,7 +1305,7 @@ msgid "Disabled"
msgstr "禁用"
#: 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/luna.py:60
msgid "Basic"
@ -1519,12 +1520,12 @@ msgstr "SSH公钥"
#: assets/models/_user.py:28 assets/models/automations/base.py:114
#: 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
#: ops/models/job.py:227 users/models/user.py:1027
#: ops/models/job.py:227 users/models/user.py:1024
msgid "Date created"
msgstr "创建日期"
#: 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"
msgstr "更新日期"
@ -1774,7 +1775,7 @@ msgstr "默认"
msgid "Default asset group"
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"
msgstr "系统"
@ -2476,7 +2477,7 @@ msgstr "认证令牌"
#: audits/signal_handlers/login_log.py:40 authentication/notifications.py:73
#: authentication/views/login.py:77 authentication/views/wecom.py:159
#: 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"
msgstr "企业微信"
@ -2484,14 +2485,14 @@ msgstr "企业微信"
#: authentication/views/login.py:89 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:747
#: users/models/user.py:852
#: users/models/user.py:849
msgid "FeiShu"
msgstr "飞书"
#: audits/signal_handlers/login_log.py:42 authentication/views/dingtalk.py:159
#: authentication/views/login.py:83 notifications/backends/__init__.py:12
#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:746
#: users/models/user.py:851
#: users/models/user.py:848
msgid "DingTalk"
msgstr "钉钉"
@ -2521,23 +2522,23 @@ msgstr "该操作需要验证您的 MFA, 请先开启并配置"
msgid "Reusable connection token is not allowed, global setting not enabled"
msgstr "不允许使用可重复使用的连接令牌,未启用全局设置"
#: authentication/api/connection_token.py:352
#: authentication/api/connection_token.py:357
msgid "Anonymous account is not supported for this asset"
msgstr "匿名账号不支持当前资产"
#: authentication/api/connection_token.py:371
#: authentication/api/connection_token.py:376
msgid "Account not found"
msgstr "账号未找到"
#: authentication/api/connection_token.py:374
#: authentication/api/connection_token.py:379
msgid "Permission expired"
msgstr "授权已过期"
#: authentication/api/connection_token.py:401
#: authentication/api/connection_token.py:406
msgid "ACL action is reject: {}({})"
msgstr "ACL 动作是拒绝: {}({})"
#: authentication/api/connection_token.py:405
#: authentication/api/connection_token.py:410
msgid "ACL action is review"
msgstr "ACL 动作是复核"
@ -2578,54 +2579,20 @@ msgstr "认证"
msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期"
#: authentication/backends/drf.py:54
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
#: authentication/backends/drf.py:39
msgid "Invalid token header. No credentials provided."
msgstr "无效的令牌头。没有提供任何凭据。"
#: authentication/backends/drf.py:128
#: authentication/backends/drf.py:42
msgid "Invalid token header. Sign string should not contain spaces."
msgstr "无效的令牌头。符号字符串不应包含空格。"
#: authentication/backends/drf.py:135
#: authentication/backends/drf.py:48
msgid ""
"Invalid token header. Sign string should not contain invalid characters."
msgstr "无效的令牌头。符号字符串不应包含无效字符。"
#: authentication/backends/drf.py:146
#: authentication/backends/drf.py:61
msgid "Invalid token or cache refreshed."
msgstr "刷新的令牌或缓存无效。"
@ -2642,6 +2609,8 @@ msgid "Added on"
msgstr "附加"
#: authentication/backends/passkey/models.py:14
#: authentication/models/access_key.py:21
#: authentication/models/private_token.py:8
msgid "Date last used"
msgstr "最后使用日期"
@ -3008,7 +2977,7 @@ msgstr "可以查看超级连接令牌密文"
msgid "Super connection token"
msgstr "超级连接令牌"
#: authentication/models/private_token.py:9
#: authentication/models/private_token.py:11
msgid "Private Token"
msgstr "私有令牌"
@ -3066,7 +3035,7 @@ msgstr "动作"
#: authentication/serializers/connection_token.py:42
#: 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"
msgstr "已过期"
@ -3085,9 +3054,9 @@ msgstr "邮箱"
msgid "The {} cannot be empty"
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
#: users/serializers/user.py:171
#: users/serializers/user.py:168
msgid "Is valid"
msgstr "是否有效"
@ -6168,7 +6137,7 @@ msgstr "不支持批量创建"
msgid "Storage is invalid"
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"
msgstr "社区版"
@ -7290,7 +7259,7 @@ msgstr "用户设置"
msgid "Force enable"
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"
msgstr "服务账号"
@ -7302,7 +7271,7 @@ msgstr "头像"
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:812 users/serializers/user.py:109
#: users/models/user.py:812 users/serializers/user.py:106
msgid "Phone"
msgstr "手机"
@ -7316,43 +7285,47 @@ msgid "Private key"
msgstr "ssh私钥"
#: users/models/user.py:830 users/serializers/profile.py:125
#: users/serializers/user.py:169
#: users/serializers/user.py:166
msgid "Is first login"
msgstr "首次登录"
#: users/models/user.py:844
#: users/models/user.py:840
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:847
#: users/models/user.py:843
msgid "Need update password"
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"
msgstr "无法删除管理员用户"
#: users/models/user.py:997
#: users/models/user.py:994
msgid "Can invite user"
msgstr "可以邀请用户"
#: users/models/user.py:998
#: users/models/user.py:995
msgid "Can remove user"
msgstr "可以移除用户"
#: users/models/user.py:999
#: users/models/user.py:996
msgid "Can match user"
msgstr "可以匹配用户"
#: users/models/user.py:1008
#: users/models/user.py:1005
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:1011
#: users/models/user.py:1008
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/models/user.py:1036
#: users/models/user.py:1033
msgid "User password history"
msgstr "用户密码历史"
@ -7391,6 +7364,12 @@ msgstr "重置 MFA"
msgid "File name conflict resolution"
msgstr "文件名冲突解决方案"
#: users/serializers/preference/koko.py:14
#, fuzzy
#| msgid "Terminal setting"
msgid "Terminal theme name"
msgstr "终端设置"
#: users/serializers/preference/lina.py:13
msgid "New file encryption password"
msgstr "文件加密密码"
@ -7479,7 +7458,7 @@ msgstr "强制 MFA"
msgid "Login blocked"
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"
msgstr "是否绑定了虚拟 MFA"
@ -7487,27 +7466,27 @@ msgstr "是否绑定了虚拟 MFA"
msgid "Can public key authentication"
msgstr "可以使用公钥认证"
#: users/serializers/user.py:173
#: users/serializers/user.py:170
msgid "Is org admin"
msgstr "组织管理员"
#: users/serializers/user.py:175
#: users/serializers/user.py:172
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:179
#: users/serializers/user.py:176
msgid "MFA level"
msgstr "MFA 级别"
#: users/serializers/user.py:285
#: users/serializers/user.py:282
msgid "Select users"
msgstr "选择用户"
#: users/serializers/user.py:286
#: users/serializers/user.py:283
msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:319
#: users/serializers/user.py:316
msgid "name not unique"
msgstr "名称重复"
@ -8416,7 +8395,7 @@ msgstr "许可证导入成功"
msgid "License is invalid"
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"
msgstr "许可证"
@ -8436,5 +8415,32 @@ msgstr "企业专业版"
msgid "Ultimate edition"
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"
#~ 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'))
updated_by = models.CharField(max_length=30, default='', blank=True, verbose_name=_('Updated by'))
source = models.CharField(
max_length=30, default=Source.local,
choices=Source.choices,
verbose_name=_('Source')
)
source = models.CharField(max_length=30, default=Source.local, choices=Source.choices, verbose_name=_('Source'))
date_password_last_updated = models.DateTimeField(
auto_now_add=True, blank=True, null=True,
verbose_name=_('Date password last updated')
@ -846,6 +842,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract
need_update_password = models.BooleanField(
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'))
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'))

View File

@ -101,10 +101,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
source="can_use_ssh_key_login", label=_("Can public key authentication"),
read_only=True
)
password = EncryptedField(
label=_("Password"), required=False, allow_blank=True,
allow_null=True, max_length=1024,
)
password = EncryptedField(label=_("Password"), required=False, allow_blank=True, allow_null=True, max_length=1024, )
phone = PhoneField(
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", # 通用字段
]
fields_date = [
"date_expired", "date_joined",
"last_login", "date_updated" # 日期字段
"date_expired", "date_joined", "last_login",
"date_updated", "date_api_key_last_used",
]
fields_bool = [
"is_superuser", "is_org_admin",
@ -155,7 +152,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
read_only_fields = [
"date_joined", "last_login", "created_by",
"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"]
extra_kwargs = {