Unverified Commit af2b6742 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3066 from jumpserver/dev

Dev
parents f51c6efd 42547751
......@@ -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)
......
......@@ -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(">>>>>>")
......
......@@ -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
......@@ -86,6 +87,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
......
......@@ -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()
......
......@@ -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 %}
......
......@@ -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: 45px;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 %}
......@@ -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>
......
......@@ -100,6 +100,7 @@ class UserLoginView(FormView):
# 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)
......
......@@ -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)
......
......@@ -5,6 +5,7 @@ import re
import pytz
from django.utils import timezone
from django.shortcuts import HttpResponse
from django.conf import settings
from .utils import set_current_request
......@@ -56,6 +57,7 @@ class RequestMiddleware:
def __call__(self, request):
set_current_request(request)
response = self.get_response(request)
age = request.session.get_expiry_age()
request.session.set_expiry(age)
if not settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
age = request.session.get_expiry_age()
request.session.set_expiry(age)
return response
This diff is collapsed.
......@@ -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,)
......
from __future__ import unicode_literals
from django.conf import settings
from django.apps import AppConfig
......@@ -8,4 +9,6 @@ class PermsConfig(AppConfig):
def ready(self):
from . import signals_handler
if not settings.XPACK_ENABLED:
settings.ASSETS_PERM_CACHE_ENABLE = False
return super().ready()
{% 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.
......
......@@ -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
......
......@@ -402,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:
......@@ -411,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
......
# -*- 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)
......@@ -89,20 +89,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),
......@@ -122,24 +122,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(
......@@ -155,18 +155,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),
......@@ -299,6 +320,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)
......@@ -307,9 +334,8 @@ def clean_failed_count(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)
count = cache.get(key_limit, 0)
limit_count = settings.SECURITY_LOGIN_LIMIT_COUNT
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