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
f2888319
Commit
f2888319
authored
Aug 09, 2019
by
BaiJiangJie
Browse files
Options
Browse Files
Download
Plain Diff
[Update] Merge branch dev_beta to branch dev_beta_db
parents
b265a386
8074e669
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
283 additions
and
91 deletions
+283
-91
node.py
apps/assets/models/node.py
+2
-1
base.py
apps/assets/serializers/base.py
+1
-0
system_user.py
apps/assets/serializers/system_user.py
+11
-1
signals_handler.py
apps/assets/signals_handler.py
+1
-0
api_urls.py
apps/assets/urls/api_urls.py
+2
-4
apps.py
apps/audits/apps.py
+4
-0
serializers.py
apps/audits/serializers.py
+27
-2
signals_handler.py
apps/audits/signals_handler.py
+37
-7
forms.py
apps/authentication/forms.py
+31
-2
login.html
apps/authentication/templates/authentication/login.html
+6
-4
new_login.html
apps/authentication/templates/authentication/new_login.html
+3
-1
login.py
apps/authentication/views/login.py
+5
-3
permissions.py
apps/common/permissions.py
+4
-0
tasks.py
apps/common/tasks.py
+1
-1
common.py
apps/common/utils/common.py
+4
-0
conf.py
apps/jumpserver/conf.py
+2
-0
settings.py
apps/jumpserver/settings.py
+25
-28
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
command.py
apps/ops/api/command.py
+2
-1
tasks.py
apps/ops/tasks.py
+2
-0
jumpserver.js
apps/static/js/jumpserver.js
+3
-2
elfinder.zh_CN.js
apps/static/plugins/elfinder/i18n/elfinder.zh_CN.js
+1
-1
_message.html
apps/templates/_message.html
+17
-1
v1.py
apps/terminal/serializers/v1.py
+1
-0
user.py
apps/users/models/user.py
+20
-9
tasks.py
apps/users/tasks.py
+27
-5
utils.py
apps/users/utils.py
+44
-18
No files found.
apps/assets/models/node.py
View file @
f2888319
...
...
@@ -212,12 +212,13 @@ class AssetsAmountMixin:
if
cached
is
not
None
:
return
cached
assets_amount
=
self
.
get_all_assets
()
.
count
()
cache
.
set
(
cache_key
,
assets_amount
,
self
.
cache_time
)
return
assets_amount
@assets_amount.setter
def
assets_amount
(
self
,
value
):
self
.
_assets_amount
=
value
cache_key
=
self
.
_assets_amount_cache_key
.
format
(
self
.
key
)
cache
.
set
(
cache_key
,
value
,
self
.
cache_time
)
def
expire_assets_amount
(
self
):
ancestor_keys
=
self
.
get_ancestor_keys
(
with_self
=
True
)
...
...
apps/assets/serializers/base.py
View file @
f2888319
...
...
@@ -59,6 +59,7 @@ class AuthSerializerMixin:
value
=
validated_data
.
get
(
field
)
if
not
value
:
validated_data
.
pop
(
field
,
None
)
# print(validated_data)
# raise serializers.ValidationError(">>>>>>")
...
...
apps/assets/serializers/system_user.py
View file @
f2888319
...
...
@@ -3,6 +3,7 @@ from rest_framework import serializers
from
django.utils.translation
import
ugettext_lazy
as
_
from
common.serializers
import
AdaptedBulkListSerializer
from
common.utils
import
ssh_pubkey_gen
from
orgs.mixins
import
BulkOrgResourceModelSerializer
from
..models
import
SystemUser
from
.base
import
AuthSerializer
,
AuthSerializerMixin
...
...
@@ -71,7 +72,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
super
()
.
validate_password
(
password
)
auto_gen_key
=
self
.
initial_data
.
get
(
"auto_generate_key"
,
False
)
private_key
=
self
.
initial_data
.
get
(
"private_key"
)
if
not
self
.
instance
and
not
auto_gen_key
and
not
password
and
not
private_key
:
login_mode
=
self
.
initial_data
.
get
(
"login_mode"
)
if
not
self
.
instance
and
not
auto_gen_key
and
not
password
and
\
not
private_key
and
login_mode
==
SystemUser
.
LOGIN_AUTO
:
raise
serializers
.
ValidationError
(
_
(
"Password or private key required"
))
return
password
...
...
@@ -86,6 +89,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
private_key
,
public_key
=
SystemUser
.
gen_key
(
username
)
attrs
[
"private_key"
]
=
private_key
attrs
[
"public_key"
]
=
public_key
# 如果设置了private key,没有设置public key则生成
elif
attrs
.
get
(
"private_key"
,
None
):
private_key
=
attrs
[
"private_key"
]
password
=
attrs
.
get
(
"password"
)
public_key
=
ssh_pubkey_gen
(
private_key
,
password
=
password
,
username
=
username
)
attrs
[
"public_key"
]
=
public_key
attrs
.
pop
(
"auto_generate_key"
,
None
)
return
attrs
...
...
apps/assets/signals_handler.py
View file @
f2888319
...
...
@@ -112,6 +112,7 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
@receiver
(
post_save
,
sender
=
Node
)
def
on_node_update_or_created
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
if
instance
and
not
created
:
Asset
.
expire_all_nodes_keys_cache
()
instance
.
expire_full_value
()
...
...
apps/assets/urls/api_urls.py
View file @
f2888319
...
...
@@ -94,10 +94,8 @@ urlpatterns = [
]
old_version_urlpatterns
=
[
re_path
(
'(?P<resource>admin
_user|system_
user|domain|gateway|cmd-filter|asset-user)/.*'
,
capi
.
redirect_plural_name_api
)
re_path
(
'(?P<resource>admin
-user|system-
user|domain|gateway|cmd-filter|asset-user)/.*'
,
capi
.
redirect_plural_name_api
)
]
urlpatterns
+=
router
.
urls
+
old_version_urlpatterns
urlpatterns
+=
router
.
urls
+
cmd_filter_router
.
urls
urlpatterns
+=
router
.
urls
+
cmd_filter_router
.
urls
+
old_version_urlpatterns
apps/audits/apps.py
View file @
f2888319
from
django.apps
import
AppConfig
from
django.conf
import
settings
from
django.db.models.signals
import
post_save
class
AuditsConfig
(
AppConfig
):
...
...
@@ -6,3 +8,5 @@ class AuditsConfig(AppConfig):
def
ready
(
self
):
from
.
import
signals_handler
if
settings
.
SYSLOG_ENABLE
:
post_save
.
connect
(
signals_handler
.
on_audits_log_create
)
apps/audits/serializers.py
View file @
f2888319
...
...
@@ -3,11 +3,36 @@
from
rest_framework
import
serializers
from
.models
import
FTPLog
from
terminal.models
import
Session
from
.
import
models
class
FTPLogSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
FTPLog
model
=
models
.
FTPLog
fields
=
'__all__'
class
LoginLogSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
models
.
UserLoginLog
fields
=
'__all__'
class
OperateLogSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
models
.
OperateLog
fields
=
'__all__'
class
PasswordChangeLogSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
models
.
PasswordChangeLog
fields
=
'__all__'
class
SessionAuditSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
Session
fields
=
'__all__'
apps/audits/signals_handler.py
View file @
f2888319
...
...
@@ -4,13 +4,18 @@
from
django.db.models.signals
import
post_save
,
post_delete
from
django.dispatch
import
receiver
from
django.db
import
transaction
from
rest_framework.renderers
import
JSONRenderer
from
jumpserver.utils
import
current_request
from
common.utils
import
get_request_ip
,
get_logger
from
common.utils
import
get_request_ip
,
get_logger
,
get_syslogger
from
users.models
import
User
from
.models
import
OperateLog
,
PasswordChangeLog
from
terminal.models
import
Session
from
.
import
models
from
.
import
serializers
logger
=
get_logger
(
__name__
)
sys_logger
=
get_syslogger
(
"audits"
)
json_render
=
JSONRenderer
()
MODELS_NEED_RECORD
=
(
...
...
@@ -36,7 +41,7 @@ def create_operate_log(action, sender, resource):
}
with
transaction
.
atomic
():
try
:
OperateLog
.
objects
.
create
(
**
data
)
models
.
OperateLog
.
objects
.
create
(
**
data
)
except
Exception
as
e
:
logger
.
error
(
"Create operate log error: {}"
.
format
(
e
))
...
...
@@ -44,15 +49,15 @@ def create_operate_log(action, sender, resource):
@receiver
(
post_save
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_object_created_or_update
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
if
created
:
action
=
OperateLog
.
ACTION_CREATE
action
=
models
.
OperateLog
.
ACTION_CREATE
else
:
action
=
OperateLog
.
ACTION_UPDATE
action
=
models
.
OperateLog
.
ACTION_UPDATE
create_operate_log
(
action
,
sender
,
instance
)
@receiver
(
post_delete
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_object_delete
(
sender
,
instance
=
None
,
**
kwargs
):
create_operate_log
(
OperateLog
.
ACTION_DELETE
,
sender
,
instance
)
create_operate_log
(
models
.
OperateLog
.
ACTION_DELETE
,
sender
,
instance
)
@receiver
(
post_save
,
sender
=
User
,
dispatch_uid
=
"my_unique_identifier"
)
...
...
@@ -61,7 +66,32 @@ def on_user_change_password(sender, instance=None, **kwargs):
if
not
current_request
or
not
current_request
.
user
.
is_authenticated
:
return
with
transaction
.
atomic
():
PasswordChangeLog
.
objects
.
create
(
models
.
PasswordChangeLog
.
objects
.
create
(
user
=
instance
,
change_by
=
current_request
.
user
,
remote_addr
=
get_request_ip
(
current_request
),
)
def
on_audits_log_create
(
sender
,
instance
=
None
,
**
kwargs
):
if
sender
==
models
.
UserLoginLog
:
category
=
"login_log"
serializer
=
serializers
.
LoginLogSerializer
elif
sender
==
models
.
FTPLog
:
serializer
=
serializers
.
FTPLogSerializer
category
=
"ftp_log"
elif
sender
==
models
.
OperateLog
:
category
=
"operation_log"
serializer
=
serializers
.
OperateLogSerializer
elif
sender
==
models
.
PasswordChangeLog
:
category
=
"password_change_log"
serializer
=
serializers
.
PasswordChangeLogSerializer
elif
sender
==
Session
:
category
=
"host_session_log"
serializer
=
serializers
.
SessionAuditSerializer
else
:
return
s
=
serializer
(
instance
=
instance
)
data
=
json_render
.
render
(
s
.
data
)
.
decode
(
errors
=
'ignore'
)
msg
=
"{} - {}"
.
format
(
category
,
data
)
sys_logger
.
info
(
msg
)
apps/authentication/forms.py
View file @
f2888319
...
...
@@ -5,6 +5,8 @@ from django import forms
from
django.contrib.auth.forms
import
AuthenticationForm
from
django.utils.translation
import
gettext_lazy
as
_
from
captcha.fields
import
CaptchaField
from
django.conf
import
settings
from
users.utils
import
get_login_failed_count
class
UserLoginForm
(
AuthenticationForm
):
...
...
@@ -16,10 +18,18 @@ class UserLoginForm(AuthenticationForm):
error_messages
=
{
'invalid_login'
:
_
(
"
Please enter a correct username and password. Note that both
"
"
fields may be case-sensitive
."
"
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
):
...
...
@@ -28,6 +38,25 @@ class UserLoginForm(AuthenticationForm):
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
,
)
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
):
captcha
=
CaptchaField
()
...
...
apps/authentication/templates/authentication/login.html
View file @
f2888319
...
...
@@ -58,6 +58,7 @@
{% 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
class=
"form-group"
>
...
...
@@ -78,10 +79,11 @@
{% endif %}
<div
class=
"text-muted text-center"
>
<div>
<a
href=
"{% url 'users:forgot-password' %}"
>
<small>
{% trans 'Forgot password' %}?
</small>
</a>
<div>
<a
href=
"{% url 'users:forgot-password' %}"
>
<small>
{% trans 'Forgot password' %}?
</small>
</a>
</div>
</div>
{% if AUTH_OPENID %}
...
...
apps/authentication/templates/authentication/new_login.html
View file @
f2888319
...
...
@@ -72,9 +72,10 @@
<div
class=
"contact-form col-md-10"
style=
"margin-top: 10px;height: 35px"
>
<form
id=
"contact-form"
action=
""
method=
"post"
role=
"form"
novalidate=
"novalidate"
>
{% csrf_token %}
<div
style=
"height:
45
px;color: red;line-height: 17px;"
>
<div
style=
"height:
70
px;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 %}
...
...
@@ -83,6 +84,7 @@
{% 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>
...
...
apps/authentication/views/login.py
View file @
f2888319
...
...
@@ -26,6 +26,7 @@ from users.utils import (
)
from
..signals
import
post_auth_success
,
post_auth_failed
from
..
import
forms
from
..
import
const
__all__
=
[
...
...
@@ -77,7 +78,7 @@ class UserLoginView(FormView):
user
=
form
.
get_user
()
# user password expired
if
user
.
password_has_expired
:
reason
=
LoginLog
.
REASON_PASSWORD_EXPIRED
reason
=
const
.
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
))
...
...
@@ -92,10 +93,11 @@ class UserLoginView(FormView):
# write login failed log
username
=
form
.
cleaned_data
.
get
(
'username'
)
exist
=
User
.
objects
.
filter
(
username
=
username
)
.
first
()
reason
=
LoginLog
.
REASON_PASSWORD
if
exist
else
LoginLog
.
REASON_NOT_EXIST
reason
=
const
.
password_failed
if
exist
else
const
.
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
)
self
.
send_auth_signal
(
success
=
False
,
username
=
username
,
reason
=
reason
)
...
...
@@ -162,7 +164,7 @@ class UserLoginOtpView(FormView):
else
:
self
.
send_auth_signal
(
success
=
False
,
username
=
user
.
username
,
reason
=
LoginLog
.
REASON_MFA
reason
=
const
.
mfa_failed
)
form
.
add_error
(
'otp_code'
,
_
(
'MFA code invalid, or ntp sync server time'
)
...
...
apps/common/permissions.py
View file @
f2888319
...
...
@@ -49,6 +49,8 @@ class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser"""
def
has_permission
(
self
,
request
,
view
):
if
not
current_org
:
return
False
return
super
(
IsOrgAdmin
,
self
)
.
has_permission
(
request
,
view
)
\
and
current_org
.
can_admin_by
(
request
.
user
)
...
...
@@ -57,6 +59,8 @@ class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user"""
def
has_permission
(
self
,
request
,
view
):
if
not
current_org
:
return
False
return
super
(
IsOrgAdminOrAppUser
,
self
)
.
has_permission
(
request
,
view
)
\
and
(
current_org
.
can_admin_by
(
request
.
user
)
or
request
.
user
.
is_app
)
...
...
apps/common/tasks.py
View file @
f2888319
...
...
@@ -29,6 +29,6 @@ def send_mail_async(*args, **kwargs):
args
=
tuple
(
args
)
try
:
send_mail
(
*
args
,
**
kwargs
)
return
send_mail
(
*
args
,
**
kwargs
)
except
Exception
as
e
:
logger
.
error
(
"Sending mail error: {}"
.
format
(
e
))
apps/common/utils/common.py
View file @
f2888319
...
...
@@ -31,6 +31,10 @@ def get_logger(name=None):
return
logging
.
getLogger
(
'jumpserver.
%
s'
%
name
)
def
get_syslogger
(
name
=
None
):
return
logging
.
getLogger
(
'jms.
%
s'
%
name
)
def
timesince
(
dt
,
since
=
''
,
default
=
"just now"
):
"""
Returns string representing "time since" e.g.
...
...
apps/jumpserver/conf.py
View file @
f2888319
...
...
@@ -379,6 +379,8 @@ defaults = {
'ASSETS_PERM_CACHE_TIME'
:
3600
*
24
,
'SECURITY_MFA_VERIFY_TTL'
:
3600
,
'ASSETS_PERM_CACHE_ENABLE'
:
False
,
'SYSLOG_ADDR'
:
''
,
# '192.168.0.1:514'
'SYSLOG_FACILITY'
:
'user'
,
}
...
...
apps/jumpserver/settings.py
View file @
f2888319
...
...
@@ -214,6 +214,9 @@ LOGGING = {
'simple'
:
{
'format'
:
'
%(levelname)
s
%(message)
s'
},
'syslog'
:
{
'format'
:
'jumpserver:
%(message)
s'
},
'msg'
:
{
'format'
:
'
%(message)
s'
}
...
...
@@ -246,19 +249,10 @@ LOGGING = {
'backupCount'
:
7
,
'filename'
:
ANSIBLE_LOG_FILE
,
},
'gunicorn_file'
:
{
'encoding'
:
'utf8'
,
'level'
:
'DEBUG'
,
'class'
:
'logging.handlers.RotatingFileHandler'
,
'formatter'
:
'msg'
,
'maxBytes'
:
1024
*
1024
*
100
,
'backupCount'
:
2
,
'filename'
:
GUNICORN_LOG_FILE
,
},
'gunicorn_console'
:
{
'level'
:
'DEBUG'
,
'class'
:
'logging.StreamHandler'
,
'formatter'
:
'msg'
'syslog'
:
{
'level'
:
'INFO'
,
'class'
:
'logging.NullHandler'
,
'formatter'
:
'syslog'
},
},
'loggers'
:
{
...
...
@@ -268,25 +262,17 @@ LOGGING = {
'level'
:
LOG_LEVEL
,
},
'django.request'
:
{
'handlers'
:
[
'console'
,
'file'
],
'handlers'
:
[
'console'
,
'file'
,
'syslog'
],
'level'
:
LOG_LEVEL
,
'propagate'
:
False
,
},
'django.server'
:
{
'handlers'
:
[
'console'
,
'file'
],
'handlers'
:
[
'console'
,
'file'
,
'syslog'
],
'level'
:
LOG_LEVEL
,
'propagate'
:
False
,
},
'jumpserver'
:
{
'handlers'
:
[
'console'
,
'file'
],
'level'
:
LOG_LEVEL
,
},
'jumpserver.users.api'
:
{
'handlers'
:
[
'console'
,
'file'
],
'level'
:
LOG_LEVEL
,
},
'jumpserver.users.view'
:
{
'handlers'
:
[
'console'
,
'file'
],
'handlers'
:
[
'console'
,
'file'
,
'syslog'
],
'level'
:
LOG_LEVEL
,
},
'ops.ansible_api'
:
{
...
...
@@ -297,10 +283,10 @@ LOGGING = {
'handlers'
:
[
'console'
,
'file'
],
'level'
:
"INFO"
,
},
# 'gunicorn
': {
# 'handlers': ['gunicorn_console', 'gunicorn_file
'],
# 'level': 'INFO',
#
},
'jms_audits
'
:
{
'handlers'
:
[
'syslog
'
],
'level'
:
'INFO'
},
# 'django.db': {
# 'handlers': ['console', 'file'],
# 'level': 'DEBUG'
...
...
@@ -308,6 +294,17 @@ LOGGING = {
}
}
SYSLOG_ENABLE
=
False
if
CONFIG
.
SYSLOG_ADDR
!=
''
and
len
(
CONFIG
.
SYSLOG_ADDR
.
split
(
':'
))
==
2
:
host
,
port
=
CONFIG
.
SYSLOG_ADDR
.
split
(
':'
)
SYSLOG_ENABLE
=
True
LOGGING
[
'handlers'
][
'syslog'
]
.
update
({
'class'
:
'logging.handlers.SysLogHandler'
,
'facility'
:
CONFIG
.
SYSLOG_FACILITY
,
'address'
:
(
host
,
int
(
port
)),
})
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
# LANGUAGE_CODE = 'en'
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
f2888319
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
f2888319
This diff is collapsed.
Click to expand it.
apps/ops/api/command.py
View file @
f2888319
...
...
@@ -4,13 +4,14 @@ from rest_framework import viewsets
from
django.db
import
transaction
from
django.conf
import
settings
from
orgs.mixins
import
RootOrgViewMixin
from
common.permissions
import
IsValidUser
from
..models
import
CommandExecution
from
..serializers
import
CommandExecutionSerializer
from
..tasks
import
run_command_execution
class
CommandExecutionViewSet
(
viewsets
.
ModelViewSet
):
class
CommandExecutionViewSet
(
RootOrgViewMixin
,
viewsets
.
ModelViewSet
):
serializer_class
=
CommandExecutionSerializer
permission_classes
=
(
IsValidUser
,)
...
...
apps/ops/tasks.py
View file @
f2888319
...
...
@@ -109,7 +109,9 @@ def hello(name, callback=None):
# @after_app_shutdown_clean_periodic
# @register_as_period_task(interval=30)
def
hello123
():
p
=
subprocess
.
Popen
(
'ls /tmp'
,
shell
=
True
)
print
(
"{} Hello world"
.
format
(
datetime
.
datetime
.
now
()
.
strftime
(
"
%
H:
%
M:
%
S"
)))
return
None
@shared_task
...
...
apps/static/js/jumpserver.js
View file @
f2888319
...
...
@@ -608,8 +608,9 @@ jumpserver.initServerSideDataTable = function (options) {
var
kv
=
val
.
split
(
":"
);
if
(
kv
.
length
===
2
)
{
var
value
=
kv
[
1
];
value
=
value
.
replace
(
"+"
,
" "
);
search_attr
[
kv
[
0
]]
=
value
var
key
=
kv
[
0
].
trim
();
value
=
value
.
replace
(
"+"
,
" "
).
trim
();
search_attr
[
key
]
=
value
}
else
{
search_raw
.
push
(
kv
)
}
...
...
apps/static/plugins/elfinder/i18n/elfinder.zh_CN.js
View file @
f2888319
...
...
@@ -422,7 +422,7 @@
'minsLeft'
:
'剩余 $1 分钟'
,
// from v2.1.17 added 13.11.2016
'openAsEncoding'
:
'使用所选编码重新打开'
,
// from v2.1.19 added 2.12.2016
'saveAsEncoding'
:
'使用所选编码保存'
,
// from v2.1.19 added 2.12.2016
'selectFolder'
:
'选择目录
(暂不支持)
'
,
// from v2.1.20 added 13.12.2016
'selectFolder'
:
'选择目录'
,
// from v2.1.20 added 13.12.2016
'firstLetterSearch'
:
'首字母搜索'
,
// from v2.1.23 added 24.3.2017
'presets'
:
'预置'
,
// from v2.1.25 added 26.5.2017
'tooManyToTrash'
:
'项目太多,不能移动到回收站.'
,
// from v2.1.25 added 9.6.2017
...
...
apps/templates/_message.html
View file @
f2888319
{% load i18n %}
{% block user_expired_message %}
{% if request.user.is_expired %}
<div
class=
"alert alert-danger help-message alert-dismissable"
>
{% blocktrans %}
Your account has expired, please contact the administrator.
{% endblocktrans %}
<button
aria-hidden=
"true"
data-dismiss=
"alert"
class=
"close"
type=
"button"
style=
"outline: none;"
>
×
</button>
</div>
{% elif request.user.will_expired %}
<div
class=
"alert alert-danger help-message alert-dismissable"
>
{% trans 'Your account will at' %} {{ request.user.date_expired }} {% trans 'expired. ' %}
<button
aria-hidden=
"true"
data-dismiss=
"alert"
class=
"close"
type=
"button"
style=
"outline: none;"
>
×
</button>
</div>
{% endif %}
{% endblock %}
{% block password_expired_message %}
{% url 'users:user-password-update' as user_password_update_url %}
{% if request.user.password_has_expired
%}
{% if request.user.password_has_expired %}
<div
class=
"alert alert-danger help-message alert-dismissable"
>
{% blocktrans %}
Your password has expired, please click
<a
href=
"{{ user_password_update_url }}"
>
this link
</a>
update password.
...
...
apps/terminal/serializers/v1.py
View file @
f2888319
...
...
@@ -27,6 +27,7 @@ class TerminalSerializer(serializers.ModelSerializer):
class
SessionSerializer
(
BulkOrgResourceModelSerializer
):
command_amount
=
serializers
.
IntegerField
(
read_only
=
True
)
org_id
=
serializers
.
CharField
(
allow_blank
=
True
)
class
Meta
:
model
=
Session
...
...
apps/users/models/user.py
View file @
f2888319
...
...
@@ -207,20 +207,19 @@ class TokenMixin:
@property
def
private_token
(
self
):
from
authentication.models
import
PrivateToken
try
:
token
=
PrivateToken
.
objects
.
get
(
user
=
self
)
except
PrivateToken
.
DoesNotExist
:
token
=
self
.
create_private_token
()
return
token
return
self
.
create_private_token
()
def
create_private_token
(
self
):
from
authentication.models
import
PrivateToken
token
=
PrivateToken
.
objects
.
create
(
user
=
self
)
token
,
created
=
PrivateToken
.
objects
.
get_or_
create
(
user
=
self
)
return
token
def
delete_private_token
(
self
):
from
authentication.models
import
PrivateToken
PrivateToken
.
objects
.
filter
(
user
=
self
)
.
delete
()
def
refresh_private_token
(
self
):
self
.
private_token
.
delete
()
self
.
delete_private_token
()
return
self
.
create_private_token
()
def
create_bearer_token
(
self
,
request
=
None
):
...
...
@@ -403,6 +402,18 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
else
:
return
False
@property
def
expired_remain_days
(
self
):
date_remain
=
self
.
date_expired
-
timezone
.
now
()
return
date_remain
.
days
@property
def
will_expired
(
self
):
if
0
<=
self
.
expired_remain_days
<
5
:
return
True
else
:
return
False
@property
def
is_valid
(
self
):
if
self
.
is_active
and
not
self
.
is_expired
:
...
...
@@ -412,7 +423,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
@property
def
is_local
(
self
):
return
self
.
source
==
self
.
SOURCE_LOCAL
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
name
:
self
.
name
=
self
.
username
...
...
apps/users/tasks.py
View file @
f2888319
# -*- coding: utf-8 -*-
#
import
datetime
from
django.utils
import
timezone
from
django.conf
import
settings
from
celery
import
shared_task
from
ops.celery.utils
import
create_or_update_celery_periodic_tasks
from
ops.celery.decorator
import
after_app_ready_start
,
register_as_period_task
from
ops.celery.decorator
import
after_app_ready_start
from
common.utils
import
get_logger
from
.models
import
User
from
.utils
import
send_password_expiration_reminder_mail
from
.utils
import
(
send_password_expiration_reminder_mail
,
send_user_expiration_reminder_mail
)
logger
=
get_logger
(
__file__
)
...
...
@@ -43,4 +42,27 @@ def check_password_expired_periodic():
create_or_update_celery_periodic_tasks
(
tasks
)
@shared_task
def
check_user_expired
():
users
=
User
.
objects
.
exclude
(
role
=
User
.
ROLE_APP
)
for
user
in
users
:
if
not
user
.
is_valid
:
continue
if
not
user
.
will_expired
:
continue
send_user_expiration_reminder_mail
(
user
)
@shared_task
@after_app_ready_start
def
check_user_expired_periodic
():
tasks
=
{
'check_user_expired_periodic'
:
{
'task'
:
check_user_expired
.
name
,
'interval'
:
None
,
'crontab'
:
'0 14 * * *'
,
'enabled'
:
True
,
}
}
create_or_update_celery_periodic_tasks
(
tasks
)
apps/users/utils.py
View file @
f2888319
...
...
@@ -85,20 +85,20 @@ def send_reset_password_mail(user):
recipient_list
=
[
user
.
email
]
message
=
_
(
"""
Hello
%(name)
s:
<
/
br>
<br>
Please click the link below to reset your password, if not your request, concern your account security
<
/
br>
<br>
<a href="
%(rest_password_url)
s?token=
%(rest_password_token)
s">Click here reset password</a>
<
/
br>
<br>
This link is valid for 1 hour. After it expires, <a href="
%(forget_password_url)
s?email=
%(email)
s">request new one</a>
<
/
br>
<br>
---
<
/
br>
<br>
<a href="
%(login_url)
s">Login direct</a>
<
/
br>
<br>
"""
)
%
{
'name'
:
user
.
name
,
'rest_password_url'
:
reverse
(
'users:reset-password'
,
external
=
True
),
...
...
@@ -118,24 +118,24 @@ def send_password_expiration_reminder_mail(user):
recipient_list
=
[
user
.
email
]
message
=
_
(
"""
Hello
%(name)
s:
<
/
br>
<br>
Your password will expire in
%(date_password_expired)
s,
<
/
br>
<br>
For your account security, please click on the link below to update your password in time
<
/
br>
<br>
<a href="
%(update_password_url)
s">Click here update password</a>
<
/
br>
<br>
If your password has expired, please click
<a href="
%(forget_password_url)
s?email=
%(email)
s">Password expired</a>
to apply for a password reset email.
<
/
br>
<br>
---
<
/
br>
<br>
<a href="
%(login_url)
s">Login direct</a>
<
/
br>
<br>
"""
)
%
{
'name'
:
user
.
name
,
'date_password_expired'
:
datetime
.
fromtimestamp
(
datetime
.
timestamp
(
...
...
@@ -151,18 +151,39 @@ def send_password_expiration_reminder_mail(user):
send_mail_async
.
delay
(
subject
,
message
,
recipient_list
,
html_message
=
message
)
def
send_user_expiration_reminder_mail
(
user
):
subject
=
_
(
'Expiration notice'
)
recipient_list
=
[
user
.
email
]
message
=
_
(
"""
Hello
%(name)
s:
<br>
Your account will expire in
%(date_expired)
s,
<br>
In order not to affect your normal work, please contact the administrator for confirmation.
<br>
"""
)
%
{
'name'
:
user
.
name
,
'date_expired'
:
datetime
.
fromtimestamp
(
datetime
.
timestamp
(
user
.
date_expired
))
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M'
),
}
if
settings
.
DEBUG
:
logger
.
debug
(
message
)
send_mail_async
.
delay
(
subject
,
message
,
recipient_list
,
html_message
=
message
)
def
send_reset_ssh_key_mail
(
user
):
subject
=
_
(
'SSH Key Reset'
)
recipient_list
=
[
user
.
email
]
message
=
_
(
"""
Hello
%(name)
s:
<
/
br>
<br>
Your ssh public key has been reset by site administrator.
Please login and reset your ssh public key.
<
/
br>
<br>
<a href="
%(login_url)
s">Login direct</a>
<
/
br>
<br>
"""
)
%
{
'name'
:
user
.
name
,
'login_url'
:
reverse
(
'authentication:login'
,
external
=
True
),
...
...
@@ -264,6 +285,12 @@ def increase_login_failed_count(username, ip):
cache
.
set
(
key_limit
,
count
,
int
(
limit_time
)
*
60
)
def
get_login_failed_count
(
username
,
ip
):
key_limit
=
key_prefix_limit
.
format
(
username
,
ip
)
count
=
cache
.
get
(
key_limit
,
0
)
return
count
def
clean_failed_count
(
username
,
ip
):
key_limit
=
key_prefix_limit
.
format
(
username
,
ip
)
key_block
=
key_prefix_block
.
format
(
username
)
...
...
@@ -272,9 +299,8 @@ def clean_failed_count(username, ip):
def
is_block_login
(
username
,
ip
):
key_limit
=
key_prefix_limit
.
forma
t
(
username
,
ip
)
count
=
get_login_failed_coun
t
(
username
,
ip
)
key_block
=
key_prefix_block
.
format
(
username
)
count
=
cache
.
get
(
key_limit
,
0
)
limit_count
=
settings
.
SECURITY_LOGIN_LIMIT_COUNT
limit_time
=
settings
.
SECURITY_LOGIN_LIMIT_TIME
...
...
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