[Update] users认证逻辑迁移到authentication中

pull/2461/head
BaiJiangJie 2019-02-27 20:55:28 +08:00
parent 21714cc411
commit 6700dc969f
16 changed files with 263 additions and 239 deletions

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
#
from .auth import *

View File

@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.core.cache import cache
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
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 users.serializers import UserSerializer
from users.models import User, LoginLog
from users.utils import (
check_user_valid, check_otp_code, increase_login_failed_count,
is_block_login, clean_failed_count
)
from users.hands import Asset, SystemUser
logger = get_logger(__name__)
class UserAuthApi(RootOrgViewMixin, APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
def post(self, request):
# limit login
username = request.data.get('username')
ip = request.data.get('remote_addr', None)
ip = ip or get_request_ip(request)
if is_block_login(username, ip):
msg = _("Log in frequently and try again later")
logger.warn(msg + ': ' + username + ':' + ip)
return Response({'msg': msg}, status=401)
user, msg = self.check_user_valid(request)
if not user:
username = request.data.get('username', '')
exist = User.objects.filter(username=username).first()
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
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:
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:
self.send_auth_signal(success=True, user=user)
# 登陆成功,清除原来的缓存计数
clean_failed_count(username, ip)
token = user.create_bearer_token(request)
return Response(
{'token': token, 'user': self.serializer_class(user).data}
)
seed = uuid.uuid4().hex
cache.set(seed, user, 300)
return Response(
{
'code': 101,
'msg': _('Please carry seed value and '
'conduct MFA secondary certification'),
'otp_url': reverse('api-auth:user-otp-auth'),
'seed': seed,
'user': self.serializer_class(user).data
}, status=300
)
@staticmethod
def check_user_valid(request):
username = request.data.get('username', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, password=password,
public_key=public_key
)
return user, msg
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):
permission_classes = (IsOrgAdminOrAppUser,)
def post(self, request):
user_id = request.data.get('user', '')
asset_id = request.data.get('asset', '')
system_user_id = request.data.get('system_user', '')
token = str(uuid.uuid4())
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
value = {
'user': user_id,
'username': user.username,
'asset': asset_id,
'hostname': asset.hostname,
'system_user': system_user_id,
'system_user_name': system_user.name
}
cache.set(token, value, timeout=20)
return Response({"token": token}, status=201)
def get(self, request):
token = request.query_params.get('token')
user_only = request.query_params.get('user-only', None)
value = cache.get(token, None)
if not value:
return Response('', status=404)
if not user_only:
return Response(value)
else:
return Response({'user': value['user']})
def get_permissions(self):
if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,)
return super().get_permissions()
class UserToken(APIView):
permission_classes = (AllowAny,)
def post(self, request):
if not request.user.is_authenticated:
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, email=email,
password=password, public_key=public_key)
else:
user = request.user
msg = None
if user:
token = user.create_bearer_token(request)
return Response({'Token': token, 'Keyword': 'Bearer'}, status=200)
else:
return Response({'error': msg}, status=406)
class UserOtpAuthApi(RootOrgViewMixin, APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
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):
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
return Response({'msg': _('MFA certification failed')}, status=401)
self.send_auth_signal(success=True, user=user)
token = user.create_bearer_token(request)
data = {'token': token, 'user': self.serializer_class(user).data}
return Response(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
)

View File

@ -14,7 +14,7 @@ from rest_framework import authentication, exceptions
from rest_framework.authentication import CSRFCheck
from common.utils import get_object_or_none, make_signature, http_to_unixtime
from .models import User, AccessKey, PrivateToken
from users.models import User, AccessKey, PrivateToken
def get_request_date_header(request):

View File

@ -1 +1,19 @@
# coding:utf-8
#
from __future__ import absolute_import
from django.urls import path
from .. import api
app_name = 'authentication'
urlpatterns = [
# path('token/', api.UserToken.as_view(), name='user-token'),
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
path('connection-token/',
api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
]

View File

@ -2,6 +2,7 @@
#
from django.urls import path
from .. import views
app_name = 'authentication'
@ -9,6 +10,11 @@ app_name = 'authentication'
urlpatterns = [
# openid
path('openid/login/', views.OpenIDLoginView.as_view(), name='openid-login'),
path('openid/login/complete/', views.OpenIDLoginCompleteView.as_view(),
name='openid-login-complete'),
path('openid/login/complete/',
views.OpenIDLoginCompleteView.as_view(), name='openid-login-complete'),
# login
path('login/', views.UserLoginView.as_view(), name='login'),
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
path('logout/', views.UserLogoutView.as_view(), name='logout'),
]

View File

@ -2,3 +2,4 @@
#
from .openid import *
from .login import *

View File

@ -1,15 +1,11 @@
# ~*~ 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.http import HttpResponse
from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
@ -19,23 +15,20 @@ 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 common.utils import 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
from users.models import User, LoginLog
from users.utils import (
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
set_tmp_user_to_cache, increase_login_failed_count,
redirect_user_first_login_or_index,
)
__all__ = [
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordView', 'UserResetPasswordSuccessView',
'UserFirstLoginView', 'LoginLogListView'
]
@ -122,7 +115,7 @@ class UserLoginView(FormView):
if user.otp_enabled and user.otp_secret_key:
# 1,2,mfa_setting & T
return reverse('users:login-otp')
return reverse('authentication:login-otp')
elif user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
@ -169,7 +162,9 @@ class UserLoginOtpView(FormView):
success=False, username=user.username,
reason=LoginLog.REASON_MFA
)
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
form.add_error(
'otp_code', _('MFA code invalid, or ntp sync server time')
)
return super().form_invalid(form)
def get_success_url(self):
@ -202,7 +197,7 @@ class UserLogoutView(TemplateView):
'title': _('Logout success'),
'messages': _('Logout success, return login page'),
'interval': 1,
'redirect_url': reverse('users:login'),
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)

View File

@ -135,7 +135,7 @@ TEMPLATES = [
# WSGI_APPLICATION = 'jumpserver.wsgi.applications'
LOGIN_REDIRECT_URL = reverse_lazy('index')
LOGIN_URL = reverse_lazy('users:login')
LOGIN_URL = reverse_lazy('authentication:login')
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN
CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN
@ -343,10 +343,10 @@ REST_FRAMEWORK = {
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'users.authentication.AccessKeyAuthentication',
'users.authentication.AccessTokenAuthentication',
'users.authentication.PrivateTokenAuthentication',
'users.authentication.SessionAuthentication',
'authentication.authentication.AccessKeyAuthentication',
'authentication.authentication.AccessTokenAuthentication',
'authentication.authentication.PrivateTokenAuthentication',
'authentication.authentication.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',

View File

@ -21,6 +21,7 @@ api_v1_patterns = [
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
]))
]

View File

@ -94,10 +94,10 @@
<li><a id="switch_user"><i class="fa fa-exchange"></i><span> {% trans 'User page' %}</span></a></li>
{% endif %}
{% endif %}
<li><a href="{% url 'users:logout' %}"><i class="fa fa-sign-out"></i> {% trans 'Logout' %}</a></li>
<li><a href="{% url 'authentication:logout' %}"><i class="fa fa-sign-out"></i> {% trans 'Logout' %}</a></li>
</ul>
{% else %}
<a href="{% url 'users:login' %}">
<a href="{% url 'authentication:login' %}">
<i class="fa fa-sign-in"></i>{% trans 'Login' %}
</a>
{% endif %}

View File

@ -1,198 +1,3 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.core.cache import cache
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
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 ..models import User, LoginLog
from ..utils import check_user_valid, check_otp_code, \
increase_login_failed_count, is_block_login, \
clean_failed_count
from ..hands import Asset, SystemUser
logger = get_logger(__name__)
class UserAuthApi(RootOrgViewMixin, APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
def post(self, request):
# limit login
username = request.data.get('username')
ip = request.data.get('remote_addr', None)
ip = ip or get_request_ip(request)
if is_block_login(username, ip):
msg = _("Log in frequently and try again later")
logger.warn(msg + ': ' + username + ':' + ip)
return Response({'msg': msg}, status=401)
user, msg = self.check_user_valid(request)
if not user:
username = request.data.get('username', '')
exist = User.objects.filter(username=username).first()
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
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:
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:
self.send_auth_signal(success=True, user=user)
# 登陆成功,清除原来的缓存计数
clean_failed_count(username, ip)
token = user.create_bearer_token(request)
return Response(
{'token': token, 'user': self.serializer_class(user).data}
)
seed = uuid.uuid4().hex
cache.set(seed, user, 300)
return Response(
{
'code': 101,
'msg': _('Please carry seed value and '
'conduct MFA secondary certification'),
'otp_url': reverse('api-users:user-otp-auth'),
'seed': seed,
'user': self.serializer_class(user).data
}, status=300
)
@staticmethod
def check_user_valid(request):
username = request.data.get('username', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, password=password,
public_key=public_key
)
return user, msg
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):
permission_classes = (IsOrgAdminOrAppUser,)
def post(self, request):
user_id = request.data.get('user', '')
asset_id = request.data.get('asset', '')
system_user_id = request.data.get('system_user', '')
token = str(uuid.uuid4())
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
value = {
'user': user_id,
'username': user.username,
'asset': asset_id,
'hostname': asset.hostname,
'system_user': system_user_id,
'system_user_name': system_user.name
}
cache.set(token, value, timeout=20)
return Response({"token": token}, status=201)
def get(self, request):
token = request.query_params.get('token')
user_only = request.query_params.get('user-only', None)
value = cache.get(token, None)
if not value:
return Response('', status=404)
if not user_only:
return Response(value)
else:
return Response({'user': value['user']})
def get_permissions(self):
if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,)
return super().get_permissions()
class UserToken(APIView):
permission_classes = (AllowAny,)
def post(self, request):
if not request.user.is_authenticated:
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, email=email,
password=password, public_key=public_key)
else:
user = request.user
msg = None
if user:
token = user.create_bearer_token(request)
return Response({'Token': token, 'Keyword': 'Bearer'}, status=200)
else:
return Response({'error': msg}, status=406)
class UserOtpAuthApi(RootOrgViewMixin, APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
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):
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
return Response({'msg': _('MFA certification failed')}, status=401)
self.send_auth_signal(success=True, user=user)
token = user.create_bearer_token(request)
data = {'token': token, 'user': self.serializer_class(user).data}
return Response(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
)

View File

@ -15,11 +15,7 @@ router.register(r'groups', api.UserGroupViewSet, 'user-group')
urlpatterns = [
# path('token/', api.UserToken.as_view(), name='user-token'),
path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
path('users/<uuid:pk>/password/', api.UserChangePasswordApi.as_view(), name='change-user-password'),

View File

@ -9,8 +9,6 @@ app_name = 'users'
urlpatterns = [
# Login view
path('login/', views.UserLoginView.as_view(), name='login'),
path('logout/', views.UserLogoutView.as_view(), name='logout'),
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'),
path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'),
path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'),
@ -50,5 +48,5 @@ urlpatterns = [
# Login log
# Abandon
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
# path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
]

View File

@ -62,7 +62,7 @@ def send_user_created_mail(user):
'rest_password_token': user.generate_reset_token(),
'forget_password_url': reverse('users:forgot-password', external=True),
'email': user.email,
'login_url': reverse('users:login', external=True),
'login_url': reverse('authentication:login', external=True),
}
if settings.DEBUG:
try:
@ -98,7 +98,7 @@ def send_reset_password_mail(user):
'rest_password_token': user.generate_reset_token(),
'forget_password_url': reverse('users:forgot-password', external=True),
'email': user.email,
'login_url': reverse('users:login', external=True),
'login_url': reverse('authentication:login', external=True),
}
if settings.DEBUG:
logger.debug(message)
@ -136,7 +136,7 @@ def send_password_expiration_reminder_mail(user):
'update_password_url': reverse('users:user-password-update', external=True),
'forget_password_url': reverse('users:forgot-password', external=True),
'email': user.email,
'login_url': reverse('users:login', external=True),
'login_url': reverse('authentication:login', external=True),
}
if settings.DEBUG:
logger.debug(message)
@ -158,7 +158,7 @@ def send_reset_ssh_key_mail(user):
</br>
""") % {
'name': user.name,
'login_url': reverse('users:login', external=True),
'login_url': reverse('authentication:login', external=True),
}
if settings.DEBUG:
logger.debug(message)

View File

@ -24,7 +24,7 @@ from .. import forms
__all__ = [
'UserLoginView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView',
'UserResetPasswordView', 'UserForgotPasswordView',
'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView',
]
@ -58,7 +58,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView):
'title': _('Send reset password message'),
'messages': _('Send reset password mail success, '
'login your mail box and follow it '),
'redirect_url': reverse('users:login'),
'redirect_url': reverse('authentication:login'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@ -71,7 +71,7 @@ class UserResetPasswordSuccessView(TemplateView):
context = {
'title': _('Reset password success'),
'messages': _('Reset password success, return to login page'),
'redirect_url': reverse('users:login'),
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)

View File

@ -574,7 +574,7 @@ class UserOtpSettingsSuccessView(TemplateView):
'title': title,
'messages': describe,
'interval': 1,
'redirect_url': reverse('users:login'),
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)