api: use DRF for OAUTH2 APIs (#16842)
This commit is contained in:
parent
94dab06b42
commit
85ebba8394
|
@ -0,0 +1,63 @@
|
|||
# fargo - document box
|
||||
# Copyright (C) 2016-2017 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.authentication import BasicAuthentication
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
|
||||
from .models import OAuth2Client
|
||||
|
||||
|
||||
class OAuth2User(object):
|
||||
""" Fake user class to return in case OAuth2 Client authentication
|
||||
"""
|
||||
|
||||
def __init__(self, oauth2_client):
|
||||
self.oauth2_client = oauth2_client
|
||||
self.authenticated = False
|
||||
|
||||
def has_perm(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def has_perm_any(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def has_ou_perm(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def filter_by_perm(self, perms, queryset):
|
||||
return queryset
|
||||
|
||||
def is_authenticated(self):
|
||||
return self.authenticated
|
||||
|
||||
def is_staff(self):
|
||||
return False
|
||||
|
||||
|
||||
class FargoOAUTH2Authentication(BasicAuthentication):
|
||||
|
||||
def authenticate_credentials(self, client_id, client_secret):
|
||||
try:
|
||||
client = OAuth2Client.objects.get(
|
||||
client_id=client_id, client_secret=client_secret)
|
||||
except OAuth2Client.DoesNotExist:
|
||||
raise AuthenticationFailed(_('Invalid client_id/client_secret.'))
|
||||
|
||||
user = OAuth2User(client)
|
||||
user.authenticated = True
|
||||
return user, True
|
|
@ -1,8 +1,7 @@
|
|||
import cgi
|
||||
import base64
|
||||
from urllib import unquote
|
||||
|
||||
from .models import OAuth2Authorize, OAuth2Client
|
||||
from .models import OAuth2Authorize
|
||||
|
||||
|
||||
def authenticate_bearer(request):
|
||||
|
@ -21,36 +20,6 @@ def authenticate_bearer(request):
|
|||
return False
|
||||
|
||||
|
||||
def authenticate_client(request, client=False):
|
||||
'''Authenticate client on the token endpoint'''
|
||||
|
||||
if 'HTTP_AUTHORIZATION' in request.META:
|
||||
authorization = request.META['HTTP_AUTHORIZATION'].split()
|
||||
if authorization[0] != 'Basic' or len(authorization) != 2:
|
||||
return False
|
||||
try:
|
||||
decoded = base64.b64decode(authorization[1])
|
||||
except TypeError:
|
||||
return False
|
||||
parts = decoded.split(':')
|
||||
if len(parts) != 2:
|
||||
return False
|
||||
client_id, client_secret = parts
|
||||
elif 'client_id' in request.POST:
|
||||
client_id = request.POST['client_id']
|
||||
client_secret = request.POST.get('client_secret', '')
|
||||
else:
|
||||
return False
|
||||
if not client:
|
||||
try:
|
||||
client = OAuth2Client.objects.get(client_id=client_id)
|
||||
except OAuth2Client.DoesNotExist:
|
||||
return False
|
||||
if client.client_secret != client_secret:
|
||||
return False
|
||||
return client
|
||||
|
||||
|
||||
def get_content_disposition_value(request):
|
||||
if 'HTTP_CONTENT_DISPOSITION' not in request.META:
|
||||
return None, 'missing content-disposition header'
|
||||
|
|
|
@ -20,13 +20,17 @@ from urllib import quote
|
|||
from django.core.files.base import ContentFile
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import (HttpResponse, HttpResponseBadRequest,
|
||||
HttpResponseRedirect, JsonResponse)
|
||||
HttpResponseRedirect)
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import FormView, TemplateView
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .authentication import FargoOAUTH2Authentication
|
||||
from .forms import OAuth2AuthorizeForm
|
||||
from .models import OAuth2Authorize, OAuth2Client, OAuth2TempFile
|
||||
from .utils import authenticate_bearer, authenticate_client, get_content_disposition_value
|
||||
from .utils import authenticate_bearer, get_content_disposition_value
|
||||
|
||||
from fargo.fargo.models import UserDocument, Document
|
||||
|
||||
|
@ -35,6 +39,15 @@ class OAuth2Exception(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class OAUTH2APIViewMixin(APIView):
|
||||
http_method_names = ['post']
|
||||
authentication_classes = (FargoOAUTH2Authentication,)
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(OAUTH2APIViewMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class OAuth2AuthorizeView(FormView):
|
||||
template_name = 'oauth2/authorize.html'
|
||||
form_class = OAuth2AuthorizeForm
|
||||
|
@ -98,29 +111,25 @@ class OAuth2AuthorizeView(FormView):
|
|||
return super(OAuth2AuthorizeView, self).form_valid(form)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def get_document_token(request):
|
||||
grant_type = request.POST.get('grant_type')
|
||||
redirect_uri = request.POST.get('redirect_uri')
|
||||
code = request.POST.get('code')
|
||||
client = authenticate_client(request)
|
||||
class GetDocumentTokenView(OAUTH2APIViewMixin):
|
||||
|
||||
try:
|
||||
if not client:
|
||||
raise OAuth2Exception('invalid client')
|
||||
if redirect_uri not in client.get_redirect_uris():
|
||||
raise OAuth2Exception('invalid_request')
|
||||
if grant_type != 'authorization_code':
|
||||
raise OAuth2Exception('unsupported_grant_type')
|
||||
except OAuth2Exception as e:
|
||||
return JsonResponse({'error': e.message}, status=400)
|
||||
def post(self, request, *args, **kwargs):
|
||||
client = request.user.oauth2_client
|
||||
data = request.data
|
||||
if not client.check_redirect_uri(data['redirect_uri']):
|
||||
return Response({'error': 'invalid_request'}, status=400)
|
||||
if data['grant_type'] != 'authorization_code':
|
||||
return Response({'error': 'unsopported_grant_type'}, status=400)
|
||||
|
||||
try:
|
||||
doc_token = OAuth2Authorize.objects.get(code=code).access_token
|
||||
except OAuth2Authorize.DoesNotExist:
|
||||
return JsonResponse({'error': 'invalid_request'}, status=400)
|
||||
try:
|
||||
token = OAuth2Authorize.objects.get(code=data['code']).access_token
|
||||
except OAuth2Authorize.DoesNotExist:
|
||||
return Response({'error': 'invalid_request'}, status=400)
|
||||
|
||||
return JsonResponse({'access_token': doc_token, 'expires': '3600'})
|
||||
return Response({'access_token': token, 'expires': '3600'})
|
||||
|
||||
|
||||
get_document_token = GetDocumentTokenView.as_view()
|
||||
|
||||
|
||||
def get_document(request):
|
||||
|
@ -139,26 +148,26 @@ def get_document(request):
|
|||
return response
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def put_document(request):
|
||||
client = authenticate_client(request)
|
||||
if not client:
|
||||
return HttpResponseBadRequest('basic HTTP authentication failed')
|
||||
class PutDocumentAPIView(OAUTH2APIViewMixin):
|
||||
|
||||
filename, error = get_content_disposition_value(request)
|
||||
if error:
|
||||
return HttpResponseBadRequest(error)
|
||||
def post(self, request, *args, **kwargs):
|
||||
filename, error = get_content_disposition_value(request)
|
||||
if error:
|
||||
return HttpResponseBadRequest(error)
|
||||
|
||||
f = ContentFile(request.body, name=filename)
|
||||
document = Document.objects.get_by_file(f)
|
||||
oauth2_document = OAuth2TempFile.objects.create(document=document,
|
||||
filename=filename)
|
||||
uri = reverse('oauth2-put-document-authorize',
|
||||
args=[oauth2_document.pk]) + '/'
|
||||
f = ContentFile(request.body, name=filename)
|
||||
document = Document.objects.get_by_file(f)
|
||||
oauth2_document = OAuth2TempFile.objects.create(document=document,
|
||||
filename=filename)
|
||||
uri = reverse('oauth2-put-document-authorize',
|
||||
args=[oauth2_document.pk]) + '/'
|
||||
|
||||
response = HttpResponse()
|
||||
response['Location'] = uri
|
||||
return response
|
||||
response = Response()
|
||||
response['Location'] = uri
|
||||
return response
|
||||
|
||||
|
||||
put_document = PutDocumentAPIView.as_view()
|
||||
|
||||
|
||||
class OAuth2AuthorizePutView(TemplateView):
|
||||
|
|
|
@ -86,6 +86,7 @@ def test_get_document_oauth2(app, john_doe, oauth2_client, user_doc):
|
|||
params['code'] = auth.code
|
||||
|
||||
url = reverse('oauth2-get-token')
|
||||
app.authorization = ('Basic', (oauth2_client.client_id, oauth2_client.client_secret))
|
||||
resp = app.post(url, params=params, status=200)
|
||||
assert 'access_token' in resp.json
|
||||
assert 'expires' in resp.json
|
||||
|
@ -109,8 +110,7 @@ def test_put_document(app, john_doe, oauth2_client):
|
|||
data = f.read()
|
||||
|
||||
url = reverse('oauth2-put-document')
|
||||
resp = app.post(url, params=data, status=400)
|
||||
assert 'basic HTTP authentication failed' in resp.content
|
||||
resp = app.post(url, params=data, status=401)
|
||||
|
||||
app.authorization = ('Basic', (str(oauth2_client.client_id), str(oauth2_client.client_secret)))
|
||||
resp = app.post(url, params=data, status=400)
|
||||
|
|
Loading…
Reference in New Issue