Projet

Général

Profil

0001-show-an-error-page-when-create_server-fails-57176.patch

Benjamin Dauvergne, 23 septembre 2021 10:39

Télécharger (7,83 ko)

Voir les différences:

Subject: [PATCH] show an error page when create_server fails (#57176)

 mellon/utils.py     |  6 ++++
 mellon/views.py     | 75 ++++++++++++++++++++++++++-------------------
 tests/test_views.py | 16 ++++++++++
 3 files changed, 66 insertions(+), 31 deletions(-)
mellon/utils.py
35 35
logger = logging.getLogger(__name__)
36 36

  
37 37

  
38
class CreateServerError(Exception):
39
    pass
40

  
41

  
38 42
def create_metadata(request):
39 43
    entity_id = reverse('mellon_metadata')
40 44
    login_url = reverse(app_settings.LOGIN_URL)
......
84 88
        server = lasso.Server.newFromBuffers(
85 89
            metadata, private_key_content=private_key, private_key_password=private_key_password
86 90
        )
91
        if not server:
92
            raise CreateServerError
87 93
        if app_settings.SIGNATURE_METHOD:
88 94
            symbol_name = 'SIGNATURE_METHOD_' + app_settings.SIGNATURE_METHOD.replace('-', '_').upper()
89 95
            if hasattr(lasso, symbol_name):
mellon/views.py
132 132
            args.append(idp_message)
133 133
        self.log.warning(*args)
134 134

  
135
    def dispatch(self, request, *args, **kwargs):
136
        try:
137
            return super().dispatch(request, *args, **kwargs)
138
        except utils.CreateServerError:
139
            return self.failure(
140
                request,
141
                reason=_(
142
                    'Unable to initialize a SAML server object, the private key '
143
                    'is maybe invalid or unreadable, please check its access '
144
                    'rights and content.'
145
                ),
146
            )
147

  
148
    def failure(self, request, reason='', status_codes=()):
149
        '''show error message to user after a login failure'''
150
        login = self.profile
151
        idp = utils.get_idp(login and login.remoteProviderId)
152
        if not idp and login:
153
            self.log.warning('entity id %r is unknown', login.remoteProviderId)
154
            return HttpResponseBadRequest('entity id %r is unknown' % login.remoteProviderId)
155
        error_url = utils.get_setting(idp, 'ERROR_URL')
156
        error_redirect_after_timeout = utils.get_setting(idp, 'ERROR_REDIRECT_AFTER_TIMEOUT')
157
        if error_url:
158
            error_url = resolve_url(error_url)
159
        next_url = error_url or self.get_next_url(default=resolve_url(settings.LOGIN_REDIRECT_URL))
160
        return self.render(
161
            request,
162
            'mellon/authentication_failed.html',
163
            {
164
                'debug': settings.DEBUG,
165
                'reason': reason,
166
                'status_codes': status_codes,
167
                'issuer': login and login.remoteProviderId,
168
                'next_url': next_url,
169
                'relaystate': login and login.msgRelayState,
170
                'error_redirect_after_timeout': error_redirect_after_timeout,
171
            },
172
        )
173

  
135 174

  
136 175
class LoginView(ProfileMixin, LogMixin, View):
137 176
    def dispatch(self, request, *args, **kwargs):
......
197 236
            if 'RelayState' in request.POST and utils.is_nonnull(request.POST['RelayState']):
198 237
                login.msgRelayState = request.POST['RelayState']
199 238
            return self.sso_success(request, login)
200
        return self.sso_failure(request, reason=idp_message, status_codes=status_codes)
201

  
202
    def sso_failure(self, request, reason='', status_codes=()):
203
        '''show error message to user after a login failure'''
204
        login = self.profile
205
        idp = utils.get_idp(login.remoteProviderId)
206
        if not idp:
207
            self.log.warning('entity id %r is unknown', login.remoteProviderId)
208
            return HttpResponseBadRequest('entity id %r is unknown' % login.remoteProviderId)
209
        error_url = utils.get_setting(idp, 'ERROR_URL')
210
        error_redirect_after_timeout = utils.get_setting(idp, 'ERROR_REDIRECT_AFTER_TIMEOUT')
211
        if error_url:
212
            error_url = resolve_url(error_url)
213
        next_url = error_url or self.get_next_url(default=resolve_url(settings.LOGIN_REDIRECT_URL))
214
        return self.render(
215
            request,
216
            'mellon/authentication_failed.html',
217
            {
218
                'debug': settings.DEBUG,
219
                'reason': reason,
220
                'status_codes': status_codes,
221
                'issuer': login.remoteProviderId,
222
                'next_url': next_url,
223
                'relaystate': login.msgRelayState,
224
                'error_redirect_after_timeout': error_redirect_after_timeout,
225
            },
226
        )
239
        return self.failure(request, reason=idp_message, status_codes=status_codes)
227 240

  
228 241
    def get_attribute_value(self, attribute, attribute_value):
229 242
        # check attribute_value contains only text
......
340 353
        Use a cookie to prevent looping forever.
341 354
        """
342 355
        if RETRY_LOGIN_COOKIE in self.request.COOKIES:
343
            response = self.sso_failure(
356
            response = self.failure(
344 357
                self.request, reason=_('There were too many redirections with the identity provider.')
345 358
            )
346 359
            response.delete_cookie(RETRY_LOGIN_COOKIE)
......
392 405
            )
393 406
        except RequestException as e:
394 407
            self.log.warning('unable to reach %r: %s', login.msgUrl, e)
395
            return self.sso_failure(
408
            return self.failure(
396 409
                request,
397 410
                reason=_('IdP is temporarily down, please try again ' 'later.'),
398 411
                status_codes=status_codes,
......
403 416
                result.status_code,
404 417
                result.content,
405 418
            )
406
            return self.sso_failure(request, reason=idp_message, status_codes=status_codes)
419
            return self.failure(request, reason=idp_message, status_codes=status_codes)
407 420

  
408 421
        self.log.info('Got SAML Artifact Response', extra={'saml_response': result.content})
409 422
        result.encoding = utils.get_xml_encoding(result.content)
......
447 460
            return HttpResponseBadRequest('error processing the authentication response: %r' % e)
448 461
        else:
449 462
            return self.sso_success(request, login)
450
        return self.sso_failure(request, reason=idp_message, status_codes=status_codes)
463
        return self.failure(request, reason=idp_message, status_codes=status_codes)
451 464

  
452 465
    def request_discovery_service(self, request, is_passive=False):
453 466
        return_url = request.build_absolute_uri()
tests/test_views.py
294 294
    with HTTMock(html_response):
295 295
        client.get('/login/?SAMLart=%s' % artifact)
296 296
    assert 'ArtifactResolveResponse is malformed' in caplog.text
297

  
298

  
299
def test_private_key_unreadable(private_settings, app, tmpdir):
300
    private_settings.MELLON_IDENTITY_PROVIDERS = [
301
        {
302
            'METADATA': open('tests/metadata.xml').read(),
303
        }
304
    ]
305
    # set an unreadable private key
306
    private_key = tmpdir / 'private.key'
307
    with private_key.open(mode='w') as fd:
308
        fd.write('1')
309
    private_key.chmod(0o000)
310
    private_settings.MELLON_PRIVATE_KEY = str(private_key)
311
    response = app.get('/login/?next=%2Fwhatever')
312
    assert 'Unable to initialize a SAML server object' in response
297
-