Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
b5fc76d6
Commit
b5fc76d6
authored
Apr 23, 2018
by
BaiJiangjie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 修改用户首次登录页
parent
5b52b907
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
206 additions
and
71 deletions
+206
-71
django.mo
apps/i18n/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/i18n/zh/LC_MESSAGES/django.po
+0
-0
jquery.steps.css
apps/static/css/plugins/steps/jquery.steps.css
+0
-1
_message.html
apps/templates/_message.html
+3
-0
forms.py
apps/users/forms.py
+38
-1
user.py
apps/users/models/user.py
+45
-14
_base_otp.html
apps/users/templates/users/_base_otp.html
+5
-7
first_login.html
apps/users/templates/users/first_login.html
+100
-41
first_login_done.html
apps/users/templates/users/first_login_done.html
+4
-1
user_otp_authentication.html
apps/users/templates/users/user_otp_authentication.html
+0
-1
login.py
apps/users/views/login.py
+11
-5
No files found.
apps/i18n/zh/LC_MESSAGES/django.mo
View file @
b5fc76d6
No preview for this file type
apps/i18n/zh/LC_MESSAGES/django.po
View file @
b5fc76d6
This diff is collapsed.
Click to expand it.
apps/static/css/plugins/steps/jquery.steps.css
View file @
b5fc76d6
...
...
@@ -220,7 +220,6 @@
position
:
relative
;
display
:
block
;
text-align
:
right
;
width
:
100%
;
}
.wizard.vertical
>
.actions
...
...
apps/templates/_message.html
View file @
b5fc76d6
...
...
@@ -6,9 +6,12 @@
{% blocktrans %}
Your information was incomplete. Please click
<a
href=
"{{ first_login_url }}"
>
this link
</a>
to complete your information.
{% endblocktrans %}
<button
aria-hidden=
"true"
data-dismiss=
"alert"
class=
"close"
type=
"button"
>
×
</button>
</div>
{% endif %}
{% endblock %}
{% block update_public_key_message %}
{% if request.user.is_authenticated and not request.user.is_public_key_valid and not request.COOKIE.close_public_key_msg != '1' %}
<div
class=
"alert alert-danger help-message alert-dismissable"
>
...
...
apps/users/forms.py
View file @
b5fc76d6
...
...
@@ -36,7 +36,10 @@ class UserCreateUpdateForm(forms.ModelForm):
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
max_length
=
128
,
strip
=
False
,
required
=
False
,
)
role
=
forms
.
ChoiceField
(
choices
=
role_choices
,
required
=
True
,
initial
=
User
.
ROLE_USER
,
label
=
_
(
"Role"
))
role
=
forms
.
ChoiceField
(
choices
=
role_choices
,
required
=
True
,
initial
=
User
.
ROLE_USER
,
label
=
_
(
"Role"
)
)
public_key
=
forms
.
CharField
(
label
=
_
(
'ssh public key'
),
max_length
=
5000
,
required
=
False
,
widget
=
forms
.
Textarea
(
attrs
=
{
'placeholder'
:
_
(
'ssh-rsa AAAA...'
)}),
...
...
@@ -110,6 +113,39 @@ class UserProfileForm(forms.ModelForm):
UserProfileForm
.
verbose_name
=
_
(
"Profile"
)
class
UserMFAForm
(
forms
.
ModelForm
):
mfa_description
=
_
(
'Tip: when enabled, '
'you will enter the MFA binding process the next time you log in. '
'you can also directly bind in '
'"personal information -> quick modification -> change MFA Settings"!'
)
class
Meta
:
model
=
User
fields
=
[
'otp_level'
]
widgets
=
{
'otp_level'
:
forms
.
RadioSelect
()}
help_texts
=
{
'otp_level'
:
_
(
'* Enable MFA authentication '
'to make the account more secure.'
),
}
UserMFAForm
.
verbose_name
=
_
(
"MFA"
)
class
UserFirstLoginFinishForm
(
forms
.
Form
):
finish_description
=
_
(
'In order to protect you and your company, '
'please keep your account, '
'password and key sensitive information properly. '
'(for example: setting complex password, enabling MFA authentication)'
)
UserFirstLoginFinishForm
.
verbose_name
=
_
(
"Finish"
)
class
UserPasswordForm
(
forms
.
Form
):
old_password
=
forms
.
CharField
(
max_length
=
128
,
widget
=
forms
.
PasswordInput
,
...
...
@@ -152,6 +188,7 @@ class UserPasswordForm(forms.Form):
class
UserPublicKeyForm
(
forms
.
Form
):
pubkey_description
=
_
(
'Automatically configure and download the SSH key'
)
public_key
=
forms
.
CharField
(
label
=
_
(
'ssh public key'
),
max_length
=
5000
,
required
=
False
,
widget
=
forms
.
Textarea
(
attrs
=
{
'placeholder'
:
_
(
'ssh-rsa AAAA...'
)}),
...
...
apps/users/models/user.py
View file @
b5fc76d6
...
...
@@ -36,23 +36,52 @@ class User(AbstractUser):
(
2
,
_
(
"Force enable"
)),
)
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
username
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Username'
))
username
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Username'
)
)
name
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
'Name'
))
email
=
models
.
EmailField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Email'
))
groups
=
models
.
ManyToManyField
(
'users.UserGroup'
,
related_name
=
'users'
,
blank
=
True
,
verbose_name
=
_
(
'User group'
))
role
=
models
.
CharField
(
choices
=
ROLE_CHOICES
,
default
=
'User'
,
max_length
=
10
,
blank
=
True
,
verbose_name
=
_
(
'Role'
))
avatar
=
models
.
ImageField
(
upload_to
=
"avatar"
,
null
=
True
,
verbose_name
=
_
(
'Avatar'
))
wechat
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Wechat'
))
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
))
otp_level
=
models
.
SmallIntegerField
(
default
=
0
,
choices
=
OTP_LEVEL_CHOICES
,
verbose_name
=
_
(
'MFA'
))
email
=
models
.
EmailField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Email'
)
)
groups
=
models
.
ManyToManyField
(
'users.UserGroup'
,
related_name
=
'users'
,
blank
=
True
,
verbose_name
=
_
(
'User group'
)
)
role
=
models
.
CharField
(
choices
=
ROLE_CHOICES
,
default
=
'User'
,
max_length
=
10
,
blank
=
True
,
verbose_name
=
_
(
'Role'
)
)
avatar
=
models
.
ImageField
(
upload_to
=
"avatar"
,
null
=
True
,
verbose_name
=
_
(
'Avatar'
)
)
wechat
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Wechat'
)
)
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
)
)
otp_level
=
models
.
SmallIntegerField
(
default
=
0
,
choices
=
OTP_LEVEL_CHOICES
,
verbose_name
=
_
(
'MFA'
)
)
_otp_secret_key
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
)
# Todo: Auto generate key, let user download
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Private key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Public key'
))
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Private key'
)
)
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Public key'
)
)
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
)
)
is_first_login
=
models
.
BooleanField
(
default
=
True
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
))
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
))
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
)
)
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
)
)
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
...
...
@@ -213,7 +242,9 @@ class User(AbstractUser):
return
user_default
def
generate_reset_token
(
self
):
return
signer
.
sign_t
({
'reset'
:
str
(
self
.
id
),
'email'
:
self
.
email
},
expires_in
=
3600
)
return
signer
.
sign_t
(
{
'reset'
:
str
(
self
.
id
),
'email'
:
self
.
email
},
expires_in
=
3600
)
@property
def
otp_enabled
(
self
):
...
...
apps/users/templates/users/_base_otp.html
View file @
b5fc76d6
...
...
@@ -74,13 +74,11 @@
</article>
<footer>
<div
class=
""
style=
"margin-top: 100px;"
>
{% include '_copyright.html' %}
</div>
</footer>
<footer>
<div
class=
""
style=
"margin-top: 100px;"
>
{% include '_copyright.html' %}
</div>
</footer>
</body>
</html>
...
...
apps/users/templates/users/first_login.html
View file @
b5fc76d6
...
...
@@ -9,6 +9,7 @@
<link
href=
"{% static 'css/plugins/steps/jquery.steps.css' %}"
rel=
"stylesheet"
>
{% endblock %}
{% block first_login_message %}{% endblock %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
...
...
@@ -27,58 +28,116 @@
</div>
<div
class=
"ibox-content"
>
<div
class=
"wizard"
>
<div
class=
"steps clearfix"
>
<ul
role=
"tablist"
>
{% for step in wizard.steps.all %}
<li
role=
"tab"
class=
"{% ifequal step wizard.steps.first %}first{% endifequal %} {% ifequal step wizard.steps.current %}current{% else %}disabled{% endifequal %} {% ifequal step wizard.steps.last %}last{% endifequal %}"
aria-disabled=
"false"
aria-selected=
"true"
>
<a
href=
"javascript:void(0)"
><span
class=
"number"
>
{% trans 'Step' %} {{ step }}
</span></a>
</li>
{% endfor >%}
</ul>
</div>
<div
class=
"content clearfix"
>
<form
action=
""
method=
"post"
class=
"form col-lg-8 p-m"
id=
"fl_form"
>
{% csrf_token %}
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% bootstrap_form form %}
{% endfor %}
{% else %}
{% bootstrap_form wizard.form %}
{% endif %}
</form>
</div>
<div
class=
"steps clearfix"
>
<ul
role=
"tablist"
>
{% for step in wizard.steps.all %}
<li
role=
"tab"
class=
"{% ifequal step wizard.steps.first %}first{% endifequal %} {% ifequal step wizard.steps.current %}current{% else %}disabled{% endifequal %} {% ifequal step wizard.steps.last %}last{% endifequal %}"
aria-disabled=
"false"
aria-selected=
"true"
>
<a
class=
"fl_goto"
name=
"wizard_goto_step"
data-goto=
"{{ step }}"
>
<span
class=
"number"
>
{% ifequal step '0' %}
1. {% trans "Profile" %}
{% endifequal %}
{% ifequal step '1' %}
2. {% trans "Public key" %}
{% endifequal %}
{% ifequal step '2' %}
3. {% trans "MFA" %}
{% endifequal %}
{% ifequal step '3' %}
4. {% trans "Finish" %}
{% endifequal %}
</span>
</a>
</li>
{% endfor >%}
</ul>
</div>
<div
class=
"content clearfix"
style=
"background-color: #eee; border-radius:5px;"
>
<div
class=
"row"
>
<form
action=
""
method=
"post"
class=
"form col-lg-8 p-m"
id=
"fl_form"
style=
"padding-left: 40px;"
>
{% csrf_token %}
{{ wizard.management_form }}
{#{% if wizard.form.forms %}#}
{#{{ wizard.form.management_form }}#}
{#{% for form in wizard.form %}#}
{#{% bootstrap_form form %}#}
{#{% endfor %}#}
{#{% else %}#}
{#{% endif %}#}
{% if form.finish_description %}
<b>
{{ form.finish_description }}
</b>
<br>
<input
type=
"checkbox"
id=
"acceptTerms"
>
<label
for=
"acceptTerms"
style=
"margin-top:20px"
>
{% trans "I agree with the terms and conditions." %}
</label>
{% endif %}
{% bootstrap_form wizard.form %}
{% if form.mfa_description %}
<b>
{{ form.mfa_description }}
</b>
{% endif %}
{% if form.pubkey_description %}
<span>
或者:
</span>
<a
type=
"button"
id=
"btn-reset-pubkey"
>
{{ form.pubkey_description }}
</a>
{% endif %}
</form>
<div
class=
"col-lg-4"
>
<div
class=
"text-center"
>
<div
style=
"margin-top: 20px"
>
<i
class=
"fa fa-sign-in"
style=
"font-size: 180px;color: #e5e5e5 "
></i>
</div>
</div>
</div>
</div>
</div>
<div
class=
"actions clearfix"
>
<ul>
{% if wizard.steps.prev %}
<li><a
class=
"fl_goto"
name=
"wizard_goto_step"
data-goto=
"{{ wizard.steps.prev }}"
>
{% trans "Previous" %}
</a></li>
{% endif %}
{% if wizard.steps.next %}
<li><a
class=
"fl_goto"
name=
"wizard_goto_step"
data-goto=
"{{ wizard.steps.next }}"
>
{% trans "Next" %}
</a></li>
{% endif %}
<li><a
id=
"fl_submit"
>
{% trans "Submit" %}
</a></li>
</ul>
<ul>
{% if wizard.steps.prev %}
<li><a
class=
"fl_goto"
name=
"wizard_goto_step"
data-goto=
"{{ wizard.steps.prev }}"
>
{% trans "Previous" %}
</a></li>
{% endif %}
{#{% if wizard.steps.next %}#}
{#
<li><a
class=
"fl_goto"
name=
"wizard_goto_step"
data-goto=
"{{ wizard.steps.next }}"
>
{% trans "Next" %}
</a></li>
#}
{#{% endif %}#}
{#
<li><a
id=
"fl_submit"
>
{% trans "Submit" %}
</a></li>
#}
{#将原来的下一页-替换为提交;修复 每页都提交,最后才能成功问题#}
{% if wizard.steps.next %}
<li><a
id=
"fl_submit"
>
{% trans "Next" %}
</a></li>
{% else %}
<li><a
id=
"fl_submit"
style=
"width:66px;text-align: center;"
>
{% trans "Finish" %}
</a></li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
on
(
'click'
,
".fl_goto"
,
function
(){
var
$form
=
$
(
'#fl_form'
);
$
(
'<input />'
,
{
'name'
:
'wizard_goto_step'
,
'value'
:
$
(
this
).
data
(
'goto'
),
'type'
:
'hidden'
}).
appendTo
(
$form
);
$form
.
submit
();
return
false
;
}).
on
(
'click'
,
'#fl_submit'
,
function
(){
$
(
'#fl_form'
).
submit
();
return
false
;
$
(
'#id_2-otp_level div'
).
eq
(
2
).
css
(
'display'
,
'none'
);
$
(
document
).
on
(
'click'
,
".fl_goto"
,
function
(){
var
$form
=
$
(
'#fl_form'
);
$
(
'<input />'
,
{
'name'
:
'wizard_goto_step'
,
'value'
:
$
(
this
).
data
(
'goto'
),
'type'
:
'hidden'
}).
appendTo
(
$form
);
$form
.
submit
();
return
false
;
}).
on
(
'click'
,
'#fl_submit'
,
function
(){
$
(
'#fl_form'
).
submit
();
return
false
;
}).
on
(
'click'
,
'#btn-reset-pubkey'
,
function
()
{
var
the_url
=
'{% url "users:user-pubkey-generate" %}'
;
window
.
open
(
the_url
,
"_blank"
)
})
</script>
{% endblock %}
apps/users/templates/users/first_login_done.html
View file @
b5fc76d6
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% load bootstrap3 %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
...
...
@@ -9,6 +9,7 @@
<link
href=
"{% static 'css/plugins/steps/jquery.steps.css' %}"
rel=
"stylesheet"
>
{% endblock %}
{% block first_login_message %}{% endblock %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
...
...
@@ -36,6 +37,8 @@
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
on
(
'click'
,
".fl_goto"
,
function
(){
...
...
apps/users/templates/users/user_otp_authentication.html
View file @
b5fc76d6
...
...
@@ -27,7 +27,6 @@
$
(
function
(){
$
(
'.change-color li'
).
eq
(
2
).
remove
();
$
(
'.change-color li:eq(1) div'
).
eq
(
1
).
html
(
'解绑MFA'
)
})
...
...
apps/users/views/login.py
View file @
b5fc76d6
...
...
@@ -166,8 +166,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView):
'redirect_url'
:
reverse
(
'users:login'
),
}
kwargs
.
update
(
context
)
return
super
()
\
.
get_context_data
(
**
kwargs
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
UserResetPasswordSuccessView
(
TemplateView
):
...
...
@@ -214,7 +213,12 @@ class UserResetPasswordView(TemplateView):
class
UserFirstLoginView
(
LoginRequiredMixin
,
SessionWizardView
):
template_name
=
'users/first_login.html'
form_list
=
[
forms
.
UserProfileForm
,
forms
.
UserPublicKeyForm
]
form_list
=
[
forms
.
UserProfileForm
,
forms
.
UserPublicKeyForm
,
forms
.
UserMFAForm
,
forms
.
UserFirstLoginFinishForm
]
file_storage
=
default_storage
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
@@ -255,7 +259,6 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
def
get_form
(
self
,
step
=
None
,
data
=
None
,
files
=
None
):
form
=
super
()
.
get_form
(
step
,
data
,
files
)
form
.
instance
=
self
.
request
.
user
return
form
...
...
@@ -293,7 +296,9 @@ class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
'date_to'
:
self
.
date_to
,
'user'
:
self
.
user
,
'keyword'
:
self
.
keyword
,
'user_list'
:
set
(
LoginLog
.
objects
.
all
()
.
values_list
(
'username'
,
flat
=
True
))
'user_list'
:
set
(
LoginLog
.
objects
.
all
()
.
values_list
(
'username'
,
flat
=
True
)
)
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment