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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""HTTP Basic authentication method.
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.
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.
13Example::
15 auth.methods+ {
16 type "basic"
17 realm "My Application"
18 }
19"""
21from typing import Optional
23import base64
25import gws
26import gws.base.auth
27import gws.base.web
30@gws.ext.config.authMethod('basic')
31class Config(gws.base.auth.method.Config):
32 """HTTP basic authentication."""
34 realm: Optional[str]
35 """Authentication realm sent to the client."""
38@gws.ext.object.authMethod('basic')
39class Object(gws.base.auth.method.Object):
40 """HTTP Basic authentication method."""
42 realm: str
43 """Authentication realm sent in the ``WWW-Authenticate`` header."""
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'])
50 ##
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=}')
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)
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
74 a = h.strip().split()
75 if len(a) != 2 or a[0].lower() != 'basic':
76 return
78 try:
79 b = gws.u.to_str(base64.decodebytes(gws.u.to_bytes(a[1])))
80 except ValueError:
81 return
83 c = b.split(':')
84 if len(c) != 2:
85 return
87 username = c[0].strip()
88 if not username:
89 return
91 return gws.Data(username=username, password=c[1])