Coverage for gws-app/gws/plugin/auth_method/basic/__init__.py: 96%

46 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""HTTP Basic authentication method. 

2 

3The client sends the credentials with every request in an 

4``Authorization: Basic ...`` header. The method decodes the header into 

5``username`` and ``password`` credentials, passes them to the authentication 

6providers and opens a transient session for the authenticated user. No cookie 

7is set. 

8 

9The method is registered as a middleware after ``auth``. If a GET request is 

10denied with status ``403``, the response is changed to ``401`` with a 

11``WWW-Authenticate`` header, so that browsers show a login dialog. 

12 

13Example:: 

14 

15 auth.methods+ { 

16 type "basic" 

17 realm "My Application" 

18 } 

19""" 

20 

21from typing import Optional 

22 

23import base64 

24 

25import gws 

26import gws.base.auth 

27import gws.base.web 

28 

29 

30@gws.ext.config.authMethod('basic') 

31class Config(gws.base.auth.method.Config): 

32 """HTTP basic authentication.""" 

33 

34 realm: Optional[str] 

35 """Authentication realm sent to the client.""" 

36 

37 

38@gws.ext.object.authMethod('basic') 

39class Object(gws.base.auth.method.Object): 

40 """HTTP Basic authentication method.""" 

41 

42 realm: str 

43 """Authentication realm sent in the ``WWW-Authenticate`` header.""" 

44 

45 def configure(self): 

46 self.uid = 'gws.plugin.auth_method.basic' 

47 self.realm = self.cfg('realm', default='Restricted Area') 

48 self.root.app.middlewareMgr.register(self, self.uid, depends_on=['auth']) 

49 

50 ## 

51 

52 def exit_middleware(self, req, res): 

53 if res.status == 403 and req.isGet: 

54 res.set_status(401) 

55 res.add_header('WWW-Authenticate', f'Basic realm={self.realm}, charset="UTF-8"') 

56 gws.log.debug(f'auth basic: redirect {res.status=}') 

57 

58 

59 def open_session(self, req): 

60 am = self.root.app.authMgr 

61 credentials = self._parse_header(req) 

62 if not credentials: 

63 return 

64 user = am.authenticate(self, credentials, req) 

65 if user: 

66 return am.create_transient_session(self, user) 

67 

68 def _parse_header(self, req: gws.WebRequester): 

69 """Extract the username and password from the ``Authorization`` header.""" 

70 h = req.header('Authorization') 

71 if not h: 

72 return 

73 

74 a = h.strip().split() 

75 if len(a) != 2 or a[0].lower() != 'basic': 

76 return 

77 

78 try: 

79 b = gws.u.to_str(base64.decodebytes(gws.u.to_bytes(a[1]))) 

80 except ValueError: 

81 return 

82 

83 c = b.split(':') 

84 if len(c) != 2: 

85 return 

86 

87 username = c[0].strip() 

88 if not username: 

89 return 

90 

91 return gws.Data(username=username, password=c[1])