mirror of https://github.com/jumpserver/jumpserver
[Update] 修改mfa
parent
bfd8a9c66d
commit
c9ee8edeaf
|
@ -31,11 +31,11 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
|
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
|
||||||
<div class="checkbox checkbox-success">
|
<div class="checkbox checkbox-success">
|
||||||
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-MFA' %}</label>
|
<input type="checkbox" name="enable_mfa" checked id="id_enable_mfa"><label for="id_enable_mfa">{% trans 'Enable-MFA' %}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %}
|
{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %}
|
||||||
|
|
|
@ -128,7 +128,7 @@ def generate_data(username, request):
|
||||||
def on_user_auth_success(sender, user, request, **kwargs):
|
def on_user_auth_success(sender, user, request, **kwargs):
|
||||||
logger.debug('User login success: {}'.format(user.username))
|
logger.debug('User login success: {}'.format(user.username))
|
||||||
data = generate_data(user.username, request)
|
data = generate_data(user.username, request)
|
||||||
data.update({'mfa': int(user.otp_enabled), 'status': True})
|
data.update({'mfa': int(user.mfa_enabled), 'status': True})
|
||||||
write_login_log_async.delay(**data)
|
write_login_log_async.delay(**data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,9 @@ class AuthMixin:
|
||||||
def check_user_mfa_if_need(self, user):
|
def check_user_mfa_if_need(self, user):
|
||||||
if self.request.session.get('auth_mfa'):
|
if self.request.session.get('auth_mfa'):
|
||||||
return
|
return
|
||||||
if not user.otp_enabled or not user.otp_secret_key:
|
if not user.mfa_enabled:
|
||||||
|
return
|
||||||
|
if not user.otp_secret_key and user.mfa_is_otp():
|
||||||
return
|
return
|
||||||
raise errors.MFARequiredError()
|
raise errors.MFARequiredError()
|
||||||
|
|
||||||
|
|
|
@ -130,8 +130,8 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
||||||
auth_login(self.request, user)
|
auth_login(self.request, user)
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
self.clear_auth_mark()
|
self.clear_auth_mark()
|
||||||
# 启用但是没有设置otp
|
# 启用但是没有设置otp, 排除radius
|
||||||
if user.otp_enabled and not user.otp_secret_key:
|
if user.mfa_enabled_but_not_set():
|
||||||
# 1,2,mfa_setting & F
|
# 1,2,mfa_setting & F
|
||||||
return reverse('users:user-otp-enable-authentication')
|
return reverse('users:user-otp-enable-authentication')
|
||||||
url = redirect_user_first_login_or_index(
|
url = redirect_user_first_login_or_index(
|
||||||
|
|
|
@ -172,7 +172,7 @@ class UserResetOTPApi(UserQuerysetMixin, generics.RetrieveAPIView):
|
||||||
if user == request.user:
|
if user == request.user:
|
||||||
msg = _("Could not reset self otp, use profile reset instead")
|
msg = _("Could not reset self otp, use profile reset instead")
|
||||||
return Response({"error": msg}, status=401)
|
return Response({"error": msg}, status=401)
|
||||||
if user.otp_enabled and user.otp_secret_key:
|
if user.mfa_enabled and user.otp_secret_key:
|
||||||
user.otp_secret_key = ''
|
user.otp_secret_key = ''
|
||||||
user.save()
|
user.save()
|
||||||
logout(request)
|
logout(request)
|
||||||
|
|
|
@ -61,10 +61,10 @@ class UserCreateUpdateFormMixin(OrgModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'username', 'name', 'email', 'groups', 'wechat',
|
'username', 'name', 'email', 'groups', 'wechat',
|
||||||
'source', 'phone', 'role', 'date_expired',
|
'source', 'phone', 'role', 'date_expired',
|
||||||
'comment', 'otp_level'
|
'comment', 'mfa_level'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'otp_level': forms.RadioSelect(),
|
'mfa_level': forms.RadioSelect(),
|
||||||
'groups': forms.SelectMultiple(
|
'groups': forms.SelectMultiple(
|
||||||
attrs={
|
attrs={
|
||||||
'class': 'select2',
|
'class': 'select2',
|
||||||
|
@ -126,13 +126,13 @@ class UserCreateUpdateFormMixin(OrgModelForm):
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
password = self.cleaned_data.get('password')
|
password = self.cleaned_data.get('password')
|
||||||
otp_level = self.cleaned_data.get('otp_level')
|
mfa_level = self.cleaned_data.get('mfa_level')
|
||||||
public_key = self.cleaned_data.get('public_key')
|
public_key = self.cleaned_data.get('public_key')
|
||||||
user = super().save(commit=commit)
|
user = super().save(commit=commit)
|
||||||
if password:
|
if password:
|
||||||
user.reset_password(password)
|
user.reset_password(password)
|
||||||
if otp_level:
|
if mfa_level:
|
||||||
user.otp_level = otp_level
|
user.mfa_level = mfa_level
|
||||||
user.save()
|
user.save()
|
||||||
if public_key:
|
if public_key:
|
||||||
user.public_key = public_key
|
user.public_key = public_key
|
||||||
|
@ -183,10 +183,10 @@ class UserMFAForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['otp_level']
|
fields = ['mfa_level']
|
||||||
widgets = {'otp_level': forms.RadioSelect()}
|
widgets = {'mfa_level': forms.RadioSelect()}
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'otp_level': _('* Enable MFA authentication '
|
'mfa_level': _('* Enable MFA authentication '
|
||||||
'to make the account more secure.'),
|
'to make the account more secure.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.5 on 2019-11-18 08:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0023_auto_20190724_1525'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='user',
|
||||||
|
old_name='otp_level',
|
||||||
|
new_name='mfa_level',
|
||||||
|
),
|
||||||
|
]
|
|
@ -346,35 +346,41 @@ class TokenMixin:
|
||||||
|
|
||||||
|
|
||||||
class MFAMixin:
|
class MFAMixin:
|
||||||
otp_level = 0
|
mfa_level = 0
|
||||||
otp_secret_key = ''
|
otp_secret_key = ''
|
||||||
OTP_LEVEL_CHOICES = (
|
MFA_LEVEL_CHOICES = (
|
||||||
(0, _('Disable')),
|
(0, _('Disable')),
|
||||||
(1, _('Enable')),
|
(1, _('Enable')),
|
||||||
(2, _("Force enable")),
|
(2, _("Force enable")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def otp_enabled(self):
|
def mfa_enabled(self):
|
||||||
return self.otp_force_enabled or self.otp_level > 0
|
return self.mfa_force_enabled or self.mfa_level > 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def otp_force_enabled(self):
|
def mfa_force_enabled(self):
|
||||||
if settings.SECURITY_MFA_AUTH:
|
if settings.SECURITY_MFA_AUTH:
|
||||||
return True
|
return True
|
||||||
return self.otp_level == 2
|
return self.mfa_level == 2
|
||||||
|
|
||||||
def enable_otp(self):
|
def enable_mfa(self):
|
||||||
if not self.otp_level == 2:
|
if not self.mfa_level == 2:
|
||||||
self.otp_level = 1
|
self.mfa_level = 1
|
||||||
|
|
||||||
def force_enable_otp(self):
|
def force_enable_mfa(self):
|
||||||
self.otp_level = 2
|
self.mfa_level = 2
|
||||||
|
|
||||||
def disable_otp(self):
|
def disable_mfa(self):
|
||||||
self.otp_level = 0
|
self.mfa_level = 0
|
||||||
self.otp_secret_key = None
|
self.otp_secret_key = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mfa_is_otp():
|
||||||
|
if settings.CONFIG.OTP_IN_RADIUS:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def check_otp_on_radius(self, code):
|
def check_otp_on_radius(self, code):
|
||||||
from authentication.backends.radius import RadiusBackend
|
from authentication.backends.radius import RadiusBackend
|
||||||
backend = RadiusBackend()
|
backend = RadiusBackend()
|
||||||
|
@ -390,6 +396,11 @@ class MFAMixin:
|
||||||
else:
|
else:
|
||||||
return check_otp_code(self.otp_secret_key, code)
|
return check_otp_code(self.otp_secret_key, code)
|
||||||
|
|
||||||
|
def mfa_enabled_but_not_set(self):
|
||||||
|
if self.mfa_enabled and self.mfa_is_otp() and not self.otp_secret_key:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||||
SOURCE_LOCAL = 'local'
|
SOURCE_LOCAL = 'local'
|
||||||
|
@ -428,8 +439,8 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||||
phone = models.CharField(
|
phone = models.CharField(
|
||||||
max_length=20, blank=True, null=True, verbose_name=_('Phone')
|
max_length=20, blank=True, null=True, verbose_name=_('Phone')
|
||||||
)
|
)
|
||||||
otp_level = models.SmallIntegerField(
|
mfa_level = models.SmallIntegerField(
|
||||||
default=0, choices=MFAMixin.OTP_LEVEL_CHOICES, verbose_name=_('MFA')
|
default=0, choices=MFAMixin.MFA_LEVEL_CHOICES, verbose_name=_('MFA')
|
||||||
)
|
)
|
||||||
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
|
otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True)
|
||||||
# Todo: Auto generate key, let user download
|
# Todo: Auto generate key, let user download
|
||||||
|
|
|
@ -27,7 +27,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'username', 'password', 'email', 'public_key',
|
'id', 'name', 'username', 'password', 'email', 'public_key',
|
||||||
'groups', 'groups_display',
|
'groups', 'groups_display',
|
||||||
'role', 'role_display', 'wechat', 'phone', 'otp_level',
|
'role', 'role_display', 'wechat', 'phone', 'mfa_level',
|
||||||
'comment', 'source', 'source_display', 'is_valid', 'is_expired',
|
'comment', 'source', 'source_display', 'is_valid', 'is_expired',
|
||||||
'is_active', 'created_by', 'is_first_login',
|
'is_active', 'created_by', 'is_first_login',
|
||||||
'date_password_last_updated', 'date_expired', 'avatar_url',
|
'date_password_last_updated', 'date_expired', 'avatar_url',
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
<h3>{% trans 'Auth' %}</h3>
|
||||||
{% block password %}{% endblock %}
|
{% block password %}{% endblock %}
|
||||||
{% bootstrap_field form.otp_level layout="horizontal" %}
|
{% bootstrap_field form.mfa_level layout="horizontal" %}
|
||||||
{% bootstrap_field form.source layout="horizontal" %}
|
{% bootstrap_field form.source layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2();
|
$('.select2').select2();
|
||||||
initDateRangePicker('#id_date_expired');
|
initDateRangePicker('#id_date_expired');
|
||||||
var mfa_radio = $('#id_otp_level');
|
var mfa_radio = $('#id_mfa_level');
|
||||||
mfa_radio.addClass("form-inline");
|
mfa_radio.addClass("form-inline");
|
||||||
mfa_radio.children().css("margin-right","15px");
|
mfa_radio.children().css("margin-right","15px");
|
||||||
fieldDisplay()
|
fieldDisplay()
|
||||||
|
|
|
@ -91,9 +91,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'MFA certification' %}:</td>
|
<td>{% trans 'MFA certification' %}:</td>
|
||||||
<td><b>
|
<td><b>
|
||||||
{% if user_object.otp_force_enabled %}
|
{% if user_object.mfa_force_enabled %}
|
||||||
{% trans 'Force enabled' %}
|
{% trans 'Force enabled' %}
|
||||||
{% elif user_object.otp_enabled%}
|
{% elif user_object.mfa_enabled%}
|
||||||
{% trans 'Enabled' %}
|
{% trans 'Enabled' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Disabled' %}
|
{% trans 'Disabled' %}
|
||||||
|
@ -162,9 +162,9 @@
|
||||||
<td><span class="pull-right">
|
<td><span class="pull-right">
|
||||||
<div class="switch">
|
<div class="switch">
|
||||||
<div class="onoffswitch">
|
<div class="onoffswitch">
|
||||||
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.otp_force_enabled %} checked {% endif %}
|
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.mfa_force_enabled %} checked {% endif %}
|
||||||
id="force_enable_otp">
|
id="force_enable_mfa">
|
||||||
<label class="onoffswitch-label" for="force_enable_otp">
|
<label class="onoffswitch-label" for="force_enable_mfa">
|
||||||
<span class="onoffswitch-inner"></span>
|
<span class="onoffswitch-inner"></span>
|
||||||
<span class="onoffswitch-switch"></span>
|
<span class="onoffswitch-switch"></span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -370,7 +370,7 @@ $(document).ready(function() {
|
||||||
success_message: success
|
success_message: success
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('click', '#force_enable_otp', function() {
|
.on('click', '#force_enable_mfa', function() {
|
||||||
{% if request.user == user_object %}
|
{% if request.user == user_object %}
|
||||||
toastr.error("{% trans 'Goto profile page enable MFA' %}");
|
toastr.error("{% trans 'Goto profile page enable MFA' %}");
|
||||||
return;
|
return;
|
||||||
|
@ -378,16 +378,16 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
||||||
var checked = $(this).prop('checked');
|
var checked = $(this).prop('checked');
|
||||||
var otp_level;
|
var mfa_level;
|
||||||
var otp_secret_key;
|
var otp_secret_key;
|
||||||
if(checked){
|
if(checked){
|
||||||
otp_level = 2
|
mfa_level = 2
|
||||||
}else{
|
}else{
|
||||||
otp_level = 0;
|
mfa_level = 0;
|
||||||
otp_secret_key = '';
|
otp_secret_key = '';
|
||||||
}
|
}
|
||||||
var body = {
|
var body = {
|
||||||
'otp_level': otp_level,
|
'mfa_level': mfa_level,
|
||||||
'otp_secret_key': otp_secret_key
|
'otp_secret_key': otp_secret_key
|
||||||
};
|
};
|
||||||
var success = '{% trans "Update successfully!" %}';
|
var success = '{% trans "Update successfully!" %}';
|
||||||
|
|
|
@ -86,9 +86,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-navy">{% trans 'MFA certification' %}</td>
|
<td class="text-navy">{% trans 'MFA certification' %}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if user.otp_force_enabled %}
|
{% if user.mfa_force_enabled %}
|
||||||
{% trans 'Force enable' %}
|
{% trans 'Force enable' %}
|
||||||
{% elif user.otp_enabled%}
|
{% elif user.mfa_enabled%}
|
||||||
{% trans 'Enable' %}
|
{% trans 'Enable' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Disable' %}
|
{% trans 'Disable' %}
|
||||||
|
@ -158,8 +158,8 @@
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" id=""
|
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" id=""
|
||||||
href="
|
href="
|
||||||
{% if request.user.otp_enabled and request.user.otp_secret_key %}
|
{% if request.user.mfa_enabled and request.user.otp_secret_key %}
|
||||||
{% if request.user.otp_force_enabled %}
|
{% if request.user.mfa_force_enabled %}
|
||||||
" disabled >{% trans 'Disable' %}
|
" disabled >{% trans 'Disable' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'users:user-otp-disable-authentication' %}
|
{% url 'users:user-otp-disable-authentication' %}
|
||||||
|
@ -183,7 +183,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.otp_enabled and request.user.otp_secret_key %}
|
{% if request.user.mfa_enabled and request.user.otp_secret_key %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Update MFA' %}:</td>
|
<td>{% trans 'Update MFA' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -171,12 +171,12 @@ class UserFirstLoginView(PermissionsMixin, SessionWizardView):
|
||||||
form.instance = self.request.user
|
form.instance = self.request.user
|
||||||
|
|
||||||
if isinstance(form, forms.UserMFAForm):
|
if isinstance(form, forms.UserMFAForm):
|
||||||
choices = form.fields["otp_level"].choices
|
choices = form.fields["mfa_level"].choices
|
||||||
if self.request.user.otp_force_enabled:
|
if self.request.user.mfa_force_enabled:
|
||||||
choices = [(k, v) for k, v in choices if k == 2]
|
choices = [(k, v) for k, v in choices if k == 2]
|
||||||
else:
|
else:
|
||||||
choices = [(k, v) for k, v in choices if k in [0, 1]]
|
choices = [(k, v) for k, v in choices if k in [0, 1]]
|
||||||
form.fields["otp_level"].choices = choices
|
form.fields["mfa_level"].choices = choices
|
||||||
form.fields["otp_level"].initial = self.request.user.otp_level
|
form.fields["mfa_level"].initial = self.request.user.mfa_level
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
|
@ -347,7 +347,12 @@ class UserOtpEnableAuthenticationView(FormView):
|
||||||
if not user:
|
if not user:
|
||||||
form.add_error("password", _("Password invalid"))
|
form.add_error("password", _("Password invalid"))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
return redirect(self.get_success_url())
|
if user.mfa_is_otp():
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
else:
|
||||||
|
user.enable_mfa()
|
||||||
|
user.save()
|
||||||
|
return redirect('users:user-otp-settings-success')
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('users:user-otp-enable-install-app')
|
return reverse('users:user-otp-enable-install-app')
|
||||||
|
@ -395,7 +400,7 @@ class UserOtpEnableBindView(TemplateView, FormView):
|
||||||
|
|
||||||
def save_otp(self, otp_secret_key):
|
def save_otp(self, otp_secret_key):
|
||||||
user = get_user_or_tmp_user(self.request)
|
user = get_user_or_tmp_user(self.request)
|
||||||
user.enable_otp()
|
user.enable_mfa()
|
||||||
user.otp_secret_key = otp_secret_key
|
user.otp_secret_key = otp_secret_key
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
@ -411,7 +416,7 @@ class UserOtpDisableAuthenticationView(FormView):
|
||||||
otp_secret_key = user.otp_secret_key
|
otp_secret_key = user.otp_secret_key
|
||||||
|
|
||||||
if check_otp_code(otp_secret_key, otp_code):
|
if check_otp_code(otp_secret_key, otp_code):
|
||||||
user.disable_otp()
|
user.disable_mfa()
|
||||||
user.save()
|
user.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
else:
|
else:
|
||||||
|
@ -447,7 +452,7 @@ class UserOtpSettingsSuccessView(TemplateView):
|
||||||
auth_logout(self.request)
|
auth_logout(self.request)
|
||||||
title = _('MFA enable success')
|
title = _('MFA enable success')
|
||||||
describe = _('MFA enable success, return login page')
|
describe = _('MFA enable success, return login page')
|
||||||
if not user.otp_enabled:
|
if not user.mfa_enabled:
|
||||||
title = _('MFA disable success')
|
title = _('MFA disable success')
|
||||||
describe = _('MFA disable success, return login page')
|
describe = _('MFA disable success, return login page')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue