1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
 
   2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
 
   5 from django.contrib.auth import authenticate
 
   6 from django.contrib.auth.decorators import login_required
 
   7 from django import forms
 
   8 from django.http import HttpResponse
 
   9 from django.http import Http404
 
  10 from django.shortcuts import render
 
  11 from django.views.generic.base import View
 
  12 from oauthlib.common import urlencode, generate_token
 
  13 from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
 
  14 from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
 
  15 from rest_framework.permissions import IsAuthenticated
 
  16 from rest_framework.response import Response
 
  17 from rest_framework.views import APIView
 
  18 from rest_framework.generics import GenericAPIView, RetrieveAPIView, get_object_or_404
 
  19 from catalogue.models import Book
 
  20 from .models import BookUserData, KEY_SIZE, SECRET_SIZE, Token
 
  21 from . import serializers
 
  22 from .request_validator import PistonRequestValidator
 
  23 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
 
  26 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
 
  27     def _create_request(self, *args, **kwargs):
 
  28         r = super(OAuth1RequestTokenEndpoint, self)._create_request(*args, **kwargs)
 
  29         r.redirect_uri = 'oob'
 
  32     def create_request_token(self, request, credentials):
 
  34             'oauth_token': self.token_generator()[:KEY_SIZE],
 
  35             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
 
  37         token.update(credentials)
 
  38         self.request_validator.save_request_token(token, request)
 
  39         return urlencode(token.items())
 
  43 class OAuth1RequestTokenView(View):
 
  45         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
 
  47     def dispatch(self, request):
 
  48         return oauthlib_response(
 
  49             self.endpoint.create_request_token_response(
 
  50                 **oauthlib_request(request)
 
  55 class OAuthAuthenticationForm(forms.Form):
 
  56     oauth_token = forms.CharField(widget=forms.HiddenInput)
 
  57     oauth_callback = forms.CharField(widget=forms.HiddenInput)  # changed from URLField - too strict
 
  58     # removed authorize_access - redundant
 
  61 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
 
  62     def create_verifier(self, request, credentials):
 
  63         verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
 
  65             'oauth_token': verifier['oauth_token'],
 
  70 def oauth_user_auth(request):
 
  71     endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
 
  73     if request.method == "GET":
 
  74         # Why not just get oauth_token here?
 
  75         # This is fairly straightforward, in't?
 
  77             realms, credentials = endpoint.get_realms_and_credentials(
 
  78                 **oauthlib_request(request))
 
  79         except OAuth1Error as e:
 
  80             return HttpResponse(str(e), status=400)
 
  81         callback = request.GET.get('oauth_callback')
 
  83         form = OAuthAuthenticationForm(initial={
 
  84             'oauth_token': credentials['resource_owner_key'],
 
  85             'oauth_callback': callback,
 
  88         return render(request, 'oauth/authorize_token.html', {'form': form})
 
  90     if request.method == "POST":
 
  92             response = oauthlib_response(
 
  93                 endpoint.create_authorization_response(
 
  94                     credentials={"user": request.user},
 
  95                     **oauthlib_request(request)
 
  98         except OAuth1Error as e:
 
  99             return HttpResponse(e.message, status=400)
 
 104 class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
 
 105     def _create_request(self, *args, **kwargs):
 
 106         r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
 
 107         r.verifier = 'x' * 20
 
 110     def create_access_token(self, request, credentials):
 
 111         request.realms = self.request_validator.get_realms(
 
 112             request.resource_owner_key, request)
 
 114             'oauth_token': self.token_generator()[:KEY_SIZE],
 
 115             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
 
 116             'oauth_authorized_realms': ' '.join(request.realms)
 
 118         token.update(credentials)
 
 119         self.request_validator.save_access_token(token, request)
 
 120         return urlencode(token.items())
 
 124 class OAuth1AccessTokenView(View):
 
 126         self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
 
 128     def dispatch(self, request):
 
 129         return oauthlib_response(
 
 130             self.endpoint.create_access_token_response(
 
 131                 **oauthlib_request(request)
 
 136 class LoginView(GenericAPIView):
 
 137     serializer_class = serializers.LoginSerializer
 
 139     def post(self, request):
 
 140         serializer = self.get_serializer(data=request.data)
 
 141         serializer.is_valid(raise_exception=True)
 
 142         d = serializer.validated_data
 
 143         user = authenticate(username=d['username'], password=d['password'])
 
 145             return Response({"detail": "Invalid credentials."})
 
 147         key = generate_token()[:KEY_SIZE]
 
 148         Token.objects.create(
 
 150             token_type=Token.ACCESS,
 
 154         return Response({"access_token": key})
 
 158 class UserView(RetrieveAPIView):
 
 159     permission_classes = [IsAuthenticated]
 
 160     serializer_class = serializers.UserSerializer
 
 162     def get_object(self):
 
 163         return self.request.user
 
 167 class BookUserDataView(RetrieveAPIView):
 
 168     permission_classes = [IsAuthenticated]
 
 169     serializer_class = serializers.BookUserDataSerializer
 
 170     lookup_field = 'book__slug'
 
 171     lookup_url_kwarg = 'slug'
 
 173     def get_queryset(self):
 
 174         return BookUserData.objects.filter(user=self.request.user)
 
 176     def get(self, *args, **kwargs):
 
 178             return super(BookUserDataView, self).get(*args, **kwargs)
 
 180             return Response({"state": "not_started"})
 
 182     def post(self, request, slug, state):
 
 183         if state not in ('reading', 'complete'):
 
 186         book = get_object_or_404(Book, slug=slug)
 
 187         instance = BookUserData.update(book, request.user, state)
 
 188         serializer = self.get_serializer(instance)
 
 189         return Response(serializer.data)
 
 192 class BlogView(APIView):
 
 193     def get(self, request):