diff --git a/apps/authentication/backends/oauth2/__init__.py b/apps/authentication/backends/oauth2/__init__.py new file mode 100644 index 000000000..448096520 --- /dev/null +++ b/apps/authentication/backends/oauth2/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# + +from .backends import * diff --git a/apps/authentication/backends/oauth2/backends.py b/apps/authentication/backends/oauth2/backends.py new file mode 100644 index 000000000..755d5ef54 --- /dev/null +++ b/apps/authentication/backends/oauth2/backends.py @@ -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 diff --git a/apps/authentication/backends/oauth2/signals.py b/apps/authentication/backends/oauth2/signals.py new file mode 100644 index 000000000..50c7837f8 --- /dev/null +++ b/apps/authentication/backends/oauth2/signals.py @@ -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']) + diff --git a/apps/authentication/backends/oauth2/urls.py b/apps/authentication/backends/oauth2/urls.py new file mode 100644 index 000000000..94c044a7d --- /dev/null +++ b/apps/authentication/backends/oauth2/urls.py @@ -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') +] diff --git a/apps/authentication/backends/oauth2/views.py b/apps/authentication/backends/oauth2/views.py new file mode 100644 index 000000000..cac8b2eb8 --- /dev/null +++ b/apps/authentication/backends/oauth2/views.py @@ -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) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 9866e84f3..03b71334f 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -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) ) } diff --git a/apps/authentication/backends/oidc/utils.py b/apps/authentication/backends/oidc/utils.py index 2a0f0609e..31cca6f03 100644 --- a/apps/authentication/backends/oidc/utils.py +++ b/apps/authentication/backends/oidc/utils.py @@ -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 diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py index 1c9442ef2..78019ac33 100644 --- a/apps/authentication/backends/oidc/views.py +++ b/apps/authentication/backends/oidc/views.py @@ -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()) diff --git a/apps/authentication/backends/saml2/backends.py b/apps/authentication/backends/saml2/backends.py index 0ac0efe1c..70667cd94 100644 --- a/apps/authentication/backends/saml2/backends.py +++ b/apps/authentication/backends/saml2/backends.py @@ -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) diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index ac155dcf0..7fc1fc6b5 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -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) diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 9abd61e3b..2c97fe1ea 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -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')), ] diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index c0588b206..0c9c56102 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -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 diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 09c842d88..88f580279 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -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, diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 5b2180ec0..474bf1da0 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -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 diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 5f4514bf8..9f355654c 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -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, # 企业微信 diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 95a12d01f..803315f9c 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -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模式 diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index b3b599833..13f8ad245 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:322701b975fe90b4b187c4a99ddd1837291150502c82accf0a4c6e32dddf91be -size 128721 +oid sha256:73ea6289c22c329752330fae1fef6d174573c7f46355137ffbc864407b2b8270 +size 129073 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 0dfe25a4a..76bf2a10c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: LANGUAGE \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 {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -2550,15 +2550,15 @@ msgstr "" "{} 確認を待ちます。彼女/彼へのリンクをコピーすることもできます
\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は許可されていません" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 26e44a3d7..86b39582a 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ed12e275e241284573d49c752cf01bafddb912dfe38ae2888a62e62cdb30ebd -size 106084 +oid sha256:4e8c2c0a8a9b7d9de0bd11c1fba8073bbe44fe7274ff6bb3537d5fa19b083baa +size 106370 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c8320846f..00b01519c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: JumpServer team\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 {} confirm, You also can copy link to her/him
\n" " Don't close this page" @@ -2516,15 +2516,15 @@ msgstr "" "等待 {} 确认, 你也可以复制链接发给他/她
\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 不被允许登录" diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 3e1d9336d..4cce5e718 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -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 diff --git a/apps/settings/models.py b/apps/settings/models.py index 4546f9624..8b91765cf 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -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 diff --git a/apps/settings/serializers/auth/__init__.py b/apps/settings/serializers/auth/__init__.py index c675f4070..1f9f360f9 100644 --- a/apps/settings/serializers/auth/__init__.py +++ b/apps/settings/serializers/auth/__init__.py @@ -9,3 +9,4 @@ from .sso import * from .base import * from .sms import * from .saml2 import * +from .oauth2 import * diff --git a/apps/settings/serializers/auth/oauth2.py b/apps/settings/serializers/auth/oauth2.py new file mode 100644 index 000000000..2c620d331 --- /dev/null +++ b/apps/settings/serializers/auth/oauth2.py @@ -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') + ) diff --git a/apps/static/img/login_oauth2_logo.png b/apps/static/img/login_oauth2_logo.png new file mode 100644 index 000000000..b1cf562b0 Binary files /dev/null and b/apps/static/img/login_oauth2_logo.png differ diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 2fd33aa08..9fd6ea0b9 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -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) diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py index bd9b01845..bcf50173a 100644 --- a/apps/users/signal_handlers.py +++ b/apps/users/signal_handlers.py @@ -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']: diff --git a/apps/users/utils.py b/apps/users/utils.py index 60485f497..55ebd1aa1 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -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: