Projet

Général

Profil

0001-rest_authentication-raise-APIError-for-signature-err.patch

Benjamin Dauvergne, 09 mars 2020 22:21

Télécharger (8 ko)

Voir les différences:

Subject: [PATCH] rest_authentication: raise APIError for signature errors
 (#39911)

 hobo/rest_authentication.py                 | 27 ++++---
 tests_authentic/test_rest_authentication.py | 87 ++++++++++++++++++++-
 2 files changed, 101 insertions(+), 13 deletions(-)
hobo/rest_authentication.py
1 1
import logging
2 2

  
3
from rest_framework import authentication, exceptions
3
from rest_framework import authentication, exceptions, status
4 4

  
5 5
from hobo import signature
6 6

  
......
61 61
        return 'Publik Service Admin'
62 62

  
63 63

  
64
class PublikAuthenticationFailed(exceptions.APIException):
65
    status_code = status.HTTP_401_UNAUTHORIZED
66
    default_code = 'invalid-signature'
67

  
68
    def __init__(self, code):
69
        self.detail = {'err': code}
70

  
71

  
64 72
class PublikAuthentication(authentication.BaseAuthentication):
65 73
    def __init__(self, *args, **kwargs):
66 74
        self.logger = logging.getLogger(__name__)
......
80 88
                try:
81 89
                    return User.objects.get(uuid=name_id)
82 90
                except User.DoesNotExist:
83
                    raise exceptions.AuthenticationFailed('No user matches uuid=%r' % name_id)
91
                    raise PublikAuthenticationFailed('user-not-found')
92

  
84 93
            elif UserSAMLIdentifier:
85 94
                try:
86 95
                    return UserSAMLIdentifier.objects.get(name_id=name_id).user
87 96
                except UserSAMLIdentifier.DoesNotExist:
88
                    raise exceptions.AuthenticationFailed(
89
                            'No user matches nameid=%r' % name_id)
97
                    raise PublikAuthenticationFailed('user-not-found')
90 98
            else:
91
                raise exceptions.AuthenticationFailed(
92
                            'No usable model to match nameid=%r' % name_id)
99
                raise PublikAuthenticationFailed('no-usable-model')
93 100
        else:
94 101
            orig = request.GET['orig']
95 102
            try:
......
100 107
                klass = import_string(settings.HOBO_ANONYMOUS_SERVICE_USER_CLASS)
101 108
                self.logger.info('anonymous signature validated')
102 109
                return klass()
103
            raise exceptions.AuthenticationFailed('Anonymous service user is unsupported')
110
            raise PublikAuthenticationFailed('no-user-for-orig')
104 111

  
105 112
    def get_orig_key(self, orig):
106 113
        if not hasattr(settings, 'KNOWN_SERVICES'):
107 114
            self.logger.warning('no known services')
108
            raise exceptions.AuthenticationFailed('No KNOWN_SERVICES setting')
115
            raise PublikAuthenticationFailed('no-known-services-setting')
109 116
        for service_id in settings.KNOWN_SERVICES:
110 117
            for slug, service in settings.KNOWN_SERVICES[service_id].items():
111 118
                if service.get('verif_orig') == orig and service.get('secret'):
112 119
                    return service['secret']
113 120
        self.logger.warning('no secret found for origin %r', orig)
114
        raise exceptions.AuthenticationFailed('no secret found for origin %r' % orig)
121
        raise PublikAuthenticationFailed('no-secret-found-for-orig')
115 122

  
116 123
    def authenticate(self, request):
117 124
        full_path = request.get_full_path()
......
120 127
        key = self.get_orig_key(request.GET['orig'])
121 128
        if not signature.check_url(full_path, key):
122 129
            self.logger.warning('invalid signature')
123
            raise exceptions.AuthenticationFailed('Invalid signature')
130
            raise PublikAuthenticationFailed('invalid-signature')
124 131
        user = self.resolve_user(request)
125 132
        self.logger.info('user authenticated with signature %s', user)
126 133
        return (user, None)
tests_authentic/test_rest_authentication.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2015-2020  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

  
1 17
import pytest
2 18
from django.utils.six.moves.urllib import parse as urllib
3 19

  
4
from rest_framework.exceptions import AuthenticationFailed
5

  
6 20
from django.contrib.auth import get_user_model
7 21
from django.test import RequestFactory
8 22
from tenant_schemas.utils import tenant_context
9 23

  
24
from rest_framework.views import APIView
25
from rest_framework.response import Response
26
from rest_framework import permissions
27

  
10 28
from hobo import signature, rest_authentication
11 29

  
12 30
pytestmark = pytest.mark.django_db
......
71 89
        request = factory.get(signature.sign_url(URL + AUTH_QUERY, key + 'zob'))
72 90

  
73 91
        publik_authentication = rest_authentication.PublikAuthentication()
74
        with pytest.raises(AuthenticationFailed):
92
        with pytest.raises(rest_authentication.PublikAuthenticationFailed) as exc_info:
75 93
            publik_authentication.authenticate(request)
94
        assert exc_info.value.detail['err'] == 'invalid-signature'
95

  
96

  
97
def test_response(rf, settings, tenant):
98

  
99
    with tenant_context(tenant):
100
        request = rf.get('/')
101

  
102
        del settings.KNOWN_SERVICES
103

  
104
        class View(APIView):
105
            authentication_classes = (rest_authentication.PublikAuthentication,)
106
            permission_classes = (permissions.IsAuthenticated,)
107

  
108
            def get(self, request, format=None):
109
                return Response({'err': 0})
110

  
111
        view = View.as_view()
112

  
113
        response = view(request)
114
        assert response.status_code == 403
115

  
116
        request = rf.get('/?signature=aaa&orig=zzz')
117

  
118
        response = view(request)
119
        assert response.status_code == 401
120
        assert response.data['err'] == 'no-known-services-setting'
121

  
122
        secret_key = 'bbb'
123
        settings.KNOWN_SERVICES = {
124
            'whatever': {
125
                'whatever': {
126
                    'verif_orig': 'zzz',
127
                }
128
            }
129
        }
130

  
131
        response = view(request)
132
        assert response.status_code == 401
133
        assert response.data['err'] == 'no-secret-found-for-orig'
134

  
135
        settings.KNOWN_SERVICES['whatever']['whatever']['secret'] = secret_key
136
        response = view(request)
137
        assert response.status_code == 401
138
        assert response.data['err'] == 'invalid-signature'
139

  
140
        # User authentication
141
        request = rf.get(signature.sign_url('/?orig=zzz&NameID=1234', secret_key))
142

  
143
        response = view(request)
144
        assert response.status_code == 401
145
        assert response.data == {'err': 'user-not-found'}
146

  
147
        # Service authentication
148
        request = rf.get(signature.sign_url('/?orig=zzz', secret_key))
149

  
150
        response = view(request)
151
        assert response.status_code == 200
152
        assert response.data == {'err': 0}
76 153

  
154
        del settings.HOBO_ANONYMOUS_SERVICE_USER_CLASS
155
        response = view(request)
156
        assert response.status_code == 401
157
        assert response.data == {'err': 'no-user-for-orig'}
77
-