registration
[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.contrib.auth.models import User
8 from django import forms
9 from django.http import HttpResponse
10 from django.http import Http404
11 from django.shortcuts import render
12 from django.views.generic.base import View
13 from oauthlib.common import urlencode, generate_token
14 from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
15 from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
16 from rest_framework.permissions import IsAuthenticated
17 from rest_framework.response import Response
18 from rest_framework.views import APIView
19 from rest_framework.generics import GenericAPIView, RetrieveAPIView, get_object_or_404
20 from catalogue.models import Book
21 from .models import BookUserData, KEY_SIZE, SECRET_SIZE, Token
22 from social.models import UserConfirmation
23 from . import serializers
24 from .request_validator import PistonRequestValidator
25 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
26
27
28 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
29     def _create_request(self, *args, **kwargs):
30         r = super(OAuth1RequestTokenEndpoint, self)._create_request(*args, **kwargs)
31         r.redirect_uri = 'oob'
32         return r
33
34     def create_request_token(self, request, credentials):
35         token = {
36             'oauth_token': self.token_generator()[:KEY_SIZE],
37             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
38         }
39         token.update(credentials)
40         self.request_validator.save_request_token(token, request)
41         return urlencode(token.items())
42
43
44 # Never Cache
45 class OAuth1RequestTokenView(View):
46     def __init__(self):
47         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
48
49     def dispatch(self, request):
50         return oauthlib_response(
51             self.endpoint.create_request_token_response(
52                 **oauthlib_request(request)
53             )
54         )
55
56
57 class OAuthAuthenticationForm(forms.Form):
58     oauth_token = forms.CharField(widget=forms.HiddenInput)
59     oauth_callback = forms.CharField(widget=forms.HiddenInput)  # changed from URLField - too strict
60     # removed authorize_access - redundant
61
62
63 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
64     def create_verifier(self, request, credentials):
65         verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
66         return {
67             'oauth_token': verifier['oauth_token'],
68         }
69
70
71 @login_required
72 def oauth_user_auth(request):
73     endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
74
75     if request.method == "GET":
76         # Why not just get oauth_token here?
77         # This is fairly straightforward, in't?
78         try:
79             realms, credentials = endpoint.get_realms_and_credentials(
80                 **oauthlib_request(request))
81         except OAuth1Error as e:
82             return HttpResponse(str(e), status=400)
83         callback = request.GET.get('oauth_callback')
84
85         form = OAuthAuthenticationForm(initial={
86             'oauth_token': credentials['resource_owner_key'],
87             'oauth_callback': callback,
88         })
89
90         return render(request, 'oauth/authorize_token.html', {'form': form})
91
92     if request.method == "POST":
93         try:
94             response = oauthlib_response(
95                 endpoint.create_authorization_response(
96                     credentials={"user": request.user},
97                     **oauthlib_request(request)
98                 )
99             )
100         except OAuth1Error as e:
101             return HttpResponse(e.message, status=400)
102         else:
103             return response
104
105
106 class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
107     def _create_request(self, *args, **kwargs):
108         r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
109         r.verifier = 'x' * 20
110         return r
111
112     def create_access_token(self, request, credentials):
113         request.realms = self.request_validator.get_realms(
114             request.resource_owner_key, request)
115         token = {
116             'oauth_token': self.token_generator()[:KEY_SIZE],
117             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
118             'oauth_authorized_realms': ' '.join(request.realms)
119         }
120         token.update(credentials)
121         self.request_validator.save_access_token(token, request)
122         return urlencode(token.items())
123
124
125 # Never cache
126 class OAuth1AccessTokenView(View):
127     def __init__(self):
128         self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
129
130     def dispatch(self, request):
131         return oauthlib_response(
132             self.endpoint.create_access_token_response(
133                 **oauthlib_request(request)
134             )
135         )
136
137
138 class LoginView(GenericAPIView):
139     serializer_class = serializers.LoginSerializer
140
141     def post(self, request):
142         serializer = self.get_serializer(data=request.data)
143         serializer.is_valid(raise_exception=True)
144         d = serializer.validated_data
145         user = authenticate(username=d['username'], password=d['password'])
146         if user is None:
147             return Response({"detail": "Invalid credentials."})
148
149         key = generate_token()[:KEY_SIZE]
150         Token.objects.create(
151             key=key,
152             token_type=Token.ACCESS,
153             timestamp=time(),
154             user=user,
155         )
156         return Response({"access_token": key})
157
158
159 class Login2View(GenericAPIView):
160     serializer_class = serializers.LoginSerializer
161
162     def post(self, request):
163         serializer = self.get_serializer(data=request.data)
164         serializer.is_valid(raise_exception=True)
165         d = serializer.validated_data
166         user = authenticate(username=d['username'], password=d['password'])
167         if user is None:
168             return Response({"detail": "Invalid credentials."})
169
170         access_token = generate_token()[:KEY_SIZE]
171         Token.objects.create(
172             key=access_token,
173             token_type=Token.ACCESS,
174             timestamp=time(),
175             user=user,
176         )
177         refresh_token = generate_token()[:KEY_SIZE]
178         Token.objects.create(
179             key=refresh_token,
180             token_type=Token.REFRESH,
181             timestamp=time(),
182             user=user,
183         )
184         return Response({
185             "access_token": access_token,
186             "refresh_token": refresh_token,
187             "expires": 3600,
188         })
189
190
191 @vary_on_auth
192 class UserView(RetrieveAPIView):
193     permission_classes = [IsAuthenticated]
194     serializer_class = serializers.UserSerializer
195
196     def get_object(self):
197         return self.request.user
198
199
200 @vary_on_auth
201 class BookUserDataView(RetrieveAPIView):
202     permission_classes = [IsAuthenticated]
203     serializer_class = serializers.BookUserDataSerializer
204     lookup_field = 'book__slug'
205     lookup_url_kwarg = 'slug'
206
207     def get_queryset(self):
208         return BookUserData.objects.filter(user=self.request.user)
209
210     def get(self, *args, **kwargs):
211         try:
212             return super(BookUserDataView, self).get(*args, **kwargs)
213         except Http404:
214             return Response({"state": "not_started"})
215
216     def post(self, request, slug, state):
217         if state not in ('reading', 'complete'):
218             raise Http404
219
220         book = get_object_or_404(Book, slug=slug)
221         instance = BookUserData.update(book, request.user, state)
222         serializer = self.get_serializer(instance)
223         return Response(serializer.data)
224
225
226 class BlogView(APIView):
227     def get(self, request):
228         return Response([])
229
230
231
232 class RegisterView(GenericAPIView):
233     serializer_class = serializers.RegisterSerializer
234
235     def get(self, request):
236         return Response({
237             "options": [
238                 {
239                     "id": 1,
240                     "html": "Chcę otrzymywać newsletter Wolnych Lektur",
241                     "required": False
242                 }
243             ],
244             "info": [
245                 '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>.'
246             ]            
247         })
248
249     def post(self, request):
250         serializer = self.get_serializer(data=request.data)
251         serializer.is_valid(raise_exception=True)
252         d = serializer.validated_data
253
254         user = User(
255             username=d['email'],
256             email=d['email'],
257             is_active=False
258         )
259         user.set_password(d['password'])
260
261         try:
262             user.save()
263         except:
264             return Response(
265                 {
266                     "detail": "Nie można utworzyć konta.",
267                 },
268                 status=400
269             )
270
271         UserConfirmation.request(user)
272         return Response({})
273
274
275 class RefreshTokenView(APIView):
276     serializer_class = serializers.RefreshTokenSerializer
277
278     def post(self, request):
279         serializer = self.get_serializer(data=request.data)
280         serializer.is_valid(raise_exception=True)
281         d = serializer.validated_data
282         
283         t = Token.objects.get(
284             key=d['refresh_token'],
285             token_type=Token.REFRESH
286         )
287         user = t.user
288
289         access_token = generate_token()[:KEY_SIZE]
290         Token.objects.create(
291             key=access_token,
292             token_type=Token.ACCESS,
293             timestamp=time(),
294             user=user,
295         )
296         refresh_token = generate_token()[:KEY_SIZE]
297         Token.objects.create(
298             key=refresh_token,
299             token_type=Token.REFRESH,
300             timestamp=time(),
301             user=user,
302         )
303         return Response({
304             "access_token": access_token,
305             "refresh_token": refresh_token,
306             "expires": 3600,
307         })
308
309
310 class RequestConfirmView(APIView):
311     serializer_class = serializers.RequestConfirmSerializer
312
313     def post(self, request):
314         serializer = self.get_serializer(data=request.data)
315         serializer.is_valid(raise_exception=True)
316         d = serializer.validated_data
317
318         try:
319             user = User.objects.get(
320                 username=d['email'],
321                 is_active=False
322             )
323         except User.DoesNotExist:
324             raise Http404
325
326         UserConfirmation.request(user)
327         return Response({})
328