mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-16 15:28:38 +00:00
1.5.7 Merge to dev (#3766)
* [Update] 暂存,优化解决不了问题 * [Update] 待续(小白) * [Update] 修改asset user * [Update] 计划再次更改 * [Update] 修改asset user * [Update] 暂存与喜爱 * [Update] Add id in * [Update] 阶段性完成ops task该做 * [Update] 修改asset user api * [Update] 修改asset user 任务,查看认证等 * [Update] 基本完成asset user改造 * [Update] dynamic user only allow 1 * [Update] 修改asset user task * [Update] 修改node admin user task api * [Update] remove file header license * [Update] 添加sftp root * [Update] 暂存 * [Update] 暂存 * [Update] 修改翻译 * [Update] 修改系统用户改为同名后,用户名改为空 * [Update] 基本完成CAS调研 * [Update] 支持cas server * [Update] 支持cas server * [Update] 添加requirements * [Update] 为方便调试添加mysql ipython到包中 * [Update] 添加huaweiyun翻译 * [Update] 增加下载session 录像 * [Update] 只有第一次通知replay离线的使用方法 * [Update] 暂存一下 * [Bugfix] 获取系统用户信息报错 * [Bugfix] 修改system user info * [Update] 改成清理10天git status * [Update] 修改celery日志保留时间 * [Update]修复部分pip包依赖的版本不兼容问题 (#3672) * [Update] 修复用户更新页面会清空用户public_key的问题 * Fix broken dependencies Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> * [Update] 修改获取系统用户auth info * [Update] Remove log * [Bugfix] 修复sftp home设置的bug * [Update] 授权的系统用户添加sftp root * [Update] 修改系统用户关联的用户 * [Update] 修改placeholder * [Update] 优化获取授权的系统用户 * [Update] 修改tasks * [Update] tree service update * [Update] 暂存 * [Update] 基本完成用户授权树和资产树改造 * [Update] Dashbaord perf * [update] Add huawei cloud sdk requirements * [Updte] 优化dashboard页面 * [Update] system user auth info 添加id * [Update] 修改系统用户serializer * [Update] 优化api * [Update] LDAP Test Util (#3720) * [Update] LDAPTestUtil 1 * [Update] LDAPTestUtil 2 * [Update] LDAPTestUtil 3 * [Update] LDAPTestUtil 4 * [Update] LDAPTestUtil 5 * [Update] LDAPTestUtil 6 * [Update] LDAPTestUtil 7 * [Update] session 已添加is success,并且添加display serializer * [Bugfix] 修复无法删除空节点的bug * [Update] 命令记录分组织显示 * [Update] Session is_success 添加迁移文件 * [Update] 批量命令添加org_id * [Update] 修复一些文案,修改不绑定MFA,不能ssh登录 * [Update] 修改replay api, 返回session信息 * [Update] 解决无效es导致访问命令记录页面失败的问题 * [Update] 拆分profile view * [Update] 修改一个翻译 * [Update] 修改aysnc api框架 * [Update] 命令列表添加risk level * [Update] 完成录像打包下载 * [Update] 更改登陆otp页面 * [Update] 修改command 存储redis_level * [Update] 修改翻译 * [Update] 修改系统用户的用户列表字段 * [Update] 使用新logo和统一Jumpserver为JumpServer * [Update] 优化cloud task * [Update] 统一period task * [Update] 统一period form serializer字段 * [Update] 修改period task * [Update] 修改资产网关信息 * [Update] 用户授权资产树资产信息添加domain * [Update] 修改翻译 * [Update] 测试可连接性 * 1.5.7 bai (#3764) * [Update] 修复index页面Bug;修复测试资产用户可连接性问题; * [Update] 修改测试资产用户可连接 * [Bugfix] 修复backends问题 * [Update] 修改marksafe依赖版本 * [Update] 修改测试资产用户可连接性 * [Update] 修改检测服务器性能时获取percent值 * [Update] 更新依赖boto3=1.12.14 Co-authored-by: Yanzhe Lee <lee.yanzhe@yanzhe.org> Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Co-authored-by: Bai <bugatti_it@163.com>
This commit is contained in:
4
apps/authentication/backends/cas/__init__.py
Normal file
4
apps/authentication/backends/cas/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .backends import *
|
||||
from .callback import *
|
11
apps/authentication/backends/cas/backends.py
Normal file
11
apps/authentication/backends/cas/backends.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django_cas_ng.backends import CASBackend as _CASBackend
|
||||
|
||||
|
||||
__all__ = ['CASBackend']
|
||||
|
||||
|
||||
class CASBackend(_CASBackend):
|
||||
def user_can_authenticate(self, user):
|
||||
return True
|
16
apps/authentication/backends/cas/callback.py
Normal file
16
apps/authentication/backends/cas/callback.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def cas_callback(response):
|
||||
username = response['username']
|
||||
user, user_created = User.objects.get_or_create(username=username)
|
||||
profile, created = user.get_profile()
|
||||
|
||||
profile.role = response['attributes']['role']
|
||||
profile.birth_date = response['attributes']['birth_date']
|
||||
profile.save()
|
11
apps/authentication/backends/cas/urls.py
Normal file
11
apps/authentication/backends/cas/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
import django_cas_ng.views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', django_cas_ng.views.LoginView.as_view(), name='cas-login'),
|
||||
path('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'),
|
||||
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
|
||||
]
|
@@ -29,26 +29,27 @@ class LDAPAuthorizationBackend(LDAPBackend):
|
||||
|
||||
def pre_check(self, username, password):
|
||||
if not settings.AUTH_LDAP:
|
||||
return False
|
||||
logger.info('Authentication LDAP backend')
|
||||
error = 'Not enabled auth ldap'
|
||||
return False, error
|
||||
if not username:
|
||||
logger.info('Authenticate failed: username is None')
|
||||
return False
|
||||
error = 'Username is None'
|
||||
return False, error
|
||||
if not password:
|
||||
logger.info('Authenticate failed: password is None')
|
||||
return False
|
||||
error = 'Password is None'
|
||||
return False, error
|
||||
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
|
||||
user_model = self.get_user_model()
|
||||
exist = user_model.objects.filter(username=username).exists()
|
||||
if not exist:
|
||||
msg = 'Authentication failed: user ({}) is not in the user list'
|
||||
logger.info(msg.format(username))
|
||||
return False
|
||||
return True
|
||||
error = 'user ({}) is not in the user list'.format(username)
|
||||
return False, error
|
||||
return True, ''
|
||||
|
||||
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||
match = self.pre_check(username, password)
|
||||
logger.info('Authentication LDAP backend')
|
||||
match, msg = self.pre_check(username, password)
|
||||
if not match:
|
||||
logger.info('Authenticate failed: {}'.format(msg))
|
||||
return None
|
||||
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||
user = self.authenticate_ldap_user(ldap_user, password)
|
||||
@@ -130,5 +131,5 @@ class LDAPUser(_LDAPUser):
|
||||
setattr(self._user, field, value)
|
||||
|
||||
email = getattr(self._user, 'email', '')
|
||||
email = construct_user_email(email, self._user.username)
|
||||
email = construct_user_email(self._user.username, email)
|
||||
setattr(self._user, 'email', email)
|
||||
|
@@ -19,7 +19,7 @@ class PublicKeyAuthBackend:
|
||||
return None
|
||||
else:
|
||||
if user.check_public_key(public_key) and \
|
||||
self.user_can_authenticate(user):
|
||||
self.user_can_authenticate(user):
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
|
@@ -11,6 +11,7 @@ from users.utils import (
|
||||
|
||||
reason_password_failed = 'password_failed'
|
||||
reason_mfa_failed = 'mfa_failed'
|
||||
reason_mfa_unset = 'mfa_unset'
|
||||
reason_user_not_exist = 'user_not_exist'
|
||||
reason_password_expired = 'password_expired'
|
||||
reason_user_invalid = 'user_invalid'
|
||||
@@ -18,7 +19,8 @@ reason_user_inactive = 'user_inactive'
|
||||
|
||||
reason_choices = {
|
||||
reason_password_failed: _('Username/password check failed'),
|
||||
reason_mfa_failed: _('MFA authentication failed'),
|
||||
reason_mfa_failed: _('MFA failed'),
|
||||
reason_mfa_unset: _('MFA unset'),
|
||||
reason_user_not_exist: _("Username does not exist"),
|
||||
reason_password_expired: _("Password expired"),
|
||||
reason_user_invalid: _('Disabled or expired'),
|
||||
@@ -46,6 +48,7 @@ block_login_msg = _(
|
||||
mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
|
||||
|
||||
mfa_required_msg = _("MFA required")
|
||||
mfa_unset_msg = _("MFA not set, please set it first")
|
||||
login_confirm_required_msg = _("Login confirm required")
|
||||
login_confirm_wait_msg = _("Wait login confirm ticket for accept")
|
||||
login_confirm_error_msg = _("Login confirm ticket was {}")
|
||||
@@ -116,6 +119,16 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||
super().__init__(username=username, request=request)
|
||||
|
||||
|
||||
class MFAUnsetError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||
error = reason_mfa_unset
|
||||
msg = mfa_unset_msg
|
||||
|
||||
def __init__(self, user, request, url):
|
||||
super().__init__(username=user.username, request=request)
|
||||
self.user = user
|
||||
self.url = url
|
||||
|
||||
|
||||
class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
|
||||
error = 'block_login'
|
||||
|
||||
|
@@ -6,7 +6,7 @@ from django.conf import settings
|
||||
from common.utils import get_object_or_none, get_request_ip, get_logger
|
||||
from users.models import User
|
||||
from users.utils import (
|
||||
is_block_login, clean_failed_count, increase_login_failed_count,
|
||||
is_block_login, clean_failed_count
|
||||
)
|
||||
from . import errors
|
||||
from .utils import check_user_valid
|
||||
@@ -91,8 +91,9 @@ class AuthMixin:
|
||||
return
|
||||
if not user.mfa_enabled:
|
||||
return
|
||||
if not user.otp_secret_key and user.mfa_is_otp():
|
||||
return
|
||||
unset, url = user.mfa_enabled_but_not_set()
|
||||
if unset:
|
||||
raise errors.MFAUnsetError(user, self.request, url)
|
||||
raise errors.MFARequiredError()
|
||||
|
||||
def check_user_mfa(self, code):
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<label for="mfa" class="col-sm-2 control-label">{% trans 'MFA' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="mfa" class="form-control input-sm" name="mfa">
|
||||
<span id="mfa_error" class="help-block">{% trans "Need otp auth for view auth" %}</span>
|
||||
<span id="mfa_error" class="help-block">{% trans "Need MFA for view auth" %}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
|
@@ -1,116 +1,67 @@
|
||||
{% extends '_base_only_msg_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jumpserver</title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
{% block content_title %}
|
||||
{% trans 'Login' %}
|
||||
{% endblock %}
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<h2 class="font-bold" style="text-align: center">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
<p>
|
||||
{% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Changes the world, starting with a little bit." %}
|
||||
</p>
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div style="line-height: 17px;">
|
||||
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{{ LOGO_URL }}" width="60" height="60">
|
||||
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'Login' %}</span>
|
||||
</div>
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div style="line-height: 17px;">
|
||||
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
||||
</div>
|
||||
{% elif form.errors.captcha %}
|
||||
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
||||
{% if form.errors.username %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.username.as_text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
{% if form.errors.password %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
|
||||
|
||||
{% if demo_mode %}
|
||||
<p class="text-muted font-bold" style="color: red">
|
||||
Demo账号: admin 密码: admin
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-muted text-center">
|
||||
<div>
|
||||
<a href="{% url 'users:forgot-password' %}">
|
||||
<small>{% trans 'Forgot password' %}?</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if AUTH_OPENID %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<p class="text-muted text-center">{% trans "More login options" %}</p>
|
||||
<div>
|
||||
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid:openid-login' %}'">
|
||||
<i class="fa fa-openid"></i>
|
||||
{% trans 'Keycloak' %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
{% elif form.errors.captcha %}
|
||||
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
||||
{% if form.errors.username %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.username.as_text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
{% if form.errors.password %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
|
||||
|
||||
{% if demo_mode %}
|
||||
<p class="text-muted font-bold" style="color: red">
|
||||
Demo账号: admin 密码: admin
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-muted text-center">
|
||||
<div>
|
||||
<a href="{% url 'users:forgot-password' %}">
|
||||
<small>{% trans 'Forgot password' %}?</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
|
||||
{% if AUTH_OPENID %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<p class="text-muted text-center">{% trans "More login options" %}</p>
|
||||
<div>
|
||||
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid:openid-login' %}'">
|
||||
<i class="fa fa-openid"></i>
|
||||
{% trans 'Keycloak' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@@ -1,88 +1,32 @@
|
||||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> {{ JMS_TITLE }} </title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
{% block title %}
|
||||
{% trans 'MFA' %}
|
||||
{% endblock %}
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
<p>
|
||||
{% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Changes the world, starting with a little bit." %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{% static 'img/logo.png' %}" width="60" height="60">
|
||||
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'MFA certification' %}</span>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
|
||||
<div class="form-group">
|
||||
<p style="margin:30px auto;" class="text-center"><strong style="color:#000000">{% trans 'The account protection has been opened, please complete the following operations according to the prompts' %}</strong></p>
|
||||
<div class="text-center">
|
||||
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
|
||||
</div>
|
||||
<p style="margin: 30px auto"> {% trans 'Open Authenticator and enter the 6-bit dynamic code' %}</p>
|
||||
</div>
|
||||
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
|
||||
{% csrf_token %}
|
||||
{% if 'otp_code' in form.errors %}
|
||||
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
|
||||
<a href="#">
|
||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<p class="m-t">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if 'otp_code' in form.errors %}
|
||||
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<select class="form-control">
|
||||
<option value="otp" selected>{% trans 'One-time password' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="" required="" autofocus="autofocus">
|
||||
<span class="help-block">
|
||||
{% trans 'Open Google Authenticator and enter the 6-bit dynamic code' %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
|
||||
<div>
|
||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.urls import path, include
|
||||
|
||||
from .. import views
|
||||
@@ -10,13 +8,14 @@ from .. import views
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
# openid
|
||||
path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')),
|
||||
|
||||
# login
|
||||
path('login/', views.UserLoginView.as_view(), name='login'),
|
||||
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
|
||||
path('login/wait-confirm/', views.UserLoginWaitConfirmView.as_view(), name='login-wait-confirm'),
|
||||
path('login/guard/', views.UserLoginGuardView.as_view(), name='login-guard'),
|
||||
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')),
|
||||
]
|
||||
|
@@ -20,7 +20,7 @@ from django.urls import reverse_lazy
|
||||
|
||||
from common.utils import get_request_ip, get_object_or_none
|
||||
from users.utils import (
|
||||
redirect_user_first_login_or_index, set_tmp_user_to_cache
|
||||
redirect_user_first_login_or_index
|
||||
)
|
||||
from .. import forms, mixins, errors
|
||||
|
||||
@@ -52,17 +52,29 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||
template_name = 'authentication/xpack_login.html'
|
||||
return template_name
|
||||
|
||||
def get_redirect_url_if_need(self, request):
|
||||
redirect_url = ''
|
||||
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
||||
if self.request.GET.get("admin", 0):
|
||||
return None
|
||||
if settings.AUTH_OPENID:
|
||||
redirect_url = reverse("authentication:openid:openid-login")
|
||||
elif settings.AUTH_CAS:
|
||||
redirect_url = reverse(settings.CAS_LOGIN_URL_NAME)
|
||||
|
||||
if redirect_url:
|
||||
query_string = request.GET.urlencode()
|
||||
redirect_url = "{}?{}".format(redirect_url, query_string)
|
||||
return redirect_url
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(redirect_user_first_login_or_index(
|
||||
request, self.redirect_field_name)
|
||||
)
|
||||
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
||||
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
|
||||
query_string = request.GET.urlencode()
|
||||
openid_login_url = reverse_lazy("authentication:openid:openid-login")
|
||||
login_url = "{}?{}".format(openid_login_url, query_string)
|
||||
return redirect(login_url)
|
||||
redirect_url = self.get_redirect_url_if_need(request)
|
||||
if redirect_url:
|
||||
return redirect(redirect_url)
|
||||
request.session.set_test_cookie()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@@ -127,12 +139,9 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
||||
return self.format_redirect_url(self.login_otp_url)
|
||||
except errors.LoginConfirmBaseError:
|
||||
return self.format_redirect_url(self.login_confirm_url)
|
||||
except errors.MFAUnsetError as e:
|
||||
return e.url
|
||||
else:
|
||||
# 启用但是没有设置otp, 排除radius
|
||||
if user.mfa_enabled_but_not_set():
|
||||
# 1,2,mfa_setting & F
|
||||
set_tmp_user_to_cache(self.request, user)
|
||||
return reverse('users:user-otp-enable-authentication')
|
||||
auth_login(self.request, user)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
self.clear_auth_mark()
|
||||
@@ -174,8 +183,17 @@ class UserLoginWaitConfirmView(TemplateView):
|
||||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
@staticmethod
|
||||
def get_backend_logout_url():
|
||||
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)
|
||||
|
Reference in New Issue
Block a user