Coverage for distro_tracker/signon/middleware.py: 95%

28 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2025-01-12 09:15 +0000

1# Copyright 2020-2023 Enrico Zini <enrico@debian.org> 

2# Copyright 2023 The Debusine Developers 

3# See the COPYRIGHT file at the top-level directory of this distribution 

4# 

5# This file is part of Distro Tracker. It is subject to the license terms 

6# in the LICENSE file found in the top-level directory of this 

7# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

8# including this file, may be copied, modified, propagated, or distributed 

9# except according to the terms contained in the LICENSE file. 

10 

11""" 

12Authentication middleware that authenticates using signon Providers. 

13 

14It adds a `request.signon` member that is a Signon object, providing an entry 

15point for managing externally authenticated identities. 

16""" 

17from collections.abc import Callable 

18from typing import Protocol, cast, runtime_checkable 

19 

20import django.http 

21from django.conf import settings 

22from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed 

23from django.utils.module_loading import import_string 

24 

25from distro_tracker.signon.signon import Signon 

26 

27 

28@runtime_checkable 

29class RequestSignonProtocol(Protocol): 

30 """A Django request that has been processed by :class:`SignonMiddleware`.""" 

31 

32 signon: Signon 

33 

34 

35class SignonMiddleware: 

36 """Authenticate via external signon providers.""" 

37 

38 signon_class: type[Signon] 

39 

40 def __init__( 

41 self, 

42 get_response: Callable[ 

43 [django.http.HttpRequest], django.http.HttpResponse 

44 ], 

45 ) -> None: 

46 """Middleware API entry point.""" 

47 self.providers = getattr(settings, "SIGNON_PROVIDERS", ()) 

48 if not self.providers: 

49 raise MiddlewareNotUsed() 

50 

51 # Find the Signon class to use. This allows customizing behaviour by 

52 # subclassing Signon 

53 if ( 53 ↛ 56line 53 didn't jump to line 56, because the condition on line 53 was never true

54 signon_class_path := getattr(settings, "SIGNON_CLASS", None) 

55 ) is None: 

56 self.signon_class = Signon 

57 else: 

58 self.signon_class = import_string(signon_class_path) 

59 if not issubclass(self.signon_class, Signon): 

60 raise ImproperlyConfigured( 

61 f"{signon_class_path} is not a subclass of Signon" 

62 ) 

63 

64 self.get_response = get_response 

65 

66 def __call__( 

67 self, request: django.http.HttpRequest 

68 ) -> django.http.HttpResponse: 

69 """Middleware API entry point.""" 

70 # AuthenticationMiddleware is required so that request.user exists. 

71 if not hasattr(request, 'user'): 

72 raise ImproperlyConfigured( 

73 "The signon middleware requires the authentication middleware" 

74 " to be installed. Edit your MIDDLEWARE setting to insert" 

75 " 'django.contrib.auth.middleware.AuthenticationMiddleware'" 

76 " before the SignonMiddleware class." 

77 ) 

78 

79 # Add request.signon 

80 cast(RequestSignonProtocol, request).signon = self.signon_class(request) 

81 

82 return self.get_response(request)