Skip to content

Commit 358b0df

Browse files
committed
fix: inject local user for loopback PFS requests
1 parent 6bfdec0 commit 358b0df

3 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/promptflow-devkit/promptflow/_cli/_pf/_service.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
PF_SERVICE_WORKER_NUM,
2424
)
2525
from promptflow._sdk._service.app import create_app
26+
from promptflow._sdk._service.utils.local_user_auth import LocalUserAuthMiddleware
2627
from promptflow._sdk._service.utils.utils import (
2728
check_pfs_service_status,
2829
dump_port_to_config,
@@ -47,10 +48,16 @@
4748
app = None
4849

4950

51+
def _create_app_with_local_user_auth():
52+
flask_app, _ = create_app()
53+
flask_app.wsgi_app = LocalUserAuthMiddleware(flask_app.wsgi_app)
54+
return flask_app
55+
56+
5057
def get_app(environ, start_response):
5158
global app
5259
if app is None:
53-
app, _ = create_app()
60+
app = _create_app_with_local_user_auth()
5461
if os.environ.get(PF_SERVICE_DEBUG) == "true":
5562
app.logger.setLevel(logging.DEBUG)
5663
else:
@@ -255,7 +262,7 @@ def _prepare_app_for_foreground_service(port, force_start, service_host):
255262
port = validate_port(port, force_start, service_host)
256263
global app
257264
if app is None:
258-
app, _ = create_app()
265+
app = _create_app_with_local_user_auth()
259266
if os.environ.get(PF_SERVICE_DEBUG) == "true":
260267
app.logger.setLevel(logging.DEBUG)
261268
else:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
5+
import getpass
6+
7+
_LOOPBACK_REMOTE_ADDRS = {"127.0.0.1", "::1"}
8+
9+
10+
class LocalUserAuthMiddleware:
11+
"""Inject the current OS user for local PFS loopback requests."""
12+
13+
def __init__(self, wsgi_app):
14+
self._wsgi_app = wsgi_app
15+
16+
def __call__(self, environ, start_response):
17+
if environ.get("REMOTE_ADDR") in _LOOPBACK_REMOTE_ADDRS:
18+
environ["REMOTE_USER"] = getpass.getuser()
19+
return self._wsgi_app(environ, start_response)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
5+
import getpass
6+
7+
from promptflow._sdk._service.utils.local_user_auth import LocalUserAuthMiddleware
8+
9+
10+
def _remote_user_echo_app(environ, start_response):
11+
start_response("200 OK", [])
12+
return [environ.get("REMOTE_USER", "").encode()]
13+
14+
15+
def _call_middleware(remote_addr, extra_environ=None):
16+
environ = {"REMOTE_ADDR": remote_addr}
17+
if extra_environ:
18+
environ.update(extra_environ)
19+
20+
response_status = []
21+
22+
def start_response(status, headers):
23+
response_status.append(status)
24+
25+
response_body = b"".join(LocalUserAuthMiddleware(_remote_user_echo_app)(environ, start_response)).decode()
26+
return response_status[0], response_body, environ
27+
28+
29+
def test_loopback_request_injects_current_user():
30+
status, remote_user, environ = _call_middleware("127.0.0.1")
31+
32+
assert status == "200 OK"
33+
assert remote_user == getpass.getuser()
34+
assert environ["REMOTE_USER"] == getpass.getuser()
35+
36+
37+
def test_ipv6_loopback_request_injects_current_user():
38+
_, remote_user, environ = _call_middleware("::1")
39+
40+
assert remote_user == getpass.getuser()
41+
assert environ["REMOTE_USER"] == getpass.getuser()
42+
43+
44+
def test_non_loopback_request_does_not_inject_user():
45+
_, remote_user, environ = _call_middleware("192.0.2.10")
46+
47+
assert remote_user == ""
48+
assert "REMOTE_USER" not in environ
49+
50+
51+
def test_client_supplied_remote_user_header_is_not_trusted():
52+
_, remote_user, environ = _call_middleware(
53+
"192.0.2.10",
54+
{
55+
"HTTP_REMOTE_USER": getpass.getuser(),
56+
"HTTP_X_REMOTE_USER": getpass.getuser(),
57+
},
58+
)
59+
60+
assert remote_user == ""
61+
assert "REMOTE_USER" not in environ

0 commit comments

Comments
 (0)