mirror of https://github.com/jumpserver/jumpserver
perf: 优化用户 access key 的使用和创建 (#11776)
* perf: 优化用户 access key 的使用和创建 * perf: 优化 access key api --------- Co-authored-by: ibuler <ibuler@qq.com>pull/11784/head^2
parent
30b19d31eb
commit
333746e7c4
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
]
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:56db214d34b91e9f6d712cef124890264144b35b99e50d6833b4af0e935778b0
|
||||
size 162655
|
||||
oid sha256:38bd8a6653f3f4dc63552b1c86379f82067f9f9daac227bacb3af3f9f62134f9
|
||||
size 161704
|
||||
|
|
|
@ -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 "ランダム"
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:06ff4e3474944be5a7f95106ecdeaa88d66be345fa69e1e62ec5d8c27be580ea
|
||||
size 132912
|
||||
oid sha256:25c1d449875189c84c0d586792424d70651a1f86f55b93332287dfef44db2f2f
|
||||
size 132289
|
||||
|
|
|
@ -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 "随机"
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'))
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue