from oauthlib.oauth1 import ResourceEndpoint
from rest_framework.authentication import BaseAuthentication
from .request_validator import PistonRequestValidator
+from .utils import oauthlib_request
class PistonOAuthAuthentication(BaseAuthentication):
def authenticate(self, request):
v, r = self.provider.validate_protected_resource_request(
- request.build_absolute_uri(),
- http_method=request.method,
- body=request.body,
- headers={
- "Authorization": request.META['HTTP_AUTHORIZATION'],
- "Content-Type": request.content_type,
- } if 'HTTP_AUTHORIZATION' in request.META else None
+ **oauthlib_request(request)
)
if v:
return r.token.user, r.token
# -*- coding: utf-8 -*-
-
-# modified from django-piston
-import base64
-import hmac
-
-from django import forms
-from django.conf import settings
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from oauthlib.oauth1 import AuthorizationEndpoint
from django.contrib.auth.decorators import login_required
-from django.core.urlresolvers import get_callable
-from django.http import HttpResponseRedirect, HttpResponse
-from django.shortcuts import render_to_response
-from django.template.context import RequestContext
-from piston import oauth
-from piston.authentication import initialize_server_request, INVALID_PARAMS_RESPONSE, send_oauth_error
+from django import forms
+from django.http import HttpResponseRedirect
+from django.shortcuts import render
+from .request_validator import PistonRequestValidator
+from .utils import oauthlib_request, oauthlib_response
class HttpResponseAppRedirect(HttpResponseRedirect):
oauth_token = forms.CharField(widget=forms.HiddenInput)
oauth_callback = forms.CharField(widget=forms.HiddenInput) # changed from URLField - too strict
# removed authorize_access - redundant
- csrf_signature = forms.CharField(widget=forms.HiddenInput)
-
- def __init__(self, *args, **kwargs):
- forms.Form.__init__(self, *args, **kwargs)
-
- self.fields['csrf_signature'].initial = self.initial_csrf_signature
-
- def clean_csrf_signature(self):
- sig = self.cleaned_data['csrf_signature']
- token = self.cleaned_data['oauth_token']
-
- sig1 = OAuthAuthenticationForm.get_csrf_signature(settings.SECRET_KEY, token)
-
- if sig != sig1:
- raise forms.ValidationError("CSRF signature is not valid")
-
- return sig
-
- def initial_csrf_signature(self):
- token = self.initial['oauth_token']
- return OAuthAuthenticationForm.get_csrf_signature(settings.SECRET_KEY, token)
-
- @staticmethod
- def get_csrf_signature(key, token):
- # Check signature...
- import hashlib # 2.5
- hashed = hmac.new(key, token, hashlib.sha1)
-
- # calculate the digest base 64
- return base64.b64encode(hashed.digest())
-
-
-# The only thing changed in the views below is the form used
-# and also the Http Redirect class
-
-
-def oauth_auth_view(request, token, callback, params):
- form = OAuthAuthenticationForm(initial={
- 'oauth_token': token.key,
- 'oauth_callback': callback,
- })
-
- return render_to_response('piston/authorize_token.html',
- {'form': form}, RequestContext(request))
@login_required
def oauth_user_auth(request):
- oauth_server, oauth_request = initialize_server_request(request)
-
- if oauth_request is None:
- return INVALID_PARAMS_RESPONSE
-
- try:
- token = oauth_server.fetch_request_token(oauth_request)
- except oauth.OAuthError, err:
- return send_oauth_error(err)
-
- try:
- callback = oauth_server.get_callback(oauth_request)
- except:
- callback = None
+ endpoint = AuthorizationEndpoint(PistonRequestValidator())
if request.method == "GET":
- params = oauth_request.get_normalized_parameters()
-
- oauth_view = getattr(settings, 'OAUTH_AUTH_VIEW', None)
- if oauth_view is None:
- return oauth_auth_view(request, token, callback, params)
- else:
- return get_callable(oauth_view)(request, token, callback, params)
- elif request.method == "POST":
- try:
- form = OAuthAuthenticationForm(request.POST)
- if form.is_valid():
- token = oauth_server.authorize_token(token, request.user)
- args = '?' + token.to_string(only_key=True)
- else:
- args = '?error=%s' % 'Access not granted by user.'
-
- if not callback:
- callback = getattr(settings, 'OAUTH_CALLBACK_VIEW')
- return get_callable(callback)(request, token)
+ # Why not just get oauth_token here?
+ # This is fairly straightforward, in't?
+ realms, credentials = endpoint.get_realms_and_credentials(
+ **oauthlib_request(request))
+ callback = request.GET.get('oauth_callback')
- response = HttpResponseAppRedirect(callback + args)
+ form = OAuthAuthenticationForm(initial={
+ 'oauth_token': credentials['resource_owner_key'],
+ 'oauth_callback': callback,
+ })
- except oauth.OAuthError, err:
- response = send_oauth_error(err)
- else:
- response = HttpResponse('Action not allowed.')
+ return render(request, 'piston/authorize_token.html', {'form': form})
- return response
+ elif request.method == "POST":
+ response = oauthlib_response(
+ endpoint.create_authorization_response(
+ credentials={"user": request.user},
+ **oauthlib_request(request)
+ )
+ )
+
+ return response
# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
+import time
from oauthlib.oauth1 import RequestValidator
from piston.models import Consumer, Nonce, Token
class PistonRequestValidator(RequestValidator):
+ timestamp_threshold = 300
+
dummy_access_token = '!'
realms = ['API']
def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
request, request_token=None, access_token=None):
- # TODO: validate the timestamp
+ if abs(time.time() - int(timestamp)) > self.timestamp_threshold:
+ return False
token = request_token or access_token
# Yes, this is what Piston did.
if token is None:
secret=token['oauth_token_secret'],
consumer=request.oauth_consumer,
)
+
+ def verify_request_token(self, token, request):
+ return Token.objects.filter(
+ token_type=Token.REQUEST, key=token, is_approved=False
+ ).exists()
+
+ def get_realms(self, *args, **kwargs):
+ return []
+
+ def save_verifier(self, token, verifier, request):
+ Token.objects.filter(
+ token_type=Token.REQUEST,
+ key=token,
+ is_approved=False
+ ).update(
+ is_approved=True,
+ user=verifier['user']
+ )
+
+ def get_redirect_uri(self, token, request):
+ return request.redirect_uri
from django.conf.urls import url, include
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
-from piston.authentication import oauth_access_token, oauth_request_token
+from piston.authentication import oauth_access_token
import catalogue.views
from api import handlers
from api.piston_patch import oauth_user_auth
--- /dev/null
+# -*- 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 HttpResponse
+from django.utils.encoding import iri_to_uri
+
+
+def oauthlib_request(request):
+ """Creates parameters for OAuthlib's Request from a Django Request."""
+ headers = {}
+ # We don't have request.content_type yet in 2015,
+ # while test client has no META['CONTENT_TYPE'].
+ ct = request.META.get('CONTENT_TYPE', getattr(request, 'content_type', None))
+ if ct:
+ headers["Content-Type"] = ct
+ if 'HTTP_AUTHORIZATION' in request.META:
+ headers["Authorization"] = request.META['HTTP_AUTHORIZATION']
+ return {
+ "uri": request.build_absolute_uri(),
+ "http_method": request.method,
+ "body": request.body,
+ "headers": headers,
+ }
+
+def oauthlib_response((headers, body, status)):
+ """Creates a django.http.HttpResponse from (headers, body, status) tuple from OAuthlib."""
+ response = HttpResponse(body, status=status)
+ for k, v in headers.items():
+ if k == 'Location':
+ v = iri_to_uri(v)
+ response[k] = v
+ return response
# 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.http import Http404
from oauthlib.common import urlencode
from oauthlib.oauth1 import RequestTokenEndpoint
from piston.models import KEY_SIZE, SECRET_SIZE
from .models import BookUserData
from . import serializers
from .request_validator import PistonRequestValidator
+from .utils import oauthlib_request, oauthlib_response
class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
class OAuth1RequestTokenView(APIView):
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 UserView(RetrieveAPIView):
permission_classes = [IsAuthenticated]