Projet

Général

Profil

0002-rest_authentication-add-api-client-authentication-67.patch

Emmanuel Cazenave, 22 août 2022 14:45

Télécharger (8,44 ko)

Voir les différences:

Subject: [PATCH 2/2] rest_authentication: add api client authentication
 (#67085)

 debian/debian_config_common.py    |  5 +-
 hobo/rest_authentication.py       | 51 +++++++++++++++++++
 hobo/test_urls.py                 | 12 +++++
 tests/settings.py                 |  8 +++
 tests/test_rest_authentication.py | 85 +++++++++++++++++++++++++++++++
 tox.ini                           |  1 +
 6 files changed, 161 insertions(+), 1 deletion(-)
 create mode 100644 tests/test_rest_authentication.py
debian/debian_config_common.py
397 397
if 'rest_framework' in INSTALLED_APPS:
398 398
    if 'REST_FRAMEWORK' not in globals():
399 399
        REST_FRAMEWORK = {}
400
    REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = ('hobo.rest_authentication.PublikAuthentication',)
400
    REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = (
401
        'hobo.rest_authentication.PublikAuthentication',
402
        'hobo.rest_authentication.APIClientAuthentication',
403
    )
401 404
    REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.IsAuthenticated',)
402 405
    REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = ('rest_framework.renderers.JSONRenderer',)
403 406

  
hobo/rest_authentication.py
1 1
import logging
2 2

  
3
import requests
3 4
from django.conf import settings
4 5
from django.contrib.auth import get_user_model
5 6
from django.contrib.auth.models import AnonymousUser
......
8 9
from rest_framework import authentication, exceptions, status
9 10

  
10 11
from hobo import signature
12
from hobo.requests_wrapper import Requests
11 13

  
12 14
try:
13 15
    from mellon.models import UserSAMLIdentifier
......
51 53
        return 'Publik Service Admin'
52 54

  
53 55

  
56
class APIClientUser:
57

  
58
    is_active = True
59
    is_anonymous = False
60
    is_authenticated = True
61
    is_superuser = False
62
    roles = []
63

  
64
    def __init__(self, is_active, is_anonymous, is_authenticated, is_superuser, roles):
65
        self.is_active = is_active
66
        self.is_anonymous = is_anonymous
67
        self.is_authenticated = is_authenticated
68
        self.is_superuser = is_superuser
69
        self.roles = roles
70

  
71
    @classmethod
72
    def from_dict(cls, data):
73
        return cls(
74
            is_active=data['is_active'],
75
            is_anonymous=data['is_anonymous'],
76
            is_authenticated=data['is_authenticated'],
77
            is_superuser=data['is_superuser'],
78
            roles=data['roles'],
79
        )
80

  
81

  
54 82
class PublikAuthenticationFailed(exceptions.APIException):
55 83
    status_code = status.HTTP_401_UNAUTHORIZED
56 84
    default_code = 'invalid-signature'
......
125 153
        user = self.resolve_user(request)
126 154
        self.logger.info('user authenticated with signature %s', user)
127 155
        return (user, None)
156

  
157

  
158
class APIClientAuthentication(authentication.BasicAuthentication):
159
    def authenticate_credentials(self, identifier, password, request=None):
160
        idp_services = list(getattr(settings, 'KNOWN_SERVICES', {}).get('authentic', {}).values())
161
        if not idp_services:
162
            return None
163
        authentic = idp_services[0]
164
        url = authentic['url'] + 'api/check-api-client/'
165
        response = Requests().post(url, json={'identifier': identifier, 'password': password})
166
        try:
167
            response.raise_for_status()
168
        except requests.exceptions.RequestException:
169
            return None
170

  
171
        result = response.json()
172
        if 'err' not in result or 'data' not in result or result['err'] == 1:
173
            return None
174
        try:
175
            api_client = APIClientUser.from_dict(result['data'])
176
        except Exception:
177
            return None
178
        return api_client, None
hobo/test_urls.py
3 3
from django.conf.urls import url
4 4
from django.core.exceptions import PermissionDenied
5 5
from django.http import HttpResponse
6
from rest_framework import permissions
7
from rest_framework.response import Response
8
from rest_framework.views import APIView
6 9

  
7 10

  
8 11
def helloworld(request):
......
16 19
    return HttpResponse('Hello world %s' % request.META['REMOTE_ADDR'])
17 20

  
18 21

  
22
class AuthenticatedTestView(APIView):
23

  
24
    permission_classes = [permissions.IsAuthenticated]
25

  
26
    def get(self, request):
27
        return Response({'some': 'data'})
28

  
29

  
19 30
urlpatterns = [
20 31
    url(r'^$', helloworld),
32
    url(r'^authenticated-testview/', AuthenticatedTestView.as_view()),
21 33
]
tests/settings.py
26 26
}
27 27

  
28 28
TEMPLATES[0]['OPTIONS'].setdefault('builtins', []).append('hobo.templatetags.hobo')
29

  
30
REST_FRAMEWORK = {
31
    'DEFAULT_AUTHENTICATION_CLASSES': (
32
        'hobo.rest_authentication.PublikAuthentication',
33
        'hobo.rest_authentication.APIClientAuthentication',
34
    ),
35
    'DEFAULT_RENDERER_CLASSES': ('rest_framework.renderers.JSONRenderer',),
36
}
tests/test_rest_authentication.py
1
import base64
2

  
3
import responses
4
from django.test import RequestFactory
5
from rest_framework.views import APIView
6

  
7

  
8
class TestView(APIView):
9
    def get(self, request):
10
        return Response({'some': 'data'})
11

  
12

  
13
@responses.activate
14
def test_apiclient_authentication(app, db, settings):
15
    settings.ROOT_URLCONF = 'hobo.test_urls'
16
    app.authorization = ('Basic', ('foo', 'bar'))
17
    # No KNOWN_SERVICES : 403
18
    resp = app.get('/authenticated-testview/', status=403)
19
    # No authentic in  KNOWN_SERVICES : 403
20
    settings.KNOWN_SERVICES = {}
21
    resp = app.get('/authenticated-testview/', status=403)
22

  
23
    settings.KNOWN_SERVICES = {
24
        'authentic': {
25
            'idp': {
26
                'title': 'Foobar',
27
                'url': 'https://idp.example.invalid/',
28
                'orig': 'example.org',
29
                'secret': 'xxx',
30
            }
31
        }
32
    }
33
    # check-api-client returns an error : 403
34
    responses.add(
35
        responses.POST,
36
        'https://idp.example.invalid/api/check-api-client/',
37
        status=403,
38
    )
39
    resp = app.get('/authenticated-testview/', status=403)
40

  
41
    # no 'err' key in check-api-client response : 403
42
    responses.add(
43
        responses.POST,
44
        'https://idp.example.invalid/api/check-api-client/',
45
        json={'foo': 'bar'},
46
        status=200,
47
    )
48
    resp = app.get('/authenticated-testview/', status=403)
49

  
50
    # authentication refused by a2 : 403
51
    responses.add(
52
        responses.POST,
53
        'https://idp.example.invalid/api/check-api-client/',
54
        json={'err': 1},
55
        status=200,
56
    )
57
    resp = app.get('/authenticated-testview/', status=403)
58

  
59
    # error when deserializing the api client : 403
60
    responses.add(
61
        responses.POST,
62
        'https://idp.example.invalid/api/check-api-client/',
63
        json={'err': 0, 'data': {'foo': 'bar'}},
64
        status=200,
65
    )
66
    resp = app.get('/authenticated-testview/', status=403)
67

  
68
    # accces Granted
69
    responses.add(
70
        responses.POST,
71
        'https://idp.example.invalid/api/check-api-client/',
72
        json={
73
            'err': 0,
74
            'data': {
75
                'is_active': True,
76
                'is_anonymous': False,
77
                'is_authenticated': True,
78
                'is_superuser': False,
79
                'restrict_to_anonymised_data': False,
80
                'roles': [],
81
            },
82
        },
83
        status=200,
84
    )
85
    resp = app.get('/authenticated-testview/')
tox.ini
57 57
	mock<4
58 58
	httmock
59 59
	requests
60
        responses
60 61
	pytest-freezegun
61 62
	xmlschema<1.1
62 63
	enum34<=1.1.6
63
-