Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/swagger auth #1141

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,7 @@ pytest-xdist==1.31.0
cssselect==1.1.0

# PostgreSQL database adapter for the Python
psycopg2-binary==2.8.5
psycopg2-binary==2.8.5

# Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
drf-yasg==1.20.0
4 changes: 2 additions & 2 deletions src/attendee/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

from core.authentication import TokenAuthentication
from core.authentication import BearerAuthentication
from attendee.models import Attendee


class AttendeeAPIView(views.APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

model = Attendee
Expand Down
45 changes: 43 additions & 2 deletions src/core/authentication.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
from rest_framework.authentication import TokenAuthentication

from rest_framework import exceptions
from rest_framework import HTTP_HEADER_ENCODING, exceptions
from django.utils.translation import gettext_lazy as _
from .models import Token


class TokenAuthentication(TokenAuthentication):
class BearerAuthentication(TokenAuthentication):
keyword = 'Bearer'
model = Token
def get_model(self):
if self.model is not None:
return self.model
from rest_framework.authtoken.models import Token
return Token

def get_authorization_header(self,request):
"""
Return request's 'Authorization:' header, as a bytestring.
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, str):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth

def authenticate(self, request):
auth = self.get_authorization_header(request).split()

if not auth :
return None

token = auth[0].decode()

return self.authenticate_credentials(token)

def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))

if not token.user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

return (token.user, token)
4 changes: 2 additions & 2 deletions src/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ class Token(models.Model):
"""
key = models.CharField(_("Key"), max_length=40, primary_key=True)
user = BigForeignKey(
to=settings.AUTH_USER_MODEL,
related_name='auth_token',
to=settings.AUTH_USER_MODEL,
related_name='%(app_label)s_auth_token',
verbose_name=_('user'),
on_delete=models.CASCADE,
)
Expand Down
12 changes: 6 additions & 6 deletions src/events/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.http import Http404
from django.utils.timezone import make_naive

from core.authentication import TokenAuthentication
from core.authentication import BearerAuthentication
from events.models import (
CustomEvent, Location, ProposedTalkEvent,
ProposedTutorialEvent, SponsoredEvent, Time, KeynoteEvent
Expand Down Expand Up @@ -54,7 +54,7 @@ def get_queryset(self):


class SpeechListAPIView(APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request, *args, **kwargs):
Expand All @@ -81,7 +81,7 @@ def get(self, request, *args, **kwargs):


class SpeechListByCategoryAPIView(APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request, *args, **kwargs):
Expand Down Expand Up @@ -110,7 +110,7 @@ class TutorialDetailAPIView(RetrieveAPIView):


class SpeechDetailAPIView(APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request, *args, **kwargs):
Expand Down Expand Up @@ -263,7 +263,7 @@ def display(self):


class ScheduleAPIView(APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

event_querysets = [
Expand Down Expand Up @@ -344,7 +344,7 @@ def get(self, request):


class KeynoteEventListAPIView(ListAPIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

queryset = KeynoteEvent.objects.all()
Expand Down
36 changes: 35 additions & 1 deletion src/pycontw2016/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@
'sorl.thumbnail',
'registry',
'corsheaders',
'rest_framework'
'rest_framework',
'rest_framework.authtoken',
'drf_yasg',
)

LOCAL_APPS = (
Expand All @@ -117,6 +119,38 @@
'attendee'
)

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'core.authentication.BearerAuthentication',
],
'DEFAULT_PERMISSION_CLASSES':{
'rest_framework.permissions.IsAuthenticated'

}

}

SWAGGER_SETTINGS = {
'SHOW_REQUEST_HEADERS': True,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header',
},
},
'USE_SESSION_AUTH': True,
'JSON_EDITOR': True,
'SUPPORTED_SUBMIT_METHODS': [
'get',
'post',
'put',
'delete',
'patch'
],
}

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

# Enable Postgres-specific things if we are using it.
Expand Down
25 changes: 24 additions & 1 deletion src/pycontw2016/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@
from django.conf.urls.static import static
from django.contrib import admin
from django.views.i18n import set_language
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

from core.views import error_page, flat_page, index
from users.views import user_dashboard


schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)


urlpatterns = i18n_patterns(

# Add top-level URL patterns here.
Expand All @@ -35,7 +52,8 @@
url(r'^api/events/', include('events.api.urls', namespace="events")),
url(r'^set-language/$', set_language, name='set_language'),
url(r'^admin/', admin.site.urls),
url(r'^api/attendee/', include('attendee.api.urls'))
url(r'^api/attendee/', include('attendee.api.urls')),
url(r'^api/users/', include('users.api.urls')),
]

# User-uploaded files like profile pics need to be served in development.
Expand All @@ -45,3 +63,8 @@
if settings.DEBUG:
import debug_toolbar
urlpatterns += [url(r'^__debug__/', include(debug_toolbar.urls))]
urlpatterns += [
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
6 changes: 3 additions & 3 deletions src/sponsors/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

from core.authentication import TokenAuthentication
from core.authentication import BearerAuthentication
from sponsors.models import Sponsor, OpenRole


class SponsorAPIView(views.APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request):
Expand Down Expand Up @@ -42,7 +42,7 @@ def get(self, request):


class JobAPIView(views.APIView):
authentication_classes = [TokenAuthentication]
authentication_classes = [BearerAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request):
Expand Down
Empty file added src/users/api/__init__.py
Empty file.
Empty file added src/users/api/serializers.py
Empty file.
8 changes: 8 additions & 0 deletions src/users/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import path
from users.api.views import CustomAuthToken


urlpatterns = [
path("api-token-auth/", CustomAuthToken.as_view()),

]
54 changes: 54 additions & 0 deletions src/users/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from rest_framework.response import Response
from rest_framework.authtoken.views import ObtainAuthToken

from core.models import Token
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework import exceptions
from django.contrib.auth import get_user_model
from users.models import User
from datetime import datetime, timedelta


class CustomAuthToken(ObtainAuthToken):
@swagger_auto_schema(
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['username','password'],
order=['username', 'password'],
properties={
'username':openapi.Schema(type=openapi.TYPE_STRING),
'password':openapi.Schema(type=openapi.TYPE_STRING)
},
),
operation_description='Get account token'
)

josix marked this conversation as resolved.
Show resolved Hide resolved
def post(self, request):
username = request.data['username']
try:
user = get_user_model().objects.get(email=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed(('User matching query does not exist'))

tokens = Token.objects.filter(user=user)
if len(tokens)== 0:
Token.objects.create(user=user)

token = Token.objects.get(user=user)
token = str(token)

token_create_time = Token.objects.get(key=token).created
pre_week_day = datetime.now(token_create_time.tzinfo) + timedelta(days=-7)
if token_create_time < pre_week_day:
Token.objects.get(key=token).delete()
Token.objects.create(user=user)

serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']

return Response({
'username': user.email,
'token': token
})