Skip to content

Commit 82faa52

Browse files
committed
chore(proxy): add base user proxy view
1 parent 02d90f2 commit 82faa52

1 file changed

Lines changed: 40 additions & 33 deletions

File tree

rootfs/api/views.py

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,17 +1333,41 @@ async def stream_response():
13331333

13341334

13351335
@method_decorator(csrf_exempt, name='dispatch')
1336-
class QuickwitProxyView(View):
1336+
class BaseUserProxyView(View):
13371337
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=15)
13381338
permission = permissions.IsServiceToken()
13391339
authentication = authentication.DryccAuthentication()
13401340
authentication.ignore_authentication_failed = True
1341+
1342+
async def authenticate(self, request, username):
1343+
"""
1344+
Authenticate the user based on the provided request and username.
1345+
Returns the user ID on success; returns None and an error message on failure.
1346+
"""
1347+
if self.permission.has_permission(request, None):
1348+
if username != "drycc":
1349+
return None, {'error': 'Access denied', "status_code": 403}
1350+
return -1, None
1351+
auth = await database_sync_to_async(self.authentication.authenticate)(request)
1352+
if not auth or len(auth) != 2:
1353+
return None, {'error': 'Unauthorized', "status_code": 401}
1354+
if auth[0].username != username:
1355+
return None, {'error': 'Access denied', "status_code": 403}
1356+
return auth[0].id, None
1357+
1358+
1359+
@method_decorator(csrf_exempt, name='dispatch')
1360+
class QuickwitProxyView(BaseUserProxyView):
13411361
index_url_match = re.compile(r"^indexes/?$").match
13421362
search_url_match = re.compile(r"^(?P<index>[a-zA-Z*][\w.*-,]{0,})/search/?$").match
13431363
msearch_url_match = re.compile(r"^_elastic/_msearch/?$").match
1344-
field_caps_url_match = re.compile(r"_elastic/(?P<index>[a-zA-Z*][\w.*-,]{0,})/_field_caps/?$").match
1364+
field_caps_url_match = re.compile(
1365+
r"_elastic/(?P<index>[a-zA-Z*][\w.*-,]{0,})/_field_caps/?$").match
13451366

13461367
async def proxy(self, request, username, path):
1368+
user_id, message = await self.authenticate(request, username)
1369+
if user_id is None and message is not None:
1370+
return JsonResponse(message, status=message["status_code"])
13471371
kwargs = {"request": request, "username": username}
13481372
if self.index_url_match(path):
13491373
func, kwargs["index"] = self.index, request.GET.get("index_id_patterns", "*")
@@ -1354,17 +1378,7 @@ async def proxy(self, request, username, path):
13541378
elif match := self.field_caps_url_match(path):
13551379
func, kwargs["index"] = self.field_caps, match.group("index")
13561380
else:
1357-
raise JsonResponse({'error': 'Not Found'}, status=404)
1358-
1359-
if self.permission.has_permission(request, None):
1360-
if username != "drycc":
1361-
return JsonResponse({'error': 'Access denied'}, status=403)
1362-
else:
1363-
auth = await database_sync_to_async(self.authentication.authenticate)(request)
1364-
if not auth or len(auth) != 2:
1365-
return JsonResponse({'error': 'Unauthorized'}, status=401)
1366-
if auth[0].username != username:
1367-
return JsonResponse({'error': 'Access denied'}, status=403)
1381+
return JsonResponse({'error': 'Not Found'}, status=404)
13681382
return await func(**kwargs)
13691383

13701384
async def index(self, request, username, index):
@@ -1403,7 +1417,7 @@ async def msearch(self, request, username):
14031417
).split(",")
14041418
json_lines[i] = json.dumps(request_header)
14051419
url, params = urljoin(
1406-
base_url, f"/api/v1/_elastic/_msearch"), dict(request.GET)
1420+
base_url, "/api/v1/_elastic/_msearch"), dict(request.GET)
14071421
try:
14081422
async with aiohttp.ClientSession() as session:
14091423
async with session.post(
@@ -1451,30 +1465,23 @@ async def get_app_indexes(self, username, index):
14511465

14521466

14531467
@method_decorator(csrf_exempt, name='dispatch')
1454-
class PrometheusProxyView(View):
1455-
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=15)
1456-
permission = permissions.IsServiceToken()
1457-
authentication = authentication.DryccAuthentication()
1458-
authentication.ignore_authentication_failed = True
1459-
1468+
class PrometheusProxyView(BaseUserProxyView):
14601469
async def proxy(self, request, username, path):
1461-
if self.permission.has_permission(request, None):
1462-
if username != "drycc":
1463-
return JsonResponse({'error': 'Access denied'}, status=403)
1470+
user_id, message = await self.authenticate(request, username)
1471+
if user_id is None and message is not None:
1472+
return JsonResponse(message, status=message["status_code"])
1473+
if username == "drycc":
14641474
path = f"/select/0/prometheus/{path}"
14651475
else:
1466-
auth = await database_sync_to_async(self.authentication.authenticate)(request)
1467-
if not auth or len(auth) != 2:
1468-
return JsonResponse({'error': 'Unauthorized'}, status=401)
1469-
if auth[0].username != username:
1470-
return JsonResponse({'error': 'Access denied'}, status=403)
1471-
else:
1472-
path = f"/select/{auth[0].id}/prometheus/{path}"
1473-
url = urljoin(settings.DRYCC_VICTORIAMETRICS_URL, path)
1474-
params = dict(request.GET) if request.method == "GET" else dict(request.POST)
1476+
path = f"/select/{user_id}/prometheus/{path}"
14751477
try:
14761478
async with aiohttp.ClientSession() as session:
1477-
async with session.get(url, params=params, timeout=self.timeout) as response:
1479+
async with session.post(
1480+
urljoin(settings.DRYCC_VICTORIAMETRICS_URL, path),
1481+
data=dict(request.GET) if request.method == "GET" else dict(request.POST),
1482+
headers={"Content-Type": "application/x-www-form-urlencoded"},
1483+
timeout=self.timeout
1484+
) as response:
14781485
data, status = await response.json(), response.status
14791486
except aiohttp.ClientError as e:
14801487
data, status = {'error': f'victoriametrics connection failed: {str(e)}'}, 502

0 commit comments

Comments
 (0)