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