Files

150 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Единый модуль аутентификации и создания HTTP-клиента.
Раньше _client(), _client_id(), _stand(), _token_info() были продублированы
в main.py и api_test.py с идентичным или почти идентичным кодом.
Теперь всё здесь — один источник правды для всех роутов.
Функции (все без аргументов — берут данные из Flask request/current_app):
get_token() — токен: cookie → env-переменная
get_client() — HttpClient с автоопределением стенда
get_client_id() — ClientID из JWT (base64url, без проверки подписи)
get_stand() — "dev" / "test" по токену
get_token_info() — {email, company, client_id} из JWT для UI
get_token_masked() — маскированный токен (abc...xyz) для placeholder
"""
import base64
import json
from flask import request, current_app
from api.http_client import HttpClient, detect_endpoint, stand_name, STANDS
def get_token():
"""Получить активный токен: сначала из cookie пользователя, потом из env.
Приоритет:
1. cookie "token" — пользователь ввёл свой токен в форме
2. NUBES_API_TOKEN из env — сервисный токен (для автоматических тестов)
Пользовательский токен приоритетнее — он переопределяет сервисный."""
return request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
def _make_client(*, use_polygon):
"""Фабрика HttpClient — ЕДИНСТВЕННОЕ место где выбирается endpoint.
Args:
use_polygon: если True и POLYGON_ENDPOINT задан → polygon.
Если False → всегда реальный API (для чтения сервисов/инстансов).
Returns:
HttpClient с правильным endpoint и токеном.
"""
token = get_token()
# Режим полигона — только если разрешено и переменная задана
if use_polygon:
polygon = current_app.config.get("POLYGON_ENDPOINT", "")
if polygon:
return HttpClient(polygon, token)
# Реальный API: автоопределение или NUBES_API_ENDPOINT
endpoint = current_app.config["NUBES_API_ENDPOINT"]
if endpoint in STANDS:
endpoint = detect_endpoint(token) or endpoint
return HttpClient(endpoint, token)
def get_client():
"""HttpClient для ОПЕРАЦИЙ (create/modify/delete/run/poll).
Если POLYGON_ENDPOINT задан → polygon.
Иначе → автоопределение реального стенда.
"""
return _make_client(use_polygon=True)
def get_real_client():
"""HttpClient для ЧТЕНИЯ (сервисы, список инстансов).
ВСЕГДА реальный API, даже при POLYGON_ENDPOINT.
"""
return _make_client(use_polygon=False)
def get_client_id():
"""Извлечь ClientID из payload JWT-токена (base64url, без проверки подписи).
JWT состоит из трёх частей: header.payload.signature, разделённых точкой.
Нам нужен ТОЛЬКО payload — он в base64url (не base64!).
ClientID используется для:
- Изоляции данных в БД (runs.client_id)
- Изоляции трекера инстансов (/tmp/instances-{clientId}-{stand}.json)
- Отображения в UI
Безопасность: мы НЕ проверяем подпись — это не нужно.
Токен уже проверен Nubes API (detect_endpoint делает реальный запрос)."""
token = get_token()
try:
parts = token.split(".") # [header, payload, signature]
if len(parts) >= 2:
# base64url → добавляем padding ("==") на случай если длина не кратна 4
payload = base64.urlsafe_b64decode(parts[1] + "==")
return json.loads(payload).get("ClientID", "")
except Exception:
pass # битый токен — не критично, вернём пустую строку
return ""
def get_stand():
"""Определить стенд (dev/test/mock) по активному токену.
Если POLYGON_ENDPOINT задан → "polygon".
Иначе — автоопределение или stand_name."""
if current_app.config.get("POLYGON_ENDPOINT", ""):
return "polygon"
token = get_token()
endpoint = current_app.config["NUBES_API_ENDPOINT"]
if endpoint not in STANDS:
s = stand_name(endpoint)
return s if s != "?" else "mock"
endpoint = detect_endpoint(token) or endpoint
return stand_name(endpoint)
def get_token_info():
"""Извлечь {email, company, client_id} из JWT — для отображения в топбаре UI.
Возвращает dict с ключами: email, company, client_id.
Если JWT невалиден — возвращает пустой {}."""
token = get_token()
try:
parts = token.split(".")
if len(parts) >= 2:
payload = base64.urlsafe_b64decode(parts[1] + "==")
d = json.loads(payload)
return {
"email": d.get("email", ""),
"company": d.get("company_name", ""),
"client_id": d.get("ClientID", ""),
}
except Exception:
pass
return {}
def get_token_masked():
"""Маскированный env-токен для placeholder в форме: abc...xyz.
Используется ТОЛЬКО env-токен (не пользовательский!).
Если токен короче 8 символов — возвращает пустую строку."""
token = current_app.config["NUBES_API_TOKEN"]
if not token or len(token) < 8:
return ""
# Первые 4 символа + звёздочки + последние 4 символа
return token[:4] + "*" * (len(token) - 8) + token[-4:]