Fix #17 - Implemented stateless, oauth2 like authentication.
parent
274c8fe647
commit
de95fb2a91
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base64
|
||||
import re
|
||||
|
||||
from django.core import signing
|
||||
from django.db.models import get_model
|
||||
from rest_framework.authentication import BaseAuthentication
|
||||
|
||||
import greenmine.base.exceptions as exc
|
||||
|
||||
|
||||
class Session(BaseAuthentication):
|
||||
"""
|
||||
Same as rest_framework.authentication.SessionAuthentication
|
||||
but without csrf.
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
"""
|
||||
Returns a `User` if the request session currently has a logged in user.
|
||||
Otherwise returns `None`.
|
||||
"""
|
||||
|
||||
http_request = request._request
|
||||
user = getattr(http_request, 'user', None)
|
||||
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
return (user, None)
|
||||
|
||||
|
||||
def get_token_for_user(user):
|
||||
data = {"user_id": user.id}
|
||||
return signing.dumps(data)
|
||||
|
||||
|
||||
def get_user_for_token(token):
|
||||
data = signing.loads(token)
|
||||
model_cls = get_model("users", "User")
|
||||
try:
|
||||
user = model_cls.objects.get(pk=data["user_id"])
|
||||
except model_cls.DoesNotExist:
|
||||
raise exc.BadRequest("Invalid token")
|
||||
else:
|
||||
return user
|
||||
|
||||
|
||||
class Token(BaseAuthentication):
|
||||
"""
|
||||
Stateless authentication system partially based on oauth.
|
||||
"""
|
||||
|
||||
auth_rx = re.compile(r"^Bearer (.+)$")
|
||||
|
||||
def authenticate(self, request):
|
||||
if "HTTP_AUTHORIZATION" not in request.META:
|
||||
return None
|
||||
|
||||
token_rx_match = self.auth_rx.search(request.META["HTTP_AUTHORIZATION"])
|
||||
if not token_rx_match:
|
||||
return None
|
||||
|
||||
token = token_rx_match.group(1)
|
||||
user = get_user_for_token(token)
|
||||
return (user, token)
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return 'Bearer realm="api"'
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db.models.loading import get_model
|
||||
from django.contrib.auth import logout, login, authenticate
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status, viewsets
|
||||
|
||||
from greenmine.base import exceptions as exc
|
||||
from greenmine.base import auth
|
||||
|
||||
from greenmine.base.users.models import User, Role
|
||||
from greenmine.base.users.serializers import UserSerializer
|
||||
|
||||
|
||||
class AuthViewSet(viewsets.ViewSet):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def create(self, request, **kwargs):
|
||||
username = request.DATA.get('username', None)
|
||||
password = request.DATA.get('password', None)
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
raise exc.BadRequest("Invalid username or password")
|
||||
|
||||
if not user.check_password(password):
|
||||
raise exc.BadRequest("Invalid username or password")
|
||||
|
||||
serializer = UserSerializer(user)
|
||||
response_data = serializer.data
|
||||
response_data["auth_token"] = auth.get_token_for_user(user)
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django import test
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from greenmine import urls
|
||||
from greenmine.base import auth
|
||||
from greenmine.base.users.tests import create_user
|
||||
|
||||
|
||||
class TestAuthView(APIView):
|
||||
authentication_classes = (auth.Token,)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return Response("ok")
|
||||
|
||||
|
||||
urls.urlpatterns += patterns("",
|
||||
url(r'^test-api/v1/auth/', TestAuthView.as_view(), name="test-token-auth"),
|
||||
)
|
||||
|
||||
|
||||
class SimpleTokenAuthTests(test.TestCase):
|
||||
def setUp(self):
|
||||
self.user1 = create_user(1)
|
||||
|
||||
def test_token_auth_01(self):
|
||||
response = self.client.get(reverse("test-token-auth"))
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_token_auth_02(self):
|
||||
token = auth.get_token_for_user(self.user1)
|
||||
response = self.client.get(reverse("test-token-auth"),
|
||||
HTTP_AUTHORIZATION="Bearer {}".format(token))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'"ok"')
|
||||
|
|
@ -24,7 +24,7 @@ COORS_ALLOWED_METHODS = getattr(settings, 'COORS_ALLOWED_METHODS',
|
|||
['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE', 'PATCH'])
|
||||
COORS_ALLOWED_HEADERS = getattr(settings, 'COORS_ALLOWED_HEADERS',
|
||||
['Content-Type', 'X-Requested-With',
|
||||
'X-Session-Token', 'Accept-Encoding',
|
||||
'Authorization', 'Accept-Encoding',
|
||||
'X-Disable-Pagination'])
|
||||
COORS_ALLOWED_CREDENTIALS = getattr(settings, 'COORS_ALLOWED_CREDENTIALS', True)
|
||||
|
||||
|
@ -43,7 +43,6 @@ class CoorsMiddleware(object):
|
|||
response = http.HttpResponse()
|
||||
self._populate_response(response)
|
||||
return response
|
||||
|
||||
return None
|
||||
|
||||
def process_response(self, request, response):
|
||||
|
|
|
@ -128,31 +128,3 @@ class UsersViewSet(ModelCrudViewSet):
|
|||
request.user.set_password(password)
|
||||
request.user.save(update_fields=["password"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class AuthViewSet(viewsets.ViewSet):
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def create(self, request, **kwargs):
|
||||
username = request.DATA.get('username', None)
|
||||
password = request.DATA.get('password', None)
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
raise exc.BadRequest("Invalid username or password")
|
||||
|
||||
if not user.check_password(password):
|
||||
raise exc.BadRequest("Invalid username or password")
|
||||
|
||||
user = authenticate(username=username, password=password)
|
||||
login(request, user)
|
||||
|
||||
serializer = UserSerializer(user)
|
||||
response_data = serializer.data
|
||||
response_data["auth_token"] = request.session.session_key
|
||||
return Response(response_data)
|
||||
|
||||
def destroy(self, request, pk=None):
|
||||
logout(request)
|
||||
return Response({})
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from rest_framework.authentication import BaseAuthentication
|
||||
|
||||
|
||||
class SessionAuthentication(BaseAuthentication):
|
||||
"""
|
||||
Same as rest_framework.authentication.SessionAuthentication
|
||||
but without csrf.
|
||||
"""
|
||||
|
||||
def authenticate(self, request):
|
||||
"""
|
||||
Returns a `User` if the request session currently has a logged in user.
|
||||
Otherwise returns `None`.
|
||||
"""
|
||||
|
||||
http_request = request._request
|
||||
user = getattr(http_request, 'user', None)
|
||||
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
return (user, None)
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from greenmine.base import routers
|
||||
from greenmine.base.users.api import AuthViewSet, RolesViewSet, UsersViewSet
|
||||
from greenmine.base.auth.api import AuthViewSet
|
||||
from greenmine.base.users.api import RolesViewSet, UsersViewSet
|
||||
from greenmine.base.searches.api import SearchViewSet
|
||||
from greenmine.projects.api import ProjectViewSet, MembershipViewSet
|
||||
from greenmine.projects.milestones.api import MilestoneViewSet
|
||||
|
|
|
@ -139,12 +139,17 @@ TEMPLATE_LOADERS = [
|
|||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
# Common middlewares
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'greenmine.base.middleware.GreenmineSessionMiddleware',
|
||||
'greenmine.base.middleware.CoorsMiddleware',
|
||||
|
||||
# Only needed by django admin
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
|
||||
# 'greenmine.base.middleware.GreenmineSessionMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
]
|
||||
|
||||
|
@ -270,7 +275,8 @@ MAX_SEARCH_RESULTS = 100
|
|||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'greenmine.base.users.auth.SessionAuthentication',
|
||||
'greenmine.base.auth.Token',
|
||||
'greenmine.base.auth.Session',
|
||||
),
|
||||
'FILTER_BACKEND': 'greenmine.base.filters.FilterBackend',
|
||||
'PAGINATE_BY': 50,
|
||||
|
|
Loading…
Reference in New Issue