diff --git a/Dockerfile b/Dockerfile index a45e41a45..6569db2a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN yum -y install epel-release && \ echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt) RUN cd /tmp/requirements && pip install --upgrade pip setuptools && pip install wheel && \ - pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt + pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt || pip install -r requirements.txt RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config COPY . /opt/jumpserver diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 3238e8fca..6b4d8111a 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,6 +1,5 @@ # ~*~ coding: utf-8 ~*~ # -import time from treelib import Tree from treelib.exceptions import NodeIDAbsentError from collections import defaultdict @@ -77,21 +76,9 @@ class TreeService(Tree): ancestor_ids.pop(0) return ancestor_ids - def ancestors(self, nid, with_self=False, deep=False, with_assets=True): - # now = time.time() - # print("Start get ancestor_ids") + def ancestors(self, nid, with_self=False, deep=False): ancestor_ids = self.ancestors_ids(nid, with_self=with_self) - # now2 = time.time() - # interval = (now2 - now) * 1000 - # print("Start get ancestor_ids using: {}".format(interval)) ancestors = [self.get_node(i, deep=deep) for i in ancestor_ids] - # interval = (time.time() - now2) * 1000 - # print("Get ancestors done: {}".format(interval)) - if with_assets: - return ancestors - for n in ancestors: - n.data['assets'] = set() - n.data['all_assets'] = None return ancestors def get_node_full_tag(self, nid): @@ -116,6 +103,7 @@ class TreeService(Tree): node = super().get_node(nid) if deep: node = self.copy_node(node) + node.data = {} return node def parent(self, nid, deep=False): diff --git a/apps/audits/signals_handler.py b/apps/audits/signals_handler.py index dab56fa5c..b95f6fbdf 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -136,8 +136,8 @@ def on_user_auth_success(sender, user, request, **kwargs): @receiver(post_auth_failed) -def on_user_auth_failed(sender, username, request, reason, **kwargs): +def on_user_auth_failed(sender, username, request, reason='', **kwargs): logger.debug('User login failed: {}'.format(username)) data = generate_data(username, request) - data.update({'reason': reason, 'status': False}) + data.update({'reason': reason[:128], 'status': False}) write_login_log(**data) diff --git a/apps/authentication/backends/openid.py b/apps/authentication/backends/openid.py new file mode 100644 index 000000000..a82161b8e --- /dev/null +++ b/apps/authentication/backends/openid.py @@ -0,0 +1,4 @@ +""" +使用下面的工程,进行jumpserver 的 oidc 认证 +https://github.com/BaiJiangJie/jumpserver-django-oidc-rp +""" \ No newline at end of file diff --git a/apps/authentication/backends/openid/__init__.py b/apps/authentication/backends/openid/__init__.py deleted file mode 100644 index 9ed3bea78..000000000 --- a/apps/authentication/backends/openid/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from .backends import * -from .middleware import * -from .utils import * -from .decorator import * diff --git a/apps/authentication/backends/openid/backends.py b/apps/authentication/backends/openid/backends.py deleted file mode 100644 index 938566e2a..000000000 --- a/apps/authentication/backends/openid/backends.py +++ /dev/null @@ -1,82 +0,0 @@ -# coding:utf-8 -# - -from django.contrib.auth import get_user_model -from django.conf import settings - -from common.utils import get_logger -from .utils import new_client -from .models import OIDT_ACCESS_TOKEN - -UserModel = get_user_model() - -logger = get_logger(__file__) -client = new_client() - - -__all__ = [ - 'OpenIDAuthorizationCodeBackend', 'OpenIDAuthorizationPasswordBackend', -] - - -class BaseOpenIDAuthorizationBackend(object): - @staticmethod - def user_can_authenticate(user): - """ - Reject users with is_active=False. Custom user models that don't have - that attribute are allowed. - """ - is_valid = getattr(user, 'is_valid', None) - return is_valid or is_valid is None - - def get_user(self, user_id): - try: - user = UserModel._default_manager.get(pk=user_id) - except UserModel.DoesNotExist: - return None - - return user if self.user_can_authenticate(user) else None - - -class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): - def authenticate(self, request, **kwargs): - logger.info('Authentication OpenID code backend') - code = kwargs.get('code') - redirect_uri = kwargs.get('redirect_uri') - if not code or not redirect_uri: - logger.info('Authenticate failed: No code or No redirect uri') - return None - try: - oidt_profile = client.update_or_create_from_code( - code=code, redirect_uri=redirect_uri - ) - except Exception as e: - logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) - return None - else: - # Check openid user single logout or not with access_token - request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token - user = oidt_profile.user - logger.info('Authenticate success: user -> {}'.format(user)) - return user if self.user_can_authenticate(user) else None - - -class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend): - def authenticate(self, request, username=None, password=None, **kwargs): - logger.info('Authentication OpenID password backend') - if not username: - logger.info('Authenticate failed: Not username') - return None - try: - oidt_profile = client.update_or_create_from_password( - username=username, password=password - ) - except Exception as e: - logger.error(e, exc_info=True) - logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) - return None - else: - user = oidt_profile.user - logger.info('Authenticate success: user -> {}'.format(user)) - return user if self.user_can_authenticate(user) else None - diff --git a/apps/authentication/backends/openid/decorator.py b/apps/authentication/backends/openid/decorator.py deleted file mode 100644 index 7286b7a2f..000000000 --- a/apps/authentication/backends/openid/decorator.py +++ /dev/null @@ -1,57 +0,0 @@ -# coding: utf-8 -# - -import warnings -import contextlib - -import requests -from urllib3.exceptions import InsecureRequestWarning -from django.conf import settings - -__all__ = [ - 'ssl_verification', -] - -old_merge_environment_settings = requests.Session.merge_environment_settings - - -@contextlib.contextmanager -def no_ssl_verification(): - """ - https://stackoverflow.com/questions/15445981/ - how-do-i-disable-the-security-certificate-check-in-python-requests - """ - opened_adapters = set() - - def merge_environment_settings(self, url, proxies, stream, verify, cert): - # Verification happens only once per connection so we need to close - # all the opened adapters once we're done. Otherwise, the effects of - # verify=False persist beyond the end of this context manager. - opened_adapters.add(self.get_adapter(url)) - _settings = old_merge_environment_settings( - self, url, proxies, stream, verify, cert - ) - _settings['verify'] = False - return _settings - - requests.Session.merge_environment_settings = merge_environment_settings - try: - with warnings.catch_warnings(): - warnings.simplefilter('ignore', InsecureRequestWarning) - yield - finally: - requests.Session.merge_environment_settings = old_merge_environment_settings - for adapter in opened_adapters: - try: - adapter.close() - except: - pass - - -def ssl_verification(func): - def wrapper(*args, **kwargs): - if not settings.AUTH_OPENID_IGNORE_SSL_VERIFICATION: - return func(*args, **kwargs) - with no_ssl_verification(): - return func(*args, **kwargs) - return wrapper diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py deleted file mode 100644 index bacb4858c..000000000 --- a/apps/authentication/backends/openid/middleware.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding:utf-8 -# - -from django.conf import settings -from django.contrib.auth import logout -from django.utils.deprecation import MiddlewareMixin -from django.contrib.auth import BACKEND_SESSION_KEY - -from common.utils import get_logger -from .utils import new_client -from .models import OIDT_ACCESS_TOKEN - -BACKEND_OPENID_AUTH_CODE = 'OpenIDAuthorizationCodeBackend' -logger = get_logger(__file__) -__all__ = ['OpenIDAuthenticationMiddleware'] - - -class OpenIDAuthenticationMiddleware(MiddlewareMixin): - """ - Check openid user single logout (with access_token) - """ - def process_request(self, request): - # Don't need openid auth if AUTH_OPENID is False - if not settings.AUTH_OPENID: - return - # Don't need openid auth if no shared session enabled - if not settings.AUTH_OPENID_SHARE_SESSION: - return - # Don't need check single logout if user not authenticated - if not request.user.is_authenticated: - return - elif not request.session[BACKEND_SESSION_KEY].endswith( - BACKEND_OPENID_AUTH_CODE): - return - # Check openid user single logout or not with access_token - try: - client = new_client() - client.get_userinfo(token=request.session.get(OIDT_ACCESS_TOKEN)) - except Exception as e: - logout(request) - logger.error(e) diff --git a/apps/authentication/backends/openid/models.py b/apps/authentication/backends/openid/models.py deleted file mode 100644 index a945e8eb3..000000000 --- a/apps/authentication/backends/openid/models.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import transaction -from django.contrib.auth import get_user_model -from keycloak.realm import KeycloakRealm -from keycloak.keycloak_openid import KeycloakOpenID -from users.utils import construct_user_email - -from .signals import post_create_or_update_openid_user -from .decorator import ssl_verification - -OIDT_ACCESS_TOKEN = 'oidt_access_token' - - -class Nonce(object): - """ - The openid-login is stored in cache as a temporary object, recording the - user's redirect_uri and next_pat - """ - def __init__(self, redirect_uri, next_path): - import uuid - self.state = uuid.uuid4() - self.redirect_uri = redirect_uri - self.next_path = next_path - - -class OpenIDTokenProfile(object): - def __init__(self, user, access_token, refresh_token): - """ - :param user: User object - :param access_token: - :param refresh_token: - """ - self.user = user - self.access_token = access_token - self.refresh_token = refresh_token - - def __str__(self): - return "{}'s OpenID token profile".format(self.user.username) - - -class Client(object): - def __init__(self, server_url, realm_name, client_id, client_secret): - self.server_url = server_url - self.realm_name = realm_name - self.client_id = client_id - self.client_secret = client_secret - self._openid_client = None - self._realm = None - self._openid_connect_client = None - - @property - def realm(self): - if self._realm is None: - self._realm = KeycloakRealm( - server_url=self.server_url, - realm_name=self.realm_name, - headers={} - ) - return self._realm - - @property - def openid_connect_client(self): - """ - :rtype: keycloak.openid_connect.KeycloakOpenidConnect - """ - if self._openid_connect_client is None: - self._openid_connect_client = self.realm.open_id_connect( - client_id=self.client_id, - client_secret=self.client_secret - ) - return self._openid_connect_client - - @property - def openid_client(self): - """ - :rtype: keycloak.keycloak_openid.KeycloakOpenID - """ - if self._openid_client is None: - self._openid_client = KeycloakOpenID( - server_url='%sauth/' % self.server_url, - realm_name=self.realm_name, - client_id=self.client_id, - client_secret_key=self.client_secret, - ) - return self._openid_client - - @ssl_verification - def get_url(self, name): - return self.openid_connect_client.get_url(name=name) - - def get_url_end_session_endpoint(self): - return self.get_url(name='end_session_endpoint') - - @ssl_verification - def get_authorization_url(self, redirect_uri, scope, state): - url = self.openid_connect_client.authorization_url( - redirect_uri=redirect_uri, scope=scope, state=state - ) - return url - - @ssl_verification - def get_userinfo(self, token): - user_info = self.openid_connect_client.userinfo(token=token) - return user_info - - @ssl_verification - def authorization_code(self, code, redirect_uri): - token_response = self.openid_connect_client.authorization_code( - code=code, redirect_uri=redirect_uri - ) - return token_response - - @ssl_verification - def authorization_password(self, username, password): - token_response = self.openid_client.token( - username=username, password=password - ) - return token_response - - def update_or_create_from_code(self, code, redirect_uri): - """ - Update or create an user based on an authentication code. - Response as specified in: - https://tools.ietf.org/html/rfc6749#section-4.1.4 - :param str code: authentication code - :param str redirect_uri: - :rtype: OpenIDTokenProfile - """ - token_response = self.authorization_code(code, redirect_uri) - return self._update_or_create(token_response=token_response) - - def update_or_create_from_password(self, username, password): - """ - Update or create an user based on an authentication username and password. - :param str username: authentication username - :param str password: authentication password - :return: OpenIDTokenProfile - """ - token_response = self.authorization_password(username, password) - return self._update_or_create(token_response=token_response) - - def _update_or_create(self, token_response): - """ - Update or create an user based on a token response. - `token_response` contains the items returned by the OpenIDConnect Token API - end-point: - - id_token - - access_token - - expires_in - - refresh_token - - refresh_expires_in - :param dict token_response: - :rtype: OpenIDTokenProfile - """ - userinfo = self.get_userinfo(token=token_response['access_token']) - with transaction.atomic(): - name = userinfo.get('name', '') - username = userinfo.get('preferred_username', '') - email = userinfo.get('email', '') - email = construct_user_email(username, email) - - user, created = get_user_model().objects.update_or_create( - username=username, - defaults={ - 'name': name, 'email': email, - 'first_name': userinfo.get('given_name', ''), - 'last_name': userinfo.get('family_name', ''), - } - ) - oidt_profile = OpenIDTokenProfile( - user=user, - access_token=token_response['access_token'], - refresh_token=token_response['refresh_token'], - ) - if user: - post_create_or_update_openid_user.send( - sender=user.__class__, user=user, created=created - ) - - return oidt_profile - - def __str__(self): - return self.client_id diff --git a/apps/authentication/backends/openid/signals.py b/apps/authentication/backends/openid/signals.py deleted file mode 100644 index ad81bca4a..000000000 --- a/apps/authentication/backends/openid/signals.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.dispatch import Signal - - -post_create_or_update_openid_user = Signal(providing_args=('user',)) -post_openid_login_success = Signal(providing_args=('user', 'request')) diff --git a/apps/authentication/backends/openid/tests.py b/apps/authentication/backends/openid/tests.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/authentication/backends/openid/urls.py b/apps/authentication/backends/openid/urls.py deleted file mode 100644 index 019529e12..000000000 --- a/apps/authentication/backends/openid/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.urls import path - -from . import views - -urlpatterns = [ - path('login/', views.OpenIDLoginView.as_view(), name='openid-login'), - path('login/complete/', views.OpenIDLoginCompleteView.as_view(), - name='openid-login-complete'), -] diff --git a/apps/authentication/backends/openid/utils.py b/apps/authentication/backends/openid/utils.py deleted file mode 100644 index 15160d224..000000000 --- a/apps/authentication/backends/openid/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.conf import settings -from .models import Client - -__all__ = ['new_client'] - - -def new_client(): - """ - :return: authentication.models.Client - """ - return Client( - server_url=settings.AUTH_OPENID_SERVER_URL, - realm_name=settings.AUTH_OPENID_REALM_NAME, - client_id=settings.AUTH_OPENID_CLIENT_ID, - client_secret=settings.AUTH_OPENID_CLIENT_SECRET - ) diff --git a/apps/authentication/backends/openid/views.py b/apps/authentication/backends/openid/views.py deleted file mode 100644 index 89c935452..000000000 --- a/apps/authentication/backends/openid/views.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import logging - -from django.conf import settings -from django.core.cache import cache -from django.views.generic.base import RedirectView -from django.contrib.auth import authenticate, login -from django.http.response import ( - HttpResponseBadRequest, - HttpResponseServerError, - HttpResponseRedirect -) - -from .utils import new_client -from .models import Nonce -from .signals import post_openid_login_success - -logger = logging.getLogger(__name__) -client = new_client() - -__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView'] - - -class OpenIDLoginView(RedirectView): - def get_redirect_url(self, *args, **kwargs): - redirect_uri = settings.BASE_SITE_URL + \ - str(settings.AUTH_OPENID_LOGIN_COMPLETE_URL) - nonce = Nonce( - redirect_uri=redirect_uri, - next_path=self.request.GET.get('next') - ) - cache.set(str(nonce.state), nonce, 24*3600) - - self.request.session['openid_state'] = str(nonce.state) - authorization_url = client.get_authorization_url( - redirect_uri=nonce.redirect_uri, - scope='code', - state=str(nonce.state) - ) - return authorization_url - - -class OpenIDLoginCompleteView(RedirectView): - def get(self, request, *args, **kwargs): - if 'error' in request.GET: - return HttpResponseServerError(self.request.GET['error']) - if 'code' not in self.request.GET and 'state' not in self.request.GET: - return HttpResponseBadRequest(content='Code or State is empty') - if self.request.GET['state'] != self.request.session['openid_state']: - return HttpResponseBadRequest(content='State invalid') - nonce = cache.get(self.request.GET['state']) - if not nonce: - return HttpResponseBadRequest(content='State failure') - - user = authenticate( - request=self.request, - code=self.request.GET['code'], - redirect_uri=nonce.redirect_uri - ) - cache.delete(str(nonce.state)) - if not user: - return HttpResponseBadRequest(content='Authenticate user failed') - - login(self.request, user) - post_openid_login_success.send( - sender=self.__class__, user=user, request=self.request - ) - return HttpResponseRedirect(nonce.next_path or '/') - diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index 183e69288..d782a05fc 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -93,6 +93,9 @@ class AuthFailedError(Exception): 'msg': self.msg, } + def __str__(self): + return str(self.msg) + class CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFailedError): def __init__(self, error, username, ip, request): @@ -168,7 +171,7 @@ class MFARequiredError(NeedMoreInfoError): 'error': self.error, 'msg': self.msg, 'data': { - 'choices': ['otp'], + 'choices': ['code'], 'url': reverse('api-auth:mfa-challenge') } } diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 1c4fb5aa1..5b3738c98 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -62,8 +62,7 @@ class AuthMixin: password = request.POST.get('password', '') public_key = request.POST.get('public_key', '') user, error = check_user_valid( - username=username, password=password, - public_key=public_key + request=request, username=username, password=password, public_key=public_key ) ip = self.get_request_ip() if not user: diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py index aac64df4c..461ddbb99 100644 --- a/apps/authentication/signals_handlers.py +++ b/apps/authentication/signals_handlers.py @@ -1,54 +1,15 @@ -from django.http.request import QueryDict -from django.conf import settings from django.dispatch import receiver -from django.contrib.auth.signals import user_logged_out -from django_auth_ldap.backend import populate_user -from users.models import User -from .backends.openid import new_client -from .backends.openid.signals import ( - post_create_or_update_openid_user, post_openid_login_success -) -from .signals import post_auth_success +from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success +from .signals import post_auth_success, post_auth_failed -@receiver(user_logged_out) -def on_user_logged_out(sender, request, user, **kwargs): - if not settings.AUTH_OPENID: - return - if not settings.AUTH_OPENID_SHARE_SESSION: - return - query = QueryDict('', mutable=True) - query.update({ - 'redirect_uri': settings.BASE_SITE_URL - }) - client = new_client() - openid_logout_url = "%s?%s" % ( - client.get_url_end_session_endpoint(), - query.urlencode() - ) - request.COOKIES['next'] = openid_logout_url - - -@receiver(post_create_or_update_openid_user) -def on_post_create_or_update_openid_user(sender, user=None, created=True, **kwargs): - if created and user and user.username != 'admin': - user.source = user.SOURCE_OPENID - user.save() - - -@receiver(post_openid_login_success) -def on_openid_login_success(sender, user=None, request=None, **kwargs): - post_auth_success.send(sender=sender, user=user, request=request) - - -@receiver(populate_user) -def on_ldap_create_user(sender, user, ldap_user, **kwargs): - if user and user.username not in ['admin']: - exists = User.objects.filter(username=user.username).exists() - if not exists: - user.source = user.SOURCE_LDAP - user.save() +@receiver(openid_user_login_success) +def on_oidc_user_login_success(sender, request, user, **kwargs): + post_auth_success.send(sender, user=user, request=request) +@receiver(openid_user_login_failed) +def on_oidc_user_login_failed(sender, username, request, reason, **kwargs): + post_auth_failed.send(sender, username=username, request=request, reason=reason) diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index bff33eb17..9cacdf4ff 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -56,9 +56,9 @@

{% trans "More login options" %}

-
{% endif %} diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index b9f76e731..bee5f8517 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -16,6 +16,6 @@ urlpatterns = [ path('logout/', views.UserLogoutView.as_view(), name='logout'), # openid - path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')), path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), + path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')), ] diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index c51ccbfc6..c67cf2090 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -58,7 +58,7 @@ class UserLoginView(mixins.AuthMixin, FormView): if self.request.GET.get("admin", 0): return None if settings.AUTH_OPENID: - redirect_url = reverse("authentication:openid:openid-login") + redirect_url = reverse(settings.AUTH_OPENID_AUTH_LOGIN_URL_NAME) elif settings.AUTH_CAS: redirect_url = reverse(settings.CAS_LOGIN_URL_NAME) @@ -133,7 +133,7 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView): user = self.check_user_auth_if_need() self.check_user_mfa_if_need(user) self.check_user_login_confirm_if_need(user) - except errors.CredentialError: + except (errors.CredentialError, errors.SessionEmptyError): return self.format_redirect_url(self.login_url) except errors.MFARequiredError: return self.format_redirect_url(self.login_otp_url) @@ -185,18 +185,18 @@ class UserLogoutView(TemplateView): @staticmethod def get_backend_logout_url(): + if settings.AUTH_OPENID: + return settings.AUTH_OPENID_AUTH_LOGOUT_URL_NAME # if settings.AUTH_CAS: # return settings.CAS_LOGOUT_URL_NAME return None def get(self, request, *args, **kwargs): - auth_logout(request) backend_logout_url = self.get_backend_logout_url() if backend_logout_url: return redirect(backend_logout_url) - next_uri = request.COOKIES.get("next") - if next_uri: - return redirect(next_uri) + + auth_logout(request) response = super().get(request, *args, **kwargs) return response diff --git a/apps/authentication/views/mfa.py b/apps/authentication/views/mfa.py index 57d6751da..bedbf9bcf 100644 --- a/apps/authentication/views/mfa.py +++ b/apps/authentication/views/mfa.py @@ -6,6 +6,9 @@ from django.views.generic.edit import FormView from .. import forms, errors, mixins from .utils import redirect_to_guard_view +from common.utils import get_logger + +logger = get_logger(__name__) __all__ = ['UserLoginOtpView'] @@ -22,4 +25,7 @@ class UserLoginOtpView(mixins.AuthMixin, FormView): except errors.MFAFailedError as e: form.add_error('otp_code', e.msg) return super().form_invalid(form) + except Exception as e: + logger.error(e) + return redirect_to_guard_view() diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py new file mode 100644 index 000000000..fd580093e --- /dev/null +++ b/apps/jumpserver/api.py @@ -0,0 +1,295 @@ +from django.core.cache import cache +from django.utils import timezone +from django.utils.timesince import timesince +from django.db.models import Count, Max +from django.http.response import JsonResponse +from rest_framework.views import APIView +from collections import Counter + +from users.models import User +from assets.models import Asset +from terminal.models import Session +from orgs.utils import current_org +from common.permissions import IsOrgAdmin +from common.utils import lazyproperty + +__all__ = ['IndexApi'] + + +class MonthLoginMetricMixin: + + @lazyproperty + def session_month(self): + month_ago = timezone.now() - timezone.timedelta(days=30) + session_month = Session.objects.filter(date_start__gt=month_ago) + return session_month + + @lazyproperty + def session_month_dates(self): + dates = self.session_month.dates('date_start', 'day') + return dates + + def get_month_metrics_date(self): + month_metrics_date = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0'] + return month_metrics_date + + @staticmethod + def get_cache_key(date, tp): + date_str = date.strftime("%Y%m%d") + key = "SESSION_MONTH_{}_{}_{}".format(current_org.id, tp, date_str) + return key + + def __get_data_from_cache(self, date, tp): + if date == timezone.now().date(): + return None + cache_key = self.get_cache_key(date, tp) + count = cache.get(cache_key) + return count + + def __set_data_to_cache(self, date, tp, count): + cache_key = self.get_cache_key(date, tp) + cache.set(cache_key, count, 3600*24*7) + + @staticmethod + def get_date_start_2_end(d): + time_min = timezone.datetime.min.time() + time_max = timezone.datetime.max.time() + tz = timezone.get_current_timezone() + ds = timezone.datetime.combine(d, time_min).replace(tzinfo=tz) + de = timezone.datetime.combine(d, time_max).replace(tzinfo=tz) + return ds, de + + def get_date_login_count(self, date): + tp = "LOGIN" + count = self.__get_data_from_cache(date, tp) + if count is not None: + return count + ds, de = self.get_date_start_2_end(date) + count = Session.objects.filter(date_start__range=(ds, de)).count() + self.__set_data_to_cache(date, tp, count) + return count + + def get_month_metrics_total_count_login(self): + data = [] + for d in self.session_month_dates: + count = self.get_date_login_count(d) + data.append(count) + if len(data) == 0: + data = [0] + return data + + def get_date_user_count(self, date): + tp = "USER" + count = self.__get_data_from_cache(date, tp) + if count is not None: + return count + ds, de = self.get_date_start_2_end(date) + count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('user', flat=True))) + self.__set_data_to_cache(date, tp, count) + return count + + def get_month_metrics_total_count_active_users(self): + data = [] + for d in self.session_month_dates: + count = self.get_date_user_count(d) + data.append(count) + return data + + def get_date_asset_count(self, date): + tp = "ASSET" + count = self.__get_data_from_cache(date, tp) + if count is not None: + return count + ds, de = self.get_date_start_2_end(date) + count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('asset', flat=True))) + self.__set_data_to_cache(date, tp, count) + return count + + def get_month_metrics_total_count_active_assets(self): + data = [] + for d in self.session_month_dates: + count = self.get_date_asset_count(d) + data.append(count) + return data + + @lazyproperty + def month_total_count_active_users(self): + count = len(set(self.session_month.values_list('user', flat=True))) + return count + + @lazyproperty + def month_total_count_inactive_users(self): + total = current_org.get_org_members().count() + active = self.month_total_count_active_users + count = total - active + if count < 0: + count = 0 + return count + + @lazyproperty + def month_total_count_disabled_users(self): + return current_org.get_org_members().filter(is_active=False).count() + + @lazyproperty + def month_total_count_active_assets(self): + return len(set(self.session_month.values_list('asset', flat=True))) + + @lazyproperty + def month_total_count_inactive_assets(self): + total = Asset.objects.all().count() + active = self.month_total_count_active_assets + count = total - active + if count < 0: + count = 0 + return count + + @lazyproperty + def month_total_count_disabled_assets(self): + return Asset.objects.filter(is_active=False).count() + + +class WeekSessionMetricMixin: + session_week = None + + @lazyproperty + def session_week(self): + week_ago = timezone.now() - timezone.timedelta(weeks=1) + session_week = Session.objects.filter(date_start__gt=week_ago) + return session_week + + def get_week_login_times_top5_users(self): + users = self.session_week.values_list('user', flat=True) + users = [ + {'user': user, 'total': total} + for user, total in Counter(users).most_common(5) + ] + return users + + def get_week_total_count_login_users(self): + return len(set(self.session_week.values_list('user', flat=True))) + + def get_week_total_count_login_times(self): + return self.session_week.count() + + def get_week_login_times_top10_assets(self): + assets = self.session_week.values("asset")\ + .annotate(total=Count("asset"))\ + .annotate(last=Max("date_start")).order_by("-total")[:10] + for asset in assets: + asset['last'] = str(asset['last']) + return list(assets) + + def get_week_login_times_top10_users(self): + users = self.session_week.values("user") \ + .annotate(total=Count("user")) \ + .annotate(last=Max("date_start")).order_by("-total")[:10] + for user in users: + user['last'] = str(user['last']) + return list(users) + + def get_week_login_record_top10_sessions(self): + sessions = self.session_week.order_by('-date_start')[:10] + for session in sessions: + session.avatar_url = User.get_avatar_url("") + sessions = [ + { + 'user': session.user, + 'asset': session.asset, + 'is_finished': session.is_finished, + 'date_start': str(session.date_start), + 'timesince': timesince(session.date_start) + } + for session in sessions + ] + return sessions + + +class TotalCountMixin: + @staticmethod + def get_total_count_users(): + return current_org.get_org_members().count() + + @staticmethod + def get_total_count_assets(): + return Asset.objects.all().count() + + @staticmethod + def get_total_count_online_users(): + count = len(set(Session.objects.filter(is_finished=False).values_list('user', flat=True))) + return count + + @staticmethod + def get_total_count_online_sessions(): + return Session.objects.filter(is_finished=False).count() + + +class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, APIView): + permission_classes = (IsOrgAdmin,) + http_method_names = ['get'] + + def get(self, request, *args, **kwargs): + data = {} + + query_params = self.request.query_params + + _all = query_params.get('all') + + if _all or query_params.get('total_count'): + data.update({ + 'total_count_assets': self.get_total_count_assets(), + 'total_count_users': self.get_total_count_users(), + 'total_count_online_users': self.get_total_count_online_users(), + 'total_count_online_sessions': self.get_total_count_online_sessions(), + }) + + if _all or query_params.get('month_metrics'): + data.update({ + 'month_metrics_date': self.get_month_metrics_date(), + 'month_metrics_total_count_login': self.get_month_metrics_total_count_login(), + 'month_metrics_total_count_active_users': self.get_month_metrics_total_count_active_users(), + 'month_metrics_total_count_active_assets': self.get_month_metrics_total_count_active_assets(), + }) + + if _all or query_params.get('month_total_count_users'): + data.update({ + 'month_total_count_active_users': self.month_total_count_active_users, + 'month_total_count_inactive_users': self.month_total_count_inactive_users, + 'month_total_count_disabled_users': self.month_total_count_disabled_users, + }) + + if _all or query_params.get('month_total_count_assets'): + data.update({ + 'month_total_count_active_assets': self.month_total_count_active_assets, + 'month_total_count_inactive_assets': self.month_total_count_inactive_assets, + 'month_total_count_disabled_assets': self.month_total_count_disabled_assets, + }) + + if _all or query_params.get('week_total_count'): + data.update({ + 'week_total_count_login_users': self.get_week_total_count_login_users(), + 'week_total_count_login_times': self.get_week_total_count_login_times(), + }) + + if _all or query_params.get('week_login_times_top5_users'): + data.update({ + 'week_login_times_top5_users': self.get_week_login_times_top5_users(), + }) + + if _all or query_params.get('week_login_times_top10_assets'): + data.update({ + 'week_login_times_top10_assets': self.get_week_login_times_top10_assets(), + }) + + if _all or query_params.get('week_login_times_top10_users'): + data.update({ + 'week_login_times_top10_users': self.get_week_login_times_top10_users(), + }) + + if _all or query_params.get('week_login_record_top10_sessions'): + data.update({ + 'week_login_record_top10_sessions': self.get_week_login_record_top10_sessions() + }) + + return JsonResponse(data, status=200) + + diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 04988d1d9..04002a87f 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -8,6 +8,7 @@ 3. 程序需要, 用户需要更改的写到本config中 """ import os +import re import sys import types import errno @@ -15,9 +16,12 @@ import json import yaml from importlib import import_module from django.urls import reverse_lazy +from urllib.parse import urljoin, urlparse BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) +XPACK_DIR = os.path.join(BASE_DIR, 'xpack') +HAS_XPACK = os.path.isdir(XPACK_DIR) def import_string(dotted_path): @@ -36,6 +40,38 @@ def import_string(dotted_path): ) from err +def is_absolute_uri(uri): + """ 判断一个uri是否是绝对地址 """ + if not isinstance(uri, str): + return False + + result = re.match(r'^http[s]?://.*', uri) + if result is None: + return False + + return True + + +def build_absolute_uri(base, uri): + """ 构建绝对uri地址 """ + if uri is None: + return base + + if isinstance(uri, int): + uri = str(uri) + + if not isinstance(uri, str): + return base + + if is_absolute_uri(uri): + return uri + + parsed_base = urlparse(base) + url = "{}://{}".format(parsed_base.scheme, parsed_base.netloc) + path = '{}/{}/'.format(parsed_base.path.strip('/'), uri.strip('/')) + return urljoin(url, path) + + class DoesNotExist(Exception): pass @@ -134,14 +170,32 @@ class Config(dict): 'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False, 'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1, + # OpenID 配置参数 + # OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8) 'AUTH_OPENID': False, + 'AUTH_OPENID_CLIENT_ID': 'client-id', + 'AUTH_OPENID_CLIENT_SECRET': 'client-secret', + 'AUTH_OPENID_SHARE_SESSION': True, + 'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True, + # OpenID 新配置参数 (version >= 1.5.8) + 'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://op-example.com/', + 'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT': 'https://op-example.com/authorize', + 'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT': 'https://op-example.com/token', + 'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT': 'https://op-example.com/jwks', + 'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT': 'https://op-example.com/userinfo', + 'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT': 'https://op-example.com/logout', + 'AUTH_OPENID_PROVIDER_SIGNATURE_ALG': 'HS256', + 'AUTH_OPENID_PROVIDER_SIGNATURE_KEY': None, + 'AUTH_OPENID_SCOPES': 'openid profile email', + 'AUTH_OPENID_ID_TOKEN_MAX_AGE': 60, + 'AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS': True, + 'AUTH_OPENID_USE_STATE': True, + 'AUTH_OPENID_USE_NONCE': True, + 'AUTH_OPENID_ALWAYS_UPDATE_USER': True, + # OpenID 旧配置参数 (version <= 1.5.8 (discarded)) 'BASE_SITE_URL': 'http://localhost:8080', 'AUTH_OPENID_SERVER_URL': 'http://openid', - 'AUTH_OPENID_REALM_NAME': 'jumpserver', - 'AUTH_OPENID_CLIENT_ID': 'jumpserver', - 'AUTH_OPENID_CLIENT_SECRET': '', - 'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True, - 'AUTH_OPENID_SHARE_SESSION': True, + 'AUTH_OPENID_REALM_NAME': None, 'AUTH_RADIUS': False, 'RADIUS_SERVER': 'localhost', @@ -190,7 +244,7 @@ class Config(dict): 'TASK_LOG_KEEP_DAYS': 10, 'ASSETS_PERM_CACHE_TIME': 3600 * 24, 'SECURITY_MFA_VERIFY_TTL': 3600, - 'ASSETS_PERM_CACHE_ENABLE': False, + 'ASSETS_PERM_CACHE_ENABLE': HAS_XPACK, 'SYSLOG_ADDR': '', # '192.168.0.1:514' 'SYSLOG_FACILITY': 'user', 'SYSLOG_SOCKTYPE': 2, @@ -207,6 +261,88 @@ class Config(dict): 'TIME_ZONE': 'Asia/Shanghai' } + def compatible_auth_openid_of_key(self): + """ + 兼容OpenID旧配置 (即 version <= 1.5.8) + 因为旧配置只支持OpenID协议的Keycloak实现, + 所以只需要根据旧配置和Keycloak的Endpoint说明文档, + 构造出新配置中标准OpenID协议中所需的Endpoint即可 + (Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/) + """ + if not self.AUTH_OPENID: + return + + realm_name = self.AUTH_OPENID_REALM_NAME + if realm_name is None: + return + + compatible_keycloak_config = [ + ( + 'AUTH_OPENID_PROVIDER_ENDPOINT', + self.AUTH_OPENID_SERVER_URL + ), + ( + 'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT', + '/realms/{}/protocol/openid-connect/auth'.format(realm_name) + ), + ( + 'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT', + '/realms/{}/protocol/openid-connect/token'.format(realm_name) + ), + ( + 'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT', + '/realms/{}/protocol/openid-connect/certs'.format(realm_name) + ), + ( + 'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT', + '/realms/{}/protocol/openid-connect/userinfo'.format(realm_name) + ), + ( + 'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT', + '/realms/{}/protocol/openid-connect/logout'.format(realm_name) + ) + ] + for key, value in compatible_keycloak_config: + self[key] = value + + def compatible_auth_openid_of_value(self): + """ + 兼容值的绝对路径、相对路径 + (key 为 AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置) + """ + if not self.AUTH_OPENID: + return + + base = self.AUTH_OPENID_PROVIDER_ENDPOINT + config = list(self.items()) + for key, value in config: + result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key) + if result is None: + continue + if value is None: + # None 在 url 中有特殊含义 (比如对于: end_session_endpoint) + continue + value = build_absolute_uri(base, value) + self[key] = value + + def compatible(self): + """ + 对配置做兼容处理 + 1. 对`key`的兼容 (例如:版本升级) + 2. 对`value`做兼容 (例如:True、true、1 => True) + + 处理顺序要保持先对key做处理, 再对value做处理, + 因为处理value的时候,只根据最新版本支持的key进行 + """ + parts = ['key', 'value'] + targets = ['auth_openid'] + for part in parts: + for target in targets: + method_name = 'compatible_{}_of_{}'.format(target, part) + method = getattr(self, method_name, None) + if method is not None: + method() + def convert_type(self, k, v): default_value = self.defaults.get(k) if default_value is None: @@ -283,9 +419,6 @@ class DynamicConfig: return lambda: self.get(item) def LOGIN_URL(self): - auth_openid = self.get('AUTH_OPENID') - if auth_openid: - return reverse_lazy("authentication:openid:openid-login") return self.get('LOGIN_URL') def AUTHENTICATION_BACKENDS(self): @@ -298,8 +431,8 @@ class DynamicConfig: if self.static_config.get('AUTH_CAS'): backends.insert(0, 'authentication.backends.cas.CASBackend') if self.static_config.get('AUTH_OPENID'): - backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend') - backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend') + backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthPasswordBackend') + backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthCodeBackend') if self.static_config.get('AUTH_RADIUS'): backends.insert(0, 'authentication.backends.radius.RadiusBackend') return backends @@ -480,9 +613,9 @@ class ConfigManager: manager = cls(root_path=root_path) if manager.load_from_object(): - return manager.config + config = manager.config elif manager.load_from_yml(): - return manager.config + config = manager.config else: msg = """ @@ -492,6 +625,10 @@ class ConfigManager: """ raise ImportError(msg) + # 对config进行兼容处理 + config.compatible() + return config + @classmethod def get_dynamic_config(cls, config): return DynamicConfig(config) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index bad698e4c..516671980 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -2,7 +2,6 @@ # import os import ldap -from django.urls import reverse_lazy from ..const import CONFIG, DYNAMIC, PROJECT_DIR @@ -43,19 +42,35 @@ AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS -# openid -# Auth OpenID settings -BASE_SITE_URL = CONFIG.BASE_SITE_URL + +# ============================================================================== +# 认证 OpenID 配置参数 +# 参考: https://django-oidc-rp.readthedocs.io/en/stable/settings.html +# ============================================================================== AUTH_OPENID = CONFIG.AUTH_OPENID -AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL -AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET -AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION -AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION -AUTH_OPENID_LOGIN_URL = reverse_lazy("authentication:openid:openid-login") -AUTH_OPENID_LOGIN_COMPLETE_URL = reverse_lazy("authentication:openid:openid-login-complete") +AUTH_OPENID_PROVIDER_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_ENDPOINT +AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT +AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT +AUTH_OPENID_PROVIDER_JWKS_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_JWKS_ENDPOINT +AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT +AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT +AUTH_OPENID_PROVIDER_SIGNATURE_ALG = CONFIG.AUTH_OPENID_PROVIDER_SIGNATURE_ALG +AUTH_OPENID_PROVIDER_SIGNATURE_KEY = CONFIG.AUTH_OPENID_PROVIDER_SIGNATURE_KEY +AUTH_OPENID_SCOPES = CONFIG.AUTH_OPENID_SCOPES +AUTH_OPENID_ID_TOKEN_MAX_AGE = CONFIG.AUTH_OPENID_ID_TOKEN_MAX_AGE +AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS = CONFIG.AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS +AUTH_OPENID_USE_STATE = CONFIG.AUTH_OPENID_USE_STATE +AUTH_OPENID_USE_NONCE = CONFIG.AUTH_OPENID_USE_NONCE +AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION +AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION +AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER +AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login' +AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback' +AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout' +# ============================================================================== # Radius Auth AUTH_RADIUS = CONFIG.AUTH_RADIUS diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index b4757ff99..0f8cf98a8 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -48,6 +48,7 @@ INSTALLED_APPS = [ 'authentication.apps.AuthenticationConfig', # authentication 'applications.apps.ApplicationsConfig', 'tickets.apps.TicketsConfig', + 'jms_oidc_rp', 'rest_framework', 'rest_framework_swagger', 'drf_yasg', @@ -75,7 +76,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'authentication.backends.openid.middleware.OpenIDAuthenticationMiddleware', + 'jms_oidc_rp.middleware.OIDCRefreshIDTokenMiddleware', 'django_cas_ng.middleware.CASMiddleware', 'jumpserver.middleware.TimezoneMiddleware', 'jumpserver.middleware.DemoMiddleware', @@ -103,6 +104,7 @@ TEMPLATES = [ 'django.template.context_processors.media', 'jumpserver.context_processor.jumpserver_processor', 'orgs.context_processor.org_processor', + 'jms_oidc_rp.context_processors.oidc', ], }, }, diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index c4653969b..ade854397 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -7,9 +7,10 @@ from django.conf.urls.static import static from django.conf.urls.i18n import i18n_patterns from django.views.i18n import JavaScriptCatalog -from . import views +from . import views, api api_v1 = [ + path('index/', api.IndexApi.as_view()), path('users/', include('users.urls.api_urls', namespace='api-users')), path('assets/', include('assets.urls.api_urls', namespace='api-assets')), path('perms/', include('perms.urls.api_urls', namespace='api-perms')), diff --git a/apps/jumpserver/utils.py b/apps/jumpserver/utils.py index aa808d2f2..72a836892 100644 --- a/apps/jumpserver/utils.py +++ b/apps/jumpserver/utils.py @@ -18,4 +18,3 @@ def get_current_request(): current_request = LocalProxy(partial(_find, 'current_request')) - diff --git a/apps/jumpserver/views/index.py b/apps/jumpserver/views/index.py index 1bcb2a57e..ffcb10493 100644 --- a/apps/jumpserver/views/index.py +++ b/apps/jumpserver/views/index.py @@ -1,224 +1,12 @@ -from django.core.cache import cache from django.views.generic import TemplateView -from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.db.models import Count, Max from django.shortcuts import redirect - -from users.models import User -from assets.models import Asset -from terminal.models import Session -from orgs.utils import current_org from common.permissions import PermissionsMixin, IsValidUser -from common.utils import timeit, lazyproperty __all__ = ['IndexView'] -class MonthLoginMetricMixin: - @lazyproperty - def session_month(self): - month_ago = timezone.now() - timezone.timedelta(days=30) - session_month = Session.objects.filter(date_start__gt=month_ago) - return session_month - - @lazyproperty - def session_month_dates(self): - dates = self.session_month.dates('date_start', 'day') - return dates - - def get_month_day_metrics(self): - month_str = [ - d.strftime('%m-%d') for d in self.session_month_dates - ] or ['0'] - return month_str - - @staticmethod - def get_cache_key(date, tp): - date_str = date.strftime("%Y%m%d") - key = "SESSION_MONTH_{}_{}".format(tp, date_str) - return key - - def __get_data_from_cache(self, date, tp): - if date == timezone.now().date(): - return None - cache_key = self.get_cache_key(date, tp) - count = cache.get(cache_key) - return count - - def __set_data_to_cache(self, date, tp, count): - cache_key = self.get_cache_key(date, tp) - cache.set(cache_key, count, 3600*24*7) - - @lazyproperty - def user_disabled_total(self): - return current_org.get_org_members().filter(is_active=False).count() - - @lazyproperty - def asset_disabled_total(self): - return Asset.objects.filter(is_active=False).count() - - @staticmethod - def get_date_start_2_end(d): - time_min = timezone.datetime.min.time() - time_max = timezone.datetime.max.time() - tz = timezone.get_current_timezone() - ds = timezone.datetime.combine(d, time_min).replace(tzinfo=tz) - de = timezone.datetime.combine(d, time_max).replace(tzinfo=tz) - return ds, de - - def get_date_login_count(self, date): - tp = "LOGIN" - count = self.__get_data_from_cache(date, tp) - if count is not None: - return count - ds, de = self.get_date_start_2_end(date) - count = Session.objects.filter(date_start__range=(ds, de)).count() - self.__set_data_to_cache(date, tp, count) - return count - - def get_month_login_metrics(self): - data = [] - for d in self.session_month_dates: - count = self.get_date_login_count(d) - data.append(count) - if len(data) == 0: - data = [0] - return data - - def get_date_user_count(self, date): - tp = "USER" - count = self.__get_data_from_cache(date, tp) - if count is not None: - return count - ds, de = self.get_date_start_2_end(date) - count = Session.objects.filter(date_start__range=(ds, de))\ - .values('user').distinct().count() - self.__set_data_to_cache(date, tp, count) - return count - - def get_month_active_user_metrics(self): - data = [] - for d in self.session_month_dates: - count = self.get_date_user_count(d) - data.append(count) - return data - - def get_date_asset_count(self, date): - tp = "ASSET" - count = self.__get_data_from_cache(date, tp) - if count is not None: - return count - ds, de = self.get_date_start_2_end(date) - count = Session.objects.filter(date_start__range=(ds, de)) \ - .values('asset').distinct().count() - self.__set_data_to_cache(date, tp, count) - return count - - def get_month_active_asset_metrics(self): - data = [] - for d in self.session_month_dates: - count = self.get_date_asset_count(d) - data.append(count) - return data - - @lazyproperty - def month_active_user_total(self): - count = self.session_month.values('user').distinct().count() - return count - - @lazyproperty - def month_inactive_user_total(self): - total = current_org.get_org_members().count() - active = self.month_active_user_total - count = total - active - if count < 0: - count = 0 - return count - - @lazyproperty - def month_active_asset_total(self): - return self.session_month.values('asset').distinct().count() - - @lazyproperty - def month_inactive_asset_total(self): - total = Asset.objects.all().count() - active = self.month_active_asset_total - count = total - active - if count < 0: - count = 0 - return count - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'month_str': self.get_month_day_metrics(), - 'month_total_visit_count': self.get_month_login_metrics(), - 'month_user': self.get_month_active_user_metrics(), - 'mouth_asset': self.get_month_active_asset_metrics(), - 'month_user_active': self.month_active_user_total, - 'month_user_inactive': self.month_inactive_user_total, - 'month_user_disabled': self.user_disabled_total, - 'month_asset_active': self.month_active_asset_total, - 'month_asset_inactive': self.month_inactive_asset_total, - 'month_asset_disabled': self.asset_disabled_total, - }) - return context - - -class WeekSessionMetricMixin: - session_week = None - - @lazyproperty - def session_week(self): - week_ago = timezone.now() - timezone.timedelta(weeks=1) - session_week = Session.objects.filter(date_start__gt=week_ago) - return session_week - - def get_top5_user_a_week(self): - users = self.session_week.values('user') \ - .annotate(total=Count('user')) \ - .order_by('-total')[:5] - return users - - def get_week_login_user_count(self): - return self.session_week.values('user').distinct().count() - - def get_week_login_asset_count(self): - return self.session_week.count() - - def get_week_top10_assets(self): - assets = self.session_week.values("asset")\ - .annotate(total=Count("asset"))\ - .annotate(last=Max("date_start")).order_by("-total")[:10] - return assets - - def get_week_top10_users(self): - users = self.session_week.values("user") \ - .annotate(total=Count("user")) \ - .annotate(last=Max("date_start")).order_by("-total")[:10] - return users - - def get_last10_sessions(self): - sessions = self.session_week.order_by('-date_start')[:10] - for session in sessions: - session.avatar_url = User.get_avatar_url("") - return sessions - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'user_visit_count_weekly': self.get_week_login_user_count(), - 'asset_visit_count_weekly': self.get_week_login_asset_count(), - 'user_visit_count_top_five': self.get_top5_user_a_week(), - 'last_login_ten': self.get_last10_sessions(), - 'week_asset_hot_ten': self.get_week_top10_assets(), - 'week_user_hot_ten': self.get_week_top10_users(), - }) - return context - - -class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin, TemplateView): +class IndexView(PermissionsMixin, TemplateView): template_name = 'index.html' permission_classes = [IsValidUser] @@ -229,31 +17,9 @@ class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin, return redirect('assets:user-asset-list') return super(IndexView, self).dispatch(request, *args, **kwargs) - @staticmethod - def get_user_count(): - return current_org.get_org_members().count() - - @staticmethod - def get_asset_count(): - return Asset.objects.all().count() - - @staticmethod - def get_online_user_count(): - count = Session.objects.filter(is_finished=False)\ - .values_list('user', flat=True).distinct().count() - return count - - @staticmethod - def get_online_session_count(): - return Session.objects.filter(is_finished=False).count() - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update({ - 'assets_count': self.get_asset_count(), - 'users_count': self.get_user_count(), - 'online_user_count': self.get_online_user_count(), - 'online_asset_count': self.get_online_session_count(), 'app': _("Dashboard"), }) return context diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index d505bc23d..34a68aacd 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 1ff08d362..0b752ed50 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-05-09 14:47+0800\n" +"POT-Creation-Date: 2020-05-11 16:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -26,7 +26,7 @@ msgstr "自定义" #: applications/templates/applications/remote_app_list.html:27 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:352 assets/models/authbook.py:27 +#: assets/models/asset.py:353 assets/models/authbook.py:27 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 #: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 #: assets/serializers/system_user.py:44 assets/serializers/system_user.py:176 @@ -42,7 +42,7 @@ msgstr "自定义" #: perms/templates/perms/asset_permission_asset.html:53 #: perms/templates/perms/asset_permission_create_update.html:57 #: perms/templates/perms/asset_permission_list.html:35 -#: perms/templates/perms/asset_permission_list.html:87 +#: perms/templates/perms/asset_permission_list.html:87 templates/index.html:82 #: terminal/backends/command/models.py:19 terminal/models.py:187 #: terminal/templates/terminal/command_list.html:31 #: terminal/templates/terminal/command_list.html:106 @@ -52,6 +52,17 @@ msgstr "自定义" #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_granted_remote_app.html:36 +#: xpack/plugins/change_auth_plan/forms.py:74 +#: xpack/plugins/change_auth_plan/models.py:265 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 +#: xpack/plugins/cloud/models.py:269 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:47 +#: xpack/plugins/orgs/templates/orgs/org_list.html:17 +#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15 msgid "Asset" msgstr "资产" @@ -101,7 +112,7 @@ msgstr "运行参数" #: applications/templates/applications/user_database_app_list.html:16 #: applications/templates/applications/user_remote_app_list.html:16 #: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:74 -#: assets/forms/user.py:96 assets/models/asset.py:145 assets/models/base.py:232 +#: assets/forms/user.py:96 assets/models/asset.py:146 assets/models/base.py:232 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 #: assets/models/domain.py:20 assets/models/group.py:20 #: assets/models/label.py:18 assets/templates/assets/_node_detail_modal.html:27 @@ -150,6 +161,18 @@ msgstr "运行参数" #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:57 #: users/templates/users/user_remote_app_permission.html:36 +#: xpack/plugins/change_auth_plan/forms.py:57 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:59 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 +#: xpack/plugins/cloud/models.py:35 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:47 +#: xpack/plugins/cloud/templates/cloud/account_list.html:12 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 +#: xpack/plugins/orgs/templates/orgs/org_list.html:12 +#: xpack/plugins/orgs/templates/orgs/org_users.html:46 msgid "Name" msgstr "名称" @@ -173,7 +196,7 @@ msgstr "类型" #: applications/templates/applications/database_app_detail.html:56 #: applications/templates/applications/database_app_list.html:25 #: applications/templates/applications/user_database_app_list.html:18 -#: ops/models/adhoc.py:146 templates/index.html:90 +#: ops/models/adhoc.py:146 #: users/templates/users/user_granted_database_app.html:36 msgid "Host" msgstr "主机" @@ -181,7 +204,7 @@ msgstr "主机" #: applications/models/database_app.py:27 #: applications/templates/applications/database_app_detail.html:60 #: applications/templates/applications/database_app_list.html:26 -#: assets/forms/asset.py:25 assets/models/asset.py:191 +#: assets/forms/asset.py:25 assets/models/asset.py:192 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" @@ -204,7 +227,7 @@ msgstr "数据库" #: applications/templates/applications/remote_app_list.html:28 #: applications/templates/applications/user_database_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:19 -#: assets/models/asset.py:150 assets/models/asset.py:226 +#: assets/models/asset.py:151 assets/models/asset.py:227 #: assets/models/base.py:237 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56 #: assets/models/domain.py:21 assets/models/domain.py:53 @@ -236,6 +259,17 @@ msgstr "数据库" #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:16 #: users/templates/users/user_profile.html:138 +#: xpack/plugins/change_auth_plan/models.py:75 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 +#: xpack/plugins/cloud/models.py:53 xpack/plugins/cloud/models.py:139 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:67 +#: xpack/plugins/cloud/templates/cloud/account_list.html:15 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:128 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 +#: xpack/plugins/gathered_user/models.py:26 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:63 +#: xpack/plugins/orgs/templates/orgs/org_list.html:23 msgid "Comment" msgstr "备注" @@ -275,7 +309,7 @@ msgstr "参数" #: applications/models/remote_app.py:39 #: applications/templates/applications/database_app_detail.html:72 #: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:224 assets/models/base.py:240 +#: assets/models/asset.py:225 assets/models/base.py:240 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:59 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:63 @@ -289,6 +323,10 @@ msgstr "参数" #: perms/templates/perms/remote_app_permission_detail.html:85 #: users/models/user.py:481 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 +#: xpack/plugins/change_auth_plan/models.py:79 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 +#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:145 +#: xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -297,7 +335,7 @@ msgstr "创建者" #: applications/models/remote_app.py:42 #: applications/templates/applications/database_app_detail.html:68 #: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:225 assets/models/base.py:238 +#: assets/models/asset.py:226 assets/models/base.py:238 #: assets/models/cluster.py:26 assets/models/domain.py:23 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59 @@ -313,6 +351,11 @@ msgstr "创建者" #: terminal/templates/terminal/terminal_detail.html:59 #: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:18 #: users/templates/users/user_group_detail.html:58 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103 +#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:108 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 msgid "Date created" msgstr "创建日期" @@ -362,6 +405,13 @@ msgstr "远程应用" #: users/templates/users/user_profile_update.html:67 #: users/templates/users/user_pubkey_update.html:74 #: users/templates/users/user_pubkey_update.html:80 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:65 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:52 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:40 +#: xpack/plugins/interface/templates/interface/interface.html:72 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29 +#: xpack/plugins/vault/templates/vault/vault_create.html:41 msgid "Reset" msgstr "重置" @@ -399,6 +449,10 @@ msgstr "重置" #: users/templates/users/user_password_update.html:76 #: users/templates/users/user_profile_update.html:68 #: users/templates/users/user_pubkey_update.html:81 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 +#: xpack/plugins/interface/templates/interface/interface.html:74 +#: xpack/plugins/vault/templates/vault/vault_create.html:42 msgid "Submit" msgstr "提交" @@ -426,6 +480,11 @@ msgstr "提交" #: perms/templates/perms/remote_app_permission_detail.html:13 #: perms/templates/perms/remote_app_permission_remote_app.html:13 #: perms/templates/perms/remote_app_permission_user.html:13 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:13 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:18 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106 +#: xpack/plugins/change_auth_plan/views.py:91 msgid "Detail" msgstr "详情" @@ -472,6 +531,15 @@ msgstr "详情" #: users/templates/users/user_profile.html:191 #: users/templates/users/user_profile.html:201 #: users/templates/users/user_remote_app_permission.html:110 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:27 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/account_list.html:40 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 +#: xpack/plugins/orgs/templates/orgs/org_list.html:93 msgid "Update" msgstr "更新" @@ -515,6 +583,15 @@ msgstr "更新" #: users/templates/users/user_list.html:94 #: users/templates/users/user_list.html:98 #: users/templates/users/user_remote_app_permission.html:111 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:31 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:58 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:24 +#: xpack/plugins/cloud/templates/cloud/account_list.html:42 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:28 +#: xpack/plugins/orgs/templates/orgs/org_list.html:95 msgid "Delete" msgstr "删除" @@ -565,6 +642,15 @@ msgstr "创建数据库应用" #: users/templates/users/user_group_list.html:17 #: users/templates/users/user_list.html:20 #: users/templates/users/user_remote_app_permission.html:42 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 +#: xpack/plugins/cloud/templates/cloud/account_list.html:16 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 +#: xpack/plugins/orgs/templates/orgs/org_list.html:24 +#: xpack/plugins/orgs/templates/orgs/org_users.html:47 msgid "Action" msgstr "动作" @@ -651,18 +737,24 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:195 +#: assets/forms/asset.py:83 assets/models/asset.py:196 #: assets/models/user.py:109 assets/templates/assets/asset_detail.html:186 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/system_user_assets.html:118 #: perms/models/asset_permission.py:81 +#: xpack/plugins/change_auth_plan/models.py:54 +#: xpack/plugins/gathered_user/models.py:24 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:86 assets/models/asset.py:199 +#: assets/forms/asset.py:86 assets/models/asset.py:200 #: assets/models/cluster.py:19 assets/models/user.py:65 #: assets/templates/assets/admin_user_list.html:62 #: assets/templates/assets/asset_detail.html:72 templates/_nav.html:44 +#: xpack/plugins/cloud/models.py:133 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 +#: xpack/plugins/orgs/templates/orgs/org_list.html:19 msgid "Admin user" msgstr "管理用户" @@ -670,18 +762,20 @@ msgstr "管理用户" #: assets/templates/assets/asset_create.html:48 #: assets/templates/assets/asset_create.html:50 #: assets/templates/assets/asset_list.html:13 +#: xpack/plugins/orgs/templates/orgs/org_list.html:21 msgid "Label" msgstr "标签" -#: assets/forms/asset.py:92 assets/models/asset.py:194 +#: assets/forms/asset.py:92 assets/models/asset.py:195 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/user_asset_list.html:80 +#: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:95 assets/models/asset.py:169 -#: assets/models/asset.py:193 assets/serializers/asset.py:67 +#: assets/forms/asset.py:95 assets/models/asset.py:170 +#: assets/models/asset.py:194 assets/serializers/asset.py:67 #: assets/templates/assets/asset_detail.html:100 #: assets/templates/assets/user_asset_list.html:78 msgid "Platform" @@ -697,6 +791,11 @@ msgstr "系统平台" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 +#: xpack/plugins/change_auth_plan/forms.py:75 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 +#: xpack/plugins/cloud/models.py:129 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 msgid "Node" msgstr "节点" @@ -721,6 +820,8 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, #: assets/forms/domain.py:17 assets/forms/label.py:15 #: assets/templates/assets/system_user_assets.html:102 #: perms/templates/perms/asset_permission_asset.html:74 +#: xpack/plugins/change_auth_plan/forms.py:65 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70 msgid "Select assets" msgstr "选择资产" @@ -758,16 +859,30 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 #: users/templates/users/user_profile.html:47 +#: xpack/plugins/change_auth_plan/forms.py:59 +#: xpack/plugins/change_auth_plan/models.py:45 +#: xpack/plugins/change_auth_plan/models.py:261 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:25 msgid "Username" msgstr "用户名" #: assets/forms/platform.py:20 ops/templates/ops/task_detail.html:85 #: ops/templates/ops/task_detail.html:95 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:82 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:82 msgid "Yes" msgstr "是" #: assets/forms/platform.py:21 ops/templates/ops/task_detail.html:87 #: ops/templates/ops/task_detail.html:97 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:74 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:84 msgid "No" msgstr "否" @@ -804,6 +919,9 @@ msgstr "密码或密钥密码" #: users/templates/users/user_profile_update.html:41 #: users/templates/users/user_pubkey_update.html:41 #: users/templates/users/user_update.html:20 +#: xpack/plugins/change_auth_plan/models.py:66 +#: xpack/plugins/change_auth_plan/models.py:181 +#: xpack/plugins/change_auth_plan/models.py:268 msgid "Password" msgstr "密码" @@ -862,24 +980,24 @@ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" msgid "Username is dynamic, When connect asset, using current user's username" msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" -#: assets/models/asset.py:146 +#: assets/models/asset.py:147 xpack/plugins/cloud/providers/base.py:16 msgid "Base" msgstr "基础" -#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:56 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 +#: assets/models/asset.py:149 assets/templates/assets/platform_detail.html:60 #: tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" -#: assets/models/asset.py:149 +#: assets/models/asset.py:150 msgid "Internal" msgstr "内部的" -#: assets/models/asset.py:186 assets/models/domain.py:49 +#: assets/models/asset.py:187 assets/models/domain.py:49 #: assets/serializers/asset_user.py:46 #: assets/templates/assets/_asset_list_modal.html:47 #: assets/templates/assets/_asset_user_list.html:20 @@ -892,10 +1010,12 @@ msgstr "内部的" #: settings/forms/terminal.py:16 settings/serializers/settings.py:52 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:24 msgid "IP" msgstr "IP" -#: assets/models/asset.py:187 assets/serializers/asset_user.py:45 +#: assets/models/asset.py:188 assets/serializers/asset_user.py:45 #: assets/serializers/gathered_user.py:20 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 @@ -908,10 +1028,12 @@ msgstr "IP" #: settings/forms/terminal.py:15 settings/serializers/settings.py:51 #: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:49 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:23 msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:190 assets/models/domain.py:51 +#: assets/models/asset.py:191 assets/models/domain.py:51 #: assets/models/user.py:114 assets/templates/assets/asset_detail.html:68 #: assets/templates/assets/domain_gateway_list.html:65 #: assets/templates/assets/system_user_detail.html:78 @@ -923,84 +1045,84 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:192 assets/serializers/asset.py:69 +#: assets/models/asset.py:193 assets/serializers/asset.py:69 #: assets/templates/assets/asset_create.html:24 #: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:60 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:196 assets/models/cmd_filter.py:22 +#: assets/models/asset.py:197 assets/models/cmd_filter.py:22 #: assets/models/domain.py:54 assets/models/label.py:22 #: assets/templates/assets/asset_detail.html:108 authentication/models.py:45 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:64 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:116 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:80 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:84 msgid "Model" msgstr "型号" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:209 assets/templates/assets/asset_detail.html:112 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:210 +#: assets/models/asset.py:211 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:211 +#: assets/models/asset.py:212 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:212 +#: assets/models/asset.py:213 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:213 +#: assets/models/asset.py:214 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:92 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:215 +#: assets/models/asset.py:216 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:216 +#: assets/models/asset.py:217 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:219 assets/templates/assets/asset_detail.html:104 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:219 +#: assets/models/asset.py:220 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:220 +#: assets/models/asset.py:221 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:221 +#: assets/models/asset.py:222 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:224 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1025,17 +1147,23 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:235 +#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:70 +#: xpack/plugins/change_auth_plan/models.py:188 +#: xpack/plugins/change_auth_plan/models.py:275 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models/base.py:236 +#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:73 +#: xpack/plugins/change_auth_plan/models.py:184 +#: xpack/plugins/change_auth_plan/models.py:271 msgid "SSH public key" msgstr "ssh公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 #: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:51 #: ops/models/adhoc.py:39 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:29 msgid "Date updated" msgstr "更新日期" @@ -1133,6 +1261,7 @@ msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" #: assets/models/cmd_filter.py:54 #: assets/templates/assets/cmd_filter_rule_list.html:54 +#: xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" @@ -1152,14 +1281,17 @@ msgid "Gateway" msgstr "网关" #: assets/models/gathered_user.py:16 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:28 msgid "Present" msgstr "存在" #: assets/models/gathered_user.py:17 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:26 msgid "Date last login" msgstr "最后登录日期" #: assets/models/gathered_user.py:18 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:27 msgid "IP last login" msgstr "最后登录IP" @@ -1193,7 +1325,7 @@ msgstr "默认资产组" #: perms/templates/perms/database_app_permission_list.html:15 #: perms/templates/perms/remote_app_permission_create_update.html:41 #: perms/templates/perms/remote_app_permission_list.html:15 -#: templates/index.html:86 terminal/backends/command/models.py:18 +#: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/models.py:185 terminal/templates/terminal/command_list.html:30 #: terminal/templates/terminal/command_list.html:105 #: terminal/templates/terminal/session_detail.html:48 @@ -1212,7 +1344,8 @@ msgstr "默认资产组" #: users/templates/users/user_group_list.html:15 #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 -#: users/views/profile/base.py:46 +#: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 +#: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" @@ -1273,7 +1406,7 @@ msgstr "手动登录" #: assets/views/platform.py:58 assets/views/platform.py:74 #: assets/views/system_user.py:30 assets/views/system_user.py:47 #: assets/views/system_user.py:64 assets/views/system_user.py:80 -#: templates/_nav.html:39 +#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:50 msgid "Assets" msgstr "资产管理" @@ -1347,6 +1480,7 @@ msgstr "SFTP根路径" #: users/templates/users/user_database_app_permission.html:67 #: users/templates/users/user_remote_app_permission.html:40 #: users/templates/users/user_remote_app_permission.html:67 +#: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1396,6 +1530,8 @@ msgstr "连接" #: ops/templates/ops/adhoc_history_detail.html:47 #: ops/templates/ops/task_detail.html:54 #: terminal/templates/terminal/session_list.html:24 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:45 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:44 msgid "ID" msgstr "ID" @@ -1588,7 +1724,7 @@ msgstr "启用多因子认证" #: assets/templates/assets/system_user_assets.html:26 #: assets/templates/assets/system_user_detail.html:18 #: assets/templates/assets/system_user_users.html:25 assets/views/asset.py:38 -#: templates/_nav.html:42 +#: templates/_nav.html:42 xpack/plugins/change_auth_plan/views.py:118 msgid "Asset list" msgstr "资产列表" @@ -1598,6 +1734,7 @@ msgstr "资产列表" #: ops/templates/ops/command_execution_create.html:112 #: settings/templates/settings/_ldap_list_users_modal.html:41 #: users/templates/users/_granted_assets.html:7 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:65 msgid "Loading" msgstr "加载中" @@ -1607,6 +1744,7 @@ msgstr "更新资产用户认证信息" #: assets/templates/assets/_asset_user_auth_update_modal.html:23 #: settings/templates/settings/_ldap_test_user_login_modal.html:18 +#: xpack/plugins/change_auth_plan/forms.py:61 msgid "Please input password" msgstr "请输入密码" @@ -1614,6 +1752,7 @@ msgstr "请输入密码" #: assets/templates/assets/asset_detail.html:300 #: users/templates/users/user_detail.html:356 #: users/templates/users/user_detail.html:383 +#: xpack/plugins/interface/views.py:35 msgid "Update successfully!" msgstr "更新成功" @@ -1730,6 +1869,9 @@ msgstr "重命名成功" #: perms/templates/perms/asset_permission_create_update.html:48 #: perms/templates/perms/database_app_permission_create_update.html:37 #: perms/templates/perms/remote_app_permission_create_update.html:37 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:37 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:23 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:23 msgid "Basic" msgstr "基本" @@ -1751,6 +1893,9 @@ msgstr "自动生成密钥" #: perms/templates/perms/database_app_permission_create_update.html:51 #: perms/templates/perms/remote_app_permission_create_update.html:51 #: terminal/templates/terminal/terminal_update.html:38 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:47 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:35 msgid "Other" msgstr "其它" @@ -1767,6 +1912,7 @@ msgstr "资产列表" #: assets/templates/assets/admin_user_assets.html:24 #: perms/templates/perms/asset_permission_asset.html:31 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27 msgid "Asset list of " msgstr "资产列表" @@ -1792,6 +1938,9 @@ msgstr "替换资产的管理员" #: assets/templates/assets/admin_user_detail.html:86 #: assets/templates/assets/system_user_assets.html:126 #: perms/templates/perms/asset_permission_asset.html:99 +#: xpack/plugins/change_auth_plan/forms.py:69 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95 +#: xpack/plugins/gathered_user/forms.py:33 msgid "Select nodes" msgstr "选择节点" @@ -1813,6 +1962,11 @@ msgstr "选择节点" #: users/templates/users/user_group_create_update.html:28 #: users/templates/users/user_list.html:184 #: users/templates/users/user_password_verify.html:20 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:41 +#: xpack/plugins/interface/templates/interface/interface.html:103 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30 msgid "Confirm" msgstr "确认" @@ -1848,6 +2002,9 @@ msgstr "资产用户" #: terminal/templates/terminal/session_detail.html:87 #: users/templates/users/user_detail.html:126 #: users/templates/users/user_profile.html:150 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:126 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:139 +#: xpack/plugins/license/templates/license/license_detail.html:80 msgid "Quick modify" msgstr "快速修改" @@ -1970,6 +2127,7 @@ msgstr "显示所有子节点资产" #: users/templates/users/user_detail.html:437 #: users/templates/users/user_detail.html:505 #: users/templates/users/user_list.html:178 +#: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" @@ -1982,6 +2140,7 @@ msgstr "删除选择资产" #: users/templates/users/user_detail.html:441 #: users/templates/users/user_detail.html:509 #: users/templates/users/user_list.html:182 +#: xpack/plugins/interface/templates/interface/interface.html:101 msgid "Cancel" msgstr "取消" @@ -2182,6 +2341,7 @@ msgstr "创建系统用户" #: assets/templates/assets/system_user_users.html:84 users/forms/group.py:19 #: users/forms/user.py:143 users/forms/user.py:148 +#: xpack/plugins/orgs/forms.py:17 msgid "Select users" msgstr "选择用户" @@ -2340,6 +2500,7 @@ msgstr "文件名" #: ops/templates/ops/command_execution_list.html:71 #: ops/templates/ops/task_list.html:14 #: users/templates/users/user_detail.html:487 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14 msgid "Success" msgstr "成功" @@ -2353,11 +2514,17 @@ msgstr "成功" #: perms/templates/perms/remote_app_permission_detail.html:73 #: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 #: terminal/templates/terminal/session_list.html:32 +#: xpack/plugins/change_auth_plan/models.py:167 +#: xpack/plugins/change_auth_plan/models.py:290 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 +#: xpack/plugins/gathered_user/models.py:76 msgid "Date start" msgstr "开始日期" #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 +#: xpack/plugins/vault/templates/vault/vault.html:7 msgid "Create" msgstr "创建" @@ -2387,7 +2554,7 @@ msgstr "启用" msgid "-" msgstr "" -#: audits/models.py:78 +#: audits/models.py:78 xpack/plugins/cloud/models.py:204 msgid "Failed" msgstr "失败" @@ -2418,6 +2585,9 @@ msgid "MFA" msgstr "多因子认证" #: audits/models.py:87 audits/templates/audits/login_log_list.html:63 +#: xpack/plugins/change_auth_plan/models.py:286 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 +#: xpack/plugins/cloud/models.py:217 msgid "Reason" msgstr "原因" @@ -2425,6 +2595,9 @@ msgstr "原因" #: tickets/templates/tickets/ticket_detail.html:34 #: tickets/templates/tickets/ticket_list.html:36 #: tickets/templates/tickets/ticket_list.html:104 +#: xpack/plugins/cloud/models.py:214 xpack/plugins/cloud/models.py:272 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:50 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Status" msgstr "状态" @@ -2433,6 +2606,8 @@ msgid "Date login" msgstr "登录日期" #: audits/serializers.py:65 ops/models/command.py:24 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56 +#: xpack/plugins/cloud/models.py:212 msgid "Result" msgstr "结果" @@ -2460,6 +2635,7 @@ msgstr "运行用户" #: perms/templates/perms/asset_permission_user.html:74 #: perms/templates/perms/database_app_permission_user.html:74 #: perms/templates/perms/remote_app_permission_user.html:83 +#: xpack/plugins/orgs/templates/orgs/org_users.html:67 msgid "Select user" msgstr "选择用户" @@ -2621,7 +2797,7 @@ msgid "" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" #: authentication/errors.py:48 users/views/profile/otp.py:63 -#: users/views/profile/otp.py:102 +#: users/views/profile/otp.py:100 users/views/profile/otp.py:116 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -2684,10 +2860,10 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:360 users/templates/users/user_disable_mfa.html:32 -#: users/templates/users/user_profile.html:94 +#: users/models/user.py:360 users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 +#: users/templates/users/user_verify_mfa.html:32 msgid "Disable" msgstr "禁用" @@ -2746,10 +2922,10 @@ msgstr "请打开 Google Authenticator,输入6位动态码" #: authentication/templates/authentication/login_otp.html:26 #: users/templates/users/first_login.html:100 -#: users/templates/users/user_disable_mfa.html:26 #: users/templates/users/user_otp_check_password.html:15 #: users/templates/users/user_otp_enable_bind.html:24 #: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_verify_mfa.html:26 msgid "Next" msgstr "下一步" @@ -2864,7 +3040,7 @@ msgstr "字段必须唯一" msgid "

Flow service unavailable, check it

" msgstr "" -#: jumpserver/views/index.py:257 templates/_nav.html:7 +#: jumpserver/views/index.py:23 templates/_nav.html:7 msgid "Dashboard" msgstr "仪表盘" @@ -2900,14 +3076,25 @@ msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:98 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:111 ops/mixin.py:150 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:90 msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:108 ops/mixin.py:147 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:40 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28 msgid "Periodic perform" msgstr "定时执行" @@ -2965,6 +3152,8 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:59 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Create by" msgstr "创建者" @@ -2986,6 +3175,11 @@ msgstr "完成时间" #: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 +#: xpack/plugins/change_auth_plan/models.py:170 +#: xpack/plugins/change_auth_plan/models.py:293 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 +#: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" @@ -3005,11 +3199,11 @@ msgstr "结果" msgid "Adhoc result summary" msgstr "汇总" -#: ops/models/adhoc.py:282 +#: ops/models/adhoc.py:282 xpack/plugins/change_auth_plan/utils.py:137 msgid "{} Start task: {}" msgstr "{} 任务开始: {}" -#: ops/models/adhoc.py:291 +#: ops/models/adhoc.py:291 xpack/plugins/change_auth_plan/utils.py:149 msgid "{} Task finish" msgstr "{} 任务结束" @@ -3048,6 +3242,8 @@ msgid "Version run execution" msgstr "执行历史" #: ops/templates/ops/adhoc_detail.html:92 ops/templates/ops/task_list.html:12 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19 msgid "Run times" msgstr "执行次数" @@ -3175,6 +3371,7 @@ msgid "Pending" msgstr "等待" #: ops/templates/ops/command_execution_list.html:70 +#: xpack/plugins/change_auth_plan/models.py:257 msgid "Finished" msgstr "结束" @@ -3212,6 +3409,11 @@ msgid "Contents" msgstr "内容" #: ops/templates/ops/task_list.html:73 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:135 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:148 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44 msgid "Run" msgstr "执行" @@ -3234,6 +3436,7 @@ msgid "Ops" msgstr "作业中心" #: ops/views/adhoc.py:32 templates/_nav.html:124 +#: xpack/plugins/gathered_user/views.py:35 msgid "Task list" msgstr "任务列表" @@ -3284,6 +3487,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: users/templates/users/user_list.html:17 #: users/templates/users/user_remote_app_permission.html:38 #: users/templates/users/user_remote_app_permission.html:61 +#: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "User group" msgstr "用户组" @@ -3359,6 +3563,9 @@ msgstr "用户或用户组" #: perms/templates/perms/asset_permission_asset.html:23 #: perms/templates/perms/asset_permission_detail.html:22 #: perms/templates/perms/asset_permission_user.html:23 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:16 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:21 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:20 msgid "Assets and node" msgstr "资产或节点" @@ -3379,6 +3586,10 @@ msgstr "添加资产" #: perms/templates/perms/remote_app_permission_user.html:92 #: perms/templates/perms/remote_app_permission_user.html:120 #: users/templates/users/user_group_detail.html:87 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:89 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:123 +#: xpack/plugins/orgs/templates/orgs/org_users.html:73 msgid "Add" msgstr "添加" @@ -3388,6 +3599,7 @@ msgstr "添加节点" #: perms/templates/perms/asset_permission_asset.html:105 #: users/templates/users/user_detail.html:226 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101 msgid "Join" msgstr "加入" @@ -3416,6 +3628,7 @@ msgid "User group count" msgstr "用户组数量" #: perms/templates/perms/asset_permission_detail.html:69 +#: xpack/plugins/license/templates/license/license_detail.html:63 msgid "Asset count" msgstr "资产数量" @@ -3447,6 +3660,9 @@ msgstr "刷新授权缓存" #: users/templates/users/user_database_app_permission.html:41 #: users/templates/users/user_list.html:19 #: users/templates/users/user_remote_app_permission.html:41 +#: xpack/plugins/cloud/models.py:50 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:55 +#: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3472,6 +3688,7 @@ msgstr "刷新成功" #: perms/templates/perms/asset_permission_user.html:31 #: perms/templates/perms/database_app_permission_user.html:31 #: perms/templates/perms/remote_app_permission_user.html:30 +#: xpack/plugins/orgs/templates/orgs/org_users.html:24 msgid "User list of " msgstr "用户列表" @@ -3552,6 +3769,7 @@ msgstr "添加用户组" #: perms/views/remote_app_permission.py:84 #: perms/views/remote_app_permission.py:116 #: perms/views/remote_app_permission.py:149 templates/_nav.html:75 +#: xpack/plugins/orgs/templates/orgs/org_list.html:22 msgid "Perms" msgstr "权限管理" @@ -3975,6 +4193,7 @@ msgstr "当前无勾选用户,请勾选你想要导入的用户" #: settings/templates/settings/_ldap_list_users_modal.html:172 #: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 +#: xpack/plugins/license/templates/license/license_detail.html:88 msgid "Import" msgstr "导入" @@ -4153,6 +4372,7 @@ msgid "Update setting successfully" msgstr "更新设置成功" #: templates/_base_only_msg_content.html:28 +#: xpack/plugins/interface/models.py:36 msgid "Welcome to the JumpServer open source fortress" msgstr "欢迎使用JumpServer开源堡垒机" @@ -4386,7 +4606,7 @@ msgstr "工单管理" msgid "XPack" msgstr "" -#: templates/_nav.html:171 +#: templates/_nav.html:171 xpack/plugins/cloud/views.py:28 msgid "Account list" msgstr "账户列表" @@ -4420,8 +4640,8 @@ msgid "Total users" msgstr "用户总数" #: templates/index.html:23 -msgid "Total hosts" -msgstr "主机总数" +msgid "Total assets" +msgstr "资产总数" #: templates/index.html:36 msgid "Online users" @@ -4443,120 +4663,124 @@ msgstr " 位用户登录 " msgid " times asset." msgstr " 次资产." -#: templates/index.html:66 -msgid " times/week" -msgstr " 次/周" - -#: templates/index.html:77 +#: templates/index.html:69 msgid "Active user asset ratio" msgstr "活跃用户资产占比" -#: templates/index.html:80 +#: templates/index.html:72 msgid "" "The following graphs describe the percentage of active users per month and " "assets per user host per month, respectively." msgstr "以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比" -#: templates/index.html:105 templates/index.html:120 +#: templates/index.html:97 templates/index.html:112 msgid "Top 10 assets in a week" msgstr "一周Top10资产" -#: templates/index.html:121 +#: templates/index.html:113 msgid "Login frequency and last login record." msgstr "登录次数及最近一次登录记录." -#: templates/index.html:132 templates/index.html:218 -msgid " times" -msgstr " 次" - -#: templates/index.html:135 templates/index.html:221 -msgid "The time last logged in" -msgstr "最近一次登录日期" - -#: templates/index.html:136 templates/index.html:222 -msgid "At" -msgstr "于" - -#: templates/index.html:142 templates/index.html:180 templates/index.html:228 -msgid "(No)" -msgstr "(暂无)" - -#: templates/index.html:150 +#: templates/index.html:122 msgid "Last 10 login" msgstr "最近十次登录" -#: templates/index.html:156 +#: templates/index.html:128 msgid "Login record" msgstr "登录记录" -#: templates/index.html:157 +#: templates/index.html:129 msgid "Last 10 login records." msgstr "最近十次登录记录." -#: templates/index.html:170 templates/index.html:172 -msgid "Before" -msgstr "前" - -#: templates/index.html:174 -msgid "Login in " -msgstr "登录了" - -#: templates/index.html:191 templates/index.html:206 +#: templates/index.html:143 templates/index.html:158 msgid "Top 10 users in a week" msgstr "一周Top10用户" -#: templates/index.html:207 +#: templates/index.html:159 msgid "User login frequency and last login record." msgstr "用户登录次数及最近一次登录记录" -#: templates/index.html:264 +#: templates/index.html:184 msgid "Monthly data overview" msgstr "月数据总览" -#: templates/index.html:265 +#: templates/index.html:185 msgid "History summary in one month" msgstr "一个月内历史汇总" -#: templates/index.html:273 templates/index.html:297 +#: templates/index.html:193 templates/index.html:217 msgid "Login count" msgstr "登录次数" -#: templates/index.html:273 templates/index.html:304 +#: templates/index.html:193 templates/index.html:224 msgid "Active users" msgstr "活跃用户" -#: templates/index.html:273 templates/index.html:311 +#: templates/index.html:193 templates/index.html:231 msgid "Active assets" msgstr "活跃资产" -#: templates/index.html:338 templates/index.html:388 +#: templates/index.html:262 templates/index.html:313 msgid "Monthly active users" msgstr "月活跃用户" -#: templates/index.html:338 templates/index.html:389 +#: templates/index.html:262 templates/index.html:314 msgid "Disable user" msgstr "禁用用户" -#: templates/index.html:338 templates/index.html:390 +#: templates/index.html:262 templates/index.html:315 msgid "Month not logged in user" msgstr "月未登录用户" -#: templates/index.html:364 templates/index.html:440 +#: templates/index.html:288 templates/index.html:368 msgid "Access to the source" msgstr "访问来源" -#: templates/index.html:414 templates/index.html:464 -msgid "Month is logged into the host" -msgstr "月被登录主机" +#: templates/index.html:342 +msgid "Month is logged into the asset" +msgstr "月被登录资产" -#: templates/index.html:414 templates/index.html:465 +#: templates/index.html:342 templates/index.html:393 msgid "Disable host" msgstr "禁用主机" -#: templates/index.html:414 templates/index.html:466 +#: templates/index.html:342 templates/index.html:394 msgid "Month not logged on host" msgstr "月未登录主机" +#: templates/index.html:392 +msgid "Month is logged into the host" +msgstr "月被登录主机" + +#: templates/index.html:466 +msgid " times/week" +msgstr " 次/周" + +#: templates/index.html:491 templates/index.html:527 +msgid " times" +msgstr " 次" + +#: templates/index.html:494 templates/index.html:530 +msgid "The time last logged in" +msgstr "最近一次登录日期" + +#: templates/index.html:495 templates/index.html:531 +msgid "At" +msgstr "于" + +#: templates/index.html:510 templates/index.html:545 templates/index.html:580 +msgid "(No)" +msgstr "(暂无)" + +#: templates/index.html:561 +msgid "Before" +msgstr "前" + +#: templates/index.html:562 +msgid "Login in " +msgstr "登录了" + #: templates/rest_framework/base.html:128 msgid "Filters" msgstr "过滤" @@ -4676,7 +4900,10 @@ msgid "" " " msgstr "" -#: terminal/forms/storage.py:136 +#: terminal/forms/storage.py:136 xpack/plugins/cloud/models.py:266 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:112 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:46 msgid "Region" msgstr "地域" @@ -5167,7 +5394,7 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:154 +#: users/serializers/user.py:163 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -5199,15 +5426,20 @@ msgstr "添加到用户组" msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 users/serializers/user.py:26 +#: users/forms/user.py:124 users/serializers/user.py:27 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 users/serializers/user.py:27 +#: users/forms/user.py:125 users/serializers/user.py:28 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 users/serializers/user.py:34 +#: users/forms/user.py:132 users/serializers/user.py:35 +#: xpack/plugins/change_auth_plan/models.py:59 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:16 msgid "Password strategy" msgstr "密码策略" @@ -5215,7 +5447,9 @@ msgstr "密码策略" msgid "Administrator" msgstr "管理员" -#: users/models/user.py:145 +#: users/models/user.py:145 xpack/plugins/orgs/forms.py:29 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:109 +#: xpack/plugins/orgs/templates/orgs/org_list.html:14 msgid "Auditor" msgstr "审计员" @@ -5255,39 +5489,39 @@ msgstr "Administrator是初始的超级管理员" msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" -#: users/serializers/user.py:62 +#: users/serializers/user.py:66 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:63 +#: users/serializers/user.py:67 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:64 +#: users/serializers/user.py:68 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:65 +#: users/serializers/user.py:69 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:69 +#: users/serializers/user.py:73 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:70 +#: users/serializers/user.py:74 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:71 +#: users/serializers/user.py:75 msgid "Role name" msgstr "角色名" -#: users/serializers/user.py:90 +#: users/serializers/user.py:94 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:102 +#: users/serializers/user.py:106 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5301,6 +5535,9 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:55 +#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:57 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" msgstr "账户" @@ -5539,26 +5776,6 @@ msgstr "解除用户登录限制后,此用户即可正常登录" msgid "Reset user MFA success" msgstr "重置用户多因子认证成功" -#: users/templates/users/user_disable_mfa.html:6 -#: users/templates/users/user_otp_check_password.html:6 -msgid "Authenticate" -msgstr "验证身份" - -#: users/templates/users/user_disable_mfa.html:11 -msgid "" -"The account protection has been opened, please complete the following " -"operations according to the prompts" -msgstr "账号保护已开启,请根据提示完成以下操作" - -#: users/templates/users/user_disable_mfa.html:13 -msgid "Open Authenticator and enter the 6-bit dynamic code" -msgstr "请打开 验证器,输入6位动态码" - -#: users/templates/users/user_disable_mfa.html:23 -#: users/templates/users/user_otp_enable_bind.html:22 -msgid "Six figures" -msgstr "6位数字" - #: users/templates/users/user_group_detail.html:17 #: users/templates/users/user_group_granted_asset.html:18 #: users/views/group.py:83 @@ -5617,6 +5834,11 @@ msgstr "用户已失效" msgid "User is inactive" msgstr "用户已禁用" +#: users/templates/users/user_otp_check_password.html:6 +#: users/templates/users/user_verify_mfa.html:6 +msgid "Authenticate" +msgstr "验证身份" + #: users/templates/users/user_otp_enable_bind.html:6 msgid "Bind one-time password authenticator" msgstr "绑定一次性密码验证器" @@ -5627,6 +5849,11 @@ msgid "" "code for a 6-bit verification code" msgstr "使用手机 Google Authenticator 应用扫描以下二维码,获取6位验证码" +#: users/templates/users/user_otp_enable_bind.html:22 +#: users/templates/users/user_verify_mfa.html:23 +msgid "Six figures" +msgstr "6位数字" + #: users/templates/users/user_otp_enable_install_app.html:6 msgid "Install app" msgstr "安装应用" @@ -5709,6 +5936,16 @@ msgstr "更新用户" msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" +#: users/templates/users/user_verify_mfa.html:11 +msgid "" +"The account protection has been opened, please complete the following " +"operations according to the prompts" +msgstr "账号保护已开启,请根据提示完成以下操作" + +#: users/templates/users/user_verify_mfa.html:13 +msgid "Open Authenticator and enter the 6-bit dynamic code" +msgstr "请打开 验证器,输入6位动态码" + # msgid "Update user" # msgstr "更新用户" #: users/utils.py:24 @@ -5956,19 +6193,19 @@ msgstr "首次登录" msgid "Profile setting" msgstr "个人信息设置" -#: users/views/profile/otp.py:130 +#: users/views/profile/otp.py:144 msgid "MFA enable success" msgstr "多因子认证启用成功" -#: users/views/profile/otp.py:131 +#: users/views/profile/otp.py:145 msgid "MFA enable success, return login page" msgstr "多因子认证启用成功,返回到登录页面" -#: users/views/profile/otp.py:133 +#: users/views/profile/otp.py:147 msgid "MFA disable success" msgstr "多因子认证禁用成功" -#: users/views/profile/otp.py:134 +#: users/views/profile/otp.py:148 msgid "MFA disable success, return login page" msgstr "多因子认证禁用成功,返回登录页面" @@ -6004,482 +6241,769 @@ msgstr "用户授权远程应用" msgid "User granted DatabaseApp" msgstr "用户授权数据库应用" -#~ msgid "Password length" -#~ msgstr "密码长度" - -#~ msgid "" -#~ "Tips: The username of the user on the asset to be modified. if the user " -#~ "exists, change the password; If the user does not exist, create the user." -#~ msgstr "" -#~ "提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如" -#~ "果用户不存在,则创建用户。" - -#~ msgid "Change auth plan" -#~ msgstr "改密计划" - -#~ msgid "Custom password" -#~ msgstr "自定义密码" - -#~ msgid "All assets use the same random password" -#~ msgstr "所有资产使用相同的随机密码" +#: xpack/plugins/change_auth_plan/forms.py:21 +msgid "Password length" +msgstr "密码长度" -#~ msgid "All assets use different random password" -#~ msgstr "所有资产使用不同的随机密码" - -#~ msgid "Password rules" -#~ msgstr "密码规则" - -#~ msgid "Change auth plan snapshot" -#~ msgstr "改密计划快照" - -#~ msgid "Change auth plan execution" -#~ msgstr "改密计划执行" - -#~ msgid "Step" -#~ msgstr "步骤" - -#~ msgid "Change auth plan task" -#~ msgstr "改密计划任务" - -#~ msgid "* Please enter custom password" -#~ msgstr "* 请输入自定义密码" - -#~ msgid "* Please enter the correct password length" -#~ msgstr "* 请输入正确的密码长度" - -#~ msgid "* Password length range 6-30 bits" -#~ msgstr "* 密码长度范围 6-30 位" - -#~ msgid "Plan execution list" -#~ msgstr "执行列表" - -#~ msgid "Add asset to this plan" -#~ msgstr "添加资产" - -#~ msgid "Add node to this plan" -#~ msgstr "添加节点" - -#~ msgid "" -#~ "When the user password on the asset is changed, the action is performed " -#~ "using the admin user associated with the asset" -#~ msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" - -#~ msgid "Length" -#~ msgstr "长度" - -#~ msgid "Run plan manually" -#~ msgstr "手动执行计划" - -#~ msgid "Execute failed" -#~ msgstr "执行失败" - -#~ msgid "Execution list of plan" -#~ msgstr "执行历史列表" - -#~ msgid "Log" -#~ msgstr "日志" - -#~ msgid "Retry" -#~ msgstr "重试" - -#~ msgid "Run failed" -#~ msgstr "执行失败" - -#~ msgid "Create plan" -#~ msgstr "创建计划" - -#~ msgid "Invalid/incorrect password" -#~ msgstr "无效/错误 密码" - -#~ msgid "Failed to connect to the host" -#~ msgstr "连接主机失败" - -#~ msgid "Data could not be sent to remote" -#~ msgstr "无法将数据发送到远程" - -#~ msgid "Plan list" -#~ msgstr "计划列表" - -#~ msgid "Update plan" -#~ msgstr "更新计划" - -#~ msgid "Plan execution task list" -#~ msgstr "执行任务列表" - -#~ msgid "Select account" -#~ msgstr "选择账户" - -#~ msgid "Select regions" -#~ msgstr "选择地域" - -#~ msgid "Select instances" -#~ msgstr "选择实例" - -#~ msgid "Select node" -#~ msgstr "选择节点" - -#~ msgid "Select admins" -#~ msgstr "选择管理员" - -#~ msgid "Cloud center" -#~ msgstr "云管中心" - -#~ msgid "Available" -#~ msgstr "有效" - -#~ msgid "Unavailable" -#~ msgstr "无效" - -#~ msgid "Provider" -#~ msgstr "云服务商" - -#~ msgid "Cloud account" -#~ msgstr "云账号" - -#~ msgid "Regions" -#~ msgstr "地域" - -#~ msgid "Instances" -#~ msgstr "实例" - -#~ msgid "Date last sync" -#~ msgstr "最后同步日期" - -#~ msgid "Sync instance task" -#~ msgstr "同步实例任务" - -#~ msgid "Succeed" -#~ msgstr "成功" - -#~ msgid "Date sync" -#~ msgstr "同步日期" - -#~ msgid "Unsync" -#~ msgstr "未同步" - -#~ msgid "Synced" -#~ msgstr "已同步" - -#~ msgid "Released" -#~ msgstr "已释放" - -#~ msgid "Sync task" -#~ msgstr "同步任务" - -#~ msgid "Sync instance task history" -#~ msgstr "同步实例任务历史" - -#~ msgid "Instance" -#~ msgstr "实例" +#: xpack/plugins/change_auth_plan/forms.py:79 +msgid "" +"Tips: The username of the user on the asset to be modified. if the user " +"exists, change the password; If the user does not exist, create the user." +msgstr "" +"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果" +"用户不存在,则创建用户。" + +#: xpack/plugins/change_auth_plan/meta.py:9 +#: xpack/plugins/change_auth_plan/models.py:87 +#: xpack/plugins/change_auth_plan/models.py:174 +#: xpack/plugins/change_auth_plan/views.py:33 +#: xpack/plugins/change_auth_plan/views.py:50 +#: xpack/plugins/change_auth_plan/views.py:74 +#: xpack/plugins/change_auth_plan/views.py:90 +#: xpack/plugins/change_auth_plan/views.py:117 +#: xpack/plugins/change_auth_plan/views.py:132 +#: xpack/plugins/change_auth_plan/views.py:147 +msgid "Change auth plan" +msgstr "改密计划" + +#: xpack/plugins/change_auth_plan/models.py:39 +msgid "Custom password" +msgstr "自定义密码" + +#: xpack/plugins/change_auth_plan/models.py:40 +msgid "All assets use the same random password" +msgstr "所有资产使用相同的随机密码" + +#: xpack/plugins/change_auth_plan/models.py:41 +msgid "All assets use different random password" +msgstr "所有资产使用不同的随机密码" + +#: xpack/plugins/change_auth_plan/models.py:63 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 +msgid "Password rules" +msgstr "密码规则" + +#: xpack/plugins/change_auth_plan/models.py:178 +msgid "Change auth plan snapshot" +msgstr "改密计划快照" + +#: xpack/plugins/change_auth_plan/models.py:193 +#: xpack/plugins/change_auth_plan/models.py:279 +msgid "Change auth plan execution" +msgstr "改密计划执行" + +#: xpack/plugins/change_auth_plan/models.py:252 +msgid "Ready" +msgstr "" -#~ msgid "Alibaba Cloud" -#~ msgstr "阿里云" +#: xpack/plugins/change_auth_plan/models.py:253 +msgid "Preflight check" +msgstr "" -#~ msgid "AWS (International)" -#~ msgstr "AWS (国际)" +#: xpack/plugins/change_auth_plan/models.py:254 +msgid "Change auth" +msgstr "" -#~ msgid "AWS (China)" -#~ msgstr "AWS (中国)" +#: xpack/plugins/change_auth_plan/models.py:255 +msgid "Verify auth" +msgstr "" -#~ msgid "Huawei Cloud" -#~ msgstr "华为云" +#: xpack/plugins/change_auth_plan/models.py:256 +msgid "Keep auth" +msgstr "" -#~ msgid "CN North-Beijing4" -#~ msgstr "华北-北京4" +#: xpack/plugins/change_auth_plan/models.py:283 +msgid "Step" +msgstr "步骤" -#~ msgid "CN East-Shanghai1" -#~ msgstr "华东-上海1" +#: xpack/plugins/change_auth_plan/models.py:300 +msgid "Change auth plan task" +msgstr "改密计划任务" -#~ msgid "CN East-Shanghai2" -#~ msgstr "华东-上海2" +#: xpack/plugins/change_auth_plan/serializers.py:68 +msgid "* Please enter custom password" +msgstr "* 请输入自定义密码" -#~ msgid "CN South-Guangzhou" -#~ msgstr "华南-广州" +#: xpack/plugins/change_auth_plan/serializers.py:78 +msgid "* Please enter the correct password length" +msgstr "* 请输入正确的密码长度" -#~ msgid "CN Southwest-Guiyang1" -#~ msgstr "西南-贵阳1" +#: xpack/plugins/change_auth_plan/serializers.py:81 +msgid "* Password length range 6-30 bits" +msgstr "* 密码长度范围 6-30 位" -#~ msgid "AP-Hong-Kong" -#~ msgstr "亚太-香港" +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:19 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:24 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23 +#: xpack/plugins/change_auth_plan/views.py:133 +msgid "Plan execution list" +msgstr "执行列表" -#~ msgid "AP-Bangkok" -#~ msgstr "亚太-曼谷" +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:62 +msgid "Add asset to this plan" +msgstr "添加资产" -#~ msgid "AP-Singapore" -#~ msgstr "亚太-新加坡" +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:87 +msgid "Add node to this plan" +msgstr "添加节点" -#~ msgid "AF-Johannesburg" -#~ msgstr "非洲-约翰内斯堡" +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:7 +msgid "" +"When the user password on the asset is changed, the action is performed " +"using the admin user associated with the asset" +msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 +msgid "Length" +msgstr "长度" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:132 +msgid "Run plan manually" +msgstr "手动执行计划" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:176 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:90 +msgid "Execute failed" +msgstr "执行失败" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:31 +msgid "Execution list of plan" +msgstr "执行历史列表" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:104 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:106 +msgid "Log" +msgstr "日志" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:61 +msgid "Retry" +msgstr "重试" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:95 +msgid "Run failed" +msgstr "执行失败" + +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:5 +#: xpack/plugins/change_auth_plan/views.py:51 +msgid "Create plan" +msgstr "创建计划" + +#: xpack/plugins/change_auth_plan/utils.py:437 +msgid "Invalid/incorrect password" +msgstr "无效/错误 密码" + +#: xpack/plugins/change_auth_plan/utils.py:439 +msgid "Failed to connect to the host" +msgstr "连接主机失败" + +#: xpack/plugins/change_auth_plan/utils.py:441 +msgid "Data could not be sent to remote" +msgstr "无法将数据发送到远程" + +#: xpack/plugins/change_auth_plan/views.py:34 +msgid "Plan list" +msgstr "计划列表" + +#: xpack/plugins/change_auth_plan/views.py:75 +msgid "Update plan" +msgstr "更新计划" + +#: xpack/plugins/change_auth_plan/views.py:148 +msgid "Plan execution task list" +msgstr "执行任务列表" + +#: xpack/plugins/cloud/forms.py:15 +msgid "Access Key" +msgstr "" -#~ msgid "LA-Santiago" -#~ msgstr "拉美-圣地亚哥" +#: xpack/plugins/cloud/forms.py:19 +msgid "Secret Key" +msgstr "" -#~ msgid "Tencent Cloud" -#~ msgstr "腾讯云" +#: xpack/plugins/cloud/forms.py:56 +msgid "Select account" +msgstr "选择账户" -#~ msgid "Account detail" -#~ msgstr "账户详情" +#: xpack/plugins/cloud/forms.py:62 +msgid "Select regions" +msgstr "选择地域" -#~ msgid "Create account" -#~ msgstr "创建账户" +#: xpack/plugins/cloud/forms.py:68 +msgid "Select instances" +msgstr "选择实例" -#~ msgid "Node & AdminUser" -#~ msgstr "节点 & 管理用户" +#: xpack/plugins/cloud/forms.py:74 +msgid "Select node" +msgstr "选择节点" -#~ msgid "Load failed" -#~ msgstr "加载失败" +#: xpack/plugins/cloud/forms.py:80 xpack/plugins/orgs/forms.py:20 +msgid "Select admins" +msgstr "选择管理员" -#~ msgid "Sync task detail" -#~ msgstr "同步任务详情" +#: xpack/plugins/cloud/forms.py:85 +msgid "Tips: The asset information is always covered" +msgstr "" -#~ msgid "Sync task history" -#~ msgstr "同步历史列表" +#: xpack/plugins/cloud/meta.py:9 xpack/plugins/cloud/views.py:27 +#: xpack/plugins/cloud/views.py:44 xpack/plugins/cloud/views.py:62 +#: xpack/plugins/cloud/views.py:78 xpack/plugins/cloud/views.py:92 +#: xpack/plugins/cloud/views.py:109 xpack/plugins/cloud/views.py:127 +#: xpack/plugins/cloud/views.py:143 xpack/plugins/cloud/views.py:158 +#: xpack/plugins/cloud/views.py:172 +msgid "Cloud center" +msgstr "云管中心" + +#: xpack/plugins/cloud/models.py:29 +msgid "Available" +msgstr "有效" -#~ msgid "Sync instance list" -#~ msgstr "同步实例列表" +#: xpack/plugins/cloud/models.py:30 +msgid "Unavailable" +msgstr "无效" -#~ msgid "Run task manually" -#~ msgstr "手动执行任务" +#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:51 +#: xpack/plugins/cloud/templates/cloud/account_list.html:13 +msgid "Provider" +msgstr "云服务商" -#~ msgid "Sync success" -#~ msgstr "同步成功" +#: xpack/plugins/cloud/models.py:42 +msgid "Access key id" +msgstr "" -#~ msgid "New count" -#~ msgstr "新增" +#: xpack/plugins/cloud/models.py:46 +msgid "Access key secret" +msgstr "" -#~ msgid "Unsync count" -#~ msgstr "未同步" +#: xpack/plugins/cloud/models.py:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26 +msgid "Cloud account" +msgstr "云账号" -#~ msgid "Synced count" -#~ msgstr "已同步" +#: xpack/plugins/cloud/models.py:122 +msgid "Regions" +msgstr "地域" -#~ msgid "Released count" -#~ msgstr "已释放" +#: xpack/plugins/cloud/models.py:125 +msgid "Instances" +msgstr "实例" -#~ msgid "Delete released assets" -#~ msgstr "删除已释放的资产" +#: xpack/plugins/cloud/models.py:136 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +msgid "Covered always" +msgstr "" -#~ msgid "Create sync instance task" -#~ msgstr "创建同步实例任务" +#: xpack/plugins/cloud/models.py:142 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:104 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 +msgid "Date last sync" +msgstr "最后同步日期" -#~ msgid "Run count" -#~ msgstr "执行次数" +#: xpack/plugins/cloud/models.py:153 xpack/plugins/cloud/models.py:210 +msgid "Sync instance task" +msgstr "同步实例任务" -#~ msgid "Instance count" -#~ msgstr "实例个数" +#: xpack/plugins/cloud/models.py:205 +msgid "Succeed" +msgstr "成功" -#~ msgid "Account unavailable" -#~ msgstr "账户无效" +#: xpack/plugins/cloud/models.py:220 xpack/plugins/cloud/models.py:275 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:51 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:49 +msgid "Date sync" +msgstr "同步日期" -#~ msgid "Update account" -#~ msgstr "更新账户" +#: xpack/plugins/cloud/models.py:248 +msgid "Unsync" +msgstr "未同步" -#~ msgid "Sync instance task list" -#~ msgstr "同步实例任务列表" +#: xpack/plugins/cloud/models.py:249 xpack/plugins/cloud/models.py:250 +msgid "Synced" +msgstr "已同步" -#~ msgid "Create sync Instance task" -#~ msgstr "创建同步实例任务" +#: xpack/plugins/cloud/models.py:251 +msgid "Released" +msgstr "已释放" -#~ msgid "Update sync Instance task" -#~ msgstr "更新同步实例任务" +#: xpack/plugins/cloud/models.py:256 +msgid "Sync task" +msgstr "同步任务" -#~ msgid "Gathered user" -#~ msgstr "收集用户" +#: xpack/plugins/cloud/models.py:260 +msgid "Sync instance task history" +msgstr "同步实例任务历史" -#~ msgid "Gather user task" -#~ msgstr "收集用户任务" +#: xpack/plugins/cloud/models.py:263 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45 +msgid "Instance" +msgstr "实例" -#~ msgid "Task" -#~ msgstr "任务" +#: xpack/plugins/cloud/providers/aliyun.py:19 +msgid "Alibaba Cloud" +msgstr "阿里云" -#~ msgid "gather user task execution" -#~ msgstr "收集用户执行" +#: xpack/plugins/cloud/providers/aws.py:15 +msgid "AWS (International)" +msgstr "AWS (国际)" -#~ msgid "Assets is empty, please change nodes" -#~ msgstr "资产为空,请更改节点" +#: xpack/plugins/cloud/providers/aws_china.py:9 +msgid "AWS (China)" +msgstr "AWS (中国)" -#~ msgid "Asset user" -#~ msgstr "资产用户" +#: xpack/plugins/cloud/providers/huaweicloud.py:17 +msgid "Huawei Cloud" +msgstr "华为云" -#~ msgid "Create task" -#~ msgstr "创建任务" +#: xpack/plugins/cloud/providers/huaweicloud.py:20 +msgid "AF-Johannesburg" +msgstr "非洲-约翰内斯堡" -#~ msgid "Periodic" -#~ msgstr "定时执行" +#: xpack/plugins/cloud/providers/huaweicloud.py:21 +msgid "AP-Bangkok" +msgstr "亚太-曼谷" -#~ msgid "Gathered user list" -#~ msgstr "收集用户列表" +#: xpack/plugins/cloud/providers/huaweicloud.py:22 +msgid "AP-Hong Kong" +msgstr "亚太-香港" -#~ msgid "Update task" -#~ msgstr "更新任务" +#: xpack/plugins/cloud/providers/huaweicloud.py:23 +msgid "AP-Singapore" +msgstr "亚太-新加坡" -#~ msgid "Title of login page" -#~ msgstr "登录页面标题" +#: xpack/plugins/cloud/providers/huaweicloud.py:24 +msgid "CN East-Shanghai1" +msgstr "华东-上海1" -#~ msgid "" -#~ "Tips: This will be displayed on the enterprise user login page. (eg: " -#~ "Welcome to the JumpServer open source fortress)" -#~ msgstr "" -#~ "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" +#: xpack/plugins/cloud/providers/huaweicloud.py:25 +msgid "CN East-Shanghai2" +msgstr "华东-上海2" -#~ msgid "Image of login page" -#~ msgstr "登录页面图片" +#: xpack/plugins/cloud/providers/huaweicloud.py:26 +msgid "CN North-Beijing1" +msgstr "华北-北京1" -#~ msgid "" -#~ "Tips: This will be displayed on the enterprise user login page. (suggest " -#~ "image size: 492px*472px)" -#~ msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" +#: xpack/plugins/cloud/providers/huaweicloud.py:27 +msgid "CN North-Beijing4" +msgstr "华北-北京4" -#~ msgid "Website icon" -#~ msgstr "网站图标" +#: xpack/plugins/cloud/providers/huaweicloud.py:28 +msgid "CN Northeast-Dalian" +msgstr "东北-大连" -#~ msgid "Tips: website icon. (suggest image size: 16px*16px)" -#~ msgstr "提示:网站图标(建议图片大小为: 16px*16px)" +#: xpack/plugins/cloud/providers/huaweicloud.py:29 +msgid "CN South-Guangzhou" +msgstr "华南-广州" -#~ msgid "Logo of management page" -#~ msgstr "管理页面logo" +#: xpack/plugins/cloud/providers/huaweicloud.py:30 +msgid "CN Southwest-Guiyang1" +msgstr "西南-贵阳1" -#~ msgid "" -#~ "Tips: This will appear at the top left of the administration page. " -#~ "(suggest image size: 185px*55px)" -#~ msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" +#: xpack/plugins/cloud/providers/huaweicloud.py:31 +msgid "EU-Paris" +msgstr "" -#~ msgid "Logo of logout page" -#~ msgstr "退出页面logo" +#: xpack/plugins/cloud/providers/qcloud.py:17 +msgid "Tencent Cloud" +msgstr "腾讯云" + +#: xpack/plugins/cloud/templates/cloud/account_detail.html:17 +#: xpack/plugins/cloud/views.py:79 +msgid "Account detail" +msgstr "账户详情" + +#: xpack/plugins/cloud/templates/cloud/account_list.html:5 +#: xpack/plugins/cloud/views.py:45 +msgid "Create account" +msgstr "创建账户" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33 +msgid "Node & AdminUser" +msgstr "节点 & 管理用户" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66 +msgid "Load failed" +msgstr "加载失败" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:19 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:18 +#: xpack/plugins/cloud/views.py:144 +msgid "Sync task detail" +msgstr "同步任务详情" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:22 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 +#: xpack/plugins/cloud/views.py:159 +msgid "Sync task history" +msgstr "同步历史列表" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 +#: xpack/plugins/cloud/views.py:173 +msgid "Sync instance list" +msgstr "同步实例列表" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:145 +msgid "Run task manually" +msgstr "手动执行任务" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:188 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:102 +msgid "Sync success" +msgstr "同步成功" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:46 +msgid "New count" +msgstr "新增" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47 +msgid "Unsync count" +msgstr "未同步" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:48 +msgid "Synced count" +msgstr "已同步" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:49 +msgid "Released count" +msgstr "已释放" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +msgid "Delete released assets" +msgstr "删除已释放的资产" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:5 +msgid "Create sync instance task" +msgstr "创建同步实例任务" + +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:14 +msgid "Run count" +msgstr "执行次数" -#~ msgid "" -#~ "Tips: This will be displayed on the enterprise user logout page. (suggest " -#~ "image size: 82px*82px)" -#~ msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 +msgid "Instance count" +msgstr "实例个数" -#~ msgid "Interface settings" -#~ msgstr "界面设置" +#: xpack/plugins/cloud/utils.py:38 +msgid "Account unavailable" +msgstr "账户无效" -#~ msgid "Interface setting" -#~ msgstr "界面设置" +#: xpack/plugins/cloud/views.py:63 +msgid "Update account" +msgstr "更新账户" -#~ msgid "Restore Default" -#~ msgstr "恢复默认" +#: xpack/plugins/cloud/views.py:93 +msgid "Sync instance task list" +msgstr "同步实例任务列表" -#~ msgid "This will restore default Settings of the interface !!!" -#~ msgstr "您确定要恢复默认初始化吗?" +#: xpack/plugins/cloud/views.py:110 +msgid "Create sync Instance task" +msgstr "创建同步实例任务" -#~ msgid "Restore default successfully." -#~ msgstr "恢复默认成功!" +#: xpack/plugins/cloud/views.py:128 +msgid "Update sync Instance task" +msgstr "更新同步实例任务" -#~ msgid "Restore default failed." -#~ msgstr "恢复默认失败!" +#: xpack/plugins/gathered_user/meta.py:11 +#: xpack/plugins/gathered_user/views.py:21 +#: xpack/plugins/gathered_user/views.py:34 +#: xpack/plugins/gathered_user/views.py:49 +#: xpack/plugins/gathered_user/views.py:69 +msgid "Gathered user" +msgstr "收集用户" -#~ msgid "It is already in the default setting state!" -#~ msgstr "当前已经是初始化状态!" +#: xpack/plugins/gathered_user/models.py:39 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:13 +msgid "Gather user task" +msgstr "收集用户任务" -#~ msgid "License" -#~ msgstr "许可证" +#: xpack/plugins/gathered_user/models.py:73 +msgid "Task" +msgstr "任务" -#~ msgid "Standard edition" -#~ msgstr "标准版" +#: xpack/plugins/gathered_user/models.py:85 +msgid "gather user task execution" +msgstr "收集用户执行" -#~ msgid "Enterprise edition" -#~ msgstr "企业版" +#: xpack/plugins/gathered_user/models.py:91 +msgid "Assets is empty, please change nodes" +msgstr "资产为空,请更改节点" -#~ msgid "Ultimate edition" -#~ msgstr "旗舰版" +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:170 +msgid "Asset user" +msgstr "资产用户" -#~ msgid "Import license" -#~ msgstr "导入许可证" +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:7 +#: xpack/plugins/gathered_user/views.py:50 +msgid "Create task" +msgstr "创建任务" -#~ msgid "License file" -#~ msgstr "许可证文件" +#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:18 +msgid "Periodic" +msgstr "定时执行" -#~ msgid "Please Import License" -#~ msgstr "请导入许可证" +#: xpack/plugins/gathered_user/views.py:22 +msgid "Gathered user list" +msgstr "收集用户列表" -#~ msgid "License has expired" -#~ msgstr "许可证已经过期" +#: xpack/plugins/gathered_user/views.py:70 +msgid "Update task" +msgstr "更新任务" -#~ msgid "The license will at " -#~ msgstr "许可证将在 " +#: xpack/plugins/interface/forms.py:17 xpack/plugins/interface/models.py:15 +msgid "Title of login page" +msgstr "登录页面标题" -#~ msgid " expired." -#~ msgstr " 过期。" +#: xpack/plugins/interface/forms.py:19 +msgid "" +"Tips: This will be displayed on the enterprise user login page. (eg: Welcome " +"to the JumpServer open source fortress)" +msgstr "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" -#~ msgid "License detail" -#~ msgstr "许可证详情" +#: xpack/plugins/interface/forms.py:25 xpack/plugins/interface/models.py:19 +msgid "Image of login page" +msgstr "登录页面图片" -#~ msgid "No license" -#~ msgstr "暂无许可证" +#: xpack/plugins/interface/forms.py:27 +msgid "" +"Tips: This will be displayed on the enterprise user login page. (suggest " +"image size: 492px*472px)" +msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" -#~ msgid "Subscription ID" -#~ msgstr "订阅授权ID" +#: xpack/plugins/interface/forms.py:33 xpack/plugins/interface/models.py:23 +msgid "Website icon" +msgstr "网站图标" -#~ msgid "Corporation" -#~ msgstr "公司" +#: xpack/plugins/interface/forms.py:35 +msgid "Tips: website icon. (suggest image size: 16px*16px)" +msgstr "提示:网站图标(建议图片大小为: 16px*16px)" -#~ msgid "Expired" -#~ msgstr "过期时间" +#: xpack/plugins/interface/forms.py:40 xpack/plugins/interface/models.py:27 +msgid "Logo of management page" +msgstr "管理页面logo" -#~ msgid "Edition" -#~ msgstr "版本" +#: xpack/plugins/interface/forms.py:42 +msgid "" +"Tips: This will appear at the top left of the administration page. (suggest " +"image size: 185px*55px)" +msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" -#~ msgid "Technology consulting" -#~ msgstr "技术咨询" +#: xpack/plugins/interface/forms.py:48 xpack/plugins/interface/models.py:31 +msgid "Logo of logout page" +msgstr "退出页面logo" -#~ msgid "Consult" -#~ msgstr "咨询" +#: xpack/plugins/interface/forms.py:50 +msgid "" +"Tips: This will be displayed on the enterprise user logout page. (suggest " +"image size: 82px*82px)" +msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" + +#: xpack/plugins/interface/meta.py:10 +msgid "Interface settings" +msgstr "界面设置" + +#: xpack/plugins/interface/templates/interface/interface.html:15 +#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 +msgid "Interface setting" +msgstr "界面设置" + +#: xpack/plugins/interface/templates/interface/interface.html:73 +#: xpack/plugins/interface/templates/interface/interface.html:108 +#: xpack/plugins/interface/templates/interface/interface.html:115 +msgid "Restore Default" +msgstr "恢复默认" + +#: xpack/plugins/interface/templates/interface/interface.html:98 +msgid "This will restore default Settings of the interface !!!" +msgstr "您确定要恢复默认初始化吗?" + +#: xpack/plugins/interface/templates/interface/interface.html:107 +#: xpack/plugins/interface/views.py:55 +msgid "Restore default successfully." +msgstr "恢复默认成功!" + +#: xpack/plugins/interface/templates/interface/interface.html:114 +msgid "Restore default failed." +msgstr "恢复默认失败!" + +#: xpack/plugins/interface/views.py:51 +msgid "It is already in the default setting state!" +msgstr "当前已经是初始化状态!" + +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 +#: xpack/plugins/license/templates/license/license_detail.html:41 +#: xpack/plugins/license/templates/license/license_detail.html:46 +#: xpack/plugins/license/views.py:32 +msgid "License" +msgstr "许可证" + +#: xpack/plugins/license/models.py:74 +msgid "Standard edition" +msgstr "标准版" + +#: xpack/plugins/license/models.py:76 +msgid "Enterprise edition" +msgstr "企业版" + +#: xpack/plugins/license/models.py:78 +msgid "Ultimate edition" +msgstr "旗舰版" + +#: xpack/plugins/license/templates/license/_license_import_modal.html:4 +#: xpack/plugins/license/templates/license/license_detail.html:86 +msgid "Import license" +msgstr "导入许可证" + +#: xpack/plugins/license/templates/license/_license_import_modal.html:9 +msgid "License file" +msgstr "许可证文件" + +#: xpack/plugins/license/templates/license/license_detail.html:11 +msgid "Please Import License" +msgstr "请导入许可证" + +#: xpack/plugins/license/templates/license/license_detail.html:13 +#: xpack/plugins/license/templates/license/license_detail.html:47 +msgid "License has expired" +msgstr "许可证已经过期" + +#: xpack/plugins/license/templates/license/license_detail.html:15 +msgid "The license will at " +msgstr "许可证将在 " + +#: xpack/plugins/license/templates/license/license_detail.html:15 +msgid " expired." +msgstr " 过期。" + +#: xpack/plugins/license/templates/license/license_detail.html:28 +#: xpack/plugins/license/views.py:33 +msgid "License detail" +msgstr "许可证详情" + +#: xpack/plugins/license/templates/license/license_detail.html:42 +msgid "No license" +msgstr "暂无许可证" + +#: xpack/plugins/license/templates/license/license_detail.html:51 +msgid "Subscription ID" +msgstr "订阅授权ID" + +#: xpack/plugins/license/templates/license/license_detail.html:55 +msgid "Corporation" +msgstr "公司" + +#: xpack/plugins/license/templates/license/license_detail.html:59 +msgid "Expired" +msgstr "过期时间" + +#: xpack/plugins/license/templates/license/license_detail.html:67 +msgid "Edition" +msgstr "版本" -#~ msgid "License import successfully" -#~ msgstr "许可证导入成功" +#: xpack/plugins/license/templates/license/license_detail.html:93 +msgid "Technology consulting" +msgstr "技术咨询" -#~ msgid "License is invalid" -#~ msgstr "无效的许可证" +#: xpack/plugins/license/templates/license/license_detail.html:96 +msgid "Consult" +msgstr "咨询" -#~ msgid "Select auditor" -#~ msgstr "选择审计员" +#: xpack/plugins/license/views.py:47 +msgid "License import successfully" +msgstr "许可证导入成功" -#~ msgid "Admin" -#~ msgstr "管理员" +#: xpack/plugins/license/views.py:49 +msgid "License is invalid" +msgstr "无效的许可证" -#~ msgid "Organizations" -#~ msgstr "组织管理" +#: xpack/plugins/orgs/forms.py:23 +msgid "Select auditor" +msgstr "选择审计员" -#~ msgid "Org detail" -#~ msgstr "组织详情" +#: xpack/plugins/orgs/forms.py:28 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:75 +#: xpack/plugins/orgs/templates/orgs/org_list.html:13 +msgid "Admin" +msgstr "管理员" -#~ msgid "Add admin" -#~ msgstr "添加管理员" +#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:27 +#: xpack/plugins/orgs/views.py:44 xpack/plugins/orgs/views.py:62 +#: xpack/plugins/orgs/views.py:85 xpack/plugins/orgs/views.py:116 +msgid "Organizations" +msgstr "组织管理" + +#: xpack/plugins/orgs/templates/orgs/org_detail.html:17 +#: xpack/plugins/orgs/templates/orgs/org_users.html:13 +#: xpack/plugins/orgs/views.py:86 +msgid "Org detail" +msgstr "组织详情" + +#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 +#: xpack/plugins/orgs/templates/orgs/org_users.html:16 +msgid "Org users" +msgstr "组织用户" + +#: xpack/plugins/orgs/templates/orgs/org_detail.html:83 +msgid "Add admin" +msgstr "添加管理员" + +#: xpack/plugins/orgs/templates/orgs/org_detail.html:117 +msgid "Add auditor" +msgstr "添加审计员" + +#: xpack/plugins/orgs/templates/orgs/org_list.html:5 +msgid "Create organization " +msgstr "创建组织" + +#: xpack/plugins/orgs/templates/orgs/org_users.html:59 +msgid "Add user to organization" +msgstr "添加用户" -#~ msgid "Create organization " -#~ msgstr "创建组织" +#: xpack/plugins/orgs/views.py:28 +msgid "Org list" +msgstr "组织列表" -#~ msgid "Org list" -#~ msgstr "组织列表" +#: xpack/plugins/orgs/views.py:45 +msgid "Create org" +msgstr "创建组织" -#~ msgid "Create org" -#~ msgstr "创建组织" +#: xpack/plugins/orgs/views.py:63 +msgid "Update org" +msgstr "更新组织" -#~ msgid "Update org" -#~ msgstr "更新组织" +#: xpack/plugins/orgs/views.py:117 +msgid "Org user list" +msgstr "组织用户列表" -#~ msgid "Vault" -#~ msgstr "密码匣子" +#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 +#: xpack/plugins/vault/views.py:38 +msgid "Vault" +msgstr "密码匣子" -#~ msgid "Import vault" -#~ msgstr "导入密码" +#: xpack/plugins/vault/templates/vault/_xpack_import_modal.html:4 +msgid "Import vault" +msgstr "导入密码" -#~ msgid "vault" -#~ msgstr "密码匣子" +#: xpack/plugins/vault/templates/vault/vault.html:64 +msgid "vault" +msgstr "密码匣子" -#~ msgid "vault list" -#~ msgstr "密码匣子" +#: xpack/plugins/vault/views.py:24 +msgid "vault list" +msgstr "密码匣子" -#~ msgid "vault create" -#~ msgstr "创建" +#: xpack/plugins/vault/views.py:39 +msgid "vault create" +msgstr "创建" -#~ msgid "Tips: The asset information is always covered" -#~ msgstr "提示:资产信息总是被覆盖" +#~ msgid "LA-Santiago" +#~ msgstr "拉美-圣地亚哥" -#~ msgid "Covered always" -#~ msgstr "总是覆盖" +#~ msgid "Total hosts" +#~ msgstr "主机总数" #~ msgid "* For security, do not change {}'s password" #~ msgstr "* 为了安全,不能修改 {} 的密码" diff --git a/apps/orgs/api.py b/apps/orgs/api.py index ea4a48955..63eebb25d 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- # -from rest_framework import status +from django.shortcuts import get_object_or_404 +from rest_framework import status, generics from rest_framework.views import Response from rest_framework_bulk import BulkModelViewSet from common.permissions import IsSuperUserOrAppUser from .models import Organization from .serializers import OrgSerializer, OrgReadSerializer, \ - OrgMembershipUserSerializer, OrgMembershipAdminSerializer + OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \ + OrgAllUserSerializer from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission @@ -67,3 +69,15 @@ class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet membership_class = Organization.users.through permission_classes = (IsSuperUserOrAppUser, ) + +class OrgAllUserListApi(generics.ListAPIView): + permission_classes = (IsSuperUserOrAppUser,) + serializer_class = OrgAllUserSerializer + filter_fields = ("username", "name") + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + org = get_object_or_404(Organization, pk=pk) + users = org.get_org_users().only(*self.serializer_class.Meta.only_fields) + return users diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 281b9ec75..5ff3b5f4d 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -80,3 +80,15 @@ class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer) model = Organization.users.through list_serializer_class = AdaptedBulkListSerializer fields = '__all__' + + +class OrgAllUserSerializer(serializers.Serializer): + user = serializers.UUIDField(read_only=True, source='id') + user_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'username', 'name'] + + @staticmethod + def get_user_display(obj): + return str(obj) diff --git a/apps/orgs/urls/api_urls.py b/apps/orgs/urls/api_urls.py index 782269c81..17f8c3c8a 100644 --- a/apps/orgs/urls/api_urls.py +++ b/apps/orgs/urls/api_urls.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.urls import re_path +from django.urls import re_path, path from rest_framework.routers import DefaultRouter from common import api as capi @@ -24,6 +24,7 @@ old_version_urlpatterns = [ ] urlpatterns = [ + path('/users/all/', api.OrgAllUserListApi.as_view(), name='org-all-users'), ] urlpatterns += router.urls + old_version_urlpatterns diff --git a/apps/perms/apps.py b/apps/perms/apps.py index d6fa5f712..d40373e08 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -1,14 +1,7 @@ from __future__ import unicode_literals -from django.conf import settings from django.apps import AppConfig class PermsConfig(AppConfig): name = 'perms' - - def ready(self): - from . import signals_handler - if not settings.XPACK_ENABLED: - settings.ASSETS_PERM_CACHE_ENABLE = False - return super().ready() diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index cdcf4389f..fce4ee12d 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -302,7 +302,6 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): continue ancestors = self.full_tree.ancestors( child.identifier, with_self=False, deep=True, - with_assets=False, ) # print("Get ancestors: {}".format(len(ancestors))) if not ancestors: diff --git a/apps/templates/index.html b/apps/templates/index.html index b667f473d..1942ad2cb 100644 --- a/apps/templates/index.html +++ b/apps/templates/index.html @@ -11,7 +11,7 @@
{% trans 'Total users' %}
-

{{ users_count }}

+

All users
@@ -19,12 +19,12 @@
- Hosts -
{% trans 'Total hosts' %}
+ Assets +
{% trans 'Total assets' %}
-

{{ assets_count }}

- All hosts +

+ All assets
@@ -36,7 +36,7 @@
{% trans 'Online users' %}
-

{{ online_user_count }}

+

Online users
@@ -50,7 +50,7 @@
-

{{ online_asset_count }}

+

Online sessions
@@ -58,19 +58,11 @@
- {% trans 'In the past week, a total of ' %}{{ user_visit_count_weekly }}{% trans ' users have logged in ' %}{{ asset_visit_count_weekly }}{% trans ' times asset.' %} -
    - {% for data in user_visit_count_top_five %} -
  • - - {{ data.total }}{% trans ' times/week' %} - - {{ forloop.counter }} {{ data.user }} -
  • - {% endfor %} + {% trans 'In the past week, a total of ' %}{% trans ' users have logged in ' %}{% trans ' times asset.' %} +
-
+

@@ -81,13 +73,13 @@

-
+
{% trans 'User' %}
-
-
{% trans 'Host' %}
+
+
{% trans 'Asset' %}
@@ -120,27 +112,7 @@

{% trans 'Top 10 assets in a week'%}

{% trans 'Login frequency and last login record.' %}
-
- {% if week_asset_hot_ten %} - {% for data in week_asset_hot_ten %} -
-
-
- - {{ data.asset }} -
- {{ data.total }}{% trans ' times' %} -
-
-

{% trans 'The time last logged in' %}

-

{% trans 'At' %} {{ data.last|date:"Y-m-d H:i:s" }}

-
-
-
- {% endfor %} - {% else %} -

{% trans '(No)' %}

- {% endif %} +
@@ -158,27 +130,7 @@

-
- {% if last_login_ten %} - {% for login in last_login_ten %} -
- - image - -
- {% ifequal login.is_finished 0 %} - {{ login.date_start|timesince }} {% trans 'Before' %} - {% else %} - {{ login.date_start|timesince }} {% trans 'Before' %} - {% endifequal %} - {{ login.user }} {% trans 'Login in ' %}{{ login.asset }}
- {{ login.date_start }} -
-
- {% endfor %} - {% else %} -

{% trans '(No)' %}

- {% endif %} +
@@ -206,27 +158,7 @@

{% trans 'Top 10 users in a week' %}

{% trans 'User login frequency and last login record.' %}
-
- {% if week_user_hot_ten %} - {% for data in week_user_hot_ten %} -
-
-
- - {{ data.user }} -
- {{ data.total }}{% trans ' times' %} -
-
-

{% trans 'The time last logged in' %}

-

{% trans 'At' %} {{ data.last|date:"Y-m-d H:i:s" }}

-
-
-
- {% endfor %} - {% else %} -

{% trans '(No)' %}

- {% endif %} +
@@ -238,27 +170,15 @@ {% block custom_foot_js %} {% endblock %} diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index 1df4b40e6..c7a91d009 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -16,7 +16,7 @@ class TerminalSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', 'remote_addr', 'http_port', 'ssh_port', 'comment', 'is_accepted', "is_active", 'session_online', - 'is_alive' + 'is_alive', 'date_created', 'command_storage', 'replay_storage' ] @staticmethod diff --git a/apps/users/api/mixins.py b/apps/users/api/mixins.py index 273ab9074..2ff60b615 100644 --- a/apps/users/api/mixins.py +++ b/apps/users/api/mixins.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- # from .. import utils +from users.models import User class UserQuerysetMixin: def get_queryset(self): - queryset = utils.get_current_org_members() + if self.request.query_params.get('all'): + queryset = User.objects.exclude(role=User.ROLE_APP) + else: + queryset = utils.get_current_org_members() return queryset diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index aa1458c7d..37f7c42fc 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -3,12 +3,17 @@ from django.dispatch import receiver from django.db.models.signals import m2m_changed +from django_auth_ldap.backend import populate_user +from django.conf import settings from django_cas_ng.signals import cas_user_authenticated +from jms_oidc_rp.signals import openid_create_or_update_user + from common.utils import get_logger from .signals import post_user_create from .models import User + logger = get_logger(__file__) @@ -37,3 +42,33 @@ def on_cas_user_authenticated(sender, user, created, **kwargs): if created: user.source = user.SOURCE_CAS user.save() + + +@receiver(populate_user) +def on_ldap_create_user(sender, user, ldap_user, **kwargs): + if user and user.username not in ['admin']: + exists = User.objects.filter(username=user.username).exists() + if not exists: + user.source = user.SOURCE_LDAP + user.save() + + +@receiver(openid_create_or_update_user) +def on_openid_create_or_update_user(sender, request, user, created, name, username, email, **kwargs): + if created: + logger.debug( + "Receive OpenID user created signal: {}, " + "Set user source is: {}".format(user, User.SOURCE_OPENID) + ) + user.source = User.SOURCE_OPENID + user.save() + elif not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER: + logger.debug( + "Receive OpenID user updated signal: {}, " + "Update user info: {}" + "".format(user, "name: {}|username: {}|email: {}".format(name, username, email)) + ) + user.name = name + user.username = username + user.email = email + user.save() diff --git a/apps/users/templates/users/user_disable_mfa.html b/apps/users/templates/users/user_verify_mfa.html similarity index 100% rename from apps/users/templates/users/user_disable_mfa.html rename to apps/users/templates/users/user_verify_mfa.html diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index 7bae70c58..2d823f5ab 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -83,12 +83,26 @@ class UserOtpEnableBindView(TemplateView, FormView): return super().get_context_data(**kwargs) -class UserDisableMFAView(FormView): - template_name = 'users/user_disable_mfa.html' +class UserVerifyMFAView(FormView): + template_name = 'users/user_verify_mfa.html' form_class = forms.UserCheckOtpCodeForm success_url = reverse_lazy('users:user-otp-settings-success') permission_classes = [IsValidUser] + def form_valid(self, form): + user = self.request.user + otp_code = form.cleaned_data.get('otp_code') + + valid = user.check_mfa(otp_code) + if valid: + return super().form_valid(form) + else: + error = _('MFA code invalid, or ntp sync server time') + form.add_error('otp_code', error) + return super().form_invalid(form) + + +class UserDisableMFAView(UserVerifyMFAView): def form_valid(self, form): user = self.request.user otp_code = form.cleaned_data.get('otp_code') @@ -104,7 +118,7 @@ class UserDisableMFAView(FormView): return super().form_invalid(form) -class UserOtpUpdateView(UserDisableMFAView): +class UserOtpUpdateView(UserVerifyMFAView): success_url = reverse_lazy('users:user-otp-enable-bind') diff --git a/config_example.yml b/config_example.yml index 5699f4ff0..30cfabc3e 100644 --- a/config_example.yml +++ b/config_example.yml @@ -29,7 +29,6 @@ BOOTSTRAP_TOKEN: # 使用单文件sqlite数据库 # DB_ENGINE: sqlite3 # DB_NAME: - # MySQL or postgres setting like: # 使用Mysql作为数据库 DB_ENGINE: mysql @@ -54,16 +53,27 @@ REDIS_PORT: 6379 # REDIS_DB_CELERY: 3 # REDIS_DB_CACHE: 4 -# Use OpenID authorization -# 使用OpenID 来进行认证设置 -# BASE_SITE_URL: http://localhost:8080 -# AUTH_OPENID: false # True or False -# AUTH_OPENID_SERVER_URL: https://openid-auth-server.com/ -# AUTH_OPENID_REALM_NAME: realm-name +# Use OpenID Authorization +# 使用 OpenID 进行认证设置 +# AUTH_OPENID: False # True or False # AUTH_OPENID_CLIENT_ID: client-id # AUTH_OPENID_CLIENT_SECRET: client-secret -# AUTH_OPENID_IGNORE_SSL_VERIFICATION: True +# AUTH_OPENID_PROVIDER_ENDPOINT: https://op-example.com/ +# AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT: https://op-example.com/authorize +# AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT: https://op-example.com/token +# AUTH_OPENID_PROVIDER_JWKS_ENDPOINT: https://op-example.com/jwks +# AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT: https://op-example.com/userinfo +# AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT: https://op-example.com/logout +# AUTH_OPENID_PROVIDER_SIGNATURE_ALG: HS256 +# AUTH_OPENID_PROVIDER_SIGNATURE_KEY: None +# AUTH_OPENID_SCOPES: "openid profile email" +# AUTH_OPENID_ID_TOKEN_MAX_AGE: 60 +# AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS: True +# AUTH_OPENID_USE_STATE: True +# AUTH_OPENID_USE_NONCE: True # AUTH_OPENID_SHARE_SESSION: True +# AUTH_OPENID_IGNORE_SSL_VERIFICATION: True +# AUTH_OPENID_ALWAYS_UPDATE_USER: True # Use Radius authorization # 使用Radius来认证 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 06b420565..4eb34c22c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -74,8 +74,6 @@ Werkzeug==0.15.3 drf-nested-routers==0.91 aliyun-python-sdk-core-v3==2.9.1 aliyun-python-sdk-ecs==4.10.1 -python-keycloak==0.13.3 -python-keycloak-client==0.1.3 rest_condition==1.0.3 python-ldap==3.1.0 tencentcloud-sdk-python==3.0.40 @@ -98,3 +96,4 @@ ipython huaweicloud-sdk-python==1.0.21 django-redis==4.11.0 python-redis-lock==3.5.0 +jumpserver-django-oidc-rp==0.3.7.3