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
6ce9815d
Commit
6ce9815d
authored
Nov 05, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 基本完成登录逻辑
parent
9d201bbf
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
662 additions
and
622 deletions
+662
-622
models.py
apps/audits/models.py
+9
-0
signals_handler.py
apps/audits/signals_handler.py
+41
-2
tasks.py
apps/audits/tasks.py
+6
-0
login_log_list.html
apps/audits/templates/audits/login_log_list.html
+1
-1
utils.py
apps/audits/utils.py
+16
-0
auth.py
apps/authentication/api/auth.py
+3
-169
login_confirm.py
apps/authentication/api/login_confirm.py
+32
-1
mfa.py
apps/authentication/api/mfa.py
+46
-1
token.py
apps/authentication/api/token.py
+10
-118
errors.py
apps/authentication/errors.py
+164
-25
forms.py
apps/authentication/forms.py
+2
-36
mixins.py
apps/authentication/mixins.py
+127
-0
serializers.py
apps/authentication/serializers.py
+25
-42
signals_handlers.py
apps/authentication/signals_handlers.py
+1
-36
tasks.py
apps/authentication/tasks.py
+0
-9
login.html
apps/authentication/templates/authentication/login.html
+15
-12
login_wait_confirm.html
...tication/templates/authentication/login_wait_confirm.html
+2
-2
xpack_login.html
.../authentication/templates/authentication/xpack_login.html
+22
-13
api_urls.py
apps/authentication/urls/api_urls.py
+1
-2
utils.py
apps/authentication/utils.py
+17
-26
__init__.py
apps/authentication/views/__init__.py
+1
-1
login.py
apps/authentication/views/login.py
+36
-90
mfa.py
apps/authentication/views/mfa.py
+25
-0
utils.py
apps/authentication/views/utils.py
+8
-0
common.py
apps/common/utils/common.py
+8
-0
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
asset_permission_create_update.html
...perms/templates/perms/asset_permission_create_update.html
+2
-12
remote_app_permission_create_update.html
.../templates/perms/remote_app_permission_create_update.html
+2
-2
jumpserver.js
apps/static/js/jumpserver.js
+28
-0
_head_css_js.html
apps/templates/_head_css_js.html
+0
-0
user.py
apps/users/models/user.py
+4
-4
_user.html
apps/users/templates/users/_user.html
+2
-11
user_detail.html
apps/users/templates/users/user_detail.html
+1
-1
api_urls.py
apps/users/urls/api_urls.py
+0
-3
utils.py
apps/users/utils.py
+5
-3
No files found.
apps/audits/models.py
View file @
6ce9815d
...
...
@@ -110,5 +110,14 @@ class UserLoginLog(models.Model):
login_logs
=
login_logs
.
filter
(
username__in
=
username_list
)
return
login_logs
@property
def
reason_display
(
self
):
from
authentication.errors
import
reason_choices
,
old_reason_choices
reason
=
reason_choices
.
get
(
self
.
reason
)
if
reason
:
return
reason
reason
=
old_reason_choices
.
get
(
self
.
reason
,
self
.
reason
)
return
reason
class
Meta
:
ordering
=
[
'-datetime'
,
'username'
]
apps/audits/signals_handler.py
View file @
6ce9815d
...
...
@@ -4,15 +4,18 @@
from
django.db.models.signals
import
post_save
,
post_delete
from
django.dispatch
import
receiver
from
django.db
import
transaction
from
django.utils
import
timezone
from
rest_framework.renderers
import
JSONRenderer
from
rest_framework.request
import
Request
from
jumpserver.utils
import
current_request
from
common.utils
import
get_request_ip
,
get_logger
,
get_syslogger
from
users.models
import
User
from
authentication.signals
import
post_auth_failed
,
post_auth_success
from
terminal.models
import
Session
,
Command
from
terminal.backends.command.serializers
import
SessionCommandSerializer
from
.
import
models
from
.
import
serializers
from
.
import
models
,
serializers
from
.
tasks
import
write_login_log_async
logger
=
get_logger
(
__name__
)
sys_logger
=
get_syslogger
(
"audits"
)
...
...
@@ -99,3 +102,39 @@ def on_audits_log_create(sender, instance=None, **kwargs):
data
=
json_render
.
render
(
s
.
data
)
.
decode
(
errors
=
'ignore'
)
msg
=
"{} - {}"
.
format
(
category
,
data
)
sys_logger
.
info
(
msg
)
def
generate_data
(
username
,
request
):
user_agent
=
request
.
META
.
get
(
'HTTP_USER_AGENT'
,
''
)
if
isinstance
(
request
,
Request
):
login_ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
login_type
=
request
.
data
.
get
(
'login_type'
,
''
)
else
:
login_ip
=
get_request_ip
(
request
)
login_type
=
'W'
data
=
{
'username'
:
username
,
'ip'
:
login_ip
,
'type'
:
login_type
,
'user_agent'
:
user_agent
,
'datetime'
:
timezone
.
now
()
}
return
data
@receiver
(
post_auth_success
)
def
on_user_auth_success
(
sender
,
user
,
request
,
**
kwargs
):
logger
.
debug
(
'User login success: {}'
.
format
(
user
.
username
))
data
=
generate_data
(
user
.
username
,
request
)
data
.
update
({
'mfa'
:
int
(
user
.
otp_enabled
),
'status'
:
True
})
write_login_log_async
.
delay
(
**
data
)
@receiver
(
post_auth_failed
)
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
})
write_login_log_async
.
delay
(
**
data
)
apps/audits/tasks.py
View file @
6ce9815d
...
...
@@ -7,6 +7,7 @@ from celery import shared_task
from
ops.celery.decorator
import
register_as_period_task
from
.models
import
UserLoginLog
from
.utils
import
write_login_log
@register_as_period_task
(
interval
=
3600
*
24
)
...
...
@@ -19,3 +20,8 @@ def clean_login_log_period():
days
=
90
expired_day
=
now
-
datetime
.
timedelta
(
days
=
days
)
UserLoginLog
.
objects
.
filter
(
datetime__lt
=
expired_day
)
.
delete
()
@shared_task
def
write_login_log_async
(
*
args
,
**
kwargs
):
write_login_log
(
*
args
,
**
kwargs
)
apps/audits/templates/audits/login_log_list.html
View file @
6ce9815d
...
...
@@ -78,7 +78,7 @@
<td
class=
"text-center"
>
{{ login_log.ip }}
</td>
<td
class=
"text-center"
>
{{ login_log.city }}
</td>
<td
class=
"text-center"
>
{{ login_log.get_mfa_display }}
</td>
<td
class=
"text-center"
>
{
% trans login_log.reason %
}
</td>
<td
class=
"text-center"
>
{
{ login_log.reason_display }
}
</td>
<td
class=
"text-center"
>
{{ login_log.get_status_display }}
</td>
<td
class=
"text-center"
>
{{ login_log.datetime }}
</td>
</tr>
...
...
apps/audits/utils.py
View file @
6ce9815d
import
csv
import
codecs
from
django.http
import
HttpResponse
from
django.utils.translation
import
ugettext
as
_
from
common.utils
import
validate_ip
,
get_ip_city
def
get_excel_response
(
filename
):
...
...
@@ -20,3 +23,16 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None):
data
=
[
getattr
(
log
,
field
.
name
)
for
field
in
fields
]
writer
.
writerow
(
data
)
return
response
def
write_login_log
(
*
args
,
**
kwargs
):
from
audits.models
import
UserLoginLog
default_city
=
_
(
"Unknown"
)
ip
=
kwargs
.
get
(
'ip'
)
or
''
if
not
(
ip
and
validate_ip
(
ip
)):
ip
=
ip
[:
15
]
city
=
default_city
else
:
city
=
get_ip_city
(
ip
)
or
default_city
kwargs
.
update
({
'ip'
:
ip
,
'city'
:
city
})
UserLoginLog
.
objects
.
create
(
**
kwargs
)
apps/authentication/api/auth.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
import
uuid
import
time
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.shortcuts
import
get_object_or_404
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.permissions
import
AllowAny
from
rest_framework.response
import
Response
from
rest_framework.generics
import
CreateAPIView
from
rest_framework.views
import
APIView
from
common.utils
import
get_logger
,
get_request_ip
,
get_object_or_none
from
common.permissions
import
IsOrgAdminOrAppUser
,
IsValidUser
from
common.utils
import
get_logger
from
common.permissions
import
IsOrgAdminOrAppUser
from
orgs.mixins.api
import
RootOrgViewMixin
from
users.serializers
import
UserSerializer
from
users.models
import
User
from
assets.models
import
Asset
,
SystemUser
from
users.utils
import
(
check_otp_code
,
increase_login_failed_count
,
is_block_login
,
clean_failed_count
)
from
..
import
errors
from
..utils
import
check_user_valid
from
..serializers
import
OtpVerifySerializer
from
..signals
import
post_auth_success
,
post_auth_failed
logger
=
get_logger
(
__name__
)
__all__
=
[
'UserAuthApi'
,
'UserConnectionTokenApi'
,
'UserOtpAuthApi'
,
'UserOtpVerifyApi'
,
'UserOrderAcceptAuthApi'
,
'UserConnectionTokenApi'
,
]
class
UserAuthApi
(
RootOrgViewMixin
,
APIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
def
get_serializer_context
(
self
):
return
{
'request'
:
self
.
request
,
'view'
:
self
}
def
get_serializer
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'context'
]
=
self
.
get_serializer_context
()
return
self
.
serializer_class
(
*
args
,
**
kwargs
)
def
post
(
self
,
request
):
# limit login
username
=
request
.
data
.
get
(
'username'
)
ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
ip
=
ip
or
get_request_ip
(
request
)
if
is_block_login
(
username
,
ip
):
msg
=
_
(
"Log in frequently and try again later"
)
logger
.
warn
(
msg
+
': '
+
username
+
':'
+
ip
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
user
,
msg
=
self
.
check_user_valid
(
request
)
if
not
user
:
username
=
request
.
data
.
get
(
'username'
,
''
)
self
.
send_auth_signal
(
success
=
False
,
username
=
username
,
reason
=
msg
)
increase_login_failed_count
(
username
,
ip
)
return
Response
({
'msg'
:
msg
},
status
=
401
)
if
not
user
.
otp_enabled
:
self
.
send_auth_signal
(
success
=
True
,
user
=
user
)
# 登陆成功,清除原来的缓存计数
clean_failed_count
(
username
,
ip
)
token
,
expired_at
=
user
.
create_bearer_token
(
request
)
return
Response
(
{
'token'
:
token
,
'user'
:
self
.
get_serializer
(
user
)
.
data
}
)
seed
=
uuid
.
uuid4
()
.
hex
cache
.
set
(
seed
,
user
,
300
)
return
Response
(
{
'code'
:
101
,
'msg'
:
_
(
'Please carry seed value and '
'conduct MFA secondary certification'
),
'otp_url'
:
reverse
(
'api-auth:user-otp-auth'
),
'seed'
:
seed
,
'user'
:
self
.
get_serializer
(
user
)
.
data
},
status
=
300
)
@staticmethod
def
check_user_valid
(
request
):
username
=
request
.
data
.
get
(
'username'
,
''
)
password
=
request
.
data
.
get
(
'password'
,
''
)
public_key
=
request
.
data
.
get
(
'public_key'
,
''
)
user
,
msg
=
check_user_valid
(
username
=
username
,
password
=
password
,
public_key
=
public_key
)
return
user
,
msg
def
send_auth_signal
(
self
,
success
=
True
,
user
=
None
,
username
=
''
,
reason
=
''
):
if
success
:
post_auth_success
.
send
(
sender
=
self
.
__class__
,
user
=
user
,
request
=
self
.
request
)
else
:
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
username
,
request
=
self
.
request
,
reason
=
reason
)
class
UserConnectionTokenApi
(
RootOrgViewMixin
,
APIView
):
permission_classes
=
(
IsOrgAdminOrAppUser
,)
...
...
@@ -150,82 +61,5 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
return
super
()
.
get_permissions
()
class
UserOtpAuthApi
(
RootOrgViewMixin
,
APIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
def
get_serializer_context
(
self
):
return
{
'request'
:
self
.
request
,
'view'
:
self
}
def
get_serializer
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'context'
]
=
self
.
get_serializer_context
()
return
self
.
serializer_class
(
*
args
,
**
kwargs
)
def
post
(
self
,
request
):
otp_code
=
request
.
data
.
get
(
'otp_code'
,
''
)
seed
=
request
.
data
.
get
(
'seed'
,
''
)
user
=
cache
.
get
(
seed
,
None
)
if
not
user
:
return
Response
(
{
'msg'
:
_
(
'Please verify the user name and password first'
)},
status
=
401
)
if
not
check_otp_code
(
user
.
otp_secret_key
,
otp_code
):
self
.
send_auth_signal
(
success
=
False
,
username
=
user
.
username
,
reason
=
errors
.
mfa_failed
)
return
Response
({
'msg'
:
_
(
'MFA certification failed'
)},
status
=
401
)
self
.
send_auth_signal
(
success
=
True
,
user
=
user
)
token
,
expired_at
=
user
.
create_bearer_token
(
request
)
data
=
{
'token'
:
token
,
'user'
:
self
.
get_serializer
(
user
)
.
data
}
return
Response
(
data
)
def
send_auth_signal
(
self
,
success
=
True
,
user
=
None
,
username
=
''
,
reason
=
''
):
if
success
:
post_auth_success
.
send
(
sender
=
self
.
__class__
,
user
=
user
,
request
=
self
.
request
)
else
:
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
username
,
request
=
self
.
request
,
reason
=
reason
)
class
UserOtpVerifyApi
(
CreateAPIView
):
permission_classes
=
(
IsValidUser
,)
serializer_class
=
OtpVerifySerializer
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
data
)
serializer
.
is_valid
(
raise_exception
=
True
)
code
=
serializer
.
validated_data
[
"code"
]
if
request
.
user
.
check_otp
(
code
):
request
.
session
[
"MFA_VERIFY_TIME"
]
=
int
(
time
.
time
())
return
Response
({
"ok"
:
"1"
})
else
:
return
Response
({
"error"
:
"Code not valid"
},
status
=
400
)
class
UserOrderAcceptAuthApi
(
APIView
):
permission_classes
=
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
from
orders.models
import
LoginConfirmOrder
order_id
=
self
.
request
.
session
.
get
(
"auth_order_id"
)
logger
.
debug
(
'Login confirm order id: {}'
.
format
(
order_id
))
if
not
order_id
:
order
=
None
else
:
order
=
get_object_or_none
(
LoginConfirmOrder
,
pk
=
order_id
)
if
not
order
:
error
=
_
(
"No order found or order expired"
)
return
Response
({
"error"
:
error
,
"status"
:
"not found"
},
status
=
404
)
if
order
.
status
==
order
.
STATUS_ACCEPTED
:
self
.
request
.
session
[
"auth_confirm"
]
=
"1"
return
Response
({
"msg"
:
"ok"
})
elif
order
.
status
==
order
.
STATUS_REJECTED
:
error
=
_
(
"Order was rejected by {}"
)
.
format
(
order
.
assignee_display
)
else
:
error
=
"Order status: {}"
.
format
(
order
.
status
)
return
Response
({
"error"
:
error
,
"status"
:
order
.
status
},
status
=
400
)
apps/authentication/api/login_confirm.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
rest_framework.generics
import
UpdateAPIView
from
rest_framework.response
import
Response
from
rest_framework.views
import
APIView
from
django.shortcuts
import
get_object_or_404
from
common.utils
import
get_logger
,
get_object_or_none
from
common.permissions
import
IsOrgAdmin
from
..models
import
LoginConfirmSetting
from
..serializers
import
LoginConfirmSettingSerializer
from
..
import
errors
__all__
=
[
'LoginConfirmSettingUpdateApi'
]
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'UserOrderAcceptAuthApi'
]
logger
=
get_logger
(
__name__
)
class
LoginConfirmSettingUpdateApi
(
UpdateAPIView
):
...
...
@@ -23,3 +28,29 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
defaults
,
user
=
user
,
)
return
s
class
UserOrderAcceptAuthApi
(
APIView
):
permission_classes
=
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
from
orders.models
import
LoginConfirmOrder
order_id
=
self
.
request
.
session
.
get
(
"auth_order_id"
)
logger
.
debug
(
'Login confirm order id: {}'
.
format
(
order_id
))
if
not
order_id
:
order
=
None
else
:
order
=
get_object_or_none
(
LoginConfirmOrder
,
pk
=
order_id
)
try
:
if
not
order
:
raise
errors
.
LoginConfirmOrderNotFound
(
order_id
)
if
order
.
status
==
order
.
STATUS_ACCEPTED
:
self
.
request
.
session
[
"auth_confirm"
]
=
"1"
return
Response
({
"msg"
:
"ok"
})
elif
order
.
status
==
order
.
STATUS_REJECTED
:
raise
errors
.
LoginConfirmRejectedError
(
order_id
)
else
:
return
errors
.
LoginConfirmWaitError
(
order_id
)
except
errors
.
AuthFailedError
as
e
:
data
=
e
.
as_data
()
return
Response
(
data
,
status
=
400
)
apps/authentication/api/mfa.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
import
time
from
rest_framework.permissions
import
AllowAny
from
rest_framework.generics
import
CreateAPIView
from
rest_framework.serializers
import
ValidationError
from
rest_framework.response
import
Response
from
common.permissions
import
IsValidUser
from
..serializers
import
OtpVerifySerializer
from
..
import
serializers
from
..
import
errors
from
..mixins
import
AuthMixin
class
MFAChallengeApi
(
CreateAPIView
):
__all__
=
[
'MFAChallengeApi'
,
'UserOtpVerifyApi'
]
class
MFAChallengeApi
(
AuthMixin
,
CreateAPIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
serializers
.
MFAChallengeSerializer
def
perform_create
(
self
,
serializer
):
try
:
user
=
self
.
get_user_from_session
()
code
=
serializer
.
validated_data
.
get
(
'code'
)
valid
=
user
.
check_otp
(
code
)
if
not
valid
:
self
.
request
.
session
[
'auth_mfa'
]
=
''
raise
errors
.
MFAFailedError
(
username
=
user
.
username
,
request
=
self
.
request
)
except
errors
.
AuthFailedError
as
e
:
data
=
{
"error"
:
e
.
error
,
"msg"
:
e
.
reason
}
raise
ValidationError
(
data
)
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
super
()
.
create
(
request
,
*
args
,
**
kwargs
)
return
Response
({
'msg'
:
'ok'
})
class
UserOtpVerifyApi
(
CreateAPIView
):
permission_classes
=
(
IsValidUser
,)
serializer_class
=
OtpVerifySerializer
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
data
)
serializer
.
is_valid
(
raise_exception
=
True
)
code
=
serializer
.
validated_data
[
"code"
]
if
request
.
user
.
check_otp
(
code
):
request
.
session
[
"MFA_VERIFY_TIME"
]
=
int
(
time
.
time
())
return
Response
({
"ok"
:
"1"
})
else
:
return
Response
({
"error"
:
"Code not valid"
},
status
=
400
)
apps/authentication/api/token.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.permissions
import
AllowAny
from
rest_framework.response
import
Response
from
rest_framework.generics
import
CreateAPIView
from
common.utils
import
get_request_ip
,
get_logger
,
get_object_or_none
from
users.utils
import
(
check_otp_code
,
increase_login_failed_count
,
is_block_login
,
clean_failed_count
)
from
users.models
import
User
from
..utils
import
check_user_valid
from
..signals
import
post_auth_success
,
post_auth_failed
from
common.utils
import
get_logger
from
..
import
serializers
,
errors
from
..mixins
import
AuthMixin
logger
=
get_logger
(
__name__
)
...
...
@@ -22,126 +16,24 @@ logger = get_logger(__name__)
__all__
=
[
'TokenCreateApi'
]
class
TokenCreateApi
(
CreateAPIView
):
class
TokenCreateApi
(
AuthMixin
,
CreateAPIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
serializers
.
BearerTokenSerializer
def
check_session
(
self
):
pass
def
get_request_ip
(
self
):
ip
=
self
.
request
.
data
.
get
(
'remote_addr'
,
None
)
ip
=
ip
or
get_request_ip
(
self
.
request
)
return
ip
def
check_is_block
(
self
):
username
=
self
.
request
.
data
.
get
(
"username"
)
ip
=
self
.
get_request_ip
()
if
is_block_login
(
username
,
ip
):
msg
=
errors
.
ip_blocked
logger
.
warn
(
msg
+
': '
+
username
+
':'
+
ip
)
raise
errors
.
AuthFailedError
(
msg
,
'blocked'
)
def
get_user_from_session
(
self
):
user_id
=
self
.
request
.
session
[
"user_id"
]
user
=
get_object_or_none
(
User
,
pk
=
user_id
)
if
not
user
:
error
=
"Not user in session: {}"
.
format
(
user_id
)
raise
errors
.
AuthFailedError
(
error
,
'session_error'
)
return
user
def
check_user_auth
(
self
):
request
=
self
.
request
if
request
.
session
.
get
(
"auth_password"
)
and
\
request
.
session
.
get
(
'user_id'
):
user
=
self
.
get_user_from_session
()
return
user
self
.
check_is_block
()
username
=
request
.
data
.
get
(
'username'
,
''
)
password
=
request
.
data
.
get
(
'password'
,
''
)
public_key
=
request
.
data
.
get
(
'public_key'
,
''
)
user
,
msg
=
check_user_valid
(
username
=
username
,
password
=
password
,
public_key
=
public_key
)
ip
=
self
.
get_request_ip
()
if
not
user
:
raise
errors
.
AuthFailedError
(
msg
,
error
=
'auth_failed'
,
username
=
username
)
clean_failed_count
(
username
,
ip
)
request
.
session
[
'auth_password'
]
=
1
request
.
session
[
'user_id'
]
=
str
(
user
.
id
)
return
user
def
check_user_mfa_if_need
(
self
,
user
):
if
self
.
request
.
session
.
get
(
'auth_mfa'
):
return
True
if
not
user
.
otp_enabled
or
not
user
.
otp_secret_key
:
return
True
otp_code
=
self
.
request
.
data
.
get
(
"otp_code"
)
if
not
otp_code
:
raise
errors
.
MFARequiredError
()
if
not
check_otp_code
(
user
.
otp_secret_key
,
otp_code
):
raise
errors
.
AuthFailedError
(
errors
.
mfa_failed
,
error
=
'mfa_failed'
,
username
=
user
.
username
,
)
return
True
def
check_user_login_confirm_if_need
(
self
,
user
):
from
orders.models
import
LoginConfirmOrder
confirm_setting
=
user
.
get_login_confirm_setting
()
if
self
.
request
.
session
.
get
(
'auth_confirm'
)
or
not
confirm_setting
:
return
order
=
None
if
self
.
request
.
session
.
get
(
'auth_order_id'
):
order_id
=
self
.
request
.
session
[
'auth_order_id'
]
order
=
get_object_or_none
(
LoginConfirmOrder
,
pk
=
order_id
)
if
not
order
:
order
=
confirm_setting
.
create_confirm_order
(
self
.
request
)
self
.
request
.
session
[
'auth_order_id'
]
=
str
(
order
.
id
)
if
order
.
status
==
"accepted"
:
return
elif
order
.
status
==
"rejected"
:
raise
errors
.
LoginConfirmRejectedError
()
else
:
raise
errors
.
LoginConfirmWaitError
()
def
create_session_if_need
(
self
):
if
self
.
request
.
session
.
is_empty
():
self
.
request
.
session
.
create
()
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
c
heck_session
()
self
.
c
reate_session_if_need
()
# 如果认证没有过,检查账号密码
try
:
user
=
self
.
check_user_auth
()
self
.
check_user_mfa_if_need
(
user
)
self
.
check_user_login_confirm_if_need
(
user
)
self
.
send_auth_signal
(
success
=
True
,
user
=
user
)
self
.
clear_auth_mark
()
resp
=
super
()
.
create
(
request
,
*
args
,
**
kwargs
)
return
resp
except
errors
.
AuthFailedError
as
e
:
if
e
.
username
:
increase_login_failed_count
(
e
.
username
,
self
.
get_request_ip
())
self
.
send_auth_signal
(
success
=
False
,
username
=
e
.
username
,
reason
=
e
.
reason
)
return
Response
({
'msg'
:
e
.
reason
,
'error'
:
e
.
error
},
status
=
401
)
except
errors
.
MFARequiredError
:
msg
=
_
(
"MFA required"
)
data
=
{
'msg'
:
msg
,
"choices"
:
[
"otp"
],
"error"
:
'mfa_required'
}
return
Response
(
data
,
status
=
300
)
except
errors
.
LoginConfirmRejectedError
as
e
:
pass
except
errors
.
LoginConfirmWaitError
as
e
:
pass
except
errors
.
LoginConfirmRequiredError
as
e
:
pass
def
send_auth_signal
(
self
,
success
=
True
,
user
=
None
,
username
=
''
,
reason
=
''
):
if
success
:
post_auth_success
.
send
(
sender
=
self
.
__class__
,
user
=
user
,
request
=
self
.
request
)
else
:
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
username
,
request
=
self
.
request
,
reason
=
reason
)
return
Response
(
e
.
as_data
(),
status
=
401
)
apps/authentication/errors.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.urls
import
reverse
from
django.conf
import
settings
password_failed
=
_
(
'Username/password check failed'
)
mfa_failed
=
_
(
'MFA authentication failed'
)
user_not_exist
=
_
(
"Username does not exist"
)
password_expired
=
_
(
"Password expired"
)
user_invalid
=
_
(
'Disabled or expired'
)
ip_blocked
=
_
(
"Log in frequently and try again later"
)
from
.signals
import
post_auth_failed
from
users.utils
import
(
increase_login_failed_count
,
get_login_failed_count
)
mfa_required
=
_
(
"MFA required"
)
login_confirm_required
=
_
(
"Login confirm required"
)
login_confirm_wait
=
_
(
"Wait login confirm"
)
reason_password_failed
=
'password_failed'
reason_mfa_failed
=
'mfa_failed'
reason_user_not_exist
=
'user_not_exist'
reason_password_expired
=
'password_expired'
reason_user_invalid
=
'user_invalid'
reason_user_inactive
=
'user_inactive'
reason_choices
=
{
reason_password_failed
:
_
(
'Username/password check failed'
),
reason_mfa_failed
:
_
(
'MFA authentication failed'
),
reason_user_not_exist
:
_
(
"Username does not exist"
),
reason_password_expired
:
_
(
"Password expired"
),
reason_user_invalid
:
_
(
'Disabled or expired'
),
reason_user_inactive
:
_
(
"This account is inactive."
)
}
old_reason_choices
=
{
'0'
:
'-'
,
'1'
:
reason_choices
[
reason_password_failed
],
'2'
:
reason_choices
[
reason_mfa_failed
],
'3'
:
reason_choices
[
reason_user_not_exist
],
'4'
:
reason_choices
[
reason_password_expired
],
}
session_empty_msg
=
_
(
"No session found, check your cookie"
)
invalid_login_msg
=
_
(
"The username or password you entered is incorrect, "
"please enter it again. "
"You can also try {times_try} times "
"(The account will be temporarily locked for {block_time} minutes)"
)
block_login_msg
=
_
(
"The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
)
mfa_failed_msg
=
_
(
"MFA code invalid, or ntp sync server time"
)
mfa_required_msg
=
_
(
"MFA required"
)
login_confirm_required_msg
=
_
(
"Login confirm required"
)
login_confirm_wait_msg
=
_
(
"Wait login confirm order for accept"
)
login_confirm_rejected_msg
=
_
(
"Login confirm order was rejected"
)
login_confirm_order_not_found_msg
=
_
(
"Order not found"
)
class
AuthFailedNeedLogMixin
:
username
=
''
request
=
None
error
=
''
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
()
.
__init__
(
*
args
,
**
kwargs
)
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
self
.
username
,
request
=
self
.
request
,
reason
=
self
.
error
)
class
AuthFailedNeedBlockMixin
:
username
=
''
ip
=
''
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
()
.
__init__
(
*
args
,
**
kwargs
)
increase_login_failed_count
(
self
.
username
,
self
.
ip
)
class
AuthFailedError
(
Exception
):
def
__init__
(
self
,
reason
,
error
=
None
,
username
=
None
):
self
.
reason
=
reason
self
.
error
=
error
self
.
username
=
username
username
=
''
msg
=
''
error
=
''
request
=
None
ip
=
''
def
__init__
(
self
,
**
kwargs
):
for
k
,
v
in
kwargs
.
items
():
setattr
(
self
,
k
,
v
)
def
as_data
(
self
):
return
{
'error'
:
self
.
error
,
'msg'
:
self
.
msg
,
}
class
CredentialError
(
AuthFailedNeedLogMixin
,
AuthFailedNeedBlockMixin
,
AuthFailedError
):
def
__init__
(
self
,
error
,
username
,
ip
,
request
):
super
()
.
__init__
(
error
=
error
,
username
=
username
,
ip
=
ip
,
request
=
request
)
times_up
=
settings
.
SECURITY_LOGIN_LIMIT_COUNT
times_failed
=
get_login_failed_count
(
username
,
ip
)
times_try
=
int
(
times_up
)
-
int
(
times_failed
)
block_time
=
settings
.
SECURITY_LOGIN_LIMIT_TIME
default_msg
=
invalid_login_msg
.
format
(
times_try
=
times_try
,
block_time
=
block_time
)
if
error
==
reason_password_failed
:
self
.
msg
=
default_msg
else
:
self
.
msg
=
reason_choices
.
get
(
error
,
default_msg
)
class
MFAFailedError
(
AuthFailedNeedLogMixin
,
AuthFailedError
):
reason
=
reason_mfa_failed
error
=
'mfa_failed'
msg
=
mfa_failed_msg
def
__init__
(
self
,
username
,
request
):
super
()
.
__init__
(
username
=
username
,
request
=
request
)
class
BlockLoginError
(
AuthFailedNeedBlockMixin
,
AuthFailedError
):
error
=
'block_login'
msg
=
block_login_msg
.
format
(
settings
.
SECURITY_LOGIN_LIMIT_TIME
)
def
__init__
(
self
,
username
,
ip
):
super
()
.
__init__
(
username
=
username
,
ip
=
ip
)
class
SessionEmptyError
(
AuthFailedError
):
msg
=
session_empty_msg
error
=
'session_empty_msg'
class
MFARequiredError
(
AuthFailedError
):
msg
=
mfa_required_msg
error
=
'mfa_required_msg'
def
as_data
(
self
):
return
{
'error'
:
self
.
error
,
'msg'
:
self
.
msg
,
'choices'
:
[
'otp'
],
'url'
:
reverse
(
'api-auth:mfa-challenge'
)
}
class
LoginConfirmRequiredError
(
AuthFailedError
):
msg
=
login_confirm_required_msg
error
=
'login_confirm_required_msg'
class
LoginConfirmError
(
AuthFailedError
):
msg
=
login_confirm_wait_msg
error
=
'login_confirm_wait_msg'
def
__init__
(
self
,
order_id
,
**
kwargs
):
self
.
order_id
=
order_id
super
()
.
__init__
(
**
kwargs
)
class
MFARequiredError
(
Exception
):
reason
=
mfa_required
error
=
'mfa_required'
def
as_data
(
self
):
return
{
"error"
:
self
.
error
,
"msg"
:
self
.
msg
,
"order_id"
:
self
.
order_id
}
class
LoginConfirm
RequiredError
(
Exception
):
reason
=
login_confirm_required
error
=
'login_confirm_
required
'
class
LoginConfirm
WaitError
(
LoginConfirmError
):
msg
=
login_confirm_wait_msg
error
=
'login_confirm_
wait_msg
'
class
LoginConfirm
WaitError
(
Exception
):
reason
=
login_confirm_wait
error
=
'login_confirm_
wait
'
class
LoginConfirm
RejectedError
(
LoginConfirmError
):
msg
=
login_confirm_rejected_msg
error
=
'login_confirm_
rejected_msg
'
class
LoginConfirm
RejectedError
(
Exception
):
reason
=
login_confirm_wait
error
=
'login_confirm_
rejected
'
class
LoginConfirm
OrderNotFound
(
LoginConfirmError
):
msg
=
login_confirm_order_not_found_msg
error
=
'login_confirm_
order_not_found_msg
'
apps/authentication/forms.py
View file @
6ce9815d
...
...
@@ -9,53 +9,19 @@ from django.conf import settings
from
users.utils
import
get_login_failed_count
class
UserLoginForm
(
Authentication
Form
):
class
UserLoginForm
(
forms
.
Form
):
username
=
forms
.
CharField
(
label
=
_
(
'Username'
),
max_length
=
100
)
password
=
forms
.
CharField
(
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
max_length
=
128
,
strip
=
False
)
error_messages
=
{
'invalid_login'
:
_
(
"The username or password you entered is incorrect, "
"please enter it again."
),
'inactive'
:
_
(
"This account is inactive."
),
'limit_login'
:
_
(
"You can also try {times_try} times "
"(The account will be temporarily locked for {block_time} minutes)"
),
'block_login'
:
_
(
"The account has been locked "
"(please contact admin to unlock it or try again after {} minutes)"
)
}
def
confirm_login_allowed
(
self
,
user
):
if
not
user
.
is_staff
:
raise
forms
.
ValidationError
(
self
.
error_messages
[
'inactive'
],
code
=
'inactive'
,)
def
get_limit_login_error_message
(
self
,
username
,
ip
):
times_up
=
settings
.
SECURITY_LOGIN_LIMIT_COUNT
times_failed
=
get_login_failed_count
(
username
,
ip
)
times_try
=
int
(
times_up
)
-
int
(
times_failed
)
block_time
=
settings
.
SECURITY_LOGIN_LIMIT_TIME
if
times_try
<=
0
:
error_message
=
self
.
error_messages
[
'block_login'
]
error_message
=
error_message
.
format
(
block_time
)
else
:
error_message
=
self
.
error_messages
[
'limit_login'
]
error_message
=
error_message
.
format
(
times_try
=
times_try
,
block_time
=
block_time
,
code
=
'inactive'
,
)
return
error_message
def
add_limit_login_error
(
self
,
username
,
ip
):
error
=
self
.
get_limit_login_error_message
(
username
,
ip
)
self
.
add_error
(
'password'
,
error
)
class
UserLoginCaptchaForm
(
UserLoginForm
):
...
...
apps/authentication/mixins.py
0 → 100644
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
import
time
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
)
from
.
import
errors
from
.utils
import
check_user_valid
from
.signals
import
post_auth_success
,
post_auth_failed
logger
=
get_logger
(
__name__
)
class
AuthMixin
:
request
=
None
def
get_user_from_session
(
self
):
if
self
.
request
.
session
.
is_empty
():
raise
errors
.
SessionEmptyError
()
if
self
.
request
.
user
and
not
self
.
request
.
user
.
is_anonymous
:
return
self
.
request
.
user
user_id
=
self
.
request
.
session
.
get
(
'user_id'
)
if
not
user_id
:
user
=
None
else
:
user
=
get_object_or_none
(
User
,
pk
=
user_id
)
if
not
user
:
raise
errors
.
SessionEmptyError
()
return
user
def
get_request_ip
(
self
):
ip
=
''
if
hasattr
(
self
.
request
,
'data'
):
ip
=
self
.
request
.
data
.
get
(
'remote_addr'
,
''
)
ip
=
ip
or
get_request_ip
(
self
.
request
)
return
ip
def
check_is_block
(
self
):
if
hasattr
(
self
.
request
,
'data'
):
username
=
self
.
request
.
data
.
get
(
"username"
)
else
:
username
=
self
.
request
.
POST
.
get
(
"username"
)
ip
=
self
.
get_request_ip
()
if
is_block_login
(
username
,
ip
):
logger
.
warn
(
'Ip was blocked'
+
': '
+
username
+
':'
+
ip
)
raise
errors
.
BlockLoginError
(
username
=
username
,
ip
=
ip
)
def
check_user_auth
(
self
):
request
=
self
.
request
self
.
check_is_block
()
if
hasattr
(
request
,
'data'
):
username
=
request
.
data
.
get
(
'username'
,
''
)
password
=
request
.
data
.
get
(
'password'
,
''
)
public_key
=
request
.
data
.
get
(
'public_key'
,
''
)
else
:
username
=
request
.
POST
.
get
(
'username'
,
''
)
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
)
ip
=
self
.
get_request_ip
()
if
not
user
:
raise
errors
.
CredentialError
(
username
=
username
,
error
=
error
,
ip
=
ip
,
request
=
request
)
clean_failed_count
(
username
,
ip
)
request
.
session
[
'auth_password'
]
=
1
request
.
session
[
'user_id'
]
=
str
(
user
.
id
)
return
user
def
check_user_mfa_if_need
(
self
,
user
):
if
self
.
request
.
session
.
get
(
'auth_mfa'
):
return
True
if
not
user
.
otp_enabled
or
not
user
.
otp_secret_key
:
return
True
raise
errors
.
MFARequiredError
()
def
check_user_mfa
(
self
,
code
):
user
=
self
.
get_user_from_session
()
ok
=
user
.
check_otp
(
code
)
if
ok
:
self
.
request
.
session
[
'auth_mfa'
]
=
1
self
.
request
.
session
[
'auth_mfa_time'
]
=
time
.
time
()
self
.
request
.
session
[
'auth_mfa_type'
]
=
'otp'
return
raise
errors
.
MFAFailedError
(
username
=
user
.
username
,
request
=
self
.
request
)
def
check_user_login_confirm_if_need
(
self
,
user
):
from
orders.models
import
LoginConfirmOrder
confirm_setting
=
user
.
get_login_confirm_setting
()
if
self
.
request
.
session
.
get
(
'auth_confirm'
)
or
not
confirm_setting
:
return
order
=
None
if
self
.
request
.
session
.
get
(
'auth_order_id'
):
order_id
=
self
.
request
.
session
[
'auth_order_id'
]
order
=
get_object_or_none
(
LoginConfirmOrder
,
pk
=
order_id
)
if
not
order
:
order
=
confirm_setting
.
create_confirm_order
(
self
.
request
)
self
.
request
.
session
[
'auth_order_id'
]
=
str
(
order
.
id
)
if
order
.
status
==
"accepted"
:
return
elif
order
.
status
==
"rejected"
:
raise
errors
.
LoginConfirmRejectedError
(
order
.
id
)
else
:
raise
errors
.
LoginConfirmWaitError
(
order
.
id
)
def
clear_auth_mark
(
self
):
self
.
request
.
session
[
'auth_password'
]
=
''
self
.
request
.
session
[
'auth_mfa'
]
=
''
self
.
request
.
session
[
'auth_confirm'
]
=
''
def
send_auth_signal
(
self
,
success
=
True
,
user
=
None
,
username
=
''
,
reason
=
''
):
if
success
:
post_auth_success
.
send
(
sender
=
self
.
__class__
,
user
=
user
,
request
=
self
.
request
)
else
:
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
username
,
request
=
self
.
request
,
reason
=
reason
)
apps/authentication/serializers.py
View file @
6ce9815d
...
...
@@ -3,6 +3,7 @@
from
django.core.cache
import
cache
from
rest_framework
import
serializers
from
common.utils
import
get_object_or_none
from
users.models
import
User
from
.models
import
AccessKey
,
LoginConfirmSetting
...
...
@@ -24,7 +25,12 @@ class OtpVerifySerializer(serializers.Serializer):
code
=
serializers
.
CharField
(
max_length
=
6
,
min_length
=
6
)
class
BearerTokenMixin
(
serializers
.
Serializer
):
class
BearerTokenSerializer
(
serializers
.
Serializer
):
username
=
serializers
.
CharField
(
allow_null
=
True
,
required
=
False
)
password
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
public_key
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
token
=
serializers
.
CharField
(
read_only
=
True
)
keyword
=
serializers
.
SerializerMethodField
()
date_expired
=
serializers
.
DateTimeField
(
read_only
=
True
)
...
...
@@ -33,58 +39,35 @@ class BearerTokenMixin(serializers.Serializer):
def
get_keyword
(
obj
):
return
'Bearer'
def
create_response
(
self
,
username
):
request
=
self
.
context
.
get
(
"request"
)
try
:
user
=
User
.
objects
.
get
(
username
=
username
)
except
User
.
DoesNotExist
:
raise
serializers
.
ValidationError
(
"username
%
s not exist"
%
username
)
def
create
(
self
,
validated_data
):
request
=
self
.
context
.
get
(
'request'
)
if
request
.
user
and
not
request
.
user
.
is_anonymous
:
user
=
request
.
user
else
:
user_id
=
request
.
session
.
get
(
'user_id'
)
user
=
get_object_or_none
(
User
,
pk
=
user_id
)
if
not
user
:
raise
serializers
.
ValidationError
(
"user id {} not exist"
.
format
(
user_id
)
)
token
,
date_expired
=
user
.
create_bearer_token
(
request
)
instance
=
{
"username"
:
username
,
"username"
:
user
.
user
name
,
"token"
:
token
,
"date_expired"
:
date_expired
,
}
return
instance
def
update
(
self
,
instance
,
validated_data
):
pass
class
BearerTokenSerializer
(
BearerTokenMixin
,
serializers
.
Serializer
):
username
=
serializers
.
CharField
()
password
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
public_key
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
def
create
(
self
,
validated_data
):
username
=
validated_data
.
get
(
"username"
)
return
self
.
create_response
(
username
)
class
MFAChallengeSerializer
(
BearerTokenMixin
,
serializers
.
Serializer
):
req
=
serializers
.
CharField
(
write_only
=
True
)
auth_type
=
serializers
.
CharField
(
write_only
=
True
)
class
MFAChallengeSerializer
(
serializers
.
Serializer
):
auth_type
=
serializers
.
CharField
(
write_only
=
True
,
required
=
False
,
allow_blank
=
True
)
code
=
serializers
.
CharField
(
write_only
=
True
)
def
validate_req
(
self
,
attr
):
username
=
cache
.
get
(
attr
)
if
not
username
:
raise
serializers
.
ValidationError
(
"Not valid, may be expired"
)
self
.
context
[
"username"
]
=
username
def
validate_code
(
self
,
code
):
username
=
self
.
context
[
"username"
]
user
=
User
.
objects
.
get
(
username
=
username
)
ok
=
user
.
check_otp
(
code
)
if
not
ok
:
msg
=
"Otp code not valid, may be expired"
raise
serializers
.
ValidationError
(
msg
)
def
create
(
self
,
validated_data
):
username
=
self
.
context
[
"username"
]
return
self
.
create_response
(
username
)
pass
def
update
(
self
,
instance
,
validated_data
):
pass
class
LoginConfirmSettingSerializer
(
serializers
.
ModelSerializer
):
...
...
apps/authentication/signals_handlers.py
View file @
6ce9815d
from
rest_framework.request
import
Request
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.utils
import
timezone
from
django_auth_ldap.backend
import
populate_user
from
common.utils
import
get_request_ip
from
.backends.openid
import
new_client
from
.backends.openid.signals
import
(
post_create_openid_user
,
post_openid_login_success
)
from
.tasks
import
write_login_log_async
from
.signals
import
post_auth_success
,
post_auth_failed
from
.signals
import
post_auth_success
@receiver
(
user_logged_out
)
...
...
@@ -52,35 +48,4 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
user
.
save
()
def
generate_data
(
username
,
request
):
user_agent
=
request
.
META
.
get
(
'HTTP_USER_AGENT'
,
''
)
if
isinstance
(
request
,
Request
):
login_ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
login_type
=
request
.
data
.
get
(
'login_type'
,
''
)
else
:
login_ip
=
get_request_ip
(
request
)
login_type
=
'W'
data
=
{
'username'
:
username
,
'ip'
:
login_ip
,
'type'
:
login_type
,
'user_agent'
:
user_agent
,
'datetime'
:
timezone
.
now
()
}
return
data
@receiver
(
post_auth_success
)
def
on_user_auth_success
(
sender
,
user
,
request
,
**
kwargs
):
data
=
generate_data
(
user
.
username
,
request
)
data
.
update
({
'mfa'
:
int
(
user
.
otp_enabled
),
'status'
:
True
})
write_login_log_async
.
delay
(
**
data
)
@receiver
(
post_auth_failed
)
def
on_user_auth_failed
(
sender
,
username
,
request
,
reason
,
**
kwargs
):
data
=
generate_data
(
username
,
request
)
data
.
update
({
'reason'
:
reason
,
'status'
:
False
})
write_login_log_async
.
delay
(
**
data
)
apps/authentication/tasks.py
View file @
6ce9815d
...
...
@@ -6,17 +6,8 @@ from ops.celery.decorator import register_as_period_task
from
django.contrib.sessions.models
import
Session
from
django.utils
import
timezone
from
.utils
import
write_login_log
@shared_task
def
write_login_log_async
(
*
args
,
**
kwargs
):
write_login_log
(
*
args
,
**
kwargs
)
@register_as_period_task
(
interval
=
3600
*
24
)
@shared_task
def
clean_django_sessions
():
Session
.
objects
.
filter
(
expire_date__lt
=
timezone
.
now
())
.
delete
()
apps/authentication/templates/authentication/login.html
View file @
6ce9815d
...
...
@@ -37,7 +37,6 @@
<p>
{% trans "Changes the world, starting with a little bit." %}
</p>
</div>
<div
class=
"col-md-6"
>
<div
class=
"ibox-content"
>
...
...
@@ -47,25 +46,29 @@
</div>
<form
class=
"m-t"
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
{% if block_login %}
<p
class=
"red-fonts"
>
{% trans 'Log in frequently and try again later' %}
</p>
{% elif password_expired %}
<p
class=
"red-fonts"
>
{% trans 'The user password has expired' %}
</p>
{% elif form.errors %}
{% if 'captcha' in form.errors %}
<p
class=
"red-fonts"
>
{% trans 'Captcha invalid' %}
</p>
{% else %}
{% if form.non_field_errors %}
<div
style=
"line-height: 17px;"
>
<p
class=
"red-fonts"
>
{{ form.non_field_errors.as_text }}
</p>
{% endif %}
<p
class=
"red-fonts"
>
{{ form.errors.password.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 }}
...
...
apps/authentication/templates/authentication/login_wait_confirm.html
View file @
6ce9815d
...
...
@@ -86,7 +86,7 @@ function doRequestAuth() {
window
.
location
=
successUrl
;
},
error
:
function
(
text
,
data
)
{
if
(
data
.
status
!==
"pending
"
)
{
if
(
data
.
error
!==
"login_confirm_wait
"
)
{
if
(
!
errorMsgShow
)
{
infoMsgRef
.
hide
();
errorMsgRef
.
show
();
...
...
@@ -97,7 +97,7 @@ function doRequestAuth() {
clearInterval
(
checkInterval
);
$
(
".copy-btn"
).
attr
(
'disabled'
,
'disabled'
)
}
errorMsgRef
.
html
(
data
.
error
)
errorMsgRef
.
html
(
data
.
msg
)
},
flash_message
:
false
})
...
...
apps/authentication/templates/authentication/xpack_login.html
View file @
6ce9815d
...
...
@@ -48,6 +48,13 @@
float
:
right
;
}
.red-fonts
{
color
:
red
;
}
.field-error
{
text-align
:
left
;
}
</style>
</head>
...
...
@@ -69,30 +76,32 @@
<div
style=
"margin-bottom: 10px"
>
<div>
<div
class=
"col-md-1"
></div>
<div
class=
"contact-form col-md-10"
style=
"margin-top:
1
0px;height: 35px"
>
<div
class=
"contact-form col-md-10"
style=
"margin-top:
2
0px;height: 35px"
>
<form
id=
"contact-form"
action=
""
method=
"post"
role=
"form"
novalidate=
"novalidate"
>
{% csrf_token %}
{% if form.non_field_errors %}
<div
style=
"height: 70px;color: red;line-height: 17px;"
>
{% if block_login %}
<p
class=
"red-fonts"
>
{% trans 'Log in frequently and try again later' %}
</p>
<p
class=
"red-fonts"
>
{{ form.errors.password.as_text }}
</p>
{% elif password_expired %}
<p
class=
"red-fonts"
>
{% trans 'The user password has expired' %}
</p>
{% elif form.errors %}
{% if 'captcha' in form.errors %}
<p
class=
"red-fonts"
>
{% trans 'Captcha invalid' %}
</p>
{% else %}
<p
class=
"red-fonts"
>
{{ form.non_field_errors.as_text }}
</p>
{% endif %}
<p
class=
"red-fonts"
>
{{ form.errors.password.as_text }}
</p>
{% endif %}
</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 %}"
style=
"height: 35px"
>
{% 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
class=
"form-group"
style=
"height: 50px;margin-bottom: 0;font-size: 13px"
>
{{ form.captcha }}
...
...
apps/authentication/urls/api_urls.py
View file @
6ce9815d
...
...
@@ -12,12 +12,11 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key')
urlpatterns
=
[
# path('token/', api.UserToken.as_view(), name='user-token'),
path
(
'auth/'
,
api
.
UserAuth
Api
.
as_view
(),
name
=
'user-auth'
),
path
(
'auth/'
,
api
.
TokenCreate
Api
.
as_view
(),
name
=
'user-auth'
),
path
(
'tokens/'
,
api
.
TokenCreateApi
.
as_view
(),
name
=
'auth-token'
),
path
(
'mfa/challenge/'
,
api
.
MFAChallengeApi
.
as_view
(),
name
=
'mfa-challenge'
),
path
(
'connection-token/'
,
api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
path
(
'otp/auth/'
,
api
.
UserOtpAuthApi
.
as_view
(),
name
=
'user-otp-auth'
),
path
(
'otp/verify/'
,
api
.
UserOtpVerifyApi
.
as_view
(),
name
=
'user-otp-verify'
),
path
(
'order/auth/'
,
api
.
UserOrderAcceptAuthApi
.
as_view
(),
name
=
'user-order-auth'
),
path
(
'login-confirm-settings/<uuid:user_id>/'
,
api
.
LoginConfirmSettingUpdateApi
.
as_view
(),
name
=
'login-confirm-setting-update'
)
...
...
apps/authentication/utils.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
django.utils.translation
import
ugettext
as
_
,
ugettext_lazy
as
__
from
django.utils.translation
import
ugettext
as
_
from
django.contrib.auth
import
authenticate
from
django.utils
import
timezone
from
common.utils
import
(
get_ip_city
,
get_object_or_none
,
validate_ip
,
get_request_ip
get_ip_city
,
get_object_or_none
,
validate_ip
)
from
users.models
import
User
from
.
import
errors
def
write_login_log
(
*
args
,
**
kwargs
):
from
audits.models
import
UserLoginLog
default_city
=
_
(
"Unknown"
)
ip
=
kwargs
.
get
(
'ip'
)
or
''
if
not
(
ip
and
validate_ip
(
ip
)):
ip
=
ip
[:
15
]
city
=
default_city
else
:
city
=
get_ip_city
(
ip
)
or
default_city
kwargs
.
update
({
'ip'
:
ip
,
'city'
:
city
})
UserLoginLog
.
objects
.
create
(
**
kwargs
)
def
check_user_valid
(
**
kwargs
):
password
=
kwargs
.
pop
(
'password'
,
None
)
public_key
=
kwargs
.
pop
(
'public_key'
,
None
)
email
=
kwargs
.
pop
(
'email'
,
None
)
username
=
kwargs
.
pop
(
'username'
,
None
)
request
=
kwargs
.
get
(
'request'
)
if
username
:
user
=
get_object_or_none
(
User
,
username
=
username
)
...
...
@@ -38,21 +25,25 @@ def check_user_valid(**kwargs):
user
=
None
if
user
is
None
:
return
None
,
errors
.
user_not_exist
elif
not
user
.
is_valid
:
return
None
,
errors
.
user_invalid
return
None
,
errors
.
reason_user_not_exist
elif
user
.
is_expired
:
return
None
,
errors
.
reason_password_expired
elif
not
user
.
is_active
:
return
None
,
errors
.
reason_user_inactive
elif
user
.
password_has_expired
:
return
None
,
errors
.
password_expired
return
None
,
errors
.
reason_
password_expired
if
password
and
authenticate
(
username
=
username
,
password
=
password
):
if
password
:
user
=
authenticate
(
request
,
username
=
username
,
password
=
password
)
if
user
:
return
user
,
''
if
public_key
and
user
.
public_key
:
public_key_saved
=
user
.
public_key
.
split
()
if
len
(
public_key_saved
)
==
1
:
if
public_key
==
public_key_saved
[
0
]:
return
user
,
''
elif
len
(
public_key_saved
)
>
1
:
if
public_key
==
public_key_saved
[
1
]
:
public_key_saved
=
public_key_saved
[
0
]
else
:
public_key_saved
=
public_key_saved
[
1
]
if
public_key
==
public_key_saved
:
return
user
,
''
return
None
,
errors
.
password_failed
return
None
,
errors
.
reason_
password_failed
apps/authentication/views/__init__.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
.login
import
*
from
.mfa
import
*
apps/authentication/views/login.py
View file @
6ce9815d
...
...
@@ -16,22 +16,20 @@ from django.views.decorators.debug import sensitive_post_parameters
from
django.views.generic.base
import
TemplateView
,
RedirectView
from
django.views.generic.edit
import
FormView
from
django.conf
import
settings
from
django.urls
import
reverse_lazy
from
common.utils
import
get_request_ip
,
get_object_or_none
from
users.models
import
User
from
users.utils
import
(
check_otp_code
,
is_block_login
,
clean_failed_count
,
get_user_or_tmp_user
,
set_tmp_user_to_cache
,
increase_login_failed_count
,
get_user_or_tmp_user
,
increase_login_failed_count
,
redirect_user_first_login_or_index
)
from
..models
import
LoginConfirmSetting
from
..signals
import
post_auth_success
,
post_auth_failed
from
..
import
forms
from
..
import
errors
from
..
import
forms
,
mixins
,
errors
__all__
=
[
'UserLoginView'
,
'UserLog
inOtpView'
,
'UserLog
outView'
,
'UserLoginView'
,
'UserLogoutView'
,
'UserLoginGuardView'
,
'UserLoginWaitConfirmView'
,
]
...
...
@@ -39,10 +37,11 @@ __all__ = [
@method_decorator
(
sensitive_post_parameters
(),
name
=
'dispatch'
)
@method_decorator
(
csrf_protect
,
name
=
'dispatch'
)
@method_decorator
(
never_cache
,
name
=
'dispatch'
)
class
UserLoginView
(
FormView
):
class
UserLoginView
(
mixins
.
AuthMixin
,
FormView
):
form_class
=
forms
.
UserLoginForm
form_class_captcha
=
forms
.
UserLoginCaptchaForm
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
redirect_field_name
=
'next'
def
get_template_names
(
self
):
template_name
=
'authentication/login.html'
...
...
@@ -69,54 +68,25 @@ class UserLoginView(FormView):
request
.
session
.
set_test_cookie
()
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
# limit login authentication
ip
=
get_request_ip
(
request
)
username
=
self
.
request
.
POST
.
get
(
'username'
)
if
is_block_login
(
username
,
ip
):
return
self
.
render_to_response
(
self
.
get_context_data
(
block_login
=
True
))
return
super
()
.
post
(
request
,
*
args
,
**
kwargs
)
def
form_valid
(
self
,
form
):
if
not
self
.
request
.
session
.
test_cookie_worked
():
return
HttpResponse
(
_
(
"Please enable cookies and try again."
))
user
=
form
.
get_user
()
# user password expired
if
user
.
password_has_expired
:
reason
=
errors
.
password_expired
self
.
send_auth_signal
(
success
=
False
,
username
=
user
.
username
,
reason
=
reason
)
return
self
.
render_to_response
(
self
.
get_context_data
(
password_expired
=
True
))
set_tmp_user_to_cache
(
self
.
request
,
user
)
username
=
form
.
cleaned_data
.
get
(
'username'
)
ip
=
get_request_ip
(
self
.
request
)
# 登陆成功,清除缓存计数
clean_failed_count
(
username
,
ip
)
self
.
request
.
session
[
'auth_password'
]
=
'1'
return
self
.
redirect_to_guard_view
()
def
form_invalid
(
self
,
form
):
# write login failed log
username
=
form
.
cleaned_data
.
get
(
'username'
)
exist
=
User
.
objects
.
filter
(
username
=
username
)
.
first
()
reason
=
errors
.
password_failed
if
exist
else
errors
.
user_not_exist
# limit user login failed count
ip
=
get_request_ip
(
self
.
request
)
increase_login_failed_count
(
username
,
ip
)
form
.
add_limit_login_error
(
username
,
ip
)
# show captcha
try
:
self
.
check_user_auth
()
except
errors
.
AuthFailedError
as
e
:
form
.
add_error
(
None
,
e
.
msg
)
ip
=
self
.
get_request_ip
()
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
self
.
send_auth_signal
(
success
=
False
,
username
=
username
,
reason
=
reason
)
old_form
=
form
form
=
self
.
form_class_captcha
(
data
=
form
.
data
)
form
.
_errors
=
old_form
.
errors
return
super
()
.
form_invalid
(
form
)
context
=
self
.
get_context_data
(
form
=
form
)
return
self
.
render_to_response
(
context
)
return
self
.
redirect_to_guard_view
()
@staticmethod
def
redirect_to_guard_view
():
continue_url
=
reverse
(
'authentication:login-guard'
)
return
redirect
(
continue_url
)
def
redirect_to_guard_view
(
self
):
guard_url
=
reverse
(
'authentication:login-guard'
)
args
=
self
.
request
.
META
.
get
(
'QUERY_STRING'
,
''
)
if
args
and
self
.
query_string
:
guard_url
=
"
%
s?
%
s"
%
(
guard_url
,
args
)
return
redirect
(
guard_url
)
def
get_form_class
(
self
):
ip
=
get_request_ip
(
self
.
request
)
...
...
@@ -134,58 +104,34 @@ class UserLoginView(FormView):
return
super
()
.
get_context_data
(
**
kwargs
)
class
UserLoginOtpView
(
FormView
):
template_name
=
'authentication/login_otp.html'
form_class
=
forms
.
UserCheckOtpCodeForm
redirect_field_name
=
'next'
def
form_valid
(
self
,
form
):
user
=
get_user_or_tmp_user
(
self
.
request
)
otp_code
=
form
.
cleaned_data
.
get
(
'otp_code'
)
otp_secret_key
=
user
.
otp_secret_key
if
check_otp_code
(
otp_secret_key
,
otp_code
):
self
.
request
.
session
[
'auth_otp'
]
=
'1'
return
UserLoginView
.
redirect_to_guard_view
()
else
:
self
.
send_auth_signal
(
success
=
False
,
username
=
user
.
username
,
reason
=
errors
.
mfa_failed
)
form
.
add_error
(
'otp_code'
,
_
(
'MFA code invalid, or ntp sync server time'
)
)
return
super
()
.
form_invalid
(
form
)
def
send_auth_signal
(
self
,
success
=
True
,
user
=
None
,
username
=
''
,
reason
=
''
):
if
success
:
post_auth_success
.
send
(
sender
=
self
.
__class__
,
user
=
user
,
request
=
self
.
request
)
else
:
post_auth_failed
.
send
(
sender
=
self
.
__class__
,
username
=
username
,
request
=
self
.
request
,
reason
=
reason
)
class
UserLoginGuardView
(
RedirectView
):
class
UserLoginGuardView
(
mixins
.
AuthMixin
,
RedirectView
):
redirect_field_name
=
'next'
login_url
=
reverse_lazy
(
'authentication:login'
)
login_otp_url
=
reverse_lazy
(
'authentication:login-otp'
)
login_confirm_url
=
reverse_lazy
(
'authentication:login-wait-confirm'
)
def
format_redirect_url
(
self
,
url
):
args
=
self
.
request
.
META
.
get
(
'QUERY_STRING'
,
''
)
if
args
and
self
.
query_string
:
url
=
"
%
s?
%
s"
%
(
url
,
args
)
return
url
def
get_redirect_url
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
request
.
session
.
get
(
'auth_password'
):
return
reverse
(
'authentication:login'
)
user
=
get_user_or_tmp_user
(
self
.
request
)
return
self
.
format_redirect_url
(
self
.
login_url
)
user
=
self
.
get_user_from_session
()
# 启用并设置了otp
if
user
.
otp_enabled
and
user
.
otp_secret_key
and
\
not
self
.
request
.
session
.
get
(
'auth_
otp
'
):
return
reverse
(
'authentication:login-otp'
)
not
self
.
request
.
session
.
get
(
'auth_
mfa
'
):
return
self
.
format_redirect_url
(
self
.
login_otp_url
)
confirm_setting
=
user
.
get_login_confirm_setting
()
if
confirm_setting
and
not
self
.
request
.
session
.
get
(
'auth_confirm'
):
order
=
confirm_setting
.
create_confirm_order
(
self
.
request
)
self
.
request
.
session
[
'auth_order_id'
]
=
str
(
order
.
id
)
url
=
reverse
(
'authentication:login-wait-confirm'
)
url
=
self
.
format_redirect_url
(
self
.
login_confirm_url
)
return
url
self
.
login_success
(
user
)
self
.
clear_auth_mark
()
# 启用但是没有设置otp
if
user
.
otp_enabled
and
not
user
.
otp_secret_key
:
# 1,2,mfa_setting & F
...
...
apps/authentication/views/mfa.py
0 → 100644
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
__future__
import
unicode_literals
from
django.views.generic.edit
import
FormView
from
..
import
forms
,
errors
,
mixins
from
.utils
import
redirect_to_guard_view
__all__
=
[
'UserLoginOtpView'
]
class
UserLoginOtpView
(
mixins
.
AuthMixin
,
FormView
):
template_name
=
'authentication/login_otp.html'
form_class
=
forms
.
UserCheckOtpCodeForm
redirect_field_name
=
'next'
def
form_valid
(
self
,
form
):
otp_code
=
form
.
cleaned_data
.
get
(
'otp_code'
)
try
:
self
.
check_user_mfa
(
otp_code
)
return
redirect_to_guard_view
()
except
errors
.
MFAFailedError
as
e
:
form
.
add_error
(
'otp_code'
,
e
.
reason
)
return
super
()
.
form_invalid
(
form
)
apps/authentication/views/utils.py
0 → 100644
View file @
6ce9815d
# -*- coding: utf-8 -*-
#
from
django.shortcuts
import
reverse
,
redirect
def
redirect_to_guard_view
():
continue_url
=
reverse
(
'authentication:login-guard'
)
return
redirect
(
continue_url
)
apps/common/utils/common.py
View file @
6ce9815d
...
...
@@ -153,6 +153,14 @@ def get_request_ip(request):
return
login_ip
def
get_request_ip_or_data
(
request
):
ip
=
''
if
hasattr
(
request
,
'data'
):
ip
=
request
.
data
.
get
(
'remote_addr'
,
''
)
ip
=
ip
or
get_request_ip
(
request
)
return
ip
def
validate_ip
(
ip
):
try
:
ipaddress
.
ip_address
(
ip
)
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
6ce9815d
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
6ce9815d
This diff is collapsed.
Click to expand it.
apps/perms/templates/perms/asset_permission_create_update.html
View file @
6ce9815d
...
...
@@ -100,16 +100,6 @@
<link
rel=
"stylesheet"
type=
"text/css"
href=
{%
static
"
css
/
plugins
/
daterangepicker
/
daterangepicker
.
css
"
%}
/>
<script>
var
dateOptions
=
{
singleDatePicker
:
true
,
showDropdowns
:
true
,
timePicker
:
true
,
timePicker24Hour
:
true
,
autoApply
:
true
,
locale
:
{
format
:
'YYYY-MM-DD HH:mm'
}
};
var
api_action
=
"{{ api_action }}"
;
$
(
document
).
ready
(
function
()
{
...
...
@@ -119,8 +109,8 @@ $(document).ready(function () {
nodesSelect2Init
(
".nodes-select2"
);
usersSelect2Init
(
".users-select2"
);
$
(
'#date_start'
).
daterangepicker
(
dateOptions
);
$
(
'#date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_start'
);
initDateRangePicker
(
'#date_expired'
);
$
(
"#id_assets"
).
parent
().
find
(
".select2-selection"
).
on
(
'click'
,
function
(
e
)
{
if
(
$
(
e
.
target
).
attr
(
'class'
)
!==
'select2-selection__choice__remove'
){
...
...
apps/perms/templates/perms/remote_app_permission_create_update.html
View file @
6ce9815d
...
...
@@ -115,8 +115,8 @@ $(document).ready(function () {
closeOnSelect
:
false
});
usersSelect2Init
(
'.users-select2'
);
$
(
'#date_start'
).
daterangepicker
(
dateOptions
);
$
(
'#date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_start'
);
initDateRangePicker
(
'#date_expired'
);
})
.
on
(
"submit"
,
"form"
,
function
(
evt
)
{
evt
.
preventDefault
();
...
...
apps/static/js/jumpserver.js
View file @
6ce9815d
...
...
@@ -1289,3 +1289,31 @@ function showCeleryTaskLog(taskId) {
var
url
=
'/ops/celery/task/taskId/log/'
.
replace
(
'taskId'
,
taskId
);
window
.
open
(
url
,
''
,
'width=900,height=600'
)
}
function
initDateRangePicker
(
selector
,
options
)
{
if
(
!
options
)
{
options
=
{}
}
var
zhLocale
=
{
format
:
'YYYY-MM-DD HH:mm'
,
separator
:
' ~ '
,
applyLabel
:
"应用"
,
cancelLabel
:
"取消"
,
resetLabel
:
"重置"
,
daysOfWeek
:
[
"日"
,
"一"
,
"二"
,
"三"
,
"四"
,
"五"
,
"六"
],
//汉化处理
monthNames
:
[
"一月"
,
"二月"
,
"三月"
,
"四月"
,
"五月"
,
"六月"
,
"七月"
,
"八月"
,
"九月"
,
"十月"
,
"十一月"
,
"十二月"
],
};
var
defaultOption
=
{
singleDatePicker
:
true
,
showDropdowns
:
true
,
timePicker
:
true
,
timePicker24Hour
:
true
,
autoApply
:
true
,
};
var
userLang
=
navigator
.
language
||
navigator
.
userLanguage
;;
if
(
userLang
.
indexOf
(
'zh'
)
!==
-
1
)
{
defaultOption
.
locale
=
zhLocale
;
}
options
=
Object
.
assign
(
defaultOption
,
options
);
return
$
(
selector
).
daterangepicker
(
options
);
}
apps/templates/_head_css_js.html
View file @
6ce9815d
apps/users/models/user.py
View file @
6ce9815d
...
...
@@ -62,10 +62,6 @@ class AuthMixin:
def
can_use_ssh_key_login
(
self
):
return
settings
.
TERMINAL_PUBLIC_KEY_AUTH
def
check_otp
(
self
,
code
):
from
..utils
import
check_otp_code
return
check_otp_code
(
self
.
otp_secret_key
,
code
)
def
is_public_key_valid
(
self
):
"""
Check if the user's ssh public key is valid.
...
...
@@ -362,6 +358,10 @@ class MFAMixin:
self
.
otp_level
=
0
self
.
otp_secret_key
=
None
def
check_otp
(
self
,
code
):
from
..utils
import
check_otp_code
return
check_otp_code
(
self
.
otp_secret_key
,
code
)
class
User
(
AuthMixin
,
TokenMixin
,
RoleMixin
,
MFAMixin
,
AbstractUser
):
SOURCE_LOCAL
=
'local'
...
...
apps/users/templates/users/_user.html
View file @
6ce9815d
...
...
@@ -56,6 +56,7 @@
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js' %}"
></script>
<script
type=
"text/javascript"
src=
'{% static "js/plugins/daterangepicker/moment.min.js" %}'
></script>
<script
type=
"text/javascript"
src=
'{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'
></script>
<link
rel=
"stylesheet"
type=
"text/css"
href=
{%
static
"
css
/
plugins
/
daterangepicker
/
daterangepicker
.
css
"
%}
/>
...
...
@@ -72,19 +73,9 @@
$
(
groups_id
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
);
}}
var
dateOptions
=
{
singleDatePicker
:
true
,
showDropdowns
:
true
,
timePicker
:
true
,
timePicker24Hour
:
true
,
autoApply
:
true
,
locale
:
{
format
:
'YYYY-MM-DD HH:mm'
}
};
$
(
document
).
ready
(
function
()
{
$
(
'.select2'
).
select2
();
$
(
'#id_date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#id_date_expired'
);
var
mfa_radio
=
$
(
'#id_otp_level'
);
mfa_radio
.
addClass
(
"form-inline"
);
mfa_radio
.
children
().
css
(
"margin-right"
,
"15px"
);
...
...
apps/users/templates/users/user_detail.html
View file @
6ce9815d
...
...
@@ -212,7 +212,7 @@
</table>
</div>
</div>
{% if user
_object.is_current_org_admin or user_object
.is_superuser %}
{% if user
.is_current_org_admin or user
.is_superuser %}
<div
class=
"panel panel-info"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'User group' %}
...
...
apps/users/urls/api_urls.py
View file @
6ce9815d
...
...
@@ -20,9 +20,6 @@ router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'us
urlpatterns
=
[
path
(
'connection-token/'
,
auth_api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
path
(
'auth/'
,
auth_api
.
UserAuthApi
.
as_view
(),
name
=
'user-auth'
),
path
(
'otp/auth/'
,
auth_api
.
UserOtpAuthApi
.
as_view
(),
name
=
'user-otp-auth'
),
path
(
'profile/'
,
api
.
UserProfileApi
.
as_view
(),
name
=
'user-profile'
),
path
(
'otp/reset/'
,
api
.
UserResetOTPApi
.
as_view
(),
name
=
'my-otp-reset'
),
path
(
'users/<uuid:pk>/otp/reset/'
,
api
.
UserResetOTPApi
.
as_view
(),
name
=
'user-reset-otp'
),
...
...
apps/users/utils.py
View file @
6ce9815d
...
...
@@ -218,9 +218,11 @@ def set_tmp_user_to_cache(request, user, ttl=3600):
def
redirect_user_first_login_or_index
(
request
,
redirect_field_name
):
if
request
.
user
.
is_first_login
:
return
reverse
(
'users:user-first-login'
)
return
request
.
POST
.
get
(
redirect_field_name
,
request
.
GET
.
get
(
redirect_field_name
,
reverse
(
'index'
)))
url_in_post
=
request
.
POST
.
get
(
redirect_field_name
)
if
url_in_post
:
return
url_in_post
url_in_get
=
request
.
GET
.
get
(
redirect_field_name
,
reverse
(
'index'
))
return
url_in_get
def
generate_otp_uri
(
request
,
issuer
=
"Jumpserver"
):
...
...
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