new login api
[wolnelektury.git] / src / api / views.py
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.
3 #
4 from time import time
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
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(str(e), 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     if 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 class LoginView(GenericAPIView):
137     serializer_class = serializers.LoginSerializer
138
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'])
144         if user is None:
145             return Response({"detail": "Invalid credentials."})
146
147         key = generate_token()[:KEY_SIZE]
148         Token.objects.create(
149             key=key,
150             token_type=Token.ACCESS,
151             timestamp=time(),
152             user=user,
153         )
154         return Response({"access_token": key})
155
156
157 class Login2View(GenericAPIView):
158     serializer_class = serializers.LoginSerializer
159
160     def post(self, request):
161         serializer = self.get_serializer(data=request.data)
162         serializer.is_valid(raise_exception=True)
163         d = serializer.validated_data
164         user = authenticate(username=d['username'], password=d['password'])
165         if user is None:
166             return Response({"detail": "Invalid credentials."})
167
168         access_token = generate_token()[:KEY_SIZE]
169         Token.objects.create(
170             key=access_token,
171             token_type=Token.ACCESS,
172             timestamp=time(),
173             user=user,
174         )
175         refresh_token = generate_token()[:KEY_SIZE]
176         Token.objects.create(
177             key=refresh_token,
178             token_type=Token.REFRESH,
179             timestamp=time(),
180             user=user,
181         )
182         return Response({
183             "access_token": access_token,
184             "refresh_token": refresh_token,
185             "expires": 3600,
186         })
187
188
189 @vary_on_auth
190 class UserView(RetrieveAPIView):
191     permission_classes = [IsAuthenticated]
192     serializer_class = serializers.UserSerializer
193
194     def get_object(self):
195         return self.request.user
196
197
198 @vary_on_auth
199 class BookUserDataView(RetrieveAPIView):
200     permission_classes = [IsAuthenticated]
201     serializer_class = serializers.BookUserDataSerializer
202     lookup_field = 'book__slug'
203     lookup_url_kwarg = 'slug'
204
205     def get_queryset(self):
206         return BookUserData.objects.filter(user=self.request.user)
207
208     def get(self, *args, **kwargs):
209         try:
210             return super(BookUserDataView, self).get(*args, **kwargs)
211         except Http404:
212             return Response({"state": "not_started"})
213
214     def post(self, request, slug, state):
215         if state not in ('reading', 'complete'):
216             raise Http404
217
218         book = get_object_or_404(Book, slug=slug)
219         instance = BookUserData.update(book, request.user, state)
220         serializer = self.get_serializer(instance)
221         return Response(serializer.data)
222
223
224 class BlogView(APIView):
225     def get(self, request):
226         return Response([])
227
228
229
230 class RegisterView(APIView):
231     serializer_class = serializers.RegisterSerializer
232
233     def get(self, request):
234         return Response({
235             "options": [
236                 {
237                     "id": 1,
238                     "html": "Chcę otrzymywać newsletter Wolnych Lektur",
239                     "required": False
240                 }
241             ],
242             "info": [
243                 'Administratorem danych osobowych jest Fundacja Wolne Lektury (ul. Marszałkowska 84/92 lok. 125, 00-514 Warszawa). Podanie danych osobowych jest dobrowolne. Dane są przetwarzane w zakresie niezbędnym do prowadzenia serwisu, a także w celach prowadzenia statystyk, ewaluacji i sprawozdawczości. W przypadku wyrażenia dodatkowej zgody adres e-mail zostanie wykorzystany także w celu przesyłania newslettera Wolnych Lektur. Osobom, których dane są zbierane, przysługuje prawo dostępu do treści swoich danych oraz ich poprawiania. Więcej informacji w <a href="https://fundacja.wolnelektury.pl/prywatnosc/">polityce prywatności</a>.'
244             ]            
245         })
246
247     def post(self, request):
248         pass
249     
250
251 class RefreshTokenView(APIView):
252     serializer_class = serializers.RefreshTokenSerializer
253
254     def post(self, request):
255         serializer = self.get_serializer(data=request.data)
256         serializer.is_valid(raise_exception=True)
257         d = serializer.validated_data
258         
259         t = Token.objects.get(
260             key=d['refresh_token'],
261             token_type=Token.REFRESH
262         )
263         user = t.user
264
265         access_token = generate_token()[:KEY_SIZE]
266         Token.objects.create(
267             key=access_token,
268             token_type=Token.ACCESS,
269             timestamp=time(),
270             user=user,
271         )
272         refresh_token = generate_token()[:KEY_SIZE]
273         Token.objects.create(
274             key=refresh_token,
275             token_type=Token.REFRESH,
276             timestamp=time(),
277             user=user,
278         )
279         return Response({
280             "access_token": access_token,
281             "refresh_token": refresh_token,
282             "expires": 3600,
283         })
284
285
286 class RequestConfirmView(APIView):
287     pass