jumpserver/apps/users/utils.py

360 lines
11 KiB
Python
Raw Normal View History

2016-08-09 09:27:37 +00:00
# ~*~ coding: utf-8 ~*~
#
2016-08-31 17:12:02 +00:00
from __future__ import unicode_literals
2018-04-18 04:48:07 +00:00
import os
import re
2018-04-18 04:48:07 +00:00
import pyotp
import base64
2016-08-30 17:00:20 +00:00
import logging
2016-10-31 10:58:23 +00:00
import uuid
2016-08-30 17:00:20 +00:00
2017-12-04 12:15:47 +00:00
import requests
import ipaddress
from django.http import Http404
from django.conf import settings
2016-08-28 15:58:22 +00:00
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth import authenticate
2016-09-03 16:51:36 +00:00
from django.utils.translation import ugettext as _
from django.core.cache import cache
2016-08-28 15:58:22 +00:00
2016-08-31 17:12:02 +00:00
from common.tasks import send_mail_async
2016-09-24 16:11:31 +00:00
from common.utils import reverse, get_object_or_none
from common.models import Setting
from common.forms import SecuritySettingForm
2017-12-04 12:15:47 +00:00
from .models import User, LoginLog
2016-08-30 17:00:20 +00:00
2016-08-30 17:00:20 +00:00
logger = logging.getLogger('jumpserver')
2016-08-28 15:58:22 +00:00
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
2017-03-31 03:25:25 +00:00
if not self.request.user.is_authenticated:
return False
elif not self.request.user.is_superuser:
self.raise_exception = True
return False
return True
2016-08-30 17:00:20 +00:00
2017-12-12 04:19:45 +00:00
def send_user_created_mail(user):
2016-09-03 16:51:36 +00:00
subject = _('Create account successfully')
2016-08-31 17:12:02 +00:00
recipient_list = [user.email]
2016-09-03 16:51:36 +00:00
message = _("""
Hello %(name)s:
2016-08-31 17:12:02 +00:00
</br>
2016-09-03 16:51:36 +00:00
Your account has been created successfully
2016-08-31 17:12:02 +00:00
</br>
Username: %(username)s
</br>
2016-09-03 16:51:36 +00:00
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
2016-08-31 17:12:02 +00:00
</br>
2016-09-03 16:51:36 +00:00
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
2016-08-31 17:12:02 +00:00
</br>
---
</br>
2016-09-03 16:51:36 +00:00
<a href="%(login_url)s">Login direct</a>
2016-08-31 17:12:02 +00:00
</br>
2016-09-03 16:51:36 +00:00
""") % {
2016-08-31 17:12:02 +00:00
'name': user.name,
'username': user.username,
2016-08-31 17:12:02 +00:00
'rest_password_url': reverse('users:reset-password', external=True),
2016-09-01 15:09:58 +00:00
'rest_password_token': user.generate_reset_token(),
2016-09-06 07:09:00 +00:00
'forget_password_url': reverse('users:forgot-password', external=True),
2016-09-01 15:09:58 +00:00
'email': user.email,
'login_url': reverse('users:login', external=True),
}
2017-12-12 04:19:45 +00:00
if settings.DEBUG:
2018-01-26 08:06:23 +00:00
try:
print(message)
except OSError:
pass
2016-09-01 15:09:58 +00:00
send_mail_async.delay(subject, message, recipient_list, html_message=message)
def send_reset_password_mail(user):
2016-09-03 16:51:36 +00:00
subject = _('Reset password')
2016-09-01 15:09:58 +00:00
recipient_list = [user.email]
2016-09-03 16:51:36 +00:00
message = _("""
Hello %(name)s:
2016-09-01 15:09:58 +00:00
</br>
2016-09-03 16:51:36 +00:00
Please click the link below to reset your password, if not your request, concern your account security
2016-09-01 15:09:58 +00:00
</br>
2016-09-03 16:51:36 +00:00
<a href="%(rest_password_url)s?token=%(rest_password_token)s">Click here reset password</a>
2016-09-01 15:09:58 +00:00
</br>
2016-11-09 11:29:15 +00:00
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
2016-09-01 15:09:58 +00:00
</br>
---
</br>
2016-09-03 16:51:36 +00:00
<a href="%(login_url)s">Login direct</a>
2016-09-01 15:09:58 +00:00
</br>
2016-09-03 16:51:36 +00:00
""") % {
2016-09-01 15:09:58 +00:00
'name': user.name,
'rest_password_url': reverse('users:reset-password', external=True),
'rest_password_token': user.generate_reset_token(),
2016-09-06 07:09:00 +00:00
'forget_password_url': reverse('users:forgot-password', external=True),
2016-08-31 17:12:02 +00:00
'email': user.email,
'login_url': reverse('users:login', external=True),
}
if settings.DEBUG:
logger.debug(message)
2016-08-31 17:12:02 +00:00
send_mail_async.delay(subject, message, recipient_list, html_message=message)
2016-08-30 17:00:20 +00:00
def send_reset_ssh_key_mail(user):
subject = _('SSH Key Reset')
recipient_list = [user.email]
message = _("""
Hello %(name)s:
</br>
Your ssh public key has been reset by site administrator.
Please login and reset your ssh public key.
</br>
<a href="%(login_url)s">Login direct</a>
</br>
""") % {
'name': user.name,
'login_url': reverse('users:login', external=True),
}
if settings.DEBUG:
logger.debug(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
2016-10-31 10:58:23 +00:00
def check_user_valid(**kwargs):
2016-09-24 16:11:31 +00:00
password = kwargs.pop('password', None)
public_key = kwargs.pop('public_key', None)
2016-12-29 11:17:00 +00:00
email = kwargs.pop('email', None)
username = kwargs.pop('username', None)
2016-12-28 16:29:59 +00:00
if username:
user = get_object_or_none(User, username=username)
elif email:
user = get_object_or_none(User, email=email)
else:
user = None
if user is None:
return None, _('User not exist')
elif not user.is_valid:
return None, _('Disabled or expired')
2016-09-24 16:11:31 +00:00
2017-10-10 06:18:08 +00:00
if password and authenticate(username=username, password=password):
2016-12-28 16:29:59 +00:00
return user, ''
2017-01-02 16:11:44 +00:00
if public_key and user.public_key:
2016-11-07 08:59:52 +00:00
public_key_saved = user.public_key.split()
if len(public_key_saved) == 1:
if public_key == public_key_saved[0]:
2016-12-28 16:29:59 +00:00
return user, ''
2016-11-07 08:59:52 +00:00
elif len(public_key_saved) > 1:
if public_key == public_key_saved[1]:
2016-12-28 16:29:59 +00:00
return user, ''
2016-12-29 11:17:00 +00:00
return None, _('Password or SSH public key invalid')
2016-09-24 16:11:31 +00:00
2018-01-12 07:43:26 +00:00
def refresh_token(token, user, expiration=settings.TOKEN_EXPIRATION or 3600):
cache.set(token, user.id, expiration)
2016-12-29 11:17:00 +00:00
def generate_token(request, user):
2018-01-12 07:43:26 +00:00
expiration = settings.TOKEN_EXPIRATION or 3600
remote_addr = request.META.get('REMOTE_ADDR', '')
2017-05-15 15:39:54 +00:00
if not isinstance(remote_addr, bytes):
remote_addr = remote_addr.encode("utf-8")
2018-04-18 04:48:07 +00:00
remote_addr = base64.b16encode(remote_addr) # .replace(b'=', '')
2016-12-29 11:17:00 +00:00
token = cache.get('%s_%s' % (user.id, remote_addr))
if not token:
2017-05-15 15:39:54 +00:00
token = uuid.uuid4().hex
2016-12-29 11:17:00 +00:00
cache.set(token, user.id, expiration)
cache.set('%s_%s' % (user.id, remote_addr), token, expiration)
return token
2017-12-04 12:15:47 +00:00
def validate_ip(ip):
try:
ipaddress.ip_address(ip)
return True
except ValueError:
pass
return False
2018-04-18 04:48:07 +00:00
def get_login_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
login_ip = x_forwarded_for[0]
else:
login_ip = request.META.get('REMOTE_ADDR', '')
return login_ip
def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '')
2017-12-04 12:15:47 +00:00
if not (ip and validate_ip(ip)):
2018-01-16 03:08:05 +00:00
ip = ip[:15]
2018-01-16 01:58:33 +00:00
city = "Unknown"
else:
city = get_ip_city(ip)
kwargs.update({'ip': ip, 'city': city})
LoginLog.objects.create(**kwargs)
2017-12-04 12:15:47 +00:00
def get_ip_city(ip, timeout=10):
# Taobao ip api: http://ip.taobao.com//service/getIpInfo.php?ip=8.8.8.8
# Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json
url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=%s&format=json' % ip
try:
r = requests.get(url, timeout=timeout)
2018-03-28 08:47:36 +00:00
except:
2017-12-04 12:15:47 +00:00
r = None
city = 'Unknown'
if r and r.status_code == 200:
try:
data = r.json()
if not isinstance(data, int) and data['ret'] == 1:
city = data['country'] + ' ' + data['city']
except ValueError:
pass
return city
2018-04-18 04:48:07 +00:00
def get_user_or_tmp_user(request):
user = request.user
tmp_user = get_tmp_user_from_cache(request)
if user.is_authenticated:
return user
elif tmp_user:
return tmp_user
else:
raise Http404("Not found this user")
def get_tmp_user_from_cache(request):
if not request.session.session_key:
return None
user = cache.get(request.session.session_key+'user')
2018-04-18 04:48:07 +00:00
return user
def set_tmp_user_to_cache(request, user):
cache.set(request.session.session_key+'user', user, 600)
2018-04-18 04:48:07 +00:00
def redirect_user_first_login_or_index(request, redirect_field_name):
if request.user.is_first_login:
return reverse('users:user-first-login')
return request.POST.get(
redirect_field_name,
request.GET.get(redirect_field_name, reverse('index')))
2018-04-19 03:13:11 +00:00
def generate_otp_uri(request, issuer="Jumpserver"):
user = get_user_or_tmp_user(request)
2018-04-19 03:13:11 +00:00
otp_secret_key = cache.get(request.session.session_key+'otp_key', '')
if not otp_secret_key:
otp_secret_key = base64.b32encode(os.urandom(10)).decode('utf-8')
cache.set(request.session.session_key+'otp_key', otp_secret_key, 600)
2018-04-18 04:48:07 +00:00
totp = pyotp.TOTP(otp_secret_key)
return totp.provisioning_uri(name=user.username, issuer_name=issuer)
def check_otp_code(otp_secret_key, otp_code):
totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code)
def get_password_check_rules():
check_rules = []
min_length = settings.DEFAULT_PASSWORD_MIN_LENGTH
min_name = 'SECURITY_PASSWORD_MIN_LENGTH'
base_filed = SecuritySettingForm.base_fields
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
# 用户还没有设置过密码校验规则
label = base_filed.get(min_name).label
label += ' ' + str(min_length) + _('Bit')
id = 'rule_' + min_name
rules = {'id': id, 'label': label}
check_rules.append(rules)
for setting in password_setting:
if setting.cleaned_value:
id = 'rule_' + setting.name
label = base_filed.get(setting.name).label
if setting.name == min_name:
label += str(setting.cleaned_value) + _('Bit')
min_length = setting.cleaned_value
rules = {'id': id, 'label': label}
check_rules.append(rules)
return check_rules, min_length
def check_password_rules(password):
min_field_name = 'SECURITY_PASSWORD_MIN_LENGTH'
upper_field_name = 'SECURITY_PASSWORD_UPPER_CASE'
lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE'
number_field_name = 'SECURITY_PASSWORD_NUMBER'
special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting = Setting.objects.filter(name=min_field_name).first()
min_length = min_length_setting.value if min_length_setting else settings.DEFAULT_PASSWORD_MIN_LENGTH
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
pattern = r"^.{" + str(min_length) + ",}$"
else:
pattern = r"^"
for setting in password_setting:
if setting.cleaned_value and setting.name == upper_field_name:
pattern += '(?=.*[A-Z])'
elif setting.cleaned_value and setting.name == lower_field_name:
pattern += '(?=.*[a-z])'
elif setting.cleaned_value and setting.name == number_field_name:
pattern += '(?=.*\d)'
elif setting.cleaned_value and setting.name == special_field_name:
pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?])'
pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?]'
match_obj = re.match(pattern, password)
return bool(match_obj)
def set_user_login_failed_count_to_cache(key_limit):
count = cache.get(key_limit)
count = count + 1 if count else 1
setting_limit_time = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_TIME'
).first()
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
else settings.DEFAULT_LOGIN_LIMIT_TIME
cache.set(key_limit, count, int(limit_time)*60)
def is_block_login(key_limit):
count = cache.get(key_limit)
setting_limit_count = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_COUNT'
).first()
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count and count >= limit_count:
return True