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