Updates and fixes.
[wolnelektury.git] / src / api / views.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from django.contrib.auth.decorators import login_required
6 from django import forms
7 from django.http import HttpResponse
8 from django.http import Http404
9 from django.shortcuts import render
10 from django.views.generic.base import View
11 from oauthlib.common import urlencode
12 from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
13 from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
14 from api.models import KEY_SIZE, SECRET_SIZE
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 ListAPIView, RetrieveAPIView, get_object_or_404
19 from catalogue.models import Book
20 from .models import BookUserData
21 from . import serializers
22 from .request_validator import PistonRequestValidator
23 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
24
25
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'
30         return r
31
32     def create_request_token(self, request, credentials):
33         token = {
34             'oauth_token': self.token_generator()[:KEY_SIZE],
35             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
36         }
37         token.update(credentials)
38         self.request_validator.save_request_token(token, request)
39         return urlencode(token.items())
40
41
42 # Never Cache
43 class OAuth1RequestTokenView(View):
44     def __init__(self):
45         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
46
47     def dispatch(self, request):
48         return oauthlib_response(
49             self.endpoint.create_request_token_response(
50                 **oauthlib_request(request)
51             )
52         )
53
54
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
59
60
61 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
62     def create_verifier(self, request, credentials):
63         verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
64         return {
65             'oauth_token': verifier['oauth_token'],
66         }
67
68
69 @login_required
70 def oauth_user_auth(request):
71     endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
72
73     if request.method == "GET":
74         # Why not just get oauth_token here?
75         # This is fairly straightforward, in't?
76         try:
77             realms, credentials = endpoint.get_realms_and_credentials(
78                 **oauthlib_request(request))
79         except OAuth1Error as e:
80             return HttpResponse(e.message, status=400)
81         callback = request.GET.get('oauth_callback')
82
83         form = OAuthAuthenticationForm(initial={
84             'oauth_token': credentials['resource_owner_key'],
85             'oauth_callback': callback,
86         })
87
88         return render(request, 'oauth/authorize_token.html', {'form': form})
89
90     elif request.method == "POST":
91         try:
92             response = oauthlib_response(
93                 endpoint.create_authorization_response(
94                     credentials={"user": request.user},
95                     **oauthlib_request(request)
96                 )
97             )
98         except OAuth1Error as e:
99             return HttpResponse(e.message, status=400)
100         else:
101             return response
102
103
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
108         return r
109
110     def create_access_token(self, request, credentials):
111         request.realms = self.request_validator.get_realms(
112             request.resource_owner_key, request)
113         token = {
114             'oauth_token': self.token_generator()[:KEY_SIZE],
115             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
116             'oauth_authorized_realms': ' '.join(request.realms)
117         }
118         token.update(credentials)
119         self.request_validator.save_access_token(token, request)
120         return urlencode(token.items())
121
122
123 # Never cache
124 class OAuth1AccessTokenView(View):
125     def __init__(self):
126         self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
127
128     def dispatch(self, request):
129         return oauthlib_response(
130             self.endpoint.create_access_token_response(
131                 **oauthlib_request(request)
132             )
133         )
134
135
136 @vary_on_auth
137 class UserView(RetrieveAPIView):
138     permission_classes = [IsAuthenticated]
139     serializer_class = serializers.UserSerializer
140
141     def get_object(self):
142         return self.request.user
143
144
145 @vary_on_auth
146 class BookUserDataView(RetrieveAPIView):
147     permission_classes = [IsAuthenticated]
148     serializer_class = serializers.BookUserDataSerializer
149     lookup_field = 'book__slug'
150     lookup_url_kwarg = 'slug'
151
152     def get_queryset(self):
153         return BookUserData.objects.filter(user=self.request.user)
154
155     def get(self, *args, **kwargs):
156         try:
157             return super(BookUserDataView, self).get(*args, **kwargs)
158         except Http404:
159             return Response({"state": "not_started"})
160
161     def post(self, request, slug, state):
162         if state not in ('reading', 'complete'):
163             raise Http404
164
165         book = get_object_or_404(Book, slug=slug)
166         instance = BookUserData.update(book, request.user, state)
167         serializer = self.get_serializer(instance)
168         return Response(serializer.data)
169
170
171 class BlogView(APIView):
172     def get(self, request):
173         return Response([])