Projet

Général

Profil

0002-use-drf-for-oauth2-APIs-16842.patch

Josué Kouka, 31 janvier 2018 17:14

Télécharger (10,1 ko)

Voir les différences:

Subject: [PATCH 2/3] use drf for oauth2 APIs (#16842)

 fargo/oauth2/authentication.py | 63 ++++++++++++++++++++++++++++++
 fargo/oauth2/utils.py          | 33 +---------------
 fargo/oauth2/views.py          | 87 +++++++++++++++++++++++-------------------
 tests/test_oauth2.py           |  4 +-
 4 files changed, 114 insertions(+), 73 deletions(-)
 create mode 100644 fargo/oauth2/authentication.py
fargo/oauth2/authentication.py
1
# fargo - document box
2
# Copyright (C) 2016-2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.utils.translation import ugettext_lazy as _
18

  
19
from rest_framework.authentication import BasicAuthentication
20
from rest_framework.exceptions import AuthenticationFailed
21

  
22
from .models import OAuth2Client
23

  
24

  
25
class OAuth2User(object):
26
    """ Fake user class to return in case OAuth2 Client authentication
27
    """
28

  
29
    def __init__(self, oauth2_client):
30
        self.oauth2_client = oauth2_client
31
        self.authenticated = False
32

  
33
    def has_perm(self, *args, **kwargs):
34
        return True
35

  
36
    def has_perm_any(self, *args, **kwargs):
37
        return True
38

  
39
    def has_ou_perm(self, *args, **kwargs):
40
        return True
41

  
42
    def filter_by_perm(self, perms, queryset):
43
        return queryset
44

  
45
    def is_authenticated(self):
46
        return self.authenticated
47

  
48
    def is_staff(self):
49
        return False
50

  
51

  
52
class FargoOAUTH2Authentication(BasicAuthentication):
53

  
54
    def authenticate_credentials(self, client_id, client_secret):
55
        try:
56
            client = OAuth2Client.objects.get(
57
                client_id=client_id, client_secret=client_secret)
58
        except OAuth2Client.DoesNotExist:
59
            raise AuthenticationFailed(_('Invalid client_id/client_secret.'))
60

  
61
        user = OAuth2User(client)
62
        user.authenticated = True
63
        return user, True
fargo/oauth2/utils.py
1 1
import cgi
2
import base64
3 2
from urllib import unquote
4 3

  
5
from .models import OAuth2Authorize, OAuth2Client
4
from .models import OAuth2Authorize
6 5

  
7 6

  
8 7
def authenticate_bearer(request):
......
21 20
        return False
22 21

  
23 22

  
24
def authenticate_client(request, client=False):
25
    '''Authenticate client on the token endpoint'''
26

  
27
    if 'HTTP_AUTHORIZATION' in request.META:
28
        authorization = request.META['HTTP_AUTHORIZATION'].split()
29
        if authorization[0] != 'Basic' or len(authorization) != 2:
30
            return False
31
        try:
32
            decoded = base64.b64decode(authorization[1])
33
        except TypeError:
34
            return False
35
        parts = decoded.split(':')
36
        if len(parts) != 2:
37
            return False
38
        client_id, client_secret = parts
39
    elif 'client_id' in request.POST:
40
        client_id = request.POST['client_id']
41
        client_secret = request.POST.get('client_secret', '')
42
    else:
43
        return False
44
    if not client:
45
        try:
46
            client = OAuth2Client.objects.get(client_id=client_id)
47
        except OAuth2Client.DoesNotExist:
48
            return False
49
    if client.client_secret != client_secret:
50
        return False
51
    return client
52

  
53

  
54 23
def get_content_disposition_value(request):
55 24
    if 'HTTP_CONTENT_DISPOSITION' not in request.META:
56 25
        return None, 'missing content-disposition header'
fargo/oauth2/views.py
20 20
from django.core.files.base import ContentFile
21 21
from django.core.urlresolvers import reverse
22 22
from django.http import (HttpResponse, HttpResponseBadRequest,
23
                         HttpResponseRedirect, JsonResponse)
23
                         HttpResponseRedirect)
24 24
from django.views.decorators.csrf import csrf_exempt
25 25
from django.views.generic import FormView, TemplateView
26 26

  
27
from rest_framework.response import Response
28
from rest_framework.views import APIView
29

  
30
from .authentication import FargoOAUTH2Authentication
27 31
from .forms import OAuth2AuthorizeForm
28 32
from .models import OAuth2Authorize, OAuth2Client, OAuth2TempFile
29
from .utils import authenticate_bearer, authenticate_client, get_content_disposition_value
33
from .utils import authenticate_bearer, get_content_disposition_value
30 34

  
31 35
from fargo.fargo.models import UserDocument, Document
32 36

  
......
35 39
    pass
36 40

  
37 41

  
42
class OAUTH2APIViewMixin(APIView):
43
    http_method_names = ['post']
44
    authentication_classes = (FargoOAUTH2Authentication,)
45

  
46
    @csrf_exempt
47
    def dispatch(self, request, *args, **kwargs):
48
        return super(OAUTH2APIViewMixin, self).dispatch(request, *args, **kwargs)
49

  
50

  
38 51
class OAuth2AuthorizeView(FormView):
39 52
    template_name = 'oauth2/authorize.html'
40 53
    form_class = OAuth2AuthorizeForm
......
98 111
        return super(OAuth2AuthorizeView, self).form_valid(form)
99 112

  
100 113

  
101
@csrf_exempt
102
def get_document_token(request):
103
    grant_type = request.POST.get('grant_type')
104
    redirect_uri = request.POST.get('redirect_uri')
105
    code = request.POST.get('code')
106
    client = authenticate_client(request)
114
class GetDocumentTokenView(OAUTH2APIViewMixin):
115

  
116
    def post(self, request, *args, **kwargs):
117
        client = request.user.oauth2_client
118
        data = request.data
119
        if not client.check_redirect_uri(data['redirect_uri']):
120
            return Response({'error': 'invalid_request'}, status=400)
121
        if data['grant_type'] != 'authorization_code':
122
            return Response({'error': 'unsopported_grant_type'}, status=400)
123

  
124
        try:
125
            token = OAuth2Authorize.objects.get(code=data['code']).access_token
126
        except OAuth2Authorize.DoesNotExist:
127
            return Response({'error': 'invalid_request'}, status=400)
107 128

  
108
    try:
109
        if not client:
110
            raise OAuth2Exception('invalid client')
111
        if redirect_uri not in client.get_redirect_uris():
112
            raise OAuth2Exception('invalid_request')
113
        if grant_type != 'authorization_code':
114
            raise OAuth2Exception('unsupported_grant_type')
115
    except OAuth2Exception as e:
116
        return JsonResponse({'error': e.message}, status=400)
129
        return Response({'access_token': token, 'expires': '3600'})
117 130

  
118
    try:
119
        doc_token = OAuth2Authorize.objects.get(code=code).access_token
120
    except OAuth2Authorize.DoesNotExist:
121
        return JsonResponse({'error': 'invalid_request'}, status=400)
122 131

  
123
    return JsonResponse({'access_token': doc_token, 'expires': '3600'})
132
get_document_token = GetDocumentTokenView.as_view()
124 133

  
125 134

  
126 135
def get_document(request):
......
139 148
    return response
140 149

  
141 150

  
142
@csrf_exempt
143
def put_document(request):
144
    client = authenticate_client(request)
145
    if not client:
146
        return HttpResponseBadRequest('basic HTTP authentication failed')
151
class PutDocumentAPIView(OAUTH2APIViewMixin):
152

  
153
    def post(self, request, *args, **kwargs):
154
        filename, error = get_content_disposition_value(request)
155
        if error:
156
            return HttpResponseBadRequest(error)
147 157

  
148
    filename, error = get_content_disposition_value(request)
149
    if error:
150
        return HttpResponseBadRequest(error)
158
        f = ContentFile(request.body, name=filename)
159
        document = Document.objects.get_by_file(f)
160
        oauth2_document = OAuth2TempFile.objects.create(document=document,
161
                                                        filename=filename)
162
        uri = reverse('oauth2-put-document-authorize',
163
                      args=[oauth2_document.pk]) + '/'
151 164

  
152
    f = ContentFile(request.body, name=filename)
153
    document = Document.objects.get_by_file(f)
154
    oauth2_document = OAuth2TempFile.objects.create(document=document,
155
                                                    filename=filename)
156
    uri = reverse('oauth2-put-document-authorize',
157
                  args=[oauth2_document.pk]) + '/'
165
        response = Response()
166
        response['Location'] = uri
167
        return response
158 168

  
159
    response = HttpResponse()
160
    response['Location'] = uri
161
    return response
169

  
170
put_document = PutDocumentAPIView.as_view()
162 171

  
163 172

  
164 173
class OAuth2AuthorizePutView(TemplateView):
tests/test_oauth2.py
86 86
    params['code'] = auth.code
87 87

  
88 88
    url = reverse('oauth2-get-token')
89
    app.authorization = ('Basic', (oauth2_client.client_id, oauth2_client.client_secret))
89 90
    resp = app.post(url, params=params, status=200)
90 91
    assert 'access_token' in resp.json
91 92
    assert 'expires' in resp.json
......
109 110
        data = f.read()
110 111

  
111 112
    url = reverse('oauth2-put-document')
112
    resp = app.post(url, params=data, status=400)
113
    assert 'basic HTTP authentication failed' in resp.content
113
    resp = app.post(url, params=data, status=401)
114 114

  
115 115
    app.authorization = ('Basic', (str(oauth2_client.client_id), str(oauth2_client.client_secret)))
116 116
    resp = app.post(url, params=data, status=400)
117
-