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.
5 from allauth.account.forms import ResetPasswordForm
6 from allauth.account.utils import filter_users_by_email
7 from django.conf import settings
8 from django.contrib.auth import authenticate, login
9 from django.contrib.auth.decorators import login_required
10 from django.contrib.auth.models import User
11 from django import forms
12 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
13 from django.http import Http404
14 from django.shortcuts import redirect, render
15 from django.views.generic.base import View
16 from oauthlib.common import urlencode, generate_token
17 from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
18 from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
19 from rest_framework.permissions import IsAuthenticated
20 from rest_framework.response import Response
21 from rest_framework.views import APIView
22 from rest_framework.generics import GenericAPIView, RetrieveAPIView, get_object_or_404
23 from catalogue.models import Book
24 from .models import BookUserData, KEY_SIZE, SECRET_SIZE, Token, SessionTransferToken
25 from social.models import UserConfirmation
26 from . import serializers
27 from .request_validator import PistonRequestValidator
28 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
31 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
32 def _create_request(self, *args, **kwargs):
33 r = super(OAuth1RequestTokenEndpoint, self)._create_request(*args, **kwargs)
34 r.redirect_uri = 'oob'
37 def create_request_token(self, request, credentials):
39 'oauth_token': self.token_generator()[:KEY_SIZE],
40 'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
42 token.update(credentials)
43 self.request_validator.save_request_token(token, request)
44 return urlencode(token.items())
48 class OAuth1RequestTokenView(View):
50 self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
52 def dispatch(self, request):
53 return oauthlib_response(
54 self.endpoint.create_request_token_response(
55 **oauthlib_request(request)
60 class OAuthAuthenticationForm(forms.Form):
61 oauth_token = forms.CharField(widget=forms.HiddenInput)
62 oauth_callback = forms.CharField(widget=forms.HiddenInput) # changed from URLField - too strict
63 # removed authorize_access - redundant
66 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
67 def create_verifier(self, request, credentials):
68 verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
70 'oauth_token': verifier['oauth_token'],
75 def oauth_user_auth(request):
76 endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
78 if request.method == "GET":
79 # Why not just get oauth_token here?
80 # This is fairly straightforward, in't?
82 realms, credentials = endpoint.get_realms_and_credentials(
83 **oauthlib_request(request))
84 except OAuth1Error as e:
85 return HttpResponse(str(e), status=400)
86 callback = request.GET.get('oauth_callback')
88 form = OAuthAuthenticationForm(initial={
89 'oauth_token': credentials['resource_owner_key'],
90 'oauth_callback': callback,
93 return render(request, 'oauth/authorize_token.html', {'form': form})
95 if request.method == "POST":
97 response = oauthlib_response(
98 endpoint.create_authorization_response(
99 credentials={"user": request.user},
100 **oauthlib_request(request)
103 except OAuth1Error as e:
104 return HttpResponse(e.message, status=400)
109 class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
110 def _create_request(self, *args, **kwargs):
111 r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
112 r.verifier = 'x' * 20
115 def create_access_token(self, request, credentials):
116 request.realms = self.request_validator.get_realms(
117 request.resource_owner_key, request)
119 'oauth_token': self.token_generator()[:KEY_SIZE],
120 'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
121 'oauth_authorized_realms': ' '.join(request.realms)
123 token.update(credentials)
124 self.request_validator.save_access_token(token, request)
125 return urlencode(token.items())
129 class OAuth1AccessTokenView(View):
131 self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
133 def dispatch(self, request):
134 return oauthlib_response(
135 self.endpoint.create_access_token_response(
136 **oauthlib_request(request)
141 class LoginView(GenericAPIView):
142 serializer_class = serializers.LoginSerializer
144 def post(self, request):
145 serializer = self.get_serializer(data=request.data)
146 serializer.is_valid(raise_exception=True)
147 d = serializer.validated_data
148 user = authenticate(username=d['username'], password=d['password'])
150 return Response({"detail": "Invalid credentials."})
152 key = generate_token()[:KEY_SIZE]
153 Token.objects.create(
155 token_type=Token.ACCESS,
159 return Response({"access_token": key})
162 class Login2View(GenericAPIView):
163 serializer_class = serializers.LoginSerializer
165 def post(self, request):
166 serializer = self.get_serializer(data=request.data)
167 serializer.is_valid(raise_exception=True)
168 d = serializer.validated_data
169 user = authenticate(username=d['username'], password=d['password'])
171 return Response({"detail": "Invalid credentials."})
173 access_token = generate_token()[:KEY_SIZE]
174 Token.objects.create(
176 token_type=Token.ACCESS,
180 refresh_token = generate_token()[:KEY_SIZE]
181 Token.objects.create(
183 token_type=Token.REFRESH,
188 "access_token": access_token,
189 "refresh_token": refresh_token,
195 class UserView(RetrieveAPIView):
196 permission_classes = [IsAuthenticated]
197 serializer_class = serializers.UserSerializer
199 def get_object(self):
200 return self.request.user
204 class BookUserDataView(RetrieveAPIView):
205 permission_classes = [IsAuthenticated]
206 serializer_class = serializers.BookUserDataSerializer
207 lookup_field = 'book__slug'
208 lookup_url_kwarg = 'slug'
210 def get_queryset(self):
211 return BookUserData.objects.filter(user=self.request.user)
213 def get(self, *args, **kwargs):
215 return super(BookUserDataView, self).get(*args, **kwargs)
217 return Response({"state": "not_started"})
219 def post(self, request, slug, state):
220 if state not in ('reading', 'complete'):
223 book = get_object_or_404(Book, slug=slug)
224 instance = BookUserData.update(book, request.user, state)
225 serializer = self.get_serializer(instance)
226 return Response(serializer.data)
229 class BlogView(APIView):
230 def get(self, request):
235 class RegisterView(GenericAPIView):
236 serializer_class = serializers.RegisterSerializer
238 def get(self, request):
243 "html": "Chcę otrzymywać newsletter Wolnych Lektur",
248 '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>.'
252 def post(self, request):
253 if not settings.FEATURE_API_REGISTER:
256 "detail": "Rejestracja aktualnie niedostępna."
260 serializer = self.get_serializer(data=request.data)
261 serializer.is_valid(raise_exception=True)
262 d = serializer.validated_data
271 user.set_password(d['password'])
273 if settings.FEATURE_CONFIRM_USER:
274 user.is_active = False
277 assert not filter_users_by_email(email)
282 "detail": "Nie można utworzyć konta.",
287 if settings.FEATURE_CONFIRM_USER:
288 UserConfirmation.request(user)
292 class RefreshTokenView(APIView):
293 serializer_class = serializers.RefreshTokenSerializer
295 def post(self, request):
296 serializer = self.get_serializer(data=request.data)
297 serializer.is_valid(raise_exception=True)
298 d = serializer.validated_data
300 t = Token.objects.get(
301 key=d['refresh_token'],
302 token_type=Token.REFRESH
306 access_token = generate_token()[:KEY_SIZE]
307 Token.objects.create(
309 token_type=Token.ACCESS,
313 refresh_token = generate_token()[:KEY_SIZE]
314 Token.objects.create(
316 token_type=Token.REFRESH,
321 "access_token": access_token,
322 "refresh_token": refresh_token,
327 class RequestConfirmView(APIView):
328 serializer_class = serializers.RequestConfirmSerializer
330 def post(self, request):
331 serializer = self.get_serializer(data=request.data)
332 serializer.is_valid(raise_exception=True)
333 d = serializer.validated_data
336 user = User.objects.get(
340 except User.DoesNotExist:
343 UserConfirmation.request(user)
347 class DeleteAccountView(GenericAPIView):
348 permission_classes = [IsAuthenticated]
349 serializer_class = serializers.DeleteAccountSerializer
351 def post(self, request):
353 serializer = self.get_serializer(
357 serializer.is_valid(raise_exception=True)
358 d = serializer.validated_data
364 class PasswordView(GenericAPIView):
365 permission_classes = [IsAuthenticated]
366 serializer_class = serializers.PasswordSerializer
368 def post(self, request):
370 serializer = self.get_serializer(
374 serializer.is_valid(raise_exception=True)
375 d = serializer.validated_data
376 u.set_password(d['new_password'])
381 class ResetPasswordView(GenericAPIView):
382 serializer_class = serializers.ResetPasswordSerializer
384 def post(self, request):
385 serializer = serializers.ResetPasswordSerializer(data=request.data)
386 serializer.is_valid(raise_exception=True)
387 form = ResetPasswordForm({"email": serializer.validated_data['email']})
393 class SessionTransferTokenView(APIView):
394 permission_classes = [IsAuthenticated]
396 def post(self, request):
397 ott = SessionTransferToken.create_for_user(request.user)
399 "token": str(ott.token)
403 class ConsumeSessionTransferTokenView(View):
404 def get(self, request):
405 token_str = request.GET.get("token")
406 next_url = request.GET.get("next", "/") #TODO: validate
409 return HttpResponseBadRequest("Missing token")
412 ott = SessionTransferToken.objects.get(token=token_str)
413 except SessionTransferToken.DoesNotExist:
414 return HttpResponseBadRequest("Invalid token")
416 if not ott.is_valid():
417 return HttpResponseForbidden("Token expired or already used")
421 ott.save(update_fields=["used"])
423 # Log in the user via Django session
424 login(request, ott.user)
426 return redirect(next_url)