General A/B testing.
[wolnelektury.git] / src / api / views.py
index 6d462fa..69f9b8b 100644 (file)
@@ -1,20 +1,25 @@
-# -*- coding: utf-8 -*-
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.http import Http404, HttpResponse
+from django.contrib.auth.decorators import login_required
+from django import forms
+from django.http import HttpResponse
+from django.http import Http404
+from django.shortcuts import render
+from django.views.generic.base import View
 from oauthlib.common import urlencode
-from oauthlib.oauth1 import RequestTokenEndpoint
-from piston.models import KEY_SIZE, SECRET_SIZE
+from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
+from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
+from api.models import KEY_SIZE, SECRET_SIZE
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.views import APIView
 from rest_framework.generics import ListAPIView, RetrieveAPIView, get_object_or_404
-from migdal.models import Entry
 from catalogue.models import Book
 from .models import BookUserData
 from . import serializers
 from .request_validator import PistonRequestValidator
+from .utils import oauthlib_request, oauthlib_response, vary_on_auth
 
 
 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
@@ -33,25 +38,101 @@ class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
         return urlencode(token.items())
 
 
-class OAuth1RequestTokenView(APIView):
+# Never Cache
+class OAuth1RequestTokenView(View):
     def __init__(self):
         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
+
     def dispatch(self, request):
-        headers, body, status = self.endpoint.create_request_token_response(
-            request.build_absolute_uri(),
-            request.method,
-            request.body,
-            {
-                "Authorization": request.META['HTTP_AUTHORIZATION']
-            } if 'HTTP_AUTHORIZATION' in request.META else None
+        return oauthlib_response(
+            self.endpoint.create_request_token_response(
+                **oauthlib_request(request)
+            )
         )
 
-        response = HttpResponse(body, status=status)
-        for k, v in headers.items():
-            response[k] = v
-        return response
+
+class OAuthAuthenticationForm(forms.Form):
+    oauth_token = forms.CharField(widget=forms.HiddenInput)
+    oauth_callback = forms.CharField(widget=forms.HiddenInput)  # changed from URLField - too strict
+    # removed authorize_access - redundant
+
+
+class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
+    def create_verifier(self, request, credentials):
+        verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
+        return {
+            'oauth_token': verifier['oauth_token'],
+        }
+
+
+@login_required
+def oauth_user_auth(request):
+    endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
+
+    if request.method == "GET":
+        # Why not just get oauth_token here?
+        # This is fairly straightforward, in't?
+        try:
+            realms, credentials = endpoint.get_realms_and_credentials(
+                **oauthlib_request(request))
+        except OAuth1Error as e:
+            return HttpResponse(e.message, status=400)
+        callback = request.GET.get('oauth_callback')
+
+        form = OAuthAuthenticationForm(initial={
+            'oauth_token': credentials['resource_owner_key'],
+            'oauth_callback': callback,
+        })
+
+        return render(request, 'oauth/authorize_token.html', {'form': form})
+
+    elif request.method == "POST":
+        try:
+            response = oauthlib_response(
+                endpoint.create_authorization_response(
+                    credentials={"user": request.user},
+                    **oauthlib_request(request)
+                )
+            )
+        except OAuth1Error as e:
+            return HttpResponse(e.message, status=400)
+        else:
+            return response
+
+
+class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
+    def _create_request(self, *args, **kwargs):
+        r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
+        r.verifier = 'x' * 20
+        return r
+
+    def create_access_token(self, request, credentials):
+        request.realms = self.request_validator.get_realms(
+            request.resource_owner_key, request)
+        token = {
+            'oauth_token': self.token_generator()[:KEY_SIZE],
+            'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
+            'oauth_authorized_realms': ' '.join(request.realms)
+        }
+        token.update(credentials)
+        self.request_validator.save_access_token(token, request)
+        return urlencode(token.items())
 
 
+# Never cache
+class OAuth1AccessTokenView(View):
+    def __init__(self):
+        self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
+
+    def dispatch(self, request):
+        return oauthlib_response(
+            self.endpoint.create_access_token_response(
+                **oauthlib_request(request)
+            )
+        )
+
+
+@vary_on_auth
 class UserView(RetrieveAPIView):
     permission_classes = [IsAuthenticated]
     serializer_class = serializers.UserSerializer
@@ -60,6 +141,7 @@ class UserView(RetrieveAPIView):
         return self.request.user
 
 
+@vary_on_auth
 class BookUserDataView(RetrieveAPIView):
     permission_classes = [IsAuthenticated]
     serializer_class = serializers.BookUserDataSerializer
@@ -85,15 +167,6 @@ class BookUserDataView(RetrieveAPIView):
         return Response(serializer.data)
 
 
-class BlogView(ListAPIView):
-    serializer_class = serializers.BlogSerializer
-
-    def get_queryset(self):
-        after = self.request.query_params.get('after')
-        count = int(self.request.query_params.get('count', 20))
-        entries = Entry.published_objects.filter(in_stream=True).order_by('-first_published_at')
-        if after:
-            entries = entries.filter(first_published_at__lt=after)
-        if count:
-            entries = entries[:count]
-        return entries
+class BlogView(APIView):
+    def get(self, request):
+        return Response([])