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/README.md b/README.md index 052952256..43803762c 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,28 @@ # JumpServer 多云环境下更好用的堡垒机 [![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/) -[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/) -[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/) -[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/) +[![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/) -JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 机制的运维安全审计系统。 +JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。 -JumpServer 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。 +JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。 JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。 改变世界,从一点点开始。 -注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。 +> 注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。 -## 核心功能列表 +## 特色优势 + +- 开源: 零门槛,线上快速获取和安装; +- 分布式: 轻松支持大规模并发访问; +- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验; +- 多云支持: 一套系统,同时管理不同云上面的资产; +- 云端存储: 审计录像云端存储,永不丢失; +- 多租户: 一套系统,多个子公司和部门同时使用。 + +## 功能列表 @@ -172,22 +179,26 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向
-## 安装及使用指南 - -- [Docker 快速安装文档](http://docs.jumpserver.org/zh/docs/dockerinstall.html) -- [Step by Step 安装文档](http://docs.jumpserver.org/zh/docs/step_by_step.html) -- [完整文档](http://docs.jumpserver.org) - -## 演示视频和截屏 - -我们提供了演示视频和系统截图可以让你快速了解 JumpServer: +## 快速开始 +- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/) +- [完整文档](https://docs.jumpserver.org) - [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4) -- [系统截图](http://docs.JumpServer.org/zh/docs/snapshot.html) + +## 案例研究 + +- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147); +- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882); +- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851); +- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516); +- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732); +- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708); +- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687); +- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。 ## License & Copyright -Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved. +Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved. Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/apps/assets/filters.py b/apps/assets/filters.py index b68d9d127..94a49a3f5 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -58,7 +58,9 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): if query_all: pattern = node.get_all_children_pattern(with_self=True) else: - pattern = node.get_children_key_pattern(with_self=True) + # pattern = node.get_children_key_pattern(with_self=True) + # 只显示当前节点下资产 + pattern = r"^{}$".format(node.key) return self.perform_query(pattern, queryset) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 39ea8420f..6b5e716e0 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -41,11 +41,10 @@ def default_node(): class AssetManager(OrgManager): - # def get_queryset(self): - # return super().get_queryset().annotate( - # platform_base=models.F('platform__base') - # ) - pass + def get_queryset(self): + return super().get_queryset().annotate( + platform_base=models.F('platform__base') + ) class AssetQuerySet(models.QuerySet): diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html index fa2c9830c..2a8c8cf64 100644 --- a/apps/assets/templates/assets/user_asset_list.html +++ b/apps/assets/templates/assets/user_asset_list.html @@ -76,8 +76,9 @@ $(document).ready(function () { 'ip': "{% trans 'IP' %}", 'protocols': "{% trans 'Protocols' %}", 'platform': "{% trans 'Platform' %}", - 'system_users_join': "{% trans 'System user' %}", + {#'system_users_join': "{% trans 'System user' %}",#} 'domain': "{% trans 'Domain' %}", + 'comment': "{% trans 'Comment' %}", }; var value; for (var i = 0; i < data.results.length; i++) { 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/_access_key_modal.html b/apps/authentication/templates/authentication/_access_key_modal.html index f0b34cf30..71b1f67f5 100644 --- a/apps/authentication/templates/authentication/_access_key_modal.html +++ b/apps/authentication/templates/authentication/_access_key_modal.html @@ -135,6 +135,19 @@ $(document).ready(function () { } }; requestApi(data) +}).on('click', '.btn-api-keydel', function (){ + var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}"; + url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ; + var data = { + url: url, + method: "DELETE", + success: function () { + ak_table.ajax.reload(); + }, + success_message: "{% trans 'Delete success' %}" + + }; + requestApi(data) }) {% endblock %} 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 index e98a44d6b..fd580093e 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -219,7 +219,7 @@ class TotalCountMixin: return count @staticmethod - def get_total_count_online_assets(): + def get_total_count_online_sessions(): return Session.objects.filter(is_finished=False).count() @@ -239,7 +239,7 @@ class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, A '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_assets': self.get_total_count_online_assets(), + 'total_count_online_sessions': self.get_total_count_online_sessions(), }) if _all or query_params.get('month_metrics'): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index b30160d3a..5da97cfb7 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,6 +16,7 @@ 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) @@ -38,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 @@ -136,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, - '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, + 'BASE_SITE_URL': None, + '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.9) + '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)) + 'AUTH_OPENID_SERVER_URL': 'http://openid', + 'AUTH_OPENID_REALM_NAME': None, 'AUTH_RADIUS': False, 'RADIUS_SERVER': 'localhost', @@ -207,6 +259,88 @@ class Config(dict): 'ORG_CHANGE_TO_URL': '' } + 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 +417,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 +429,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 +611,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 +623,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/const.py b/apps/jumpserver/const.py index 2a97a00d2..b7caa6114 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -7,6 +7,6 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) -VERSION = '1.5.8' +VERSION = '1.5.9' CONFIG = ConfigManager.load_user_config() DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index bad698e4c..b7633ace7 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,36 @@ 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 +BASE_SITE_URL = CONFIG.BASE_SITE_URL 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 4a2f2ca88..d1fbb5a36 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/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/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 954523412..c51d13e46 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 9ea83491f..8207c4b7a 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-11 16:53+0800\n" +"POT-Creation-Date: 2020-05-20 19:25+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:353 assets/models/authbook.py:27 +#: assets/models/asset.py:352 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 @@ -112,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:146 assets/models/base.py:232 +#: assets/forms/user.py:96 assets/models/asset.py:145 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 @@ -204,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:192 +#: assets/forms/asset.py:25 assets/models/asset.py:191 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" @@ -227,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:151 assets/models/asset.py:227 +#: assets/models/asset.py:150 assets/models/asset.py:226 #: 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 @@ -244,7 +244,8 @@ msgstr "数据库" #: assets/templates/assets/platform_detail.html:64 #: assets/templates/assets/platform_list.html:18 #: assets/templates/assets/system_user_detail.html:112 -#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 +#: assets/templates/assets/system_user_list.html:29 +#: assets/templates/assets/user_asset_list.html:81 ops/models/adhoc.py:37 #: orgs/models.py:18 perms/models/base.py:56 #: perms/templates/perms/asset_permission_detail.html:97 #: perms/templates/perms/database_app_permission_detail.html:93 @@ -309,7 +310,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:225 assets/models/base.py:240 +#: assets/models/asset.py:224 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 @@ -335,7 +336,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:226 assets/models/base.py:238 +#: assets/models/asset.py:225 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 @@ -737,7 +738,7 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:196 +#: assets/forms/asset.py:83 assets/models/asset.py:195 #: 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 @@ -748,7 +749,7 @@ msgstr "最新版本的不能被删除" msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:86 assets/models/asset.py:200 +#: assets/forms/asset.py:86 assets/models/asset.py:199 #: 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 @@ -766,7 +767,7 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:92 assets/models/asset.py:195 +#: assets/forms/asset.py:92 assets/models/asset.py:194 #: 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 @@ -774,8 +775,8 @@ msgstr "标签" msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:95 assets/models/asset.py:170 -#: assets/models/asset.py:194 assets/serializers/asset.py:67 +#: assets/forms/asset.py:95 assets/models/asset.py:169 +#: assets/models/asset.py:193 assets/serializers/asset.py:67 #: assets/templates/assets/asset_detail.html:100 #: assets/templates/assets/user_asset_list.html:78 msgid "Platform" @@ -980,24 +981,24 @@ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" msgid "Username is dynamic, When connect asset, using current user's username" msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" -#: assets/models/asset.py:147 xpack/plugins/cloud/providers/base.py:16 +#: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:16 msgid "Base" msgstr "基础" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:149 assets/templates/assets/platform_detail.html:60 +#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 #: tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" -#: assets/models/asset.py:150 +#: assets/models/asset.py:149 msgid "Internal" msgstr "内部的" -#: assets/models/asset.py:187 assets/models/domain.py:49 +#: assets/models/asset.py:186 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 @@ -1014,7 +1015,7 @@ msgstr "内部的" msgid "IP" msgstr "IP" -#: assets/models/asset.py:188 assets/serializers/asset_user.py:45 +#: assets/models/asset.py:187 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 @@ -1031,7 +1032,7 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:191 assets/models/domain.py:51 +#: assets/models/asset.py:190 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 @@ -1043,84 +1044,84 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:193 assets/serializers/asset.py:69 +#: assets/models/asset.py:192 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:197 assets/models/cmd_filter.py:22 +#: assets/models/asset.py:196 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:203 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 msgid "Model" msgstr "型号" -#: assets/models/asset.py:209 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:211 +#: assets/models/asset.py:210 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:212 +#: assets/models/asset.py:211 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:213 +#: assets/models/asset.py:212 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:214 +#: assets/models/asset.py:213 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:216 +#: assets/models/asset.py:215 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:217 +#: assets/models/asset.py:216 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:219 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:220 +#: assets/models/asset.py:219 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:221 +#: assets/models/asset.py:220 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:222 +#: assets/models/asset.py:221 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:224 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1449,8 +1450,7 @@ msgid "SFTP Root" msgstr "SFTP根路径" #: assets/models/user.py:195 assets/templates/assets/system_user_list.html:73 -#: assets/templates/assets/user_asset_list.html:79 audits/models.py:21 -#: audits/templates/audits/ftp_log_list.html:53 +#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:53 #: audits/templates/audits/ftp_log_list.html:76 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/models/asset_permission.py:82 @@ -1671,7 +1671,7 @@ msgstr "定期测试系统用户可连接性: {}" #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" -msgstr "资产或许不支持ansible, 跳过: {}" +msgstr "资产已经被禁用, 跳过: {}" #: assets/tasks/utils.py:21 msgid "Asset may not be support ansible, skipped: {}" @@ -1771,7 +1771,7 @@ msgstr "获取认证信息错误" #: assets/templates/assets/_asset_user_auth_view_modal.html:101 #: assets/templates/assets/_node_detail_modal.html:67 #: assets/templates/assets/_user_asset_detail_modal.html:23 -#: authentication/templates/authentication/_access_key_modal.html:142 +#: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 #: settings/templates/settings/_ldap_list_users_modal.html:171 #: templates/_modal.html:22 tickets/models/ticket.py:68 @@ -1817,6 +1817,7 @@ msgid "Push" msgstr "推送" #: assets/templates/assets/_asset_user_list.html:167 +#: authentication/templates/authentication/_access_key_modal.html:147 msgid "Delete success" msgstr "删除成功" @@ -2769,7 +2770,7 @@ msgid "" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" #: authentication/errors.py:48 users/views/profile/otp.py:63 -#: users/views/profile/otp.py:100 users/views/profile/otp.py:116 +#: users/views/profile/otp.py:102 users/views/profile/otp.py:121 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -2881,8 +2882,8 @@ msgid "More login options" msgstr "更多登录方式" #: authentication/templates/authentication/login.html:61 -msgid "Keycloak" -msgstr "" +msgid "OpenID" +msgstr "OpenID" #: authentication/templates/authentication/login_otp.html:17 msgid "One-time password" @@ -5598,7 +5599,7 @@ msgstr "上一步" #: users/templates/users/first_login_done.html:31 msgid "Welcome to use jumpserver, visit " -msgstr "欢迎使用JumpServer开源跳板机系统" +msgstr "欢迎使用 JumpServer 堡垒机" #: users/templates/users/first_login_done.html:32 msgid "Use guide" @@ -6183,19 +6184,19 @@ msgstr "首次登录" msgid "Profile setting" msgstr "个人信息设置" -#: users/views/profile/otp.py:144 +#: users/views/profile/otp.py:145 msgid "MFA enable success" msgstr "多因子认证启用成功" -#: users/views/profile/otp.py:145 +#: users/views/profile/otp.py:146 msgid "MFA enable success, return login page" msgstr "多因子认证启用成功,返回到登录页面" -#: users/views/profile/otp.py:147 +#: users/views/profile/otp.py:148 msgid "MFA disable success" msgstr "多因子认证禁用成功" -#: users/views/profile/otp.py:148 +#: users/views/profile/otp.py:149 msgid "MFA disable success, return login page" msgstr "多因子认证禁用成功,返回登录页面" @@ -6587,11 +6588,15 @@ msgstr "华南-广州" #: xpack/plugins/cloud/providers/huaweicloud.py:30 msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" +msgstr "西南-贵阳一" #: xpack/plugins/cloud/providers/huaweicloud.py:31 msgid "EU-Paris" -msgstr "" +msgstr "欧洲-巴黎" + +#: xpack/plugins/cloud/providers/huaweicloud.py:32 +msgid "LA-Santiago" +msgstr "拉美-圣地亚哥" #: xpack/plugins/cloud/providers/qcloud.py:17 msgid "Tencent Cloud" @@ -6989,9 +6994,6 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" -#~ msgid "LA-Santiago" -#~ msgstr "拉美-圣地亚哥" - #~ msgid "Total hosts" #~ msgstr "主机总数" diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index afdd659fc..8d9e18e24 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -19,10 +19,13 @@ class AdHocExecutionSerializer(serializers.ModelSerializer): @staticmethod def get_stat(obj): + count_failed_hosts = len(obj.failed_hosts) + count_success_hosts = len(obj.success_hosts) + count_total = count_success_hosts + count_failed_hosts return { - "total": obj.hosts_amount, - "success": len(obj.summary.get("contacted", [])), - "failed": len(obj.summary.get("dark", [])), + "total": count_total, + "success": count_success_hosts, + "failed": count_failed_hosts } def get_field_names(self, declared_fields, info): diff --git a/apps/perms/apps.py b/apps/perms/apps.py index d40373e08..5bb7420bb 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -5,3 +5,7 @@ from django.apps import AppConfig class PermsConfig(AppConfig): name = 'perms' + + def ready(self): + super().ready() + from . import signals_handler diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html index bb9ca375a..088d80c75 100644 --- a/apps/perms/templates/perms/asset_permission_user.html +++ b/apps/perms/templates/perms/asset_permission_user.html @@ -218,7 +218,7 @@ function addGroups(groupsId) { } function removeGroup(groupId) { - var theUrl = "{% url 'api-perms:asset-permissions-user-groups-relation-list' %}?assetpermission={{ object.id }}"; + var theUrl = "{% url 'api-perms:asset-permissions-user-groups-relation-list' %}?assetpermission={{ object.id }}&usergroup=groupId"; theUrl = theUrl.replace("groupId", groupId); var success = function(data) { location.reload(); diff --git a/apps/perms/templates/perms/database_app_permission_user.html b/apps/perms/templates/perms/database_app_permission_user.html index 603f60a6e..8b109fa1a 100644 --- a/apps/perms/templates/perms/database_app_permission_user.html +++ b/apps/perms/templates/perms/database_app_permission_user.html @@ -218,7 +218,7 @@ function addGroups(groupsId) { } function removeGroup(groupId) { - var theUrl = "{% url 'api-perms:database-app-permissions-user-groups-relation-list' %}?databaseapppermission={{ object.id }}"; + var theUrl = "{% url 'api-perms:database-app-permissions-user-groups-relation-list' %}?databaseapppermission={{ object.id }}&usergroup=groupId"; theUrl = theUrl.replace("groupId", groupId); var success = function(data) { location.reload(); diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html index d222dfb7c..9fa623585 100644 --- a/apps/perms/templates/perms/remote_app_permission_user.html +++ b/apps/perms/templates/perms/remote_app_permission_user.html @@ -179,9 +179,13 @@ var body = { user_groups: groups }; + var success = function(data) { + location.reload(); + }; requestApi({ url: the_url, - body: JSON.stringify(body) + body: JSON.stringify(body), + success: success }); } $(document).ready(function () { diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index cd6e1805e..80b1c19e5 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -477,6 +477,7 @@ class ParserNode: 'platform': asset.platform_base, 'domain': asset.domain_id, 'org_name': asset.org_name, + 'org_id': asset.org_id }, } } diff --git a/apps/templates/index.html b/apps/templates/index.html index 36f57137c..1942ad2cb 100644 --- a/apps/templates/index.html +++ b/apps/templates/index.html @@ -50,7 +50,7 @@
-

+

Online sessions
@@ -422,7 +422,7 @@ function renderTotalCount(){ $('#total_count_assets').html(data['total_count_assets']); $('#total_count_users').html(data['total_count_users']); $('#total_count_online_users').html(data['total_count_online_users']); - $('#total_count_online_assets').html(data['total_count_online_assets']); + $('#total_count_online_sessions').html(data['total_count_online_sessions']); }; renderRequestApi('total_count=1', success); } 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/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/views/profile/otp.py b/apps/users/views/profile/otp.py index 2d823f5ab..83918114e 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -83,26 +83,12 @@ class UserOtpEnableBindView(TemplateView, FormView): return super().get_context_data(**kwargs) -class UserVerifyMFAView(FormView): +class UserDisableMFAView(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') @@ -118,8 +104,23 @@ class UserDisableMFAView(UserVerifyMFAView): return super().form_invalid(form) -class UserOtpUpdateView(UserVerifyMFAView): +class UserOtpUpdateView(FormView): + template_name = 'users/user_verify_mfa.html' + form_class = forms.UserCheckOtpCodeForm success_url = reverse_lazy('users:user-otp-enable-bind') + 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 UserOtpSettingsSuccessView(TemplateView): diff --git a/apps/users/views/profile/password.py b/apps/users/views/profile/password.py index c9bb97f38..bb7caa9a1 100644 --- a/apps/users/views/profile/password.py +++ b/apps/users/views/profile/password.py @@ -67,7 +67,7 @@ class UserVerifyPasswordView(FormView): def form_valid(self, form): user = get_user_or_pre_auth_user(self.request) password = form.cleaned_data.get('password') - user = authenticate(username=user.username, password=password) + user = authenticate(request=self.request, username=user.username, password=password) if not user: form.add_error("password", _("Password invalid")) return self.form_invalid(form) diff --git a/config_example.yml b/config_example.yml index 5699f4ff0..fd8710c78 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,28 @@ 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 +# BASE_SITE_URL: None # 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 f459ae2fa..59e86f1ac 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.4