Commit f2888319 authored by BaiJiangJie's avatar BaiJiangJie

[Update] Merge branch dev_beta to branch dev_beta_db

parents b265a386 8074e669
...@@ -212,12 +212,13 @@ class AssetsAmountMixin: ...@@ -212,12 +212,13 @@ class AssetsAmountMixin:
if cached is not None: if cached is not None:
return cached return cached
assets_amount = self.get_all_assets().count() assets_amount = self.get_all_assets().count()
cache.set(cache_key, assets_amount, self.cache_time)
return assets_amount return assets_amount
@assets_amount.setter @assets_amount.setter
def assets_amount(self, value): def assets_amount(self, value):
self._assets_amount = 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): def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True) ancestor_keys = self.get_ancestor_keys(with_self=True)
......
...@@ -59,6 +59,7 @@ class AuthSerializerMixin: ...@@ -59,6 +59,7 @@ class AuthSerializerMixin:
value = validated_data.get(field) value = validated_data.get(field)
if not value: if not value:
validated_data.pop(field, None) validated_data.pop(field, None)
# print(validated_data) # print(validated_data)
# raise serializers.ValidationError(">>>>>>") # raise serializers.ValidationError(">>>>>>")
......
...@@ -3,6 +3,7 @@ from rest_framework import serializers ...@@ -3,6 +3,7 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.utils import ssh_pubkey_gen
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import SystemUser from ..models import SystemUser
from .base import AuthSerializer, AuthSerializerMixin from .base import AuthSerializer, AuthSerializerMixin
...@@ -71,7 +72,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -71,7 +72,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
super().validate_password(password) super().validate_password(password)
auto_gen_key = self.initial_data.get("auto_generate_key", False) auto_gen_key = self.initial_data.get("auto_generate_key", False)
private_key = self.initial_data.get("private_key") 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")) raise serializers.ValidationError(_("Password or private key required"))
return password return password
...@@ -86,6 +89,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ...@@ -86,6 +89,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
private_key, public_key = SystemUser.gen_key(username) private_key, public_key = SystemUser.gen_key(username)
attrs["private_key"] = private_key attrs["private_key"] = private_key
attrs["public_key"] = public_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) attrs.pop("auto_generate_key", None)
return attrs return attrs
......
...@@ -112,6 +112,7 @@ def on_node_assets_changed(sender, instance=None, **kwargs): ...@@ -112,6 +112,7 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
@receiver(post_save, sender=Node) @receiver(post_save, sender=Node)
def on_node_update_or_created(sender, instance=None, created=False, **kwargs): def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
if instance and not created: if instance and not created:
Asset.expire_all_nodes_keys_cache()
instance.expire_full_value() instance.expire_full_value()
......
...@@ -94,10 +94,8 @@ urlpatterns = [ ...@@ -94,10 +94,8 @@ urlpatterns = [
] ]
old_version_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 + old_version_urlpatterns
urlpatterns += router.urls + cmd_filter_router.urls
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings
from django.db.models.signals import post_save
class AuditsConfig(AppConfig): class AuditsConfig(AppConfig):
...@@ -6,3 +8,5 @@ class AuditsConfig(AppConfig): ...@@ -6,3 +8,5 @@ class AuditsConfig(AppConfig):
def ready(self): def ready(self):
from . import signals_handler from . import signals_handler
if settings.SYSLOG_ENABLE:
post_save.connect(signals_handler.on_audits_log_create)
...@@ -3,11 +3,36 @@ ...@@ -3,11 +3,36 @@
from rest_framework import serializers from rest_framework import serializers
from .models import FTPLog from terminal.models import Session
from . import models
class FTPLogSerializer(serializers.ModelSerializer): class FTPLogSerializer(serializers.ModelSerializer):
class Meta: 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__' fields = '__all__'
...@@ -4,13 +4,18 @@ ...@@ -4,13 +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 rest_framework.renderers import JSONRenderer
from jumpserver.utils import current_request 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 users.models import User
from .models import OperateLog, PasswordChangeLog from terminal.models import Session
from . import models
from . import serializers
logger = get_logger(__name__) logger = get_logger(__name__)
sys_logger = get_syslogger("audits")
json_render = JSONRenderer()
MODELS_NEED_RECORD = ( MODELS_NEED_RECORD = (
...@@ -36,7 +41,7 @@ def create_operate_log(action, sender, resource): ...@@ -36,7 +41,7 @@ def create_operate_log(action, sender, resource):
} }
with transaction.atomic(): with transaction.atomic():
try: try:
OperateLog.objects.create(**data) models.OperateLog.objects.create(**data)
except Exception as e: except Exception as e:
logger.error("Create operate log error: {}".format(e)) logger.error("Create operate log error: {}".format(e))
...@@ -44,15 +49,15 @@ def create_operate_log(action, sender, resource): ...@@ -44,15 +49,15 @@ def create_operate_log(action, sender, resource):
@receiver(post_save, dispatch_uid="my_unique_identifier") @receiver(post_save, dispatch_uid="my_unique_identifier")
def on_object_created_or_update(sender, instance=None, created=False, **kwargs): def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
if created: if created:
action = OperateLog.ACTION_CREATE action = models.OperateLog.ACTION_CREATE
else: else:
action = OperateLog.ACTION_UPDATE action = models.OperateLog.ACTION_UPDATE
create_operate_log(action, sender, instance) create_operate_log(action, sender, instance)
@receiver(post_delete, dispatch_uid="my_unique_identifier") @receiver(post_delete, dispatch_uid="my_unique_identifier")
def on_object_delete(sender, instance=None, **kwargs): 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") @receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
...@@ -61,7 +66,32 @@ def on_user_change_password(sender, instance=None, **kwargs): ...@@ -61,7 +66,32 @@ def on_user_change_password(sender, instance=None, **kwargs):
if not current_request or not current_request.user.is_authenticated: if not current_request or not current_request.user.is_authenticated:
return return
with transaction.atomic(): with transaction.atomic():
PasswordChangeLog.objects.create( models.PasswordChangeLog.objects.create(
user=instance, change_by=current_request.user, user=instance, change_by=current_request.user,
remote_addr=get_request_ip(current_request), 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)
...@@ -5,6 +5,8 @@ from django import forms ...@@ -5,6 +5,8 @@ from django import forms
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from django.conf import settings
from users.utils import get_login_failed_count
class UserLoginForm(AuthenticationForm): class UserLoginForm(AuthenticationForm):
...@@ -16,10 +18,18 @@ class UserLoginForm(AuthenticationForm): ...@@ -16,10 +18,18 @@ class UserLoginForm(AuthenticationForm):
error_messages = { error_messages = {
'invalid_login': _( 'invalid_login': _(
"Please enter a correct username and password. Note that both " "The username or password you entered is incorrect, "
"fields may be case-sensitive." "please enter it again."
), ),
'inactive': _("This account is inactive."), '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):
...@@ -28,6 +38,25 @@ class UserLoginForm(AuthenticationForm): ...@@ -28,6 +38,25 @@ class UserLoginForm(AuthenticationForm):
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):
captcha = CaptchaField() captcha = CaptchaField()
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
{% else %} {% else %}
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p> <p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %} {% endif %}
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
{% endif %} {% endif %}
<div class="form-group"> <div class="form-group">
...@@ -83,6 +84,7 @@ ...@@ -83,6 +84,7 @@
<small>{% trans 'Forgot password' %}?</small> <small>{% trans 'Forgot password' %}?</small>
</a> </a>
</div> </div>
</div>
{% if AUTH_OPENID %} {% if AUTH_OPENID %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
......
...@@ -72,9 +72,10 @@ ...@@ -72,9 +72,10 @@
<div class="contact-form col-md-10" style="margin-top: 10px;height: 35px"> <div class="contact-form col-md-10" style="margin-top: 10px;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 %}
<div style="height: 45px;color: red;line-height: 17px;"> <div style="height: 70px;color: red;line-height: 17px;">
{% if block_login %} {% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p> <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 %} {% elif password_expired %}
<p class="red-fonts">{% trans 'The user password has expired' %}</p> <p class="red-fonts">{% trans 'The user password has expired' %}</p>
{% elif form.errors %} {% elif form.errors %}
...@@ -83,6 +84,7 @@ ...@@ -83,6 +84,7 @@
{% else %} {% else %}
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p> <p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %} {% endif %}
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
{% endif %} {% endif %}
</div> </div>
......
...@@ -26,6 +26,7 @@ from users.utils import ( ...@@ -26,6 +26,7 @@ from users.utils import (
) )
from ..signals import post_auth_success, post_auth_failed from ..signals import post_auth_success, post_auth_failed
from .. import forms from .. import forms
from .. import const
__all__ = [ __all__ = [
...@@ -77,7 +78,7 @@ class UserLoginView(FormView): ...@@ -77,7 +78,7 @@ class UserLoginView(FormView):
user = form.get_user() user = form.get_user()
# user password expired # user password expired
if user.password_has_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) self.send_auth_signal(success=False, username=user.username, reason=reason)
return self.render_to_response(self.get_context_data(password_expired=True)) return self.render_to_response(self.get_context_data(password_expired=True))
...@@ -92,10 +93,11 @@ class UserLoginView(FormView): ...@@ -92,10 +93,11 @@ class UserLoginView(FormView):
# write login failed log # write login failed log
username = form.cleaned_data.get('username') username = form.cleaned_data.get('username')
exist = User.objects.filter(username=username).first() 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 # limit user login failed count
ip = get_request_ip(self.request) ip = get_request_ip(self.request)
increase_login_failed_count(username, ip) increase_login_failed_count(username, ip)
form.add_limit_login_error(username, ip)
# show captcha # 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) self.send_auth_signal(success=False, username=username, reason=reason)
...@@ -162,7 +164,7 @@ class UserLoginOtpView(FormView): ...@@ -162,7 +164,7 @@ class UserLoginOtpView(FormView):
else: else:
self.send_auth_signal( self.send_auth_signal(
success=False, username=user.username, success=False, username=user.username,
reason=LoginLog.REASON_MFA reason=const.mfa_failed
) )
form.add_error( form.add_error(
'otp_code', _('MFA code invalid, or ntp sync server time') 'otp_code', _('MFA code invalid, or ntp sync server time')
......
...@@ -49,6 +49,8 @@ class IsOrgAdmin(IsValidUser): ...@@ -49,6 +49,8 @@ class IsOrgAdmin(IsValidUser):
"""Allows access only to superuser""" """Allows access only to superuser"""
def has_permission(self, request, view): def has_permission(self, request, view):
if not current_org:
return False
return super(IsOrgAdmin, self).has_permission(request, view) \ return super(IsOrgAdmin, self).has_permission(request, view) \
and current_org.can_admin_by(request.user) and current_org.can_admin_by(request.user)
...@@ -57,6 +59,8 @@ class IsOrgAdminOrAppUser(IsValidUser): ...@@ -57,6 +59,8 @@ class IsOrgAdminOrAppUser(IsValidUser):
"""Allows access between superuser and app user""" """Allows access between superuser and app user"""
def has_permission(self, request, view): def has_permission(self, request, view):
if not current_org:
return False
return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \ return super(IsOrgAdminOrAppUser, self).has_permission(request, view) \
and (current_org.can_admin_by(request.user) or request.user.is_app) and (current_org.can_admin_by(request.user) or request.user.is_app)
......
...@@ -29,6 +29,6 @@ def send_mail_async(*args, **kwargs): ...@@ -29,6 +29,6 @@ def send_mail_async(*args, **kwargs):
args = tuple(args) args = tuple(args)
try: try:
send_mail(*args, **kwargs) return send_mail(*args, **kwargs)
except Exception as e: except Exception as e:
logger.error("Sending mail error: {}".format(e)) logger.error("Sending mail error: {}".format(e))
...@@ -31,6 +31,10 @@ def get_logger(name=None): ...@@ -31,6 +31,10 @@ def get_logger(name=None):
return logging.getLogger('jumpserver.%s' % name) return logging.getLogger('jumpserver.%s' % name)
def get_syslogger(name=None):
return logging.getLogger('jms.%s' % name)
def timesince(dt, since='', default="just now"): def timesince(dt, since='', default="just now"):
""" """
Returns string representing "time since" e.g. Returns string representing "time since" e.g.
......
...@@ -379,6 +379,8 @@ defaults = { ...@@ -379,6 +379,8 @@ defaults = {
'ASSETS_PERM_CACHE_TIME': 3600*24, 'ASSETS_PERM_CACHE_TIME': 3600*24,
'SECURITY_MFA_VERIFY_TTL': 3600, 'SECURITY_MFA_VERIFY_TTL': 3600,
'ASSETS_PERM_CACHE_ENABLE': False, 'ASSETS_PERM_CACHE_ENABLE': False,
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
} }
......
...@@ -214,6 +214,9 @@ LOGGING = { ...@@ -214,6 +214,9 @@ LOGGING = {
'simple': { 'simple': {
'format': '%(levelname)s %(message)s' 'format': '%(levelname)s %(message)s'
}, },
'syslog': {
'format': 'jumpserver: %(message)s'
},
'msg': { 'msg': {
'format': '%(message)s' 'format': '%(message)s'
} }
...@@ -246,19 +249,10 @@ LOGGING = { ...@@ -246,19 +249,10 @@ LOGGING = {
'backupCount': 7, 'backupCount': 7,
'filename': ANSIBLE_LOG_FILE, 'filename': ANSIBLE_LOG_FILE,
}, },
'gunicorn_file': { 'syslog': {
'encoding': 'utf8', 'level': 'INFO',
'level': 'DEBUG', 'class': 'logging.NullHandler',
'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'syslog'
'formatter': 'msg',
'maxBytes': 1024*1024*100,
'backupCount': 2,
'filename': GUNICORN_LOG_FILE,
},
'gunicorn_console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'msg'
}, },
}, },
'loggers': { 'loggers': {
...@@ -268,25 +262,17 @@ LOGGING = { ...@@ -268,25 +262,17 @@ LOGGING = {
'level': LOG_LEVEL, 'level': LOG_LEVEL,
}, },
'django.request': { 'django.request': {
'handlers': ['console', 'file'], 'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL, 'level': LOG_LEVEL,
'propagate': False, 'propagate': False,
}, },
'django.server': { 'django.server': {
'handlers': ['console', 'file'], 'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL, 'level': LOG_LEVEL,
'propagate': False, 'propagate': False,
}, },
'jumpserver': { 'jumpserver': {
'handlers': ['console', 'file'], 'handlers': ['console', 'file', 'syslog'],
'level': LOG_LEVEL,
},
'jumpserver.users.api': {
'handlers': ['console', 'file'],
'level': LOG_LEVEL,
},
'jumpserver.users.view': {
'handlers': ['console', 'file'],
'level': LOG_LEVEL, 'level': LOG_LEVEL,
}, },
'ops.ansible_api': { 'ops.ansible_api': {
...@@ -297,10 +283,10 @@ LOGGING = { ...@@ -297,10 +283,10 @@ LOGGING = {
'handlers': ['console', 'file'], 'handlers': ['console', 'file'],
'level': "INFO", 'level': "INFO",
}, },
# 'gunicorn': { 'jms_audits': {
# 'handlers': ['gunicorn_console', 'gunicorn_file'], 'handlers': ['syslog'],
# 'level': 'INFO', 'level': 'INFO'
# }, },
# 'django.db': { # 'django.db': {
# 'handlers': ['console', 'file'], # 'handlers': ['console', 'file'],
# 'level': 'DEBUG' # 'level': 'DEBUG'
...@@ -308,6 +294,17 @@ LOGGING = { ...@@ -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 # Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/ # https://docs.djangoproject.com/en/1.10/topics/i18n/
# LANGUAGE_CODE = 'en' # LANGUAGE_CODE = 'en'
......
This diff is collapsed.
...@@ -4,13 +4,14 @@ from rest_framework import viewsets ...@@ -4,13 +4,14 @@ from rest_framework import viewsets
from django.db import transaction from django.db import transaction
from django.conf import settings from django.conf import settings
from orgs.mixins import RootOrgViewMixin
from common.permissions import IsValidUser from common.permissions import IsValidUser
from ..models import CommandExecution from ..models import CommandExecution
from ..serializers import CommandExecutionSerializer from ..serializers import CommandExecutionSerializer
from ..tasks import run_command_execution from ..tasks import run_command_execution
class CommandExecutionViewSet(viewsets.ModelViewSet): class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
serializer_class = CommandExecutionSerializer serializer_class = CommandExecutionSerializer
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
......
...@@ -109,7 +109,9 @@ def hello(name, callback=None): ...@@ -109,7 +109,9 @@ def hello(name, callback=None):
# @after_app_shutdown_clean_periodic # @after_app_shutdown_clean_periodic
# @register_as_period_task(interval=30) # @register_as_period_task(interval=30)
def hello123(): def hello123():
p = subprocess.Popen('ls /tmp', shell=True)
print("{} Hello world".format(datetime.datetime.now().strftime("%H:%M:%S"))) print("{} Hello world".format(datetime.datetime.now().strftime("%H:%M:%S")))
return None
@shared_task @shared_task
......
...@@ -608,8 +608,9 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -608,8 +608,9 @@ jumpserver.initServerSideDataTable = function (options) {
var kv = val.split(":"); var kv = val.split(":");
if (kv.length === 2) { if (kv.length === 2) {
var value = kv[1]; var value = kv[1];
value = value.replace("+", " "); var key = kv[0].trim();
search_attr[kv[0]] = value value = value.replace("+", " ").trim();
search_attr[key] = value
} else { } else {
search_raw.push(kv) search_raw.push(kv)
} }
......
...@@ -422,7 +422,7 @@ ...@@ -422,7 +422,7 @@
'minsLeft' : '剩余 $1 分钟', // from v2.1.17 added 13.11.2016 'minsLeft' : '剩余 $1 分钟', // from v2.1.17 added 13.11.2016
'openAsEncoding' : '使用所选编码重新打开', // from v2.1.19 added 2.12.2016 'openAsEncoding' : '使用所选编码重新打开', // from v2.1.19 added 2.12.2016
'saveAsEncoding' : '使用所选编码保存', // 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 'firstLetterSearch': '首字母搜索', // from v2.1.23 added 24.3.2017
'presets' : '预置', // from v2.1.25 added 26.5.2017 'presets' : '预置', // from v2.1.25 added 26.5.2017
'tooManyToTrash' : '项目太多,不能移动到回收站.', // from v2.1.25 added 9.6.2017 'tooManyToTrash' : '项目太多,不能移动到回收站.', // from v2.1.25 added 9.6.2017
......
{% load i18n %} {% 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 %} {% block password_expired_message %}
{% url 'users:user-password-update' as user_password_update_url %} {% url 'users:user-password-update' as user_password_update_url %}
{% if request.user.password_has_expired %} {% if request.user.password_has_expired %}
......
...@@ -27,6 +27,7 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -27,6 +27,7 @@ class TerminalSerializer(serializers.ModelSerializer):
class SessionSerializer(BulkOrgResourceModelSerializer): class SessionSerializer(BulkOrgResourceModelSerializer):
command_amount = serializers.IntegerField(read_only=True) command_amount = serializers.IntegerField(read_only=True)
org_id = serializers.CharField(allow_blank=True)
class Meta: class Meta:
model = Session model = Session
......
...@@ -207,20 +207,19 @@ class TokenMixin: ...@@ -207,20 +207,19 @@ class TokenMixin:
@property @property
def private_token(self): def private_token(self):
from authentication.models import PrivateToken return self.create_private_token()
try:
token = PrivateToken.objects.get(user=self)
except PrivateToken.DoesNotExist:
token = self.create_private_token()
return token
def create_private_token(self): def create_private_token(self):
from authentication.models import PrivateToken from authentication.models import PrivateToken
token = PrivateToken.objects.create(user=self) token, created = PrivateToken.objects.get_or_create(user=self)
return token return token
def delete_private_token(self):
from authentication.models import PrivateToken
PrivateToken.objects.filter(user=self).delete()
def refresh_private_token(self): def refresh_private_token(self):
self.private_token.delete() self.delete_private_token()
return self.create_private_token() return self.create_private_token()
def create_bearer_token(self, request=None): def create_bearer_token(self, request=None):
...@@ -403,6 +402,18 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): ...@@ -403,6 +402,18 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
else: else:
return False 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 @property
def is_valid(self): def is_valid(self):
if self.is_active and not self.is_expired: if self.is_active and not self.is_expired:
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import datetime
from django.utils import timezone
from django.conf import settings
from celery import shared_task from celery import shared_task
from ops.celery.utils import create_or_update_celery_periodic_tasks 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 common.utils import get_logger
from .models import User 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__) logger = get_logger(__file__)
...@@ -43,4 +42,27 @@ def check_password_expired_periodic(): ...@@ -43,4 +42,27 @@ def check_password_expired_periodic():
create_or_update_celery_periodic_tasks(tasks) 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)
...@@ -85,20 +85,20 @@ def send_reset_password_mail(user): ...@@ -85,20 +85,20 @@ def send_reset_password_mail(user):
recipient_list = [user.email] recipient_list = [user.email]
message = _(""" message = _("""
Hello %(name)s: Hello %(name)s:
</br> <br>
Please click the link below to reset your password, if not your request, concern your account security 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> <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> 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> <a href="%(login_url)s">Login direct</a>
</br> <br>
""") % { """) % {
'name': user.name, 'name': user.name,
'rest_password_url': reverse('users:reset-password', external=True), 'rest_password_url': reverse('users:reset-password', external=True),
...@@ -118,24 +118,24 @@ def send_password_expiration_reminder_mail(user): ...@@ -118,24 +118,24 @@ def send_password_expiration_reminder_mail(user):
recipient_list = [user.email] recipient_list = [user.email]
message = _(""" message = _("""
Hello %(name)s: Hello %(name)s:
</br> <br>
Your password will expire in %(date_password_expired)s, 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 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> <a href="%(update_password_url)s">Click here update password</a>
</br> <br>
If your password has expired, please click If your password has expired, please click
<a href="%(forget_password_url)s?email=%(email)s">Password expired</a> <a href="%(forget_password_url)s?email=%(email)s">Password expired</a>
to apply for a password reset email. to apply for a password reset email.
</br> <br>
--- ---
</br> <br>
<a href="%(login_url)s">Login direct</a> <a href="%(login_url)s">Login direct</a>
</br> <br>
""") % { """) % {
'name': user.name, 'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp( 'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
...@@ -151,18 +151,39 @@ def send_password_expiration_reminder_mail(user): ...@@ -151,18 +151,39 @@ def send_password_expiration_reminder_mail(user):
send_mail_async.delay(subject, message, recipient_list, html_message=message) 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): def send_reset_ssh_key_mail(user):
subject = _('SSH Key Reset') subject = _('SSH Key Reset')
recipient_list = [user.email] recipient_list = [user.email]
message = _(""" message = _("""
Hello %(name)s: Hello %(name)s:
</br> <br>
Your ssh public key has been reset by site administrator. Your ssh public key has been reset by site administrator.
Please login and reset your ssh public key. Please login and reset your ssh public key.
</br> <br>
<a href="%(login_url)s">Login direct</a> <a href="%(login_url)s">Login direct</a>
</br> <br>
""") % { """) % {
'name': user.name, 'name': user.name,
'login_url': reverse('authentication:login', external=True), 'login_url': reverse('authentication:login', external=True),
...@@ -264,6 +285,12 @@ def increase_login_failed_count(username, ip): ...@@ -264,6 +285,12 @@ def increase_login_failed_count(username, ip):
cache.set(key_limit, count, int(limit_time)*60) 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): def clean_failed_count(username, ip):
key_limit = key_prefix_limit.format(username, ip) key_limit = key_prefix_limit.format(username, ip)
key_block = key_prefix_block.format(username) key_block = key_prefix_block.format(username)
...@@ -272,9 +299,8 @@ def clean_failed_count(username, ip): ...@@ -272,9 +299,8 @@ def clean_failed_count(username, ip):
def is_block_login(username, ip): def is_block_login(username, ip):
key_limit = key_prefix_limit.format(username, ip) count = get_login_failed_count(username, ip)
key_block = key_prefix_block.format(username) key_block = key_prefix_block.format(username)
count = cache.get(key_limit, 0)
limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT
limit_time = settings.SECURITY_LOGIN_LIMIT_TIME limit_time = settings.SECURITY_LOGIN_LIMIT_TIME
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment