Remove piston residue.
[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 migdal.models import Entry
20 from catalogue.models import Book
21 from .models import BookUserData
22 from . import serializers
23 from .request_validator import PistonRequestValidator
24 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
25
26
27 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
28     def _create_request(self, *args, **kwargs):
29         r = super(OAuth1RequestTokenEndpoint, self)._create_request(*args, **kwargs)
30         r.redirect_uri = 'oob'
31         return r
32
33     def create_request_token(self, request, credentials):
34         token = {
35             'oauth_token': self.token_generator()[:KEY_SIZE],
36             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
37         }
38         token.update(credentials)
39         self.request_validator.save_request_token(token, request)
40         return urlencode(token.items())
41
42
43 # Never Cache
44 class OAuth1RequestTokenView(View):
45     def __init__(self):
46         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
47
48     def dispatch(self, request):
49         return oauthlib_response(
50             self.endpoint.create_request_token_response(
51                 **oauthlib_request(request)
52             )
53         )
54
55
56 class OAuthAuthenticationForm(forms.Form):
57     oauth_token = forms.CharField(widget=forms.HiddenInput)
58     oauth_callback = forms.CharField(widget=forms.HiddenInput)  # changed from URLField - too strict
59     # removed authorize_access - redundant
60
61
62 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
63     def create_verifier(self, request, credentials):
64         verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
65         return {
66             'oauth_token': verifier['oauth_token'],
67         }
68
69
70 @login_required
71 def oauth_user_auth(request):
72     endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
73
74     if request.method == "GET":
75         # Why not just get oauth_token here?
76         # This is fairly straightforward, in't?
77         try:
78             realms, credentials = endpoint.get_realms_and_credentials(
79                 **oauthlib_request(request))
80         except OAuth1Error as e:
81             return HttpResponse(e.message, status=400)
82         callback = request.GET.get('oauth_callback')
83
84         form = OAuthAuthenticationForm(initial={
85             'oauth_token': credentials['resource_owner_key'],
86             'oauth_callback': callback,
87         })
88
89         return render(request, 'oauth/authorize_token.html', {'form': form})
90
91     elif request.method == "POST":
92         try:
93             response = oauthlib_response(
94                 endpoint.create_authorization_response(
95                     credentials={"user": request.user},
96                     **oauthlib_request(request)
97                 )
98             )
99         except OAuth1Error as e:
100             return HttpResponse(e.message, status=400)
101         else:
102             return response
103
104
105 class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
106     def _create_request(self, *args, **kwargs):
107         r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
108         r.verifier = 'x' * 20
109         return r
110
111     def create_access_token(self, request, credentials):
112         request.realms = self.request_validator.get_realms(
113             request.resource_owner_key, request)
114         token = {
115             'oauth_token': self.token_generator()[:KEY_SIZE],
116             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
117             'oauth_authorized_realms': ' '.join(request.realms)
118         }
119         token.update(credentials)
120         self.request_validator.save_access_token(token, request)
121         return urlencode(token.items())
122
123
124 # Never cache
125 class OAuth1AccessTokenView(View):
126     def __init__(self):
127         self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
128
129     def dispatch(self, request):
130         return oauthlib_response(
131             self.endpoint.create_access_token_response(
132                 **oauthlib_request(request)
133             )
134         )
135
136
137 @vary_on_auth
138 class UserView(RetrieveAPIView):
139     permission_classes = [IsAuthenticated]
140     serializer_class = serializers.UserSerializer
141
142     def get_object(self):
143         return self.request.user
144
145
146 @vary_on_auth
147 class BookUserDataView(RetrieveAPIView):
148     permission_classes = [IsAuthenticated]
149     serializer_class = serializers.BookUserDataSerializer
150     lookup_field = 'book__slug'
151     lookup_url_kwarg = 'slug'
152
153     def get_queryset(self):
154         return BookUserData.objects.filter(user=self.request.user)
155
156     def get(self, *args, **kwargs):
157         try:
158             return super(BookUserDataView, self).get(*args, **kwargs)
159         except Http404:
160             return Response({"state": "not_started"})
161
162     def post(self, request, slug, state):
163         if state not in ('reading', 'complete'):
164             raise Http404
165
166         book = get_object_or_404(Book, slug=slug)
167         instance = BookUserData.update(book, request.user, state)
168         serializer = self.get_serializer(instance)
169         return Response(serializer.data)
170
171
172 class BlogView(ListAPIView):
173     serializer_class = serializers.BlogSerializer
174
175     def get_queryset(self):
176         after = self.request.query_params.get('after')
177         count = int(self.request.query_params.get('count', 20))
178         entries = Entry.published_objects.filter(in_stream=True).order_by('-first_published_at')
179         if after:
180             entries = entries.filter(first_published_at__lt=after)
181         if count:
182             entries = entries[:count]
183         return entries