Commit 21714cc4 authored by ibuler's avatar ibuler

[Update] Stash

parent 1a247d60
......@@ -2,3 +2,5 @@ from django.dispatch import Signal
post_create_openid_user = Signal(providing_args=('user',))
post_auth_success = Signal(providing_args=('user', 'request'))
post_auth_failed = Signal(providing_args=('username', 'request', 'reason'))
......@@ -2,9 +2,15 @@ from django.http.request import QueryDict
from django.conf import settings
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_out
from django.utils import timezone
from django_auth_ldap.backend import populate_user
from common.utils import get_request_ip
from .openid import client
from .signals import post_create_openid_user
from .tasks import write_login_log_async
from .signals import (
post_create_openid_user, post_auth_success, post_auth_failed
)
@receiver(user_logged_out)
......@@ -38,3 +44,36 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user and user.name != 'admin':
user.source = user.SOURCE_LDAP
user.save()
def generate_data(username, request):
if not request.user.is_anonymous and request.user.is_app:
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
else:
login_ip = get_request_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '')
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)
# -*- coding: utf-8 -*-
#
from celery import shared_task
from .utils import write_login_log
@shared_task
def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs)
......@@ -2,15 +2,13 @@
#
from django.urls import path
from authentication.openid import views
from .. import views
app_name = 'authentication'
urlpatterns = [
# openid
path('openid/login/', views.LoginView.as_view(), name='openid-login'),
path('openid/login/complete/', views.LoginCompleteView.as_view(),
path('openid/login/', views.OpenIDLoginView.as_view(), name='openid-login'),
path('openid/login/complete/', views.OpenIDLoginCompleteView.as_view(),
name='openid-login-complete'),
# other
]
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from common.utils import get_ip_city, validate_ip
def write_login_log(*args, **kwargs):
from users.models import LoginLog
default_city = _("Unknown")
ip = kwargs.get('ip', '')
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})
LoginLog.objects.create(**kwargs)
# -*- coding: utf-8 -*-
#
from .openid import *
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os
from django.core.cache import cache
from django.shortcuts import render
from django.utils import timezone
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from django.core.files.storage import default_storage
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django.conf import settings
from formtools.wizard.views import SessionWizardView
from common.utils import get_object_or_none, get_request_ip
from authentication.signals import post_auth_success, post_auth_failed
from users.models import User, LoginLog
from users.utils import send_reset_password_mail, check_otp_code, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
is_block_login, increase_login_failed_count, clean_failed_count
from users import forms
__all__ = [
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordView', 'UserResetPasswordSuccessView',
'UserFirstLoginView', 'LoginLogListView'
]
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(never_cache, name='dispatch')
class UserLoginView(FormView):
form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next'
key_prefix_captcha = "_LOGIN_INVALID_{}"
def get_template_names(self):
template_name = 'users/login.html'
if not settings.XPACK_ENABLED:
return template_name
from xpack.plugins.license.models import License
if not License.has_valid_license():
return template_name
template_name = 'users/new_login.html'
return template_name
def get(self, request, *args, **kwargs):
if request.user.is_staff:
return redirect(redirect_user_first_login_or_index(
request, self.redirect_field_name)
)
request.session.set_test_cookie()
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):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
user = form.get_user()
# user password expired
if user.password_has_expired:
reason = LoginLog.REASON_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))
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)
return redirect(self.get_success_url())
def form_invalid(self, form):
# 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
# limit user login failed count
ip = get_request_ip(self.request)
increase_login_failed_count(username, ip)
# show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
self.send_auth_signal(success=False, username=username, reason=reason)
old_form = form
form = self.form_class_captcha(data=form.data)
form._errors = old_form.errors
return super().form_invalid(form)
def get_form_class(self):
ip = get_request_ip(self.request)
if cache.get(self.key_prefix_captcha.format(ip)):
return self.form_class_captcha
else:
return self.form_class
def get_success_url(self):
user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key:
# 1,2,mfa_setting & T
return reverse('users:login-otp')
elif user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled:
# 0 & T,F
auth_login(self.request, user)
self.send_auth_signal(success=True, user=user)
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs):
context = {
'demo_mode': os.environ.get("DEMO_MODE"),
'AUTH_OPENID': settings.AUTH_OPENID,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
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 UserLoginOtpView(FormView):
template_name = 'users/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):
auth_login(self.request, user)
self.send_auth_signal(success=True, user=user)
return redirect(self.get_success_url())
else:
self.send_auth_signal(
success=False, username=user.username,
reason=LoginLog.REASON_MFA
)
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form)
def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
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
)
@method_decorator(never_cache, name='dispatch')
class UserLogoutView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
auth_logout(request)
next_uri = request.COOKIES.get("next")
if next_uri:
return redirect(next_uri)
response = super().get(request, *args, **kwargs)
return response
def get_context_data(self, **kwargs):
context = {
'title': _('Logout success'),
'messages': _('Logout success, return login page'),
'interval': 1,
'redirect_url': reverse('users:login'),
'auto_redirect': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -14,43 +14,36 @@ from django.http.response import (
HttpResponseRedirect
)
from . import client
from .models import Nonce
from users.models import LoginLog
from users.tasks import write_login_log_async
from common.utils import get_request_ip
from ..openid import client
from ..openid.models import Nonce
from ..signals import post_auth_success
logger = logging.getLogger(__name__)
def get_base_site_url():
return settings.BASE_SITE_URL
__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
class LoginView(RedirectView):
class OpenIDLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
redirect_uri = settings.BASE_SITE_URL + \
reverse("authentication:openid-login-complete")
nonce = Nonce(
redirect_uri=get_base_site_url() + reverse(
"authentication:openid-login-complete"),
redirect_uri=redirect_uri,
next_path=self.request.GET.get('next')
)
cache.set(str(nonce.state), nonce, 24*3600)
self.request.session['openid_state'] = str(nonce.state)
authorization_url = client.openid_connect_client.\
authorization_url(
redirect_uri=nonce.redirect_uri, scope='code',
state=str(nonce.state)
)
return authorization_url
class LoginCompleteView(RedirectView):
class OpenIDLoginCompleteView(RedirectView):
def get(self, request, *args, **kwargs):
if 'error' in request.GET:
......@@ -79,24 +72,6 @@ class LoginCompleteView(RedirectView):
return HttpResponseBadRequest()
login(self.request, user)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
return HttpResponseRedirect(nonce.next_path or '/')
def write_login_log(self, data):
login_ip = get_request_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
# -*- coding: utf-8 -*-
#
from .common import *
from .django import *
from .encode import *
from .http import *
from .ipip import *
# -*- coding: utf-8 -*-
#
import re
import sys
from collections import OrderedDict
from six import string_types
import base64
import os
from itertools import chain
import logging
import datetime
import time
import hashlib
from email.utils import formatdate
import calendar
import threading
from io import StringIO
import uuid
from functools import wraps
import copy
import paramiko
import sshpubkeys
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
BadSignature, SignatureExpired
from django.shortcuts import reverse as dj_reverse
from django.conf import settings
from django.utils import timezone
import ipaddress
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
def reverse(view_name, urlconf=None, args=None, kwargs=None,
current_app=None, external=False):
url = dj_reverse(view_name, urlconf=urlconf, args=args,
kwargs=kwargs, current_app=current_app)
if external:
site_url = settings.SITE_URL
url = site_url.strip('/') + url
return url
def get_object_or_none(model, **kwargs):
try:
obj = model.objects.get(**kwargs)
except model.DoesNotExist:
return None
return obj
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
else:
return cls.__instance
class Signer(metaclass=Singleton):
"""用来加密,解密,和基于时间戳的方式验证token"""
def __init__(self, secret_key=None):
self.secret_key = secret_key
def sign(self, value):
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
return s.dumps(value).decode()
def unsign(self, value):
if value is None:
return value
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
try:
return s.loads(value)
except BadSignature:
return {}
def sign_t(self, value, expires_in=3600):
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
return str(s.dumps(value), encoding="utf8")
def unsign_t(self, value):
s = TimedJSONWebSignatureSerializer(self.secret_key)
try:
return s.loads(value)
except (BadSignature, SignatureExpired):
return {}
def date_expired_default():
try:
years = int(settings.DEFAULT_EXPIRED_YEARS)
except TypeError:
years = 70
return timezone.now() + timezone.timedelta(days=365*years)
ipip_db = None
def combine_seq(s1, s2, callback=None):
......@@ -146,88 +60,6 @@ def timesince(dt, since='', default="just now"):
return default
def ssh_key_string_to_obj(text, password=None):
key = None
try:
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
try:
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
return key
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
if isinstance(private_key, bytes):
private_key = private_key.decode("utf-8")
if isinstance(private_key, string_types):
private_key = ssh_key_string_to_obj(private_key, password=password)
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
raise IOError('Invalid private key')
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
'key_type': private_key.get_name(),
'key_content': private_key.get_base64(),
'username': username,
'hostname': hostname,
}
return public_key
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
"""Generate user ssh private and public key
Use paramiko RSAKey generate it.
:return private key str and public key str
"""
if hostname is None:
hostname = os.uname()[1]
f = StringIO()
try:
if type == 'rsa':
private_key_obj = paramiko.RSAKey.generate(length)
elif type == 'dsa':
private_key_obj = paramiko.DSSKey.generate(length)
else:
raise IOError('SSH private key must be `rsa` or `dsa`')
private_key_obj.write_private_key(f, password=password)
private_key = f.getvalue()
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
return private_key, public_key
except IOError:
raise IOError('These is error when generate ssh key.')
def validate_ssh_private_key(text, password=None):
if isinstance(text, bytes):
try:
text = text.decode("utf-8")
except UnicodeDecodeError:
return False
key = ssh_key_string_to_obj(text, password=password)
if key is None:
return False
else:
return True
def validate_ssh_public_key(text):
ssh = sshpubkeys.SSHKey(text)
try:
ssh.parse()
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
return False
except NotImplementedError as e:
return False
return True
def setattr_bulk(seq, key, value):
def set_attr(obj):
setattr(obj, key, value)
......@@ -243,70 +75,6 @@ def set_or_append_attr_bulk(seq, key, value):
setattr(obj, key, value)
def content_md5(data):
"""计算data的MD5值,经过Base64编码并返回str类型。
返回值可以直接作为HTTP Content-Type头部的值
"""
if isinstance(data, str):
data = hashlib.md5(data.encode('utf-8'))
value = base64.b64encode(data.hexdigest().encode('utf-8'))
return value.decode('utf-8')
_STRPTIME_LOCK = threading.Lock()
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
def to_unixtime(time_string, format_string):
time_string = time_string.decode("ascii")
with _STRPTIME_LOCK:
return int(calendar.timegm(time.strptime(time_string, format_string)))
def http_date(timeval=None):
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
但不能使用strftime,因为strftime的结果是和locale相关的。
"""
return formatdate(timeval, usegmt=True)
def http_to_unixtime(time_string):
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
"""
return to_unixtime(time_string, _GMT_FORMAT)
def iso8601_to_unixtime(time_string):
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
return to_unixtime(time_string, _ISO8601_FORMAT)
def make_signature(access_key_secret, date=None):
if isinstance(date, bytes):
date = bytes.decode(date)
if isinstance(date, int):
date_gmt = http_date(date)
elif date is None:
date_gmt = http_date(int(time.time()))
else:
date_gmt = date
data = str(access_key_secret) + "\n" + date_gmt
return content_md5(data)
def encrypt_password(password, salt=None):
from passlib.hash import sha512_crypt
if password:
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
return None
def capacity_convert(size, expect='auto', rate=1000):
"""
:param size: '100MB', '1G'
......@@ -374,11 +142,6 @@ def is_uuid(seq):
return True
def get_signer():
signer = Signer(settings.SECRET_KEY)
return signer
def get_request_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
......@@ -388,22 +151,13 @@ def get_request_ip(request):
return login_ip
def get_command_storage_setting():
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
value = settings.TERMINAL_COMMAND_STORAGE
if not value:
return default
value.update(default)
return value
def get_replay_storage_setting():
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
value = settings.TERMINAL_REPLAY_STORAGE
if not value:
return default
value.update(default)
return value
def validate_ip(ip):
try:
ipaddress.ip_address(ip)
return True
except ValueError:
pass
return False
def with_cache(func):
......@@ -537,4 +291,4 @@ class LocalProxy(object):
__rmod__ = lambda x, o: o % x._get_current_object()
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
__copy__ = lambda x: copy.copy(x._get_current_object())
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
\ No newline at end of file
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
# -*- coding: utf-8 -*-
#
import re
from django.shortcuts import reverse as dj_reverse
from django.conf import settings
from django.utils import timezone
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
def reverse(view_name, urlconf=None, args=None, kwargs=None,
current_app=None, external=False):
url = dj_reverse(view_name, urlconf=urlconf, args=args,
kwargs=kwargs, current_app=current_app)
if external:
site_url = settings.SITE_URL
url = site_url.strip('/') + url
return url
def get_object_or_none(model, **kwargs):
try:
obj = model.objects.get(**kwargs)
except model.DoesNotExist:
return None
return obj
def date_expired_default():
try:
years = int(settings.DEFAULT_EXPIRED_YEARS)
except TypeError:
years = 70
return timezone.now() + timezone.timedelta(days=365*years)
def get_command_storage_setting():
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
value = settings.TERMINAL_COMMAND_STORAGE
if not value:
return default
value.update(default)
return value
def get_replay_storage_setting():
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
value = settings.TERMINAL_REPLAY_STORAGE
if not value:
return default
value.update(default)
return value
# -*- coding: utf-8 -*-
#
import re
from six import string_types
import base64
import os
import time
import hashlib
from io import StringIO
import paramiko
import sshpubkeys
from itsdangerous import (
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
BadSignature, SignatureExpired
)
from django.conf import settings
from .http import http_date
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
else:
return cls.__instance
class Signer(metaclass=Singleton):
"""用来加密,解密,和基于时间戳的方式验证token"""
def __init__(self, secret_key=None):
self.secret_key = secret_key
def sign(self, value):
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
return s.dumps(value).decode()
def unsign(self, value):
if value is None:
return value
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
try:
return s.loads(value)
except BadSignature:
return {}
def sign_t(self, value, expires_in=3600):
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
return str(s.dumps(value), encoding="utf8")
def unsign_t(self, value):
s = TimedJSONWebSignatureSerializer(self.secret_key)
try:
return s.loads(value)
except (BadSignature, SignatureExpired):
return {}
def ssh_key_string_to_obj(text, password=None):
key = None
try:
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
try:
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException:
pass
return key
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
if isinstance(private_key, bytes):
private_key = private_key.decode("utf-8")
if isinstance(private_key, string_types):
private_key = ssh_key_string_to_obj(private_key, password=password)
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
raise IOError('Invalid private key')
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
'key_type': private_key.get_name(),
'key_content': private_key.get_base64(),
'username': username,
'hostname': hostname,
}
return public_key
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
"""Generate user ssh private and public key
Use paramiko RSAKey generate it.
:return private key str and public key str
"""
if hostname is None:
hostname = os.uname()[1]
f = StringIO()
try:
if type == 'rsa':
private_key_obj = paramiko.RSAKey.generate(length)
elif type == 'dsa':
private_key_obj = paramiko.DSSKey.generate(length)
else:
raise IOError('SSH private key must be `rsa` or `dsa`')
private_key_obj.write_private_key(f, password=password)
private_key = f.getvalue()
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
return private_key, public_key
except IOError:
raise IOError('These is error when generate ssh key.')
def validate_ssh_private_key(text, password=None):
if isinstance(text, bytes):
try:
text = text.decode("utf-8")
except UnicodeDecodeError:
return False
key = ssh_key_string_to_obj(text, password=password)
if key is None:
return False
else:
return True
def validate_ssh_public_key(text):
ssh = sshpubkeys.SSHKey(text)
try:
ssh.parse()
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
return False
except NotImplementedError as e:
return False
return True
def content_md5(data):
"""计算data的MD5值,经过Base64编码并返回str类型。
返回值可以直接作为HTTP Content-Type头部的值
"""
if isinstance(data, str):
data = hashlib.md5(data.encode('utf-8'))
value = base64.b64encode(data.hexdigest().encode('utf-8'))
return value.decode('utf-8')
def make_signature(access_key_secret, date=None):
if isinstance(date, bytes):
date = bytes.decode(date)
if isinstance(date, int):
date_gmt = http_date(date)
elif date is None:
date_gmt = http_date(int(time.time()))
else:
date_gmt = date
data = str(access_key_secret) + "\n" + date_gmt
return content_md5(data)
def encrypt_password(password, salt=None):
from passlib.hash import sha512_crypt
if password:
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
return None
def get_signer():
signer = Signer(settings.SECRET_KEY)
return signer
# -*- coding: utf-8 -*-
#
import time
from email.utils import formatdate
import calendar
import threading
_STRPTIME_LOCK = threading.Lock()
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
def to_unixtime(time_string, format_string):
time_string = time_string.decode("ascii")
with _STRPTIME_LOCK:
return int(calendar.timegm(time.strptime(time_string, format_string)))
def http_date(timeval=None):
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
但不能使用strftime,因为strftime的结果是和locale相关的。
"""
return formatdate(timeval, usegmt=True)
def http_to_unixtime(time_string):
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
"""
return to_unixtime(time_string, _GMT_FORMAT)
def iso8601_to_unixtime(time_string):
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
return to_unixtime(time_string, _ISO8601_FORMAT)
# -*- coding: utf-8 -*-
#
from .ipdb import *
# -*- coding: utf-8 -*-
#
import os
import ipdb
ipip_db = None
def get_ip_city(ip):
global ipip_db
if ipip_db is None:
ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb')
ipip_db = ipdb.City(ipip_db_path)
info = list(set(ipip_db.find(ip, 'CN')))
if '' in info:
info.remove('')
return ' '.join(info)
\ No newline at end of file
......@@ -14,8 +14,8 @@ from rest_framework.views import APIView
from common.utils import get_logger, get_request_ip
from common.permissions import IsOrgAdminOrAppUser
from orgs.mixins import RootOrgViewMixin
from authentication.signals import post_auth_success, post_auth_failed
from ..serializers import UserSerializer
from ..tasks import write_login_log_async
from ..models import User, LoginLog
from ..utils import check_user_valid, check_otp_code, \
increase_login_failed_count, is_block_login, \
......@@ -46,37 +46,22 @@ class UserAuthApi(RootOrgViewMixin, APIView):
username = request.data.get('username', '')
exist = User.objects.filter(username=username).first()
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
'reason': reason,
'status': False
}
self.write_login_log(request, data)
self.send_auth_signal(success=False, username=username, reason=reason)
increase_login_failed_count(username, ip)
return Response({'msg': msg}, status=401)
if user.password_has_expired:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
'status': False
}
self.write_login_log(request, data)
self.send_auth_signal(
success=False, username=username,
reason=LoginLog.REASON_PASSWORD_EXPIRED
)
msg = _("The user {} password has expired, please update.".format(
user.username))
logger.info(msg)
return Response({'msg': msg}, status=401)
if not user.otp_enabled:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
self.send_auth_signal(success=True, user=user)
# 登陆成功,清除原来的缓存计数
clean_failed_count(username, ip)
token = user.create_bearer_token(request)
......@@ -108,22 +93,14 @@ class UserAuthApi(RootOrgViewMixin, APIView):
)
return user, msg
@staticmethod
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip:
login_ip = get_request_ip(request)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent,
}
data.update(tmp_data)
write_login_log_async.delay(**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 UserConnectionTokenApi(RootOrgViewMixin, APIView):
......@@ -197,52 +174,25 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
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):
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(request, data)
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
return Response({'msg': _('MFA certification failed')}, status=401)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
self.send_auth_signal(success=True, user=user)
token = user.create_bearer_token(request)
return Response(
{
'token': token,
'user': self.serializer_class(user).data
}
)
data = {'token': token, 'user': self.serializer_class(user).data}
return Response(data)
@staticmethod
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip:
login_ip = get_request_ip(request)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**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
)
......@@ -4,6 +4,7 @@
import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from rest_framework.authtoken.models import Token
from .user import User
......@@ -82,7 +83,7 @@ class LoginLog(models.Model):
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
class Meta:
ordering = ['-datetime', 'username']
......@@ -2,4 +2,3 @@ from django.dispatch import Signal
post_user_create = Signal(providing_args=('user',))
......@@ -7,17 +7,12 @@ from ops.celery.utils import create_or_update_celery_periodic_tasks
from ops.celery.decorator import after_app_ready_start
from .models import User
from common.utils import get_logger
from .utils import write_login_log, send_password_expiration_reminder_mail
from .utils import send_password_expiration_reminder_mail
logger = get_logger(__file__)
@shared_task
def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs)
@shared_task
def check_password_expired():
users = User.objects.exclude(role=User.ROLE_APP)
......
......@@ -7,7 +7,6 @@ import pyotp
import base64
import logging
import requests
import ipaddress
from django.http import Http404
from django.conf import settings
......@@ -18,7 +17,7 @@ from django.core.cache import cache
from datetime import datetime
from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none
from common.utils import reverse, get_object_or_none, get_ip_city
from .models import User, LoginLog
......@@ -199,51 +198,6 @@ def check_user_valid(**kwargs):
return None, _('Password or SSH public key invalid')
def validate_ip(ip):
try:
ipaddress.ip_address(ip)
return True
except ValueError:
pass
return False
def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '')
if not (ip and validate_ip(ip)):
ip = ip[:15]
city = "Unknown"
else:
city = get_ip_city(ip)
kwargs.update({'ip': ip, 'city': city})
LoginLog.objects.create(**kwargs)
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://ip.taobao.com/service/getIpInfo.php?ip=%s' % ip
try:
r = requests.get(url, timeout=timeout)
except:
r = None
city = 'Unknown'
if r and r.status_code == 200:
try:
data = r.json()
if not isinstance(data, int) and data['code'] == 0:
country = data['data']['country']
_city = data['data']['city']
if country == 'XX':
city = _city
else:
city = ' '.join([country, _city])
except ValueError:
pass
return city
def get_user_or_tmp_user(request):
user = request.user
tmp_user = get_tmp_user_from_cache(request)
......
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
import os
from django.core.cache import cache
from django.shortcuts import render
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from django.views.generic import RedirectView
from django.core.files.storage import default_storage
from django.http import HttpResponseRedirect, HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django.conf import settings
from django.urls import reverse_lazy
from formtools.wizard.views import SessionWizardView
from common.utils import get_object_or_none, get_request_ip
from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
is_block_login, increase_login_failed_count, clean_failed_count
from ..tasks import write_login_log_async
from common.utils import get_object_or_none
from ..models import User
from ..utils import (
send_reset_password_mail, get_password_check_rules, check_password_rules
)
from .. import forms
__all__ = [
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordView', 'UserResetPasswordSuccessView',
'UserFirstLoginView', 'LoginLogListView'
'UserLoginView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView',
'UserResetPasswordView', 'UserForgotPasswordView',
]
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(never_cache, name='dispatch')
class UserLoginView(FormView):
form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next'
key_prefix_captcha = "_LOGIN_INVALID_{}"
def get_template_names(self):
template_name = 'users/login.html'
if not settings.XPACK_ENABLED:
return template_name
from xpack.plugins.license.models import License
if not License.has_valid_license():
return template_name
template_name = 'users/new_login.html'
return template_name
def get(self, request, *args, **kwargs):
if request.user.is_staff:
return redirect(redirect_user_first_login_or_index(
request, self.redirect_field_name)
)
request.session.set_test_cookie()
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):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
user = form.get_user()
# user password expired
if user.password_has_expired:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
'status': False
}
self.write_login_log(data)
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)
return redirect(self.get_success_url())
def form_invalid(self, form):
# 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
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
'reason': reason,
'status': False
}
self.write_login_log(data)
# limit user login failed count
ip = get_request_ip(self.request)
increase_login_failed_count(username, ip)
# show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
old_form = form
form = self.form_class_captcha(data=form.data)
form._errors = old_form.errors
return super().form_invalid(form)
def get_form_class(self):
ip = get_request_ip(self.request)
if cache.get(self.key_prefix_captcha.format(ip)):
return self.form_class_captcha
else:
return self.form_class
def get_success_url(self):
user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key:
# 1,2,mfa_setting & T
return reverse('users:login-otp')
elif user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled:
# 0 & T,F
auth_login(self.request, user)
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs):
context = {
'demo_mode': os.environ.get("DEMO_MODE"),
'AUTH_OPENID': settings.AUTH_OPENID,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def write_login_log(self, data):
login_ip = get_request_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserLoginOtpView(FormView):
template_name = 'users/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):
auth_login(self.request, user)
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect(self.get_success_url())
else:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(data)
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form)
def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def write_login_log(self, data):
login_ip = get_request_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
@method_decorator(never_cache, name='dispatch')
class UserLogoutView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
auth_logout(request)
next_uri = request.COOKIES.get("next")
if next_uri:
return redirect(next_uri)
response = super().get(request, *args, **kwargs)
return response
def get_context_data(self, **kwargs):
context = {
'title': _('Logout success'),
'messages': _('Logout success, return login page'),
'interval': 1,
'redirect_url': reverse('users:login'),
'auto_redirect': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserLoginView(RedirectView):
urls = reverse_lazy('authentication:login')
class UserForgotPasswordView(TemplateView):
......@@ -386,8 +177,3 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
form.fields["otp_level"].initial = self.request.user.otp_level
return form
class LoginLogListView(ListView):
def get(self, request, *args, **kwargs):
return redirect(reverse('audits:login-log-list'))
......@@ -17,7 +17,7 @@ decorator==4.1.2
Django==2.1.7
django-auth-ldap==1.7.0
django-bootstrap3==9.1.0
django-celery-beat==1.1.1
django-celery-beat==1.4.0
django-filter==2.0.0
django-formtools==2.1
django-ranged-response==0.2.0
......@@ -79,3 +79,4 @@ rest_condition==1.0.3
python-ldap==3.1.0
tencentcloud-sdk-python==3.0.40
django-radius==1.3.3
ipip-ipdb==1.2.1
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