mirror of https://github.com/jumpserver/jumpserver
Add auth and permission backends
parent
a62a2178d0
commit
0446f449e9
|
@ -68,7 +68,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'ws4redis',
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -264,12 +263,14 @@ REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
# or allow read-only access for unauthenticated users.
|
# or allow read-only access for unauthenticated users.
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'rest_framework.permissions.IsAdminUser',
|
# 'rest_framework.permissions.IsAuthenticated',
|
||||||
|
'users.backends.IsValidUser',
|
||||||
),
|
),
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
'users.backends.AppSignAuthentication',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
# This setting is required to override the Django's main loop, when running in
|
# This setting is required to override the Django's main loop, when running in
|
||||||
|
|
|
@ -7,11 +7,12 @@ from rest_framework import generics, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||||
|
|
||||||
|
from common.mixins import BulkDeleteApiMixin
|
||||||
|
from common.utils import get_logger
|
||||||
from .models import User, UserGroup
|
from .models import User, UserGroup
|
||||||
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
|
from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
|
||||||
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
|
GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
|
||||||
from common.mixins import BulkDeleteApiMixin
|
from .backends import IsSuperUser, IsAppUser, IsValidUser, IsSuperUserOrAppUser
|
||||||
from common.utils import get_logger
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -84,6 +85,10 @@ class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserBulkUpdateSerializer
|
serializer_class = UserBulkUpdateSerializer
|
||||||
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return super(UserListUpdateApi, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import authentication, exceptions
|
from rest_framework import authentication, exceptions, permissions
|
||||||
|
from rest_framework.compat import is_authenticated
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.utils import unsign
|
from common.utils import unsign, get_object_or_none
|
||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
class APPSignAuthentication(authentication.BaseAuthentication):
|
class AppSignAuthentication(authentication.BaseAuthentication):
|
||||||
keyword = 'Sign'
|
keyword = 'Sign'
|
||||||
model = User
|
model = User
|
||||||
|
|
||||||
|
@ -30,17 +31,50 @@ class APPSignAuthentication(authentication.BaseAuthentication):
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
msg = _('Invalid token header. Sign string should not contain invalid characters.')
|
msg = _('Invalid token header. Sign string should not contain invalid characters.')
|
||||||
raise exceptions.AuthenticationFailed(msg)
|
raise exceptions.AuthenticationFailed(msg)
|
||||||
|
|
||||||
return self.authenticate_credentials(sign)
|
return self.authenticate_credentials(sign)
|
||||||
|
|
||||||
def authenticate_credentials(self, key):
|
def authenticate_credentials(self, sign):
|
||||||
try:
|
app = unsign(sign, max_age=300)
|
||||||
token = self.model.objects.select_related('user').get(key=key)
|
if app:
|
||||||
except self.model.DoesNotExist:
|
user = get_object_or_none(self.model, username=app, role='App')
|
||||||
raise exceptions.AuthenticationFailed(_('Invalid token.'))
|
else:
|
||||||
|
raise exceptions.AuthenticationFailed(_('Invalid sign.'))
|
||||||
|
|
||||||
if not token.user.is_active:
|
if not user.is_active:
|
||||||
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
|
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
|
||||||
|
return user, None
|
||||||
|
|
||||||
|
|
||||||
|
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||||
|
"""Allows access to valid user, is active and not expired"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return super(IsValidUser, self).has_permission(request, view) \
|
||||||
|
and request.user.is_valid
|
||||||
|
|
||||||
|
|
||||||
|
class IsAppUser(IsValidUser, permissions.BasePermission):
|
||||||
|
"""Allows access only to app user """
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return super(IsAppUser, self).has_permission(request, view) \
|
||||||
|
and request.user.is_app_user
|
||||||
|
|
||||||
|
|
||||||
|
class IsSuperUser(IsValidUser, permissions.BasePermission):
|
||||||
|
"""Allows access only to superuser"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return super(IsSuperUser, self).has_permission(request, view) \
|
||||||
|
and request.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
|
class IsSuperUserOrAppUser(IsValidUser, permissions.BasePermission):
|
||||||
|
"""Allows access between superuser and app user"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
|
||||||
|
and (request.user.is_superuser or request.user.is_app_user)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -69,6 +69,7 @@ class User(AbstractUser):
|
||||||
ROLE_CHOICES = (
|
ROLE_CHOICES = (
|
||||||
('Admin', _('Administrator')),
|
('Admin', _('Administrator')),
|
||||||
('User', _('User')),
|
('User', _('User')),
|
||||||
|
('App', _('Application')),
|
||||||
)
|
)
|
||||||
|
|
||||||
username = models.CharField(max_length=20, unique=True, verbose_name=_('Username'))
|
username = models.CharField(max_length=20, unique=True, verbose_name=_('Username'))
|
||||||
|
@ -148,9 +149,18 @@ class User(AbstractUser):
|
||||||
else:
|
else:
|
||||||
self.role = 'User'
|
self.role = 'User'
|
||||||
|
|
||||||
|
is_admin = is_superuser
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_app_user(self):
|
||||||
|
if self.role == 'App':
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_staff(self):
|
def is_staff(self):
|
||||||
if self.is_authenticated and self.is_active and not self.is_expired and self.is_superuser:
|
if self.is_authenticated and self.is_valid:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -185,14 +195,14 @@ class User(AbstractUser):
|
||||||
Token.objects.filter(user=self).delete()
|
Token.objects.filter(user=self).delete()
|
||||||
return Token.objects.create(user=self)
|
return Token.objects.create(user=self)
|
||||||
|
|
||||||
def generate_reset_token(self):
|
|
||||||
return signing.dumps({'reset': self.id, 'email': self.email})
|
|
||||||
|
|
||||||
def is_member_of(self, user_group):
|
def is_member_of(self, user_group):
|
||||||
if user_group in self.groups.all():
|
if user_group in self.groups.all():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def generate_reset_token(self):
|
||||||
|
return signing.dumps({'reset': self.id, 'email': self.email})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_reset_token(cls, token, max_age=3600):
|
def validate_reset_token(cls, token, max_age=3600):
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue