feat: 认证方式支持OAuth2.0协议 (#8686)

* feat: 认证方式支持OAuth2.0协议

* perf: 优化 OAuth2 认证逻辑和Logo (对接 Github)

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

* perf: 优化 OAuth2 认证逻辑和Logo,支持上传图标

Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
pull/8698/head
jiangweidong 2022-08-04 14:40:33 +08:00 committed by GitHub
parent b22aed0cc3
commit 2099baaaff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 649 additions and 209 deletions

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
#
from .backends import *

View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
#
import requests
from django.contrib.auth import get_user_model
from django.utils.http import urlencode
from django.conf import settings
from django.urls import reverse
from common.utils import get_logger
from users.utils import construct_user_email
from authentication.utils import build_absolute_uri
from common.exceptions import JMSException
from .signals import (
oauth2_create_or_update_user, oauth2_user_login_failed,
oauth2_user_login_success
)
from ..base import JMSModelBackend
__all__ = ['OAuth2Backend']
logger = get_logger(__name__)
class OAuth2Backend(JMSModelBackend):
@staticmethod
def is_enabled():
return settings.AUTH_OAUTH2
def get_or_create_user_from_userinfo(self, request, userinfo):
log_prompt = "Get or Create user [OAuth2Backend]: {}"
logger.debug(log_prompt.format('start'))
# Construct user attrs value
user_attrs = {}
for field, attr in settings.AUTH_OAUTH2_USER_ATTR_MAP.items():
user_attrs[field] = userinfo.get(attr, '')
username = user_attrs.get('username')
if not username:
error_msg = 'username is missing'
logger.error(log_prompt.format(error_msg))
raise JMSException(error_msg)
email = user_attrs.get('email', '')
email = construct_user_email(user_attrs.get('username'), email)
user_attrs.update({'email': email})
logger.debug(log_prompt.format(user_attrs))
user, created = get_user_model().objects.get_or_create(
username=username, defaults=user_attrs
)
logger.debug(log_prompt.format("user: {}|created: {}".format(user, created)))
logger.debug(log_prompt.format("Send signal => oauth2 create or update user"))
oauth2_create_or_update_user.send(
sender=self.__class__, request=request, user=user, created=created,
attrs=user_attrs
)
return user, created
@staticmethod
def get_response_data(response_data):
if response_data.get('data') is not None:
response_data = response_data['data']
return response_data
@staticmethod
def get_query_dict(response_data, query_dict):
query_dict.update({
'uid': response_data.get('uid', ''),
'access_token': response_data.get('access_token', '')
})
return query_dict
def authenticate(self, request, code=None, **kwargs):
log_prompt = "Process authenticate [OAuth2Backend]: {}"
logger.debug(log_prompt.format('Start'))
if code is None:
logger.error(log_prompt.format('code is missing'))
return None
query_dict = {
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': build_absolute_uri(
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}
access_token_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, query=urlencode(query_dict)
)
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
requests_func = getattr(requests, token_method, requests.get)
logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method))
headers = {
'Accept': 'application/json'
}
access_token_response = requests_func(access_token_url, headers=headers)
try:
access_token_response.raise_for_status()
access_token_response_data = access_token_response.json()
response_data = self.get_response_data(access_token_response_data)
except Exception as e:
error = "Json access token response error, access token response " \
"content is: {}, error is: {}".format(access_token_response.content, str(e))
logger.error(log_prompt.format(error))
return None
query_dict = self.get_query_dict(response_data, query_dict)
headers = {
'Accept': 'application/json',
'Authorization': 'token {}'.format(response_data.get('access_token', ''))
}
logger.debug(log_prompt.format('Get userinfo endpoint'))
userinfo_url = '{url}?{query}'.format(
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT,
query=urlencode(query_dict)
)
userinfo_response = requests.get(userinfo_url, headers=headers)
try:
userinfo_response.raise_for_status()
userinfo_response_data = userinfo_response.json()
if 'data' in userinfo_response_data:
userinfo = userinfo_response_data['data']
else:
userinfo = userinfo_response_data
except Exception as e:
error = "Json userinfo response error, userinfo response " \
"content is: {}, error is: {}".format(userinfo_response.content, str(e))
logger.error(log_prompt.format(error))
return None
try:
logger.debug(log_prompt.format('Update or create oauth2 user'))
user, created = self.get_or_create_user_from_userinfo(request, userinfo)
except JMSException:
return None
if self.user_can_authenticate(user):
logger.debug(log_prompt.format('OAuth2 user login success'))
logger.debug(log_prompt.format('Send signal => oauth2 user login success'))
oauth2_user_login_success.send(sender=self.__class__, request=request, user=user)
return user
else:
logger.debug(log_prompt.format('OAuth2 user login failed'))
logger.debug(log_prompt.format('Send signal => oauth2 user login failed'))
oauth2_user_login_failed.send(
sender=self.__class__, request=request, username=user.username,
reason=_('User invalid, disabled or expired')
)
return None

View File

@ -0,0 +1,9 @@
from django.dispatch import Signal
oauth2_create_or_update_user = Signal(
providing_args=['request', 'user', 'created', 'name', 'username', 'email']
)
oauth2_user_login_success = Signal(providing_args=['request', 'user'])
oauth2_user_login_failed = Signal(providing_args=['request', 'username', 'reason'])

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
#
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.OAuth2AuthRequestView.as_view(), name='login'),
path('callback/', views.OAuth2AuthCallbackView.as_view(), name='login-callback')
]

View File

@ -0,0 +1,56 @@
from django.views import View
from django.conf import settings
from django.contrib.auth import login
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.http import urlencode
from authentication.utils import build_absolute_uri
from common.utils import get_logger
from authentication.mixins import authenticate
logger = get_logger(__file__)
class OAuth2AuthRequestView(View):
def get(self, request):
log_prompt = "Process OAuth2 GET requests: {}"
logger.debug(log_prompt.format('Start'))
base_url = settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT
query_dict = {
'client_id': settings.AUTH_OAUTH2_CLIENT_ID, 'response_type': 'code',
'scope': settings.AUTH_OAUTH2_SCOPE,
'redirect_uri': build_absolute_uri(
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}
redirect_url = '{url}?{query}'.format(url=base_url, query=urlencode(query_dict))
logger.debug(log_prompt.format('Redirect login url'))
return HttpResponseRedirect(redirect_url)
class OAuth2AuthCallbackView(View):
http_method_names = ['get', ]
def get(self, request):
""" Processes GET requests. """
log_prompt = "Process GET requests [OAuth2AuthCallbackView]: {}"
logger.debug(log_prompt.format('Start'))
callback_params = request.GET
if 'code' in callback_params:
logger.debug(log_prompt.format('Process authenticate'))
user = authenticate(code=callback_params['code'], request=request)
if user and user.is_valid:
logger.debug(log_prompt.format('Login: {}'.format(user)))
login(self.request, user)
logger.debug(log_prompt.format('Redirect'))
return HttpResponseRedirect(
settings.AUTH_OAUTH2_AUTHENTICATION_REDIRECT_URI
)
logger.debug(log_prompt.format('Redirect'))
return HttpResponseRedirect(settings.AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI)

View File

@ -9,6 +9,7 @@
import base64
import requests
from rest_framework.exceptions import ParseError
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
@ -18,10 +19,11 @@ from django.urls import reverse
from django.conf import settings
from common.utils import get_logger
from authentication.utils import build_absolute_uri_for_oidc
from users.utils import construct_user_email
from ..base import JMSBaseAuthBackend
from .utils import validate_and_return_id_token, build_absolute_uri
from .utils import validate_and_return_id_token
from .decorator import ssl_verification
from .signals import (
openid_create_or_update_user, openid_user_login_failed, openid_user_login_success
@ -127,7 +129,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
token_payload = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': build_absolute_uri(
'redirect_uri': build_absolute_uri_for_oidc(
request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME)
)
}

View File

@ -8,7 +8,7 @@
import datetime as dt
from calendar import timegm
from urllib.parse import urlparse, urljoin
from urllib.parse import urlparse
from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import force_bytes, smart_bytes
@ -110,17 +110,3 @@ def _validate_claims(id_token, nonce=None, validate_nonce=True):
raise SuspiciousOperation('Incorrect id_token: nonce')
logger.debug(log_prompt.format('End'))
def build_absolute_uri(request, path=None):
"""
Build absolute redirect uri
"""
if path is None:
path = '/'
if settings.BASE_SITE_URL:
redirect_uri = urljoin(settings.BASE_SITE_URL, path)
else:
redirect_uri = request.build_absolute_uri(path)
return redirect_uri

View File

@ -20,7 +20,8 @@ from django.utils.crypto import get_random_string
from django.utils.http import is_safe_url, urlencode
from django.views.generic import View
from .utils import get_logger, build_absolute_uri
from authentication.utils import build_absolute_uri_for_oidc
from .utils import get_logger
logger = get_logger(__file__)
@ -50,7 +51,7 @@ class OIDCAuthRequestView(View):
'scope': settings.AUTH_OPENID_SCOPES,
'response_type': 'code',
'client_id': settings.AUTH_OPENID_CLIENT_ID,
'redirect_uri': build_absolute_uri(
'redirect_uri': build_absolute_uri_for_oidc(
request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME)
)
})
@ -216,7 +217,7 @@ class OIDCEndSessionView(View):
""" Returns the end-session URL. """
q = QueryDict(mutable=True)
q[settings.AUTH_OPENID_PROVIDER_END_SESSION_REDIRECT_URI_PARAMETER] = \
build_absolute_uri(self.request, path=settings.LOGOUT_REDIRECT_URL or '/')
build_absolute_uri_for_oidc(self.request, path=settings.LOGOUT_REDIRECT_URL or '/')
q[settings.AUTH_OPENID_PROVIDER_END_SESSION_ID_TOKEN_PARAMETER] = \
self.request.session['oidc_auth_id_token']
return '{}?{}'.format(settings.AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT, q.urlencode())

View File

@ -39,7 +39,7 @@ class SAML2Backend(JMSModelBackend):
return user, created
def authenticate(self, request, saml_user_data=None, **kwargs):
log_prompt = "Process authenticate [SAML2AuthCodeBackend]: {}"
log_prompt = "Process authenticate [SAML2Backend]: {}"
logger.debug(log_prompt.format('Start'))
if saml_user_data is None:
logger.error(log_prompt.format('saml_user_data is missing'))
@ -48,7 +48,7 @@ class SAML2Backend(JMSModelBackend):
logger.debug(log_prompt.format('saml data, {}'.format(saml_user_data)))
username = saml_user_data.get('username')
if not username:
logger.debug(log_prompt.format('username is missing'))
logger.warning(log_prompt.format('username is missing'))
return None
user, created = self.get_or_create_from_saml_data(request, **saml_user_data)

View File

@ -12,6 +12,9 @@ from authentication.backends.oidc.signals import (
from authentication.backends.saml2.signals import (
saml2_user_authenticated, saml2_user_authentication_failed
)
from authentication.backends.oauth2.signals import (
oauth2_user_login_failed, oauth2_user_login_success
)
from .signals import post_auth_success, post_auth_failed
@ -67,3 +70,15 @@ def on_saml2_user_login_success(sender, request, user, **kwargs):
def on_saml2_user_login_failed(sender, request, username, reason, **kwargs):
request.session['auth_backend'] = settings.AUTH_BACKEND_SAML2
post_auth_failed.send(sender, username=username, request=request, reason=reason)
@receiver(oauth2_user_login_success)
def on_oauth2_user_login_success(sender, request, user, **kwargs):
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
post_auth_success.send(sender, user=user, request=request)
@receiver(oauth2_user_login_failed)
def on_oauth2_user_login_failed(sender, username, request, reason, **kwargs):
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
post_auth_failed.send(sender, username=username, request=request, reason=reason)

View File

@ -56,9 +56,11 @@ urlpatterns = [
path('profile/otp/disable/', users_view.UserOtpDisableView.as_view(),
name='user-otp-disable'),
# openid
# other authentication protocol
path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')),
path('openid/', include(('authentication.backends.oidc.urls', 'authentication'), namespace='openid')),
path('saml2/', include(('authentication.backends.saml2.urls', 'authentication'), namespace='saml2')),
path('oauth2/', include(('authentication.backends.oauth2.urls', 'authentication'), namespace='oauth2')),
path('captcha/', include('captcha.urls')),
]

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
from urllib.parse import urljoin
from django.conf import settings
@ -29,3 +30,23 @@ def check_different_city_login_if_need(user, request):
if last_user_login and last_user_login.city != city:
DifferentCityLoginMessage(user, ip, city).publish_async()
def build_absolute_uri(request, path=None):
""" Build absolute redirect """
if path is None:
path = '/'
redirect_uri = request.build_absolute_uri(path)
return redirect_uri
def build_absolute_uri_for_oidc(request, path=None):
""" Build absolute redirect uri for OIDC """
if path is None:
path = '/'
if settings.BASE_SITE_URL:
# OIDC 专用配置项
redirect_uri = urljoin(settings.BASE_SITE_URL, path)
else:
redirect_uri = build_absolute_uri(request, path)
return redirect_uri

View File

@ -21,7 +21,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from django.contrib.auth import BACKEND_SESSION_KEY
from common.utils import FlashMessageUtil
from common.utils import FlashMessageUtil, static_or_direct
from users.utils import (
redirect_user_first_login_or_index
)
@ -39,8 +39,7 @@ class UserLoginContextMixin:
get_user_mfa_context: Callable
request: HttpRequest
@staticmethod
def get_support_auth_methods():
def get_support_auth_methods(self):
auth_methods = [
{
'name': 'OpenID',
@ -63,6 +62,13 @@ class UserLoginContextMixin:
'logo': static('img/login_saml2_logo.png'),
'auto_redirect': True
},
{
'name': settings.AUTH_OAUTH2_PROVIDER,
'enabled': settings.AUTH_OAUTH2,
'url': reverse('authentication:oauth2:login'),
'logo': static_or_direct(settings.AUTH_OAUTH2_LOGO_PATH),
'auto_redirect': True
},
{
'name': _('WeCom'),
'enabled': settings.AUTH_WECOM,

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
import re
from django.templatetags.static import static
from collections import OrderedDict
from itertools import chain
import logging
@ -365,3 +366,10 @@ def pretty_string(data: str, max_length=128, ellipsis_str='...'):
def group_by_count(it, count):
return [it[i:i+count] for i in range(0, len(it), count)]
def static_or_direct(logo_path):
if logo_path.startswith('img/'):
return static(logo_path)
else:
return logo_path

View File

@ -265,6 +265,22 @@ class Config(dict):
'AUTH_SAML2_PROVIDER_AUTHORIZATION_ENDPOINT': '/',
'AUTH_SAML2_AUTHENTICATION_FAILURE_REDIRECT_URI': '/',
# OAuth2 认证
'AUTH_OAUTH2': False,
'AUTH_OAUTH2_LOGO_PATH': 'img/login_oauth2_logo.png',
'AUTH_OAUTH2_PROVIDER': 'OAuth2',
'AUTH_OAUTH2_ALWAYS_UPDATE_USER': True,
'AUTH_OAUTH2_CLIENT_ID': 'client-id',
'AUTH_OAUTH2_SCOPE': '',
'AUTH_OAUTH2_CLIENT_SECRET': '',
'AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT': 'https://oauth2.example.com/authorize',
'AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT': 'https://oauth2.example.com/userinfo',
'AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT': 'https://oauth2.example.com/access_token',
'AUTH_OAUTH2_ACCESS_TOKEN_METHOD': 'GET',
'AUTH_OAUTH2_USER_ATTR_MAP': {
'name': 'name', 'username': 'username', 'email': 'email'
},
'AUTH_TEMP_TOKEN': False,
# 企业微信

View File

@ -143,6 +143,23 @@ SAML2_SP_ADVANCED_SETTINGS = CONFIG.SAML2_SP_ADVANCED_SETTINGS
SAML2_LOGIN_URL_NAME = "authentication:saml2:saml2-login"
SAML2_LOGOUT_URL_NAME = "authentication:saml2:saml2-logout"
# OAuth2 auth
AUTH_OAUTH2 = CONFIG.AUTH_OAUTH2
AUTH_OAUTH2_LOGO_PATH = CONFIG.AUTH_OAUTH2_LOGO_PATH
AUTH_OAUTH2_PROVIDER = CONFIG.AUTH_OAUTH2_PROVIDER
AUTH_OAUTH2_ALWAYS_UPDATE_USER = CONFIG.AUTH_OAUTH2_ALWAYS_UPDATE_USER
AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT = CONFIG.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT
AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT = CONFIG.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT
AUTH_OAUTH2_ACCESS_TOKEN_METHOD = CONFIG.AUTH_OAUTH2_ACCESS_TOKEN_METHOD
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = CONFIG.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT
AUTH_OAUTH2_CLIENT_SECRET = CONFIG.AUTH_OAUTH2_CLIENT_SECRET
AUTH_OAUTH2_CLIENT_ID = CONFIG.AUTH_OAUTH2_CLIENT_ID
AUTH_OAUTH2_SCOPE = CONFIG.AUTH_OAUTH2_SCOPE
AUTH_OAUTH2_USER_ATTR_MAP = CONFIG.AUTH_OAUTH2_USER_ATTR_MAP
AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:oauth2:login-callback'
AUTH_OAUTH2_AUTHENTICATION_REDIRECT_URI = '/'
AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI = '/'
# 临时 token
AUTH_TEMP_TOKEN = CONFIG.AUTH_TEMP_TOKEN
@ -170,6 +187,7 @@ AUTH_BACKEND_DINGTALK = 'authentication.backends.sso.DingTalkAuthentication'
AUTH_BACKEND_FEISHU = 'authentication.backends.sso.FeiShuAuthentication'
AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthentication'
AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
AUTH_BACKEND_OAUTH2 = 'authentication.backends.oauth2.OAuth2Backend'
AUTH_BACKEND_TEMP_TOKEN = 'authentication.backends.token.TempTokenAuthBackend'
@ -180,6 +198,7 @@ AUTHENTICATION_BACKENDS = [
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_LDAP, AUTH_BACKEND_RADIUS,
# 跳转形式
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
AUTH_BACKEND_OAUTH2,
# 扫码模式
AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU,
# Token模式

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:322701b975fe90b4b187c4a99ddd1837291150502c82accf0a4c6e32dddf91be
size 128721
oid sha256:73ea6289c22c329752330fae1fef6d174573c7f46355137ffbc864407b2b8270
size 129073

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-02 11:39+0800\n"
"POT-Creation-Date: 2022-08-04 14:17+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"
@ -28,11 +28,11 @@ msgstr "Acls"
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:29 settings/serializers/sms.py:6
#: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86
#: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:100 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:661
#: users/models/group.py:15 users/models/user.py:665
#: xpack/plugins/cloud/models.py:28
msgid "Name"
msgstr "名前"
@ -60,11 +60,11 @@ msgstr "アクティブ"
#: assets/models/cmd_filter.py:96 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:698
#: users/models/group.py:16 users/models/user.py:702
#: xpack/plugins/change_auth_plan/models/base.py:44
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
#: xpack/plugins/gathered_user/models.py:26
@ -94,7 +94,7 @@ msgstr "ログイン確認"
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91
#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14
#: users/models/user.py:890 users/models/user.py:921
#: users/models/user.py:894 users/models/user.py:925
#: users/serializers/group.py:19
msgid "User"
msgstr "ユーザー"
@ -160,7 +160,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること
#: authentication/models.py:260
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:663
#: users/templates/users/_msg_user_created.html:12
#: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195
@ -364,7 +364,7 @@ msgstr "タイプ表示"
#: assets/serializers/cmd_filter.py:48 common/db/models.py:114
#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30
#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92
#: users/models/group.py:18 users/models/user.py:922
#: users/models/group.py:18 users/models/user.py:926
#: xpack/plugins/cloud/models.py:125
msgid "Date created"
msgstr "作成された日付"
@ -628,7 +628,7 @@ msgstr "ラベル"
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:99 assets/models/group.py:21
#: common/db/models.py:112 common/mixins/models.py:49 orgs/models.py:71
#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:706
#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:710
#: users/serializers/group.py:33
#: xpack/plugins/change_auth_plan/models/base.py:48
#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30
@ -824,7 +824,7 @@ msgstr "帯域幅"
msgid "Contact"
msgstr "連絡先"
#: assets/models/cluster.py:22 users/models/user.py:681
#: assets/models/cluster.py:22 users/models/user.py:685
msgid "Phone"
msgstr "電話"
@ -850,7 +850,7 @@ msgid "Default"
msgstr "デフォルト"
#: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6
#: users/models/user.py:907
#: users/models/user.py:911
msgid "System"
msgstr "システム"
@ -859,7 +859,7 @@ msgid "Default Cluster"
msgstr "デフォルトクラスター"
#: assets/models/cmd_filter.py:34 perms/models/base.py:86
#: users/models/group.py:31 users/models/user.py:667
#: users/models/group.py:31 users/models/user.py:671
msgid "User group"
msgstr "ユーザーグループ"
@ -960,7 +960,7 @@ msgstr "資産グループ"
msgid "Default asset group"
msgstr "デフォルトアセットグループ"
#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30
#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:34
msgid "Value"
msgstr "値"
@ -1157,7 +1157,7 @@ msgstr "定期的なパフォーマンス"
msgid "Currently only mail sending is supported"
msgstr "現在、メール送信のみがサポートされています"
#: assets/serializers/base.py:16 users/models/user.py:689
#: assets/serializers/base.py:16 users/models/user.py:693
msgid "Private key"
msgstr "ssh秘密鍵"
@ -1507,7 +1507,7 @@ msgstr "パスワード変更ログ"
msgid "Disabled"
msgstr "無効"
#: audits/models.py:112 settings/models.py:33
#: audits/models.py:112 settings/models.py:37
msgid "Enabled"
msgstr "有効化"
@ -1535,7 +1535,7 @@ msgstr "ユーザーエージェント"
#: audits/models.py:126
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms/profile.py:65 users/models/user.py:684
#: users/forms/profile.py:65 users/models/user.py:688
#: users/serializers/profile.py:126
msgid "MFA"
msgstr "MFA"
@ -1613,20 +1613,20 @@ msgid "Auth Token"
msgstr "認証トークン"
#: audits/signal_handlers.py:53 authentication/notifications.py:73
#: authentication/views/login.py:67 authentication/views/wecom.py:178
#: notifications/backends/__init__.py:11 users/models/user.py:720
#: authentication/views/login.py:73 authentication/views/wecom.py:178
#: notifications/backends/__init__.py:11 users/models/user.py:724
msgid "WeCom"
msgstr "企業微信"
#: audits/signal_handlers.py:54 authentication/views/feishu.py:144
#: authentication/views/login.py:79 notifications/backends/__init__.py:14
#: users/models/user.py:722
#: authentication/views/login.py:85 notifications/backends/__init__.py:14
#: users/models/user.py:726
msgid "FeiShu"
msgstr "本を飛ばす"
#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179
#: authentication/views/login.py:73 notifications/backends/__init__.py:12
#: users/models/user.py:721
#: authentication/views/login.py:79 notifications/backends/__init__.py:12
#: users/models/user.py:725
msgid "DingTalk"
msgstr "DingTalk"
@ -1867,6 +1867,10 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr "無効なトークンまたはキャッシュの更新。"
#: authentication/backends/oauth2/backends.py:155 authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです"
#: authentication/confirm/password.py:16
msgid "Authentication failed password incorrect"
msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません)"
@ -2142,7 +2146,7 @@ msgstr "ひみつ"
#: authentication/models.py:74 authentication/models.py:264
#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:707
msgid "Date expired"
msgstr "期限切れの日付"
@ -2166,10 +2170,6 @@ msgstr "接続トークンの有効期限: {}"
msgid "User not exists"
msgstr "ユーザーは存在しません"
#: authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "ユーザーが無効、無効、または期限切れです"
#: authentication/models.py:163
msgid "System user not exists"
msgstr "システムユーザーが存在しません"
@ -2317,7 +2317,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:307 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:323 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@ -2530,19 +2530,19 @@ msgstr "本を飛ばすからユーザーを取得できませんでした"
msgid "Please login with a password and then bind the FeiShu"
msgstr "パスワードでログインしてから本を飛ばすをバインドしてください"
#: authentication/views/login.py:175
#: authentication/views/login.py:181
msgid "Redirecting"
msgstr "リダイレクト"
#: authentication/views/login.py:176
#: authentication/views/login.py:182
msgid "Redirecting to {} authentication"
msgstr "{} 認証へのリダイレクト"
#: authentication/views/login.py:199
#: authentication/views/login.py:205
msgid "Please enable cookies and try again."
msgstr "クッキーを有効にして、もう一度お試しください。"
#: authentication/views/login.py:301
#: authentication/views/login.py:307
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@ -2550,15 +2550,15 @@ msgstr ""
"<b>{}</b> 確認を待ちます。彼女/彼へのリンクをコピーすることもできます <br/>\n"
" このページを閉じないでください"
#: authentication/views/login.py:306
#: authentication/views/login.py:312
msgid "No ticket found"
msgstr "チケットが見つかりません"
#: authentication/views/login.py:340
#: authentication/views/login.py:346
msgid "Logout success"
msgstr "ログアウト成功"
#: authentication/views/login.py:341
#: authentication/views/login.py:347
msgid "Logout success, return login page"
msgstr "ログアウト成功、ログインページを返す"
@ -2762,11 +2762,11 @@ msgstr "特殊文字を含むべきではない"
msgid "The mobile phone number format is incorrect"
msgstr "携帯電話番号の形式が正しくありません"
#: jumpserver/conf.py:306
#: jumpserver/conf.py:322
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:308
#: jumpserver/conf.py:324
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@ -2811,7 +2811,7 @@ msgid "Notifications"
msgstr "通知"
#: notifications/backends/__init__.py:10 users/forms/profile.py:102
#: users/models/user.py:663
#: users/models/user.py:667
msgid "Email"
msgstr "メール"
@ -3045,7 +3045,7 @@ msgid "Can view all joined org"
msgstr "参加しているすべての組織を表示できます"
#: orgs/models.py:222 rbac/models/role.py:46 rbac/models/rolebinding.py:44
#: users/models/user.py:671
#: users/models/user.py:675
msgid "Role"
msgstr "ロール"
@ -3323,6 +3323,7 @@ msgid "Permission"
msgstr "権限"
#: rbac/models/role.py:31 rbac/models/rolebinding.py:38
#: settings/serializers/auth/oauth2.py:35
msgid "Scope"
msgstr "スコープ"
@ -3401,7 +3402,7 @@ msgstr "ワークスペースビュー"
msgid "Audit view"
msgstr "監査ビュー"
#: rbac/tree.py:28 settings/models.py:140
#: rbac/tree.py:28 settings/models.py:156
msgid "System setting"
msgstr "システム設定"
@ -3499,43 +3500,43 @@ msgstr "{} 人のユーザーを正常にインポートしました (組織: {}
msgid "Settings"
msgstr "設定"
#: settings/models.py:142
#: settings/models.py:158
msgid "Can change email setting"
msgstr "メール設定を変更できます"
#: settings/models.py:143
#: settings/models.py:159
msgid "Can change auth setting"
msgstr "資格認定の設定"
#: settings/models.py:144
#: settings/models.py:160
msgid "Can change system msg sub setting"
msgstr "システムmsgサブ设定を変更できます"
#: settings/models.py:145
#: settings/models.py:161
msgid "Can change sms setting"
msgstr "Smsの設定を変えることができます"
#: settings/models.py:146
#: settings/models.py:162
msgid "Can change security setting"
msgstr "セキュリティ設定を変更できます"
#: settings/models.py:147
#: settings/models.py:163
msgid "Can change clean setting"
msgstr "きれいな設定を変えることができます"
#: settings/models.py:148
#: settings/models.py:164
msgid "Can change interface setting"
msgstr "インターフェイスの設定を変えることができます"
#: settings/models.py:149
#: settings/models.py:165
msgid "Can change license setting"
msgstr "ライセンス設定を変更できます"
#: settings/models.py:150
#: settings/models.py:166
msgid "Can change terminal setting"
msgstr "ターミナルの設定を変えることができます"
#: settings/models.py:151
#: settings/models.py:167
msgid "Can change other setting"
msgstr "他の設定を変えることができます"
@ -3648,7 +3649,8 @@ msgstr "ユーザー検索フィルター"
msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)"
msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)"
#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oidc.py:36
#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:51
#: settings/serializers/auth/oidc.py:36
msgid "User attr map"
msgstr "ユーザー属性マッピング"
@ -3672,23 +3674,52 @@ msgstr "ページサイズを検索"
msgid "Enable LDAP auth"
msgstr "LDAP認証の有効化"
#: settings/serializers/auth/oidc.py:15
msgid "Base site url"
msgstr "ベースサイトのアドレス"
#: settings/serializers/auth/oauth2.py:20
msgid "Enable OAuth2 Auth"
msgstr "OAuth2認証の有効化"
#: settings/serializers/auth/oidc.py:18
#: settings/serializers/auth/oauth2.py:23
msgid "Logo"
msgstr "アイコン"
#: settings/serializers/auth/oauth2.py:26
msgid "Service provider"
msgstr "サービスプロバイダー"
#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18
msgid "Client Id"
msgstr "クライアントID"
#: settings/serializers/auth/oidc.py:21
#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21
#: xpack/plugins/cloud/serializers/account_attrs.py:36
msgid "Client Secret"
msgstr "クライアント秘密"
#: settings/serializers/auth/oidc.py:29
#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62
msgid "Provider auth endpoint"
msgstr "認証エンドポイントアドレス"
#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65
msgid "Provider token endpoint"
msgstr "プロバイダートークンエンドポイント"
#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29
msgid "Client authentication method"
msgstr "クライアント認証方式"
#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71
msgid "Provider userinfo endpoint"
msgstr "プロバイダーuserinfoエンドポイント"
#: settings/serializers/auth/oauth2.py:54 settings/serializers/auth/oidc.py:92
#: settings/serializers/auth/saml2.py:33
msgid "Always update user"
msgstr "常にユーザーを更新"
#: settings/serializers/auth/oidc.py:15
msgid "Base site url"
msgstr "ベースサイトのアドレス"
#: settings/serializers/auth/oidc.py:31
msgid "Share session"
msgstr "セッションの共有"
@ -3721,22 +3752,10 @@ msgstr "OIDC認証の有効化"
msgid "Provider endpoint"
msgstr "プロバイダーエンドポイント"
#: settings/serializers/auth/oidc.py:62
msgid "Provider auth endpoint"
msgstr "認証エンドポイントアドレス"
#: settings/serializers/auth/oidc.py:65
msgid "Provider token endpoint"
msgstr "プロバイダートークンエンドポイント"
#: settings/serializers/auth/oidc.py:68
msgid "Provider jwks endpoint"
msgstr "プロバイダーjwksエンドポイント"
#: settings/serializers/auth/oidc.py:71
msgid "Provider userinfo endpoint"
msgstr "プロバイダーuserinfoエンドポイント"
#: settings/serializers/auth/oidc.py:74
msgid "Provider end session endpoint"
msgstr "プロバイダーのセッション終了エンドポイント"
@ -3769,10 +3788,6 @@ msgstr "使用状態"
msgid "Use nonce"
msgstr "Nonceを使用"
#: settings/serializers/auth/oidc.py:92 settings/serializers/auth/saml2.py:33
msgid "Always update user"
msgstr "常にユーザーを更新"
#: settings/serializers/auth/radius.py:13
msgid "Enable Radius Auth"
msgstr "Radius認証の有効化"
@ -5686,7 +5701,7 @@ msgstr "公開鍵は古いものと同じであってはなりません。"
msgid "Not a valid ssh public key"
msgstr "有効なssh公開鍵ではありません"
#: users/forms/profile.py:161 users/models/user.py:692
#: users/forms/profile.py:161 users/models/user.py:696
msgid "Public key"
msgstr "公開キー"
@ -5698,55 +5713,55 @@ msgstr "強制有効"
msgid "Local"
msgstr "ローカル"
#: users/models/user.py:673 users/serializers/user.py:149
#: users/models/user.py:677 users/serializers/user.py:149
msgid "Is service account"
msgstr "サービスアカウントです"
#: users/models/user.py:675
#: users/models/user.py:679
msgid "Avatar"
msgstr "アバター"
#: users/models/user.py:678
#: users/models/user.py:682
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:695
#: users/models/user.py:699
msgid "Secret key"
msgstr "秘密キー"
#: users/models/user.py:711
#: users/models/user.py:715
msgid "Source"
msgstr "ソース"
#: users/models/user.py:715
#: users/models/user.py:719
msgid "Date password last updated"
msgstr "最終更新日パスワード"
#: users/models/user.py:718
#: users/models/user.py:722
msgid "Need update password"
msgstr "更新パスワードが必要"
#: users/models/user.py:892
#: users/models/user.py:896
msgid "Can invite user"
msgstr "ユーザーを招待できます"
#: users/models/user.py:893
#: users/models/user.py:897
msgid "Can remove user"
msgstr "ユーザーを削除できます"
#: users/models/user.py:894
#: users/models/user.py:898
msgid "Can match user"
msgstr "ユーザーに一致できます"
#: users/models/user.py:903
#: users/models/user.py:907
msgid "Administrator"
msgstr "管理者"
#: users/models/user.py:906
#: users/models/user.py:910
msgid "Administrator is the super user of system"
msgstr "管理者はシステムのスーパーユーザーです"
#: users/models/user.py:931
#: users/models/user.py:935
msgid "User password history"
msgstr "ユーザーパスワード履歴"
@ -6859,6 +6874,9 @@ msgstr "究極のエディション"
msgid "Community edition"
msgstr "コミュニティ版"
#~ msgid "Logo title"
#~ msgstr "アイコンタイトル"
#~ msgid "IP is not allowed"
#~ msgstr "IPは許可されていません"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9ed12e275e241284573d49c752cf01bafddb912dfe38ae2888a62e62cdb30ebd
size 106084
oid sha256:4e8c2c0a8a9b7d9de0bd11c1fba8073bbe44fe7274ff6bb3537d5fa19b083baa
size 106370

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-02 11:39+0800\n"
"POT-Creation-Date: 2022-08-04 14:17+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"
@ -27,11 +27,11 @@ msgstr "访问控制"
#: assets/models/cmd_filter.py:27 assets/models/domain.py:23
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29
#: settings/models.py:29 settings/serializers/sms.py:6
#: settings/models.py:33 settings/serializers/sms.py:6
#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86
#: terminal/models/storage.py:26 terminal/models/task.py:16
#: terminal/models/terminal.py:100 users/forms/profile.py:33
#: users/models/group.py:15 users/models/user.py:661
#: users/models/group.py:15 users/models/user.py:665
#: xpack/plugins/cloud/models.py:28
msgid "Name"
msgstr "名称"
@ -59,11 +59,11 @@ msgstr "激活中"
#: assets/models/cmd_filter.py:96 assets/models/domain.py:24
#: assets/models/domain.py:65 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38
#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96
#: terminal/models/storage.py:29 terminal/models/terminal.py:114
#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288
#: users/models/group.py:16 users/models/user.py:698
#: users/models/group.py:16 users/models/user.py:702
#: xpack/plugins/change_auth_plan/models/base.py:44
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116
#: xpack/plugins/gathered_user/models.py:26
@ -93,7 +93,7 @@ msgstr "登录复核"
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
#: terminal/models/sharing.py:33 terminal/notifications.py:91
#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14
#: users/models/user.py:890 users/models/user.py:921
#: users/models/user.py:894 users/models/user.py:925
#: users/serializers/group.py:19
msgid "User"
msgstr "用户"
@ -159,7 +159,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: authentication/models.py:260
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:663
#: users/templates/users/_msg_user_created.html:12
#: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195
@ -359,7 +359,7 @@ msgstr "类型名称"
#: assets/serializers/cmd_filter.py:48 common/db/models.py:114
#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30
#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92
#: users/models/group.py:18 users/models/user.py:922
#: users/models/group.py:18 users/models/user.py:926
#: xpack/plugins/cloud/models.py:125
msgid "Date created"
msgstr "创建日期"
@ -623,7 +623,7 @@ msgstr "标签管理"
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:52
#: assets/models/cmd_filter.py:99 assets/models/group.py:21
#: common/db/models.py:112 common/mixins/models.py:49 orgs/models.py:71
#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:706
#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:710
#: users/serializers/group.py:33
#: xpack/plugins/change_auth_plan/models/base.py:48
#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30
@ -819,7 +819,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:681
#: assets/models/cluster.py:22 users/models/user.py:685
msgid "Phone"
msgstr "手机"
@ -845,7 +845,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6
#: users/models/user.py:907
#: users/models/user.py:911
msgid "System"
msgstr "系统"
@ -854,7 +854,7 @@ msgid "Default Cluster"
msgstr "默认Cluster"
#: assets/models/cmd_filter.py:34 perms/models/base.py:86
#: users/models/group.py:31 users/models/user.py:667
#: users/models/group.py:31 users/models/user.py:671
msgid "User group"
msgstr "用户组"
@ -955,7 +955,7 @@ msgstr "资产组"
msgid "Default asset group"
msgstr "默认资产组"
#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30
#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:34
msgid "Value"
msgstr "值"
@ -1149,7 +1149,7 @@ msgstr "定时执行"
msgid "Currently only mail sending is supported"
msgstr "当前只支持邮件发送"
#: assets/serializers/base.py:16 users/models/user.py:689
#: assets/serializers/base.py:16 users/models/user.py:693
msgid "Private key"
msgstr "ssh私钥"
@ -1495,7 +1495,7 @@ msgstr "改密日志"
msgid "Disabled"
msgstr "禁用"
#: audits/models.py:112 settings/models.py:33
#: audits/models.py:112 settings/models.py:37
msgid "Enabled"
msgstr "启用"
@ -1523,7 +1523,7 @@ msgstr "用户代理"
#: audits/models.py:126
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms/profile.py:65 users/models/user.py:684
#: users/forms/profile.py:65 users/models/user.py:688
#: users/serializers/profile.py:126
msgid "MFA"
msgstr "MFA"
@ -1601,20 +1601,20 @@ msgid "Auth Token"
msgstr "认证令牌"
#: audits/signal_handlers.py:53 authentication/notifications.py:73
#: authentication/views/login.py:67 authentication/views/wecom.py:178
#: notifications/backends/__init__.py:11 users/models/user.py:720
#: authentication/views/login.py:73 authentication/views/wecom.py:178
#: notifications/backends/__init__.py:11 users/models/user.py:724
msgid "WeCom"
msgstr "企业微信"
#: audits/signal_handlers.py:54 authentication/views/feishu.py:144
#: authentication/views/login.py:79 notifications/backends/__init__.py:14
#: users/models/user.py:722
#: authentication/views/login.py:85 notifications/backends/__init__.py:14
#: users/models/user.py:726
msgid "FeiShu"
msgstr "飞书"
#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179
#: authentication/views/login.py:73 notifications/backends/__init__.py:12
#: users/models/user.py:721
#: authentication/views/login.py:79 notifications/backends/__init__.py:12
#: users/models/user.py:725
msgid "DingTalk"
msgstr "钉钉"
@ -1853,6 +1853,10 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。"
msgid "Invalid token or cache refreshed."
msgstr "刷新的令牌或缓存无效。"
#: authentication/backends/oauth2/backends.py:155 authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期"
#: authentication/confirm/password.py:16
msgid "Authentication failed password incorrect"
msgstr "认证失败 (用户名或密码不正确)"
@ -2121,7 +2125,7 @@ msgstr "密钥"
#: authentication/models.py:74 authentication/models.py:264
#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:703
#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:707
msgid "Date expired"
msgstr "失效日期"
@ -2145,10 +2149,6 @@ msgstr "连接令牌过期: {}"
msgid "User not exists"
msgstr "用户不存在"
#: authentication/models.py:158
msgid "User invalid, disabled or expired"
msgstr "用户无效,已禁用或已过期"
#: authentication/models.py:163
msgid "System user not exists"
msgstr "系统用户不存在"
@ -2292,7 +2292,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:307 ops/tasks.py:145 ops/tasks.py:148
#: jumpserver/conf.py:323 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@ -2496,19 +2496,19 @@ msgstr "从飞书获取用户失败"
msgid "Please login with a password and then bind the FeiShu"
msgstr "请使用密码登录,然后绑定飞书"
#: authentication/views/login.py:175
#: authentication/views/login.py:181
msgid "Redirecting"
msgstr "跳转中"
#: authentication/views/login.py:176
#: authentication/views/login.py:182
msgid "Redirecting to {} authentication"
msgstr "正在跳转到 {} 认证"
#: authentication/views/login.py:199
#: authentication/views/login.py:205
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:301
#: authentication/views/login.py:307
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@ -2516,15 +2516,15 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面"
#: authentication/views/login.py:306
#: authentication/views/login.py:312
msgid "No ticket found"
msgstr "没有发现工单"
#: authentication/views/login.py:340
#: authentication/views/login.py:346
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:341
#: authentication/views/login.py:347
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
@ -2728,11 +2728,11 @@ msgstr "不能包含特殊字符"
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
#: jumpserver/conf.py:306
#: jumpserver/conf.py:322
msgid "Create account successfully"
msgstr "创建账号成功"
#: jumpserver/conf.py:308
#: jumpserver/conf.py:324
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@ -2772,7 +2772,7 @@ msgid "Notifications"
msgstr "通知"
#: notifications/backends/__init__.py:10 users/forms/profile.py:102
#: users/models/user.py:663
#: users/models/user.py:667
msgid "Email"
msgstr "邮件"
@ -3005,7 +3005,7 @@ msgid "Can view all joined org"
msgstr "可以查看所有加入的组织"
#: orgs/models.py:222 rbac/models/role.py:46 rbac/models/rolebinding.py:44
#: users/models/user.py:671
#: users/models/user.py:675
msgid "Role"
msgstr "角色"
@ -3281,6 +3281,7 @@ msgid "Permission"
msgstr "权限"
#: rbac/models/role.py:31 rbac/models/rolebinding.py:38
#: settings/serializers/auth/oauth2.py:35
msgid "Scope"
msgstr "范围"
@ -3358,7 +3359,7 @@ msgstr "工作台"
msgid "Audit view"
msgstr "审计台"
#: rbac/tree.py:28 settings/models.py:140
#: rbac/tree.py:28 settings/models.py:156
msgid "System setting"
msgstr "系统设置"
@ -3456,43 +3457,43 @@ msgstr "成功导入 {} 个用户 ( 组织: {} )"
msgid "Settings"
msgstr "系统设置"
#: settings/models.py:142
#: settings/models.py:158
msgid "Can change email setting"
msgstr "邮件设置"
#: settings/models.py:143
#: settings/models.py:159
msgid "Can change auth setting"
msgstr "认证设置"
#: settings/models.py:144
#: settings/models.py:160
msgid "Can change system msg sub setting"
msgstr "消息订阅设置"
#: settings/models.py:145
#: settings/models.py:161
msgid "Can change sms setting"
msgstr "短信设置"
#: settings/models.py:146
#: settings/models.py:162
msgid "Can change security setting"
msgstr "安全设置"
#: settings/models.py:147
#: settings/models.py:163
msgid "Can change clean setting"
msgstr "定期清理"
#: settings/models.py:148
#: settings/models.py:164
msgid "Can change interface setting"
msgstr "界面设置"
#: settings/models.py:149
#: settings/models.py:165
msgid "Can change license setting"
msgstr "许可证设置"
#: settings/models.py:150
#: settings/models.py:166
msgid "Can change terminal setting"
msgstr "终端设置"
#: settings/models.py:151
#: settings/models.py:167
msgid "Can change other setting"
msgstr "其它设置"
@ -3605,7 +3606,8 @@ msgstr "用户过滤器"
msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)"
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oidc.py:36
#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:51
#: settings/serializers/auth/oidc.py:36
msgid "User attr map"
msgstr "用户属性映射"
@ -3629,23 +3631,52 @@ msgstr "搜索分页数量"
msgid "Enable LDAP auth"
msgstr "启用 LDAP 认证"
#: settings/serializers/auth/oidc.py:15
msgid "Base site url"
msgstr "JumpServer 地址"
#: settings/serializers/auth/oauth2.py:20
msgid "Enable OAuth2 Auth"
msgstr "启用 OAuth2 认证"
#: settings/serializers/auth/oidc.py:18
#: settings/serializers/auth/oauth2.py:23
msgid "Logo"
msgstr "图标"
#: settings/serializers/auth/oauth2.py:26
msgid "Service provider"
msgstr "服务提供商"
#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18
msgid "Client Id"
msgstr "客户端 ID"
#: settings/serializers/auth/oidc.py:21
#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21
#: xpack/plugins/cloud/serializers/account_attrs.py:36
msgid "Client Secret"
msgstr "客户端密钥"
#: settings/serializers/auth/oidc.py:29
#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62
msgid "Provider auth endpoint"
msgstr "授权端点地址"
#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65
msgid "Provider token endpoint"
msgstr "token 端点地址"
#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29
msgid "Client authentication method"
msgstr "客户端认证方式"
#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71
msgid "Provider userinfo endpoint"
msgstr "用户信息端点地址"
#: settings/serializers/auth/oauth2.py:54 settings/serializers/auth/oidc.py:92
#: settings/serializers/auth/saml2.py:33
msgid "Always update user"
msgstr "总是更新用户信息"
#: settings/serializers/auth/oidc.py:15
msgid "Base site url"
msgstr "JumpServer 地址"
#: settings/serializers/auth/oidc.py:31
msgid "Share session"
msgstr "共享会话"
@ -3678,22 +3709,10 @@ msgstr "启用 OIDC 认证"
msgid "Provider endpoint"
msgstr "端点地址"
#: settings/serializers/auth/oidc.py:62
msgid "Provider auth endpoint"
msgstr "授权端点地址"
#: settings/serializers/auth/oidc.py:65
msgid "Provider token endpoint"
msgstr "token 端点地址"
#: settings/serializers/auth/oidc.py:68
msgid "Provider jwks endpoint"
msgstr "jwks 端点地址"
#: settings/serializers/auth/oidc.py:71
msgid "Provider userinfo endpoint"
msgstr "用户信息端点地址"
#: settings/serializers/auth/oidc.py:74
msgid "Provider end session endpoint"
msgstr "注销会话端点地址"
@ -3726,10 +3745,6 @@ msgstr "使用状态"
msgid "Use nonce"
msgstr "临时使用"
#: settings/serializers/auth/oidc.py:92 settings/serializers/auth/saml2.py:33
msgid "Always update user"
msgstr "总是更新用户信息"
#: settings/serializers/auth/radius.py:13
msgid "Enable Radius Auth"
msgstr "启用 Radius 认证"
@ -5604,7 +5619,7 @@ msgstr "不能和原来的密钥相同"
msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法"
#: users/forms/profile.py:161 users/models/user.py:692
#: users/forms/profile.py:161 users/models/user.py:696
msgid "Public key"
msgstr "SSH公钥"
@ -5616,55 +5631,55 @@ msgstr "强制启用"
msgid "Local"
msgstr "数据库"
#: users/models/user.py:673 users/serializers/user.py:149
#: users/models/user.py:677 users/serializers/user.py:149
msgid "Is service account"
msgstr "服务账号"
#: users/models/user.py:675
#: users/models/user.py:679
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:678
#: users/models/user.py:682
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:695
#: users/models/user.py:699
msgid "Secret key"
msgstr "Secret key"
#: users/models/user.py:711
#: users/models/user.py:715
msgid "Source"
msgstr "来源"
#: users/models/user.py:715
#: users/models/user.py:719
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:718
#: users/models/user.py:722
msgid "Need update password"
msgstr "需要更新密码"
#: users/models/user.py:892
#: users/models/user.py:896
msgid "Can invite user"
msgstr "可以邀请用户"
#: users/models/user.py:893
#: users/models/user.py:897
msgid "Can remove user"
msgstr "可以移除用户"
#: users/models/user.py:894
#: users/models/user.py:898
msgid "Can match user"
msgstr "可以匹配用户"
#: users/models/user.py:903
#: users/models/user.py:907
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:906
#: users/models/user.py:910
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/models/user.py:931
#: users/models/user.py:935
msgid "User password history"
msgstr "用户密码历史"
@ -6762,6 +6777,9 @@ msgstr "旗舰版"
msgid "Community edition"
msgstr "社区版"
#~ msgid "Logo title"
#~ msgstr "图标标题"
#~ msgid "IP is not allowed"
#~ msgstr "来源 IP 不被允许登录"

View File

@ -34,6 +34,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
'cas': serializers.CASSettingSerializer,
'sso': serializers.SSOSettingSerializer,
'saml2': serializers.SAML2SettingSerializer,
'oauth2': serializers.OAuth2SettingSerializer,
'clean': serializers.CleaningSerializer,
'other': serializers.OtherSettingSerializer,
'sms': serializers.SMSSettingSerializer,
@ -113,9 +114,12 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
return data
def perform_update(self, serializer):
post_data_names = list(self.request.data.keys())
settings_items = self.parse_serializer_data(serializer)
serializer_data = getattr(serializer, 'data', {})
for item in settings_items:
if item['name'] not in post_data_names:
continue
changed, setting = Setting.update_or_create(**item)
if not changed:
continue

View File

@ -1,9 +1,13 @@
import os
import json
from django.db import models
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile
from common.utils import signer, get_logger
@ -118,6 +122,14 @@ class Setting(models.Model):
setattr(settings, key, value)
self.__class__.update_or_create(key, value, encrypted=False, category=self.category)
@classmethod
def save_to_file(cls, value: InMemoryUploadedFile):
filename = value.name
filepath = f'settings/{filename}'
path = default_storage.save(filepath, ContentFile(value.read()))
url = default_storage.url(path)
return url
@classmethod
def update_or_create(cls, name='', value='', encrypted=False, category=''):
"""
@ -128,6 +140,10 @@ class Setting(models.Model):
changed = False
if not setting:
setting = Setting(name=name, encrypted=encrypted, category=category)
if isinstance(value, InMemoryUploadedFile):
value = cls.save_to_file(value)
if setting.cleaned_value != value:
setting.encrypted = encrypted
setting.cleaned_value = value

View File

@ -9,3 +9,4 @@ from .sso import *
from .base import *
from .sms import *
from .saml2 import *
from .oauth2 import *

View File

@ -0,0 +1,55 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.drf.fields import EncryptedField
from common.utils import static_or_direct
__all__ = [
'OAuth2SettingSerializer',
]
class SettingImageField(serializers.ImageField):
def to_representation(self, value):
return static_or_direct(value)
class OAuth2SettingSerializer(serializers.Serializer):
AUTH_OAUTH2 = serializers.BooleanField(
default=False, required=False, label=_('Enable OAuth2 Auth')
)
AUTH_OAUTH2_LOGO_PATH = SettingImageField(
allow_null=True, required=False, label=_('Logo')
)
AUTH_OAUTH2_PROVIDER = serializers.CharField(
required=False, max_length=16, label=_('Service provider')
)
AUTH_OAUTH2_CLIENT_ID = serializers.CharField(
required=False, max_length=1024, label=_('Client Id')
)
AUTH_OAUTH2_CLIENT_SECRET = EncryptedField(
required=False, max_length=1024, label=_('Client Secret')
)
AUTH_OAUTH2_SCOPE = serializers.CharField(
required=False, max_length=1024, label=_('Scope'), allow_blank=True
)
AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider auth endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider token endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_METHOD = serializers.ChoiceField(
default='GET', label=_('Client authentication method'),
choices=(('GET', 'GET'), ('POST', 'POST'))
)
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider userinfo endpoint')
)
AUTH_OAUTH2_USER_ATTR_MAP = serializers.DictField(
required=False, label=_('User attr map')
)
AUTH_OAUTH2_ALWAYS_UPDATE_USER = serializers.BooleanField(
required=False, label=_('Always update user')
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -628,6 +628,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
radius = 'radius', 'Radius'
cas = 'cas', 'CAS'
saml2 = 'saml2', 'SAML2'
oauth2 = 'oauth2', 'OAuth2'
SOURCE_BACKEND_MAPPING = {
Source.local: [
@ -652,6 +653,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
Source.saml2: [
settings.AUTH_BACKEND_SAML2
],
Source.oauth2: [
settings.AUTH_BACKEND_OAUTH2
],
}
id = models.UUIDField(default=uuid.uuid4, primary_key=True)

View File

@ -9,6 +9,7 @@ from django.db.models.signals import post_save
from authentication.backends.oidc.signals import openid_create_or_update_user
from authentication.backends.saml2.signals import saml2_create_or_update_user
from authentication.backends.oauth2.signals import oauth2_create_or_update_user
from common.utils import get_logger
from common.decorator import on_transaction_commit
from .signals import post_user_create
@ -26,16 +27,18 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
user.source = source
user.save()
if not created and settings.AUTH_SAML2_ALWAYS_UPDATE_USER:
if not attrs:
return
always_update = getattr(settings, 'AUTH_%s_ALWAYS_UPDATE_USER' % source.upper(), False)
if not created and always_update:
attr_whitelist = ('user', 'username', 'email', 'phone', 'comment')
logger.debug(
"Receive saml2 user updated signal: {}, "
"Receive {} user updated signal: {}, "
"Update user info: {},"
"(Update only properties in the whitelist. [{}])"
"".format(user, str(attrs), ','.join(attr_whitelist))
"".format(source, user, str(attrs), ','.join(attr_whitelist))
)
if not attrs:
return
for key, value in attrs.items():
if key in attr_whitelist and value:
setattr(user, key, value)
@ -103,6 +106,12 @@ def on_saml2_create_or_update_user(sender, user, created, attrs, **kwargs):
user_authenticated_handle(user, created, source, attrs, **kwargs)
@receiver(oauth2_create_or_update_user)
def on_oauth2_create_or_update_user(sender, user, created, attrs, **kwargs):
source = user.Source.oauth2.value
user_authenticated_handle(user, created, source, attrs, **kwargs)
@receiver(populate_user)
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user and user.username not in ['admin']:

View File

@ -230,6 +230,8 @@ class LoginIpBlockUtil(BlockGlobalIpUtilBase):
def construct_user_email(username, email, email_suffix=''):
if email is None:
email = ''
if '@' in email:
return email
if '@' in username: