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):
...
@@ -110,5 +110,14 @@ class UserLoginLog(models.Model):
login_logs
=
login_logs
.
filter
(
username__in
=
username_list
)
login_logs
=
login_logs
.
filter
(
username__in
=
username_list
)
return
login_logs
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
:
class
Meta
:
ordering
=
[
'-datetime'
,
'username'
]
ordering
=
[
'-datetime'
,
'username'
]
apps/audits/signals_handler.py
View file @
6ce9815d
...
@@ -4,15 +4,18 @@
...
@@ -4,15 +4,18 @@
from
django.db.models.signals
import
post_save
,
post_delete
from
django.db.models.signals
import
post_save
,
post_delete
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.db
import
transaction
from
django.db
import
transaction
from
django.utils
import
timezone
from
rest_framework.renderers
import
JSONRenderer
from
rest_framework.renderers
import
JSONRenderer
from
rest_framework.request
import
Request
from
jumpserver.utils
import
current_request
from
jumpserver.utils
import
current_request
from
common.utils
import
get_request_ip
,
get_logger
,
get_syslogger
from
common.utils
import
get_request_ip
,
get_logger
,
get_syslogger
from
users.models
import
User
from
users.models
import
User
from
authentication.signals
import
post_auth_failed
,
post_auth_success
from
terminal.models
import
Session
,
Command
from
terminal.models
import
Session
,
Command
from
terminal.backends.command.serializers
import
SessionCommandSerializer
from
terminal.backends.command.serializers
import
SessionCommandSerializer
from
.
import
models
from
.
import
models
,
serializers
from
.
import
serializers
from
.
tasks
import
write_login_log_async
logger
=
get_logger
(
__name__
)
logger
=
get_logger
(
__name__
)
sys_logger
=
get_syslogger
(
"audits"
)
sys_logger
=
get_syslogger
(
"audits"
)
...
@@ -99,3 +102,39 @@ def on_audits_log_create(sender, instance=None, **kwargs):
...
@@ -99,3 +102,39 @@ def on_audits_log_create(sender, instance=None, **kwargs):
data
=
json_render
.
render
(
s
.
data
)
.
decode
(
errors
=
'ignore'
)
data
=
json_render
.
render
(
s
.
data
)
.
decode
(
errors
=
'ignore'
)
msg
=
"{} - {}"
.
format
(
category
,
data
)
msg
=
"{} - {}"
.
format
(
category
,
data
)
sys_logger
.
info
(
msg
)
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
...
@@ -7,6 +7,7 @@ from celery import shared_task
from
ops.celery.decorator
import
register_as_period_task
from
ops.celery.decorator
import
register_as_period_task
from
.models
import
UserLoginLog
from
.models
import
UserLoginLog
from
.utils
import
write_login_log
@register_as_period_task
(
interval
=
3600
*
24
)
@register_as_period_task
(
interval
=
3600
*
24
)
...
@@ -19,3 +20,8 @@ def clean_login_log_period():
...
@@ -19,3 +20,8 @@ def clean_login_log_period():
days
=
90
days
=
90
expired_day
=
now
-
datetime
.
timedelta
(
days
=
days
)
expired_day
=
now
-
datetime
.
timedelta
(
days
=
days
)
UserLoginLog
.
objects
.
filter
(
datetime__lt
=
expired_day
)
.
delete
()
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 @@
...
@@ -78,7 +78,7 @@
<td
class=
"text-center"
>
{{ login_log.ip }}
</td>
<td
class=
"text-center"
>
{{ login_log.ip }}
</td>
<td
class=
"text-center"
>
{{ login_log.city }}
</td>
<td
class=
"text-center"
>
{{ login_log.city }}
</td>
<td
class=
"text-center"
>
{{ login_log.get_mfa_display }}
</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.get_status_display }}
</td>
<td
class=
"text-center"
>
{{ login_log.datetime }}
</td>
<td
class=
"text-center"
>
{{ login_log.datetime }}
</td>
</tr>
</tr>
...
...
apps/audits/utils.py
View file @
6ce9815d
import
csv
import
csv
import
codecs
import
codecs
from
django.http
import
HttpResponse
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
):
def
get_excel_response
(
filename
):
...
@@ -20,3 +23,16 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None):
...
@@ -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
]
data
=
[
getattr
(
log
,
field
.
name
)
for
field
in
fields
]
writer
.
writerow
(
data
)
writer
.
writerow
(
data
)
return
response
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 -*-
# -*- coding: utf-8 -*-
#
#
import
uuid
import
uuid
import
time
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.shortcuts
import
get_object_or_404
from
django.shortcuts
import
get_object_or_404
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.permissions
import
AllowAny
from
rest_framework.permissions
import
AllowAny
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.generics
import
CreateAPIView
from
rest_framework.views
import
APIView
from
rest_framework.views
import
APIView
from
common.utils
import
get_logger
,
get_request_ip
,
get_object_or_none
from
common.utils
import
get_logger
from
common.permissions
import
IsOrgAdminOrAppUser
,
IsValidUser
from
common.permissions
import
IsOrgAdminOrAppUser
from
orgs.mixins.api
import
RootOrgViewMixin
from
orgs.mixins.api
import
RootOrgViewMixin
from
users.serializers
import
UserSerializer
from
users.models
import
User
from
users.models
import
User
from
assets.models
import
Asset
,
SystemUser
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__
)
logger
=
get_logger
(
__name__
)
__all__
=
[
__all__
=
[
'UserAuthApi'
,
'UserConnectionTokenApi'
,
'UserOtpAuthApi'
,
'UserConnectionTokenApi'
,
'UserOtpVerifyApi'
,
'UserOrderAcceptAuthApi'
,
]
]
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
):
class
UserConnectionTokenApi
(
RootOrgViewMixin
,
APIView
):
permission_classes
=
(
IsOrgAdminOrAppUser
,)
permission_classes
=
(
IsOrgAdminOrAppUser
,)
...
@@ -150,82 +61,5 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
...
@@ -150,82 +61,5 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
return
super
()
.
get_permissions
()
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 -*-
# -*- coding: utf-8 -*-
#
#
from
rest_framework.generics
import
UpdateAPIView
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
django.shortcuts
import
get_object_or_404
from
common.utils
import
get_logger
,
get_object_or_none
from
common.permissions
import
IsOrgAdmin
from
common.permissions
import
IsOrgAdmin
from
..models
import
LoginConfirmSetting
from
..models
import
LoginConfirmSetting
from
..serializers
import
LoginConfirmSettingSerializer
from
..serializers
import
LoginConfirmSettingSerializer
from
..
import
errors
__all__
=
[
'LoginConfirmSettingUpdateApi'
]
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'UserOrderAcceptAuthApi'
]
logger
=
get_logger
(
__name__
)
class
LoginConfirmSettingUpdateApi
(
UpdateAPIView
):
class
LoginConfirmSettingUpdateApi
(
UpdateAPIView
):
...
@@ -23,3 +28,29 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
...
@@ -23,3 +28,29 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
defaults
,
user
=
user
,
defaults
,
user
=
user
,
)
)
return
s
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 -*-
# -*- coding: utf-8 -*-
#
#
import
time
from
rest_framework.permissions
import
AllowAny
from
rest_framework.permissions
import
AllowAny
from
rest_framework.generics
import
CreateAPIView
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
serializers
from
..
import
errors
from
..mixins
import
AuthMixin
class
MFAChallengeApi
(
CreateAPIView
):
__all__
=
[
'MFAChallengeApi'
,
'UserOtpVerifyApi'
]
class
MFAChallengeApi
(
AuthMixin
,
CreateAPIView
):
permission_classes
=
(
AllowAny
,)
permission_classes
=
(
AllowAny
,)
serializer_class
=
serializers
.
MFAChallengeSerializer
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 -*-
# -*- coding: utf-8 -*-
#
#
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.permissions
import
AllowAny
from
rest_framework.permissions
import
AllowAny
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.generics
import
CreateAPIView
from
rest_framework.generics
import
CreateAPIView
from
common.utils
import
get_request_ip
,
get_logger
,
get_object_or_none
from
common.utils
import
get_logger
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
..
import
serializers
,
errors
from
..
import
serializers
,
errors
from
..mixins
import
AuthMixin
logger
=
get_logger
(
__name__
)
logger
=
get_logger
(
__name__
)
...
@@ -22,126 +16,24 @@ logger = get_logger(__name__)
...
@@ -22,126 +16,24 @@ logger = get_logger(__name__)
__all__
=
[
'TokenCreateApi'
]
__all__
=
[
'TokenCreateApi'
]
class
TokenCreateApi
(
CreateAPIView
):
class
TokenCreateApi
(
AuthMixin
,
CreateAPIView
):
permission_classes
=
(
AllowAny
,)
permission_classes
=
(
AllowAny
,)
serializer_class
=
serializers
.
BearerTokenSerializer
serializer_class
=
serializers
.
BearerTokenSerializer
def
check_session
(
self
):
def
create_session_if_need
(
self
):
pass
if
self
.
request
.
session
.
is_empty
():
self
.
request
.
session
.
create
()
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
(
self
,
request
,
*
args
,
**
kwargs
):
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
c
heck_session
()
self
.
c
reate_session_if_need
()
# 如果认证没有过,检查账号密码
# 如果认证没有过,检查账号密码
try
:
try
:
user
=
self
.
check_user_auth
()
user
=
self
.
check_user_auth
()
self
.
check_user_mfa_if_need
(
user
)
self
.
check_user_mfa_if_need
(
user
)
self
.
check_user_login_confirm_if_need
(
user
)
self
.
check_user_login_confirm_if_need
(
user
)
self
.
send_auth_signal
(
success
=
True
,
user
=
user
)
self
.
send_auth_signal
(
success
=
True
,
user
=
user
)
self
.
clear_auth_mark
()
resp
=
super
()
.
create
(
request
,
*
args
,
**
kwargs
)
resp
=
super
()
.
create
(
request
,
*
args
,
**
kwargs
)
return
resp
return
resp
except
errors
.
AuthFailedError
as
e
:
except
errors
.
AuthFailedError
as
e
:
if
e
.
username
:
return
Response
(
e
.
as_data
(),
status
=
401
)
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
)
apps/authentication/errors.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.urls
import
reverse
from
django.conf
import
settings
password_failed
=
_
(
'Username/password check failed'
)
from
.signals
import
post_auth_failed
mfa_failed
=
_
(
'MFA authentication failed'
)
from
users.utils
import
(
user_not_exist
=
_
(
"Username does not exist"
)
increase_login_failed_count
,
get_login_failed_count
password_expired
=
_
(
"Password expired"
)
)
user_invalid
=
_
(
'Disabled or expired'
)
ip_blocked
=
_
(
"Log in frequently and try again later"
)
mfa_required
=
_
(
"MFA required"
)
reason_password_failed
=
'password_failed'
login_confirm_required
=
_
(
"Login confirm required"
)
reason_mfa_failed
=
'mfa_failed'
login_confirm_wait
=
_
(
"Wait login confirm"
)
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
):
class
AuthFailedError
(
Exception
):
def
__init__
(
self
,
reason
,
error
=
None
,
username
=
None
):
username
=
''
self
.
reason
=
reason
msg
=
''
self
.
error
=
error
error
=
''
self
.
username
=
username
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
):
def
as_data
(
self
):
reason
=
mfa_required
return
{
error
=
'mfa_required'
"error"
:
self
.
error
,
"msg"
:
self
.
msg
,
"order_id"
:
self
.
order_id
}
class
LoginConfirm
RequiredError
(
Exception
):
class
LoginConfirm
WaitError
(
LoginConfirmError
):
reason
=
login_confirm_required
msg
=
login_confirm_wait_msg
error
=
'login_confirm_
required
'
error
=
'login_confirm_
wait_msg
'
class
LoginConfirm
WaitError
(
Exception
):
class
LoginConfirm
RejectedError
(
LoginConfirmError
):
reason
=
login_confirm_wait
msg
=
login_confirm_rejected_msg
error
=
'login_confirm_
wait
'
error
=
'login_confirm_
rejected_msg
'
class
LoginConfirm
RejectedError
(
Exception
):
class
LoginConfirm
OrderNotFound
(
LoginConfirmError
):
reason
=
login_confirm_wait
msg
=
login_confirm_order_not_found_msg
error
=
'login_confirm_
rejected
'
error
=
'login_confirm_
order_not_found_msg
'
apps/authentication/forms.py
View file @
6ce9815d
...
@@ -9,53 +9,19 @@ from django.conf import settings
...
@@ -9,53 +9,19 @@ from django.conf import settings
from
users.utils
import
get_login_failed_count
from
users.utils
import
get_login_failed_count
class
UserLoginForm
(
Authentication
Form
):
class
UserLoginForm
(
forms
.
Form
):
username
=
forms
.
CharField
(
label
=
_
(
'Username'
),
max_length
=
100
)
username
=
forms
.
CharField
(
label
=
_
(
'Username'
),
max_length
=
100
)
password
=
forms
.
CharField
(
password
=
forms
.
CharField
(
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
max_length
=
128
,
strip
=
False
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
):
def
confirm_login_allowed
(
self
,
user
):
if
not
user
.
is_staff
:
if
not
user
.
is_staff
:
raise
forms
.
ValidationError
(
raise
forms
.
ValidationError
(
self
.
error_messages
[
'inactive'
],
self
.
error_messages
[
'inactive'
],
code
=
'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
,
)
)
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
):
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 @@
...
@@ -3,6 +3,7 @@
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
common.utils
import
get_object_or_none
from
users.models
import
User
from
users.models
import
User
from
.models
import
AccessKey
,
LoginConfirmSetting
from
.models
import
AccessKey
,
LoginConfirmSetting
...
@@ -24,7 +25,12 @@ class OtpVerifySerializer(serializers.Serializer):
...
@@ -24,7 +25,12 @@ class OtpVerifySerializer(serializers.Serializer):
code
=
serializers
.
CharField
(
max_length
=
6
,
min_length
=
6
)
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
)
token
=
serializers
.
CharField
(
read_only
=
True
)
keyword
=
serializers
.
SerializerMethodField
()
keyword
=
serializers
.
SerializerMethodField
()
date_expired
=
serializers
.
DateTimeField
(
read_only
=
True
)
date_expired
=
serializers
.
DateTimeField
(
read_only
=
True
)
...
@@ -33,58 +39,35 @@ class BearerTokenMixin(serializers.Serializer):
...
@@ -33,58 +39,35 @@ class BearerTokenMixin(serializers.Serializer):
def
get_keyword
(
obj
):
def
get_keyword
(
obj
):
return
'Bearer'
return
'Bearer'
def
create_response
(
self
,
username
):
def
create
(
self
,
validated_data
):
request
=
self
.
context
.
get
(
"request"
)
request
=
self
.
context
.
get
(
'request'
)
try
:
if
request
.
user
and
not
request
.
user
.
is_anonymous
:
user
=
User
.
objects
.
get
(
username
=
username
)
user
=
request
.
user
except
User
.
DoesNotExist
:
else
:
raise
serializers
.
ValidationError
(
"username
%
s not exist"
%
username
)
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
)
token
,
date_expired
=
user
.
create_bearer_token
(
request
)
instance
=
{
instance
=
{
"username"
:
username
,
"username"
:
user
.
user
name
,
"token"
:
token
,
"token"
:
token
,
"date_expired"
:
date_expired
,
"date_expired"
:
date_expired
,
}
}
return
instance
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
):
class
MFAChallengeSerializer
(
serializers
.
Serializer
):
req
=
serializers
.
CharField
(
write_only
=
True
)
auth_type
=
serializers
.
CharField
(
write_only
=
True
,
required
=
False
,
allow_blank
=
True
)
auth_type
=
serializers
.
CharField
(
write_only
=
True
)
code
=
serializers
.
CharField
(
write_only
=
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
):
def
create
(
self
,
validated_data
):
username
=
self
.
context
[
"username"
]
pass
return
self
.
create_response
(
username
)
def
update
(
self
,
instance
,
validated_data
):
pass
class
LoginConfirmSettingSerializer
(
serializers
.
ModelSerializer
):
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.http.request
import
QueryDict
from
django.conf
import
settings
from
django.conf
import
settings
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.contrib.auth.signals
import
user_logged_out
from
django.contrib.auth.signals
import
user_logged_out
from
django.utils
import
timezone
from
django_auth_ldap.backend
import
populate_user
from
django_auth_ldap.backend
import
populate_user
from
common.utils
import
get_request_ip
from
.backends.openid
import
new_client
from
.backends.openid
import
new_client
from
.backends.openid.signals
import
(
from
.backends.openid.signals
import
(
post_create_openid_user
,
post_openid_login_success
post_create_openid_user
,
post_openid_login_success
)
)
from
.tasks
import
write_login_log_async
from
.signals
import
post_auth_success
from
.signals
import
post_auth_success
,
post_auth_failed
@receiver
(
user_logged_out
)
@receiver
(
user_logged_out
)
...
@@ -52,35 +48,4 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
...
@@ -52,35 +48,4 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
user
.
save
()
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
...
@@ -6,17 +6,8 @@ from ops.celery.decorator import register_as_period_task
from
django.contrib.sessions.models
import
Session
from
django.contrib.sessions.models
import
Session
from
django.utils
import
timezone
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
)
@register_as_period_task
(
interval
=
3600
*
24
)
@shared_task
@shared_task
def
clean_django_sessions
():
def
clean_django_sessions
():
Session
.
objects
.
filter
(
expire_date__lt
=
timezone
.
now
())
.
delete
()
Session
.
objects
.
filter
(
expire_date__lt
=
timezone
.
now
())
.
delete
()
apps/authentication/templates/authentication/login.html
View file @
6ce9815d
...
@@ -37,7 +37,6 @@
...
@@ -37,7 +37,6 @@
<p>
<p>
{% trans "Changes the world, starting with a little bit." %}
{% trans "Changes the world, starting with a little bit." %}
</p>
</p>
</div>
</div>
<div
class=
"col-md-6"
>
<div
class=
"col-md-6"
>
<div
class=
"ibox-content"
>
<div
class=
"ibox-content"
>
...
@@ -47,25 +46,29 @@
...
@@ -47,25 +46,29 @@
</div>
</div>
<form
class=
"m-t"
role=
"form"
method=
"post"
action=
""
>
<form
class=
"m-t"
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
{% csrf_token %}
{% if form.non_field_errors %}
{% if block_login %}
<div
style=
"line-height: 17px;"
>
<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 %}
<p
class=
"red-fonts"
>
{{ form.non_field_errors.as_text }}
</p>
<p
class=
"red-fonts"
>
{{ form.non_field_errors.as_text }}
</p>
{% endif %}
</div>
<p
class=
"red-fonts"
>
{{ form.errors.password.as_text }}
</p>
{% elif form.errors.captcha %}
<p
class=
"red-fonts"
>
{% trans 'Captcha invalid' %}
</p>
{% endif %}
{% endif %}
<div
class=
"form-group"
>
<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 %}"
>
<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>
<div
class=
"form-group"
>
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"{{ form.password.html_name }}"
placeholder=
"{% trans 'Password' %}"
required=
""
>
<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>
<div>
<div>
{{ form.captcha }}
{{ form.captcha }}
...
...
apps/authentication/templates/authentication/login_wait_confirm.html
View file @
6ce9815d
...
@@ -86,7 +86,7 @@ function doRequestAuth() {
...
@@ -86,7 +86,7 @@ function doRequestAuth() {
window
.
location
=
successUrl
;
window
.
location
=
successUrl
;
},
},
error
:
function
(
text
,
data
)
{
error
:
function
(
text
,
data
)
{
if
(
data
.
status
!==
"pending
"
)
{
if
(
data
.
error
!==
"login_confirm_wait
"
)
{
if
(
!
errorMsgShow
)
{
if
(
!
errorMsgShow
)
{
infoMsgRef
.
hide
();
infoMsgRef
.
hide
();
errorMsgRef
.
show
();
errorMsgRef
.
show
();
...
@@ -97,7 +97,7 @@ function doRequestAuth() {
...
@@ -97,7 +97,7 @@ function doRequestAuth() {
clearInterval
(
checkInterval
);
clearInterval
(
checkInterval
);
$
(
".copy-btn"
).
attr
(
'disabled'
,
'disabled'
)
$
(
".copy-btn"
).
attr
(
'disabled'
,
'disabled'
)
}
}
errorMsgRef
.
html
(
data
.
error
)
errorMsgRef
.
html
(
data
.
msg
)
},
},
flash_message
:
false
flash_message
:
false
})
})
...
...
apps/authentication/templates/authentication/xpack_login.html
View file @
6ce9815d
...
@@ -48,6 +48,13 @@
...
@@ -48,6 +48,13 @@
float
:
right
;
float
:
right
;
}
}
.red-fonts
{
color
:
red
;
}
.field-error
{
text-align
:
left
;
}
</style>
</style>
</head>
</head>
...
@@ -69,30 +76,32 @@
...
@@ -69,30 +76,32 @@
<div
style=
"margin-bottom: 10px"
>
<div
style=
"margin-bottom: 10px"
>
<div>
<div>
<div
class=
"col-md-1"
></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"
>
<form
id=
"contact-form"
action=
""
method=
"post"
role=
"form"
novalidate=
"novalidate"
>
{% csrf_token %}
{% csrf_token %}
{% if form.non_field_errors %}
<div
style=
"height: 70px;color: red;line-height: 17px;"
>
<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>
<p
class=
"red-fonts"
>
{{ form.non_field_errors.as_text }}
</p>
{% endif %}
<p
class=
"red-fonts"
>
{{ form.errors.password.as_text }}
</p>
{% endif %}
</div>
</div>
{% elif form.errors.captcha %}
<p
class=
"red-fonts"
>
{% trans 'Captcha invalid' %}
</p>
{% endif %}
<div
class=
"form-group"
>
<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"
>
<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>
<div
class=
"form-group"
>
<div
class=
"form-group"
>
<input
type=
"password"
class=
"form-control"
name=
"{{ form.password.html_name }}"
placeholder=
"{% trans 'Password' %}"
required=
""
>
<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>
<div
class=
"form-group"
style=
"height: 50px;margin-bottom: 0;font-size: 13px"
>
<div
class=
"form-group"
style=
"height: 50px;margin-bottom: 0;font-size: 13px"
>
{{ form.captcha }}
{{ form.captcha }}
...
...
apps/authentication/urls/api_urls.py
View file @
6ce9815d
...
@@ -12,12 +12,11 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key')
...
@@ -12,12 +12,11 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key')
urlpatterns
=
[
urlpatterns
=
[
# path('token/', api.UserToken.as_view(), name='user-token'),
# 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
(
'tokens/'
,
api
.
TokenCreateApi
.
as_view
(),
name
=
'auth-token'
),
path
(
'mfa/challenge/'
,
api
.
MFAChallengeApi
.
as_view
(),
name
=
'mfa-challenge'
),
path
(
'mfa/challenge/'
,
api
.
MFAChallengeApi
.
as_view
(),
name
=
'mfa-challenge'
),
path
(
'connection-token/'
,
path
(
'connection-token/'
,
api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'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
(
'otp/verify/'
,
api
.
UserOtpVerifyApi
.
as_view
(),
name
=
'user-otp-verify'
),
path
(
'order/auth/'
,
api
.
UserOrderAcceptAuthApi
.
as_view
(),
name
=
'user-order-auth'
),
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'
)
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 -*-
# -*- 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.contrib.auth
import
authenticate
from
django.utils
import
timezone
from
common.utils
import
(
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
users.models
import
User
from
.
import
errors
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
):
def
check_user_valid
(
**
kwargs
):
password
=
kwargs
.
pop
(
'password'
,
None
)
password
=
kwargs
.
pop
(
'password'
,
None
)
public_key
=
kwargs
.
pop
(
'public_key'
,
None
)
public_key
=
kwargs
.
pop
(
'public_key'
,
None
)
email
=
kwargs
.
pop
(
'email'
,
None
)
email
=
kwargs
.
pop
(
'email'
,
None
)
username
=
kwargs
.
pop
(
'username'
,
None
)
username
=
kwargs
.
pop
(
'username'
,
None
)
request
=
kwargs
.
get
(
'request'
)
if
username
:
if
username
:
user
=
get_object_or_none
(
User
,
username
=
username
)
user
=
get_object_or_none
(
User
,
username
=
username
)
...
@@ -38,21 +25,25 @@ def check_user_valid(**kwargs):
...
@@ -38,21 +25,25 @@ def check_user_valid(**kwargs):
user
=
None
user
=
None
if
user
is
None
:
if
user
is
None
:
return
None
,
errors
.
user_not_exist
return
None
,
errors
.
reason_user_not_exist
elif
not
user
.
is_valid
:
elif
user
.
is_expired
:
return
None
,
errors
.
user_invalid
return
None
,
errors
.
reason_password_expired
elif
not
user
.
is_active
:
return
None
,
errors
.
reason_user_inactive
elif
user
.
password_has_expired
:
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
,
''
return
user
,
''
if
public_key
and
user
.
public_key
:
if
public_key
and
user
.
public_key
:
public_key_saved
=
user
.
public_key
.
split
()
public_key_saved
=
user
.
public_key
.
split
()
if
len
(
public_key_saved
)
==
1
:
if
len
(
public_key_saved
)
==
1
:
if
public_key
==
public_key_saved
[
0
]:
public_key_saved
=
public_key_saved
[
0
]
return
user
,
''
else
:
elif
len
(
public_key_saved
)
>
1
:
public_key_saved
=
public_key_saved
[
1
]
if
public_key
==
public_key_saved
[
1
]
:
if
public_key
==
public_key_saved
:
return
user
,
''
return
user
,
''
return
None
,
errors
.
password_failed
return
None
,
errors
.
reason_
password_failed
apps/authentication/views/__init__.py
View file @
6ce9815d
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
.login
import
*
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
...
@@ -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.base
import
TemplateView
,
RedirectView
from
django.views.generic.edit
import
FormView
from
django.views.generic.edit
import
FormView
from
django.conf
import
settings
from
django.conf
import
settings
from
django.urls
import
reverse_lazy
from
common.utils
import
get_request_ip
,
get_object_or_none
from
common.utils
import
get_request_ip
,
get_object_or_none
from
users.models
import
User
from
users.models
import
User
from
users.utils
import
(
from
users.utils
import
(
check_otp_code
,
is_block_login
,
clean_failed_count
,
get_user_or_tmp_user
,
get_user_or_tmp_user
,
increase_login_failed_count
,
set_tmp_user_to_cache
,
increase_login_failed_count
,
redirect_user_first_login_or_index
redirect_user_first_login_or_index
)
)
from
..models
import
LoginConfirmSetting
from
..signals
import
post_auth_success
,
post_auth_failed
from
..signals
import
post_auth_success
,
post_auth_failed
from
..
import
forms
from
..
import
forms
,
mixins
,
errors
from
..
import
errors
__all__
=
[
__all__
=
[
'UserLoginView'
,
'UserLog
inOtpView'
,
'UserLog
outView'
,
'UserLoginView'
,
'UserLogoutView'
,
'UserLoginGuardView'
,
'UserLoginWaitConfirmView'
,
'UserLoginGuardView'
,
'UserLoginWaitConfirmView'
,
]
]
...
@@ -39,10 +37,11 @@ __all__ = [
...
@@ -39,10 +37,11 @@ __all__ = [
@method_decorator
(
sensitive_post_parameters
(),
name
=
'dispatch'
)
@method_decorator
(
sensitive_post_parameters
(),
name
=
'dispatch'
)
@method_decorator
(
csrf_protect
,
name
=
'dispatch'
)
@method_decorator
(
csrf_protect
,
name
=
'dispatch'
)
@method_decorator
(
never_cache
,
name
=
'dispatch'
)
@method_decorator
(
never_cache
,
name
=
'dispatch'
)
class
UserLoginView
(
FormView
):
class
UserLoginView
(
mixins
.
AuthMixin
,
FormView
):
form_class
=
forms
.
UserLoginForm
form_class
=
forms
.
UserLoginForm
form_class_captcha
=
forms
.
UserLoginCaptchaForm
form_class_captcha
=
forms
.
UserLoginCaptchaForm
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
key_prefix_captcha
=
"_LOGIN_INVALID_{}"
redirect_field_name
=
'next'
def
get_template_names
(
self
):
def
get_template_names
(
self
):
template_name
=
'authentication/login.html'
template_name
=
'authentication/login.html'
...
@@ -69,54 +68,25 @@ class UserLoginView(FormView):
...
@@ -69,54 +68,25 @@ class UserLoginView(FormView):
request
.
session
.
set_test_cookie
()
request
.
session
.
set_test_cookie
()
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
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
):
def
form_valid
(
self
,
form
):
if
not
self
.
request
.
session
.
test_cookie_worked
():
if
not
self
.
request
.
session
.
test_cookie_worked
():
return
HttpResponse
(
_
(
"Please enable cookies and try again."
))
return
HttpResponse
(
_
(
"Please enable cookies and try again."
))
user
=
form
.
get_user
()
try
:
# user password expired
self
.
check_user_auth
()
if
user
.
password_has_expired
:
except
errors
.
AuthFailedError
as
e
:
reason
=
errors
.
password_expired
form
.
add_error
(
None
,
e
.
msg
)
self
.
send_auth_signal
(
success
=
False
,
username
=
user
.
username
,
reason
=
reason
)
ip
=
self
.
get_request_ip
()
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
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
cache
.
set
(
self
.
key_prefix_captcha
.
format
(
ip
),
1
,
3600
)
self
.
send_auth_signal
(
success
=
False
,
username
=
username
,
reason
=
reason
)
context
=
self
.
get_context_data
(
form
=
form
)
return
self
.
render_to_response
(
context
)
old_form
=
form
return
self
.
redirect_to_guard_view
()
form
=
self
.
form_class_captcha
(
data
=
form
.
data
)
form
.
_errors
=
old_form
.
errors
return
super
()
.
form_invalid
(
form
)
@staticmethod
def
redirect_to_guard_view
(
self
):
def
redirect_to_guard_view
():
guard_url
=
reverse
(
'authentication:login-guard'
)
continue_url
=
reverse
(
'authentication:login-guard'
)
args
=
self
.
request
.
META
.
get
(
'QUERY_STRING'
,
''
)
return
redirect
(
continue_url
)
if
args
and
self
.
query_string
:
guard_url
=
"
%
s?
%
s"
%
(
guard_url
,
args
)
return
redirect
(
guard_url
)
def
get_form_class
(
self
):
def
get_form_class
(
self
):
ip
=
get_request_ip
(
self
.
request
)
ip
=
get_request_ip
(
self
.
request
)
...
@@ -134,58 +104,34 @@ class UserLoginView(FormView):
...
@@ -134,58 +104,34 @@ class UserLoginView(FormView):
return
super
()
.
get_context_data
(
**
kwargs
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
UserLoginOtpView
(
FormView
):
class
UserLoginGuardView
(
mixins
.
AuthMixin
,
RedirectView
):
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
):
redirect_field_name
=
'next'
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
):
def
get_redirect_url
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
request
.
session
.
get
(
'auth_password'
):
if
not
self
.
request
.
session
.
get
(
'auth_password'
):
return
reverse
(
'authentication:login'
)
return
self
.
format_redirect_url
(
self
.
login_url
)
user
=
self
.
get_user_from_session
()
user
=
get_user_or_tmp_user
(
self
.
request
)
# 启用并设置了otp
# 启用并设置了otp
if
user
.
otp_enabled
and
user
.
otp_secret_key
and
\
if
user
.
otp_enabled
and
user
.
otp_secret_key
and
\
not
self
.
request
.
session
.
get
(
'auth_
otp
'
):
not
self
.
request
.
session
.
get
(
'auth_
mfa
'
):
return
reverse
(
'authentication:login-otp'
)
return
self
.
format_redirect_url
(
self
.
login_otp_url
)
confirm_setting
=
user
.
get_login_confirm_setting
()
confirm_setting
=
user
.
get_login_confirm_setting
()
if
confirm_setting
and
not
self
.
request
.
session
.
get
(
'auth_confirm'
):
if
confirm_setting
and
not
self
.
request
.
session
.
get
(
'auth_confirm'
):
order
=
confirm_setting
.
create_confirm_order
(
self
.
request
)
order
=
confirm_setting
.
create_confirm_order
(
self
.
request
)
self
.
request
.
session
[
'auth_order_id'
]
=
str
(
order
.
id
)
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
return
url
self
.
login_success
(
user
)
self
.
login_success
(
user
)
self
.
clear_auth_mark
()
# 启用但是没有设置otp
# 启用但是没有设置otp
if
user
.
otp_enabled
and
not
user
.
otp_secret_key
:
if
user
.
otp_enabled
and
not
user
.
otp_secret_key
:
# 1,2,mfa_setting & F
# 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):
...
@@ -153,6 +153,14 @@ def get_request_ip(request):
return
login_ip
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
):
def
validate_ip
(
ip
):
try
:
try
:
ipaddress
.
ip_address
(
ip
)
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 @@
...
@@ -100,16 +100,6 @@
<link
rel=
"stylesheet"
type=
"text/css"
href=
{%
static
"
css
/
plugins
/
daterangepicker
/
daterangepicker
.
css
"
%}
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
{%
static
"
css
/
plugins
/
daterangepicker
/
daterangepicker
.
css
"
%}
/>
<script>
<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 }}"
;
var
api_action
=
"{{ api_action }}"
;
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
...
@@ -119,8 +109,8 @@ $(document).ready(function () {
...
@@ -119,8 +109,8 @@ $(document).ready(function () {
nodesSelect2Init
(
".nodes-select2"
);
nodesSelect2Init
(
".nodes-select2"
);
usersSelect2Init
(
".users-select2"
);
usersSelect2Init
(
".users-select2"
);
$
(
'#date_start'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_start'
);
$
(
'#date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_expired'
);
$
(
"#id_assets"
).
parent
().
find
(
".select2-selection"
).
on
(
'click'
,
function
(
e
)
{
$
(
"#id_assets"
).
parent
().
find
(
".select2-selection"
).
on
(
'click'
,
function
(
e
)
{
if
(
$
(
e
.
target
).
attr
(
'class'
)
!==
'select2-selection__choice__remove'
){
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 () {
...
@@ -115,8 +115,8 @@ $(document).ready(function () {
closeOnSelect
:
false
closeOnSelect
:
false
});
});
usersSelect2Init
(
'.users-select2'
);
usersSelect2Init
(
'.users-select2'
);
$
(
'#date_start'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_start'
);
$
(
'#date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#date_expired'
);
})
})
.
on
(
"submit"
,
"form"
,
function
(
evt
)
{
.
on
(
"submit"
,
"form"
,
function
(
evt
)
{
evt
.
preventDefault
();
evt
.
preventDefault
();
...
...
apps/static/js/jumpserver.js
View file @
6ce9815d
...
@@ -1289,3 +1289,31 @@ function showCeleryTaskLog(taskId) {
...
@@ -1289,3 +1289,31 @@ function showCeleryTaskLog(taskId) {
var
url
=
'/ops/celery/task/taskId/log/'
.
replace
(
'taskId'
,
taskId
);
var
url
=
'/ops/celery/task/taskId/log/'
.
replace
(
'taskId'
,
taskId
);
window
.
open
(
url
,
''
,
'width=900,height=600'
)
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:
...
@@ -62,10 +62,6 @@ class AuthMixin:
def
can_use_ssh_key_login
(
self
):
def
can_use_ssh_key_login
(
self
):
return
settings
.
TERMINAL_PUBLIC_KEY_AUTH
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
):
def
is_public_key_valid
(
self
):
"""
"""
Check if the user's ssh public key is valid.
Check if the user's ssh public key is valid.
...
@@ -362,6 +358,10 @@ class MFAMixin:
...
@@ -362,6 +358,10 @@ class MFAMixin:
self
.
otp_level
=
0
self
.
otp_level
=
0
self
.
otp_secret_key
=
None
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
):
class
User
(
AuthMixin
,
TokenMixin
,
RoleMixin
,
MFAMixin
,
AbstractUser
):
SOURCE_LOCAL
=
'local'
SOURCE_LOCAL
=
'local'
...
...
apps/users/templates/users/_user.html
View file @
6ce9815d
...
@@ -56,6 +56,7 @@
...
@@ -56,6 +56,7 @@
{% endblock %}
{% endblock %}
{% block custom_foot_js %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"
></script>
<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/moment.min.js" %}'
></script>
<script
type=
"text/javascript"
src=
'{% static "js/plugins/daterangepicker/daterangepicker.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
"
%}
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
{%
static
"
css
/
plugins
/
daterangepicker
/
daterangepicker
.
css
"
%}
/>
...
@@ -72,19 +73,9 @@
...
@@ -72,19 +73,9 @@
$
(
groups_id
).
closest
(
'.form-group'
).
removeClass
(
'hidden'
);
$
(
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
()
{
$
(
document
).
ready
(
function
()
{
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
();
$
(
'#id_date_expired'
).
daterangepicker
(
dateOptions
);
initDateRangePicker
(
'#id_date_expired'
);
var
mfa_radio
=
$
(
'#id_otp_level'
);
var
mfa_radio
=
$
(
'#id_otp_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"
);
...
...
apps/users/templates/users/user_detail.html
View file @
6ce9815d
...
@@ -212,7 +212,7 @@
...
@@ -212,7 +212,7 @@
</table>
</table>
</div>
</div>
</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 panel-info"
>
<div
class=
"panel-heading"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'User group' %}
<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
...
@@ -20,9 +20,6 @@ router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'us
urlpatterns
=
[
urlpatterns
=
[
path
(
'connection-token/'
,
auth_api
.
UserConnectionTokenApi
.
as_view
(),
path
(
'connection-token/'
,
auth_api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
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
(
'profile/'
,
api
.
UserProfileApi
.
as_view
(),
name
=
'user-profile'
),
path
(
'otp/reset/'
,
api
.
UserResetOTPApi
.
as_view
(),
name
=
'my-otp-reset'
),
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'
),
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):
...
@@ -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
):
def
redirect_user_first_login_or_index
(
request
,
redirect_field_name
):
if
request
.
user
.
is_first_login
:
if
request
.
user
.
is_first_login
:
return
reverse
(
'users:user-first-login'
)
return
reverse
(
'users:user-first-login'
)
return
request
.
POST
.
get
(
url_in_post
=
request
.
POST
.
get
(
redirect_field_name
)
redirect_field_name
,
if
url_in_post
:
request
.
GET
.
get
(
redirect_field_name
,
reverse
(
'index'
)))
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"
):
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