22
22
23
23
import os
24
24
import socket
25
+ import struct
25
26
import asyncio
26
27
import contextlib
27
28
import dataclasses
@@ -83,21 +84,30 @@ class HttpExposed:
83
84
method : str
84
85
path : str
85
86
auth_required : bool
87
+ allow_usc : bool
86
88
handler : Callable
87
89
88
90
89
91
_HTTP_EXPOSED = "_http_exposed"
90
92
_HTTP_METHOD = "_http_method"
91
93
_HTTP_PATH = "_http_path"
92
94
_HTTP_AUTH_REQUIRED = "_http_auth_required"
95
+ _HTTP_ALLOW_USC = "_http_allow_usc"
93
96
94
97
95
- def exposed_http (http_method : str , path : str , auth_required : bool = True ) -> Callable :
98
+ def exposed_http (
99
+ http_method : str ,
100
+ path : str ,
101
+ auth_required : bool = True ,
102
+ allow_usc : bool = True ,
103
+ ) -> Callable :
104
+
96
105
def set_attrs (handler : Callable ) -> Callable :
97
106
setattr (handler , _HTTP_EXPOSED , True )
98
107
setattr (handler , _HTTP_METHOD , http_method )
99
108
setattr (handler , _HTTP_PATH , path )
100
109
setattr (handler , _HTTP_AUTH_REQUIRED , auth_required )
110
+ setattr (handler , _HTTP_ALLOW_USC , allow_usc )
101
111
return handler
102
112
return set_attrs
103
113
@@ -108,6 +118,7 @@ def _get_exposed_http(obj: object) -> list[HttpExposed]:
108
118
method = getattr (handler , _HTTP_METHOD ),
109
119
path = getattr (handler , _HTTP_PATH ),
110
120
auth_required = getattr (handler , _HTTP_AUTH_REQUIRED ),
121
+ allow_usc = getattr (handler , _HTTP_ALLOW_USC ),
111
122
handler = handler ,
112
123
)
113
124
for handler in [getattr (obj , name ) for name in dir (obj )]
@@ -270,6 +281,29 @@ def set_request_auth_info(req: BaseRequest, info: str) -> None:
270
281
setattr (req , _REQUEST_AUTH_INFO , info )
271
282
272
283
284
+ @dataclasses .dataclass (frozen = True )
285
+ class RequestUnixCredentials :
286
+ pid : int
287
+ uid : int
288
+ gid : int
289
+
290
+
291
+ def get_request_unix_credentials (req : BaseRequest ) -> (RequestUnixCredentials | None ):
292
+ if req .transport is None :
293
+ return None
294
+ sock = req .transport .get_extra_info ("socket" )
295
+ if sock is None :
296
+ return None
297
+ try :
298
+ data = sock .getsockopt (socket .SOL_SOCKET , socket .SO_PEERCRED , struct .calcsize ("iii" ))
299
+ except Exception :
300
+ return None
301
+ (pid , uid , gid ) = struct .unpack ("iii" , data )
302
+ if pid <= 0 or uid < 0 or gid < 0 :
303
+ return None
304
+ return RequestUnixCredentials (pid = pid , uid = uid , gid = gid )
305
+
306
+
273
307
# =====
274
308
@dataclasses .dataclass (frozen = True )
275
309
class WsSession :
@@ -314,13 +348,14 @@ def run(
314
348
315
349
if unix_rm and os .path .exists (unix_path ):
316
350
os .remove (unix_path )
317
- server_socket = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM )
318
- server_socket .bind (unix_path )
351
+ sock = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM )
352
+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_PASSCRED , 1 )
353
+ sock .bind (unix_path )
319
354
if unix_mode :
320
355
os .chmod (unix_path , unix_mode )
321
356
322
357
run_app (
323
- sock = server_socket ,
358
+ sock = sock ,
324
359
app = self .__make_app (),
325
360
shutdown_timeout = 1 ,
326
361
access_log_format = access_log_format ,
0 commit comments