refactor(auth): remove project-local user API

This commit is contained in:
2026-08-03 10:39:55 +08:00
parent c7799e951a
commit a018850257
11 changed files with 20 additions and 480 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ Headers keep standard HTTP casing:
## Internal vs Customer Difference ## Internal vs Customer Difference
The customer backend retains local auth/user-management routes under `/api/v1/auth` and `/api/v1/users`; the internal backend has migrated to Keycloak/metadata admin routes. Treat those Customer-only auth routes as delivery compatibility surface, not a source for new internal API naming. Both backend editions use Keycloak-backed metadata users and expose user administration through `/api/v1/admin/users`. Project-local user routes such as `/api/v1/users` are no longer part of the public contract.
## Recommendation ## Recommendation
+1 -1
View File
@@ -261,7 +261,7 @@ async def is_project_locked_endpoint(
""" """
return network in lockedPrjs.keys() return network in lockedPrjs.keys()
@router.get("/projects/current/lock/ownership", summary="检查项目是否被当前用户锁定", description="检查指定项目是否被当前客户端 (IP) 锁定。") @router.get("/projects/current/lock/ownership", summary="检查项目是否被当前用户锁定", description="检查指定项目是否被当前访问地址 (IP) 锁定。")
async def is_project_locked_by_me_endpoint( async def is_project_locked_by_me_endpoint(
network: str = Query(..., description="管网名称(或数据库名称)"), network: str = Query(..., description="管网名称(或数据库名称)"),
req: Request = None req: Request = None
-36
View File
@@ -1,36 +0,0 @@
from fastapi import APIRouter, Request, Query
from typing import Any, List, Dict, Union
from app.services.tjnetwork import Any, get_all_users, get_user, get_user_schema
router = APIRouter()
###########################################################
# user 39
###########################################################
@router.get("/network-schemas/user", summary="获取用户模式", description="获取指定网络的用户模式定义")
async def fastapi_get_user_schema(network: str = Query(..., description="管网名称(或数据库名称)")) -> dict[str, dict[Any, Any]]:
"""
获取用户模式定义
返回指定网络的用户模式结构定义
"""
return get_user_schema(network)
@router.get("/users/detail", summary="获取单个用户", description="获取指定网络中的单个用户信息")
async def fastapi_get_user(network: str = Query(..., description="管网名称(或数据库名称)"), user_name: str = Query(..., description="用户名")) -> dict[Any, Any]:
"""
获取用户信息
返回指定网络中指定用户名的详细信息
"""
return get_user(network, user_name)
@router.get("/users", summary="获取所有用户", description="获取指定网络的所有用户列表")
async def fastapi_get_all_users(network: str = Query(..., description="管网名称(或数据库名称)")) -> list[dict[Any, Any]]:
"""
获取所有用户列表
返回指定网络中所有用户的信息
"""
return get_all_users(network)
-6
View File
@@ -22,7 +22,6 @@ from app.api.v1.endpoints import (
sensor_placement, sensor_placement,
simulation, simulation,
snapshots, snapshots,
users,
web_search, web_search,
) )
from app.api.v1.endpoints.components import ( from app.api.v1.endpoints.components import (
@@ -152,11 +151,6 @@ api_router.include_router(
tags=["Snapshots"], tags=["Snapshots"],
dependencies=[simulation_access], dependencies=[simulation_access],
) )
api_router.include_router(
users.router,
tags=["Users"],
dependencies=[webgis_view_access],
)
api_router.include_router( api_router.include_router(
schemes.router, schemes.router,
tags=["Schemes"], tags=["Schemes"],
-2
View File
@@ -460,8 +460,6 @@ from .s36_wda_cal import (
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
from .s38_scada_info import get_scada_info_schema, get_scada_info, get_all_scada_info from .s38_scada_info import get_scada_info_schema, get_scada_info, get_all_scada_info
from .s39_user import get_user_schema, get_user, get_all_users
from .s40_schema import get_scheme_schema, get_scheme, get_all_schemes from .s40_schema import get_scheme_schema, get_scheme, get_all_schemes
from .s41_pipe_risk_probability import ( from .s41_pipe_risk_probability import (
-37
View File
@@ -1,37 +0,0 @@
from .database import *
from .s0_base import *
class User(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'user'
self.id = str(input['user_id'])
self.name = str(input['username'])
self.password = str(input['password'])
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'name': self.name, 'password': self.password }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def get_user_schema(name: str) -> dict[str, dict[Any, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'name' : {'type': 'str' , 'optional': False , 'readonly': False},
'password' : {'type': 'str' , 'optional': False , 'readonly': False} }
def get_user(name: str, user_name: str) -> dict[Any, Any]:
t = try_read(name, f"select * from users where username = '{user_name}'")
if t == None:
return {}
d = {}
d['id'] = str(t['user_id'])
d['name'] = str(t['username'])
# d['password'] = str(t['password'])
return d
def get_all_users(name: str) -> list[dict[Any, Any]]:
return read_all(name, "select * from users")
+2 -49
View File
@@ -11,53 +11,6 @@ from app.core.config import get_pgconn_string
from app.services.time_api import parse_utc_time from app.services.time_api import parse_utc_time
# 2025/03/23
def create_user(name: str, username: str, password: str):
"""
创建用户
:param name: 数据库名称
:param username: 用户名
:param password: 密码
:return:
"""
try:
# 动态替换数据库名称
conn_string = get_pgconn_string(db_name=name)
# 连接到 PostgreSQL 数据库(这里是数据库 "bb"
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO users (username, password) VALUES (%s, %s)",
(username, password),
)
# 提交事务
conn.commit()
print("新用户创建成功!")
except Exception as e:
print(f"创建用户出错:{e}")
# 2025/03/23
def delete_user(name: str, username: str):
"""
删除用户
:param name: 数据库名称
:param username: 用户名
:return:
"""
try:
# 动态替换数据库名称
conn_string = get_pgconn_string(db_name=name)
# 连接到 PostgreSQL 数据库(这里是数据库 "bb"
with psycopg.connect(conn_string) as conn:
with conn.cursor() as cur:
cur.execute("DELETE FROM users WHERE username = %s", (username,))
conn.commit()
print(f"用户 {username} 删除成功!")
except Exception as e:
print(f"删除用户出错:{e}")
# 2025/03/23 # 2025/03/23
def scheme_name_exists(name: str, scheme_name: str) -> bool: def scheme_name_exists(name: str, scheme_name: str) -> bool:
""" """
@@ -98,8 +51,8 @@ def store_scheme_info(
:param name: 数据库名称 :param name: 数据库名称
:param scheme_name: 方案名称 :param scheme_name: 方案名称
:param scheme_type: 方案类型 :param scheme_type: 方案类型
:param username: 用户名(需在 users 表中已存在) :param username: MetaDB 中的用户名快照
:param scheme_start_time: 方案起始时间(字符串) :param scheme_start_time: 带时区的方案起始时间;写入前统一转换为 UTC
:param scheme_detail: 方案详情(字典,会转换为 JSON) :param scheme_detail: 方案详情(字典,会转换为 JSON)
:return: :return:
""" """
-13
View File
@@ -1290,19 +1290,6 @@ def get_scada_info(name: str, id: str) -> dict[str, Any]:
def get_all_scada_info(name: str) -> list[dict[str, Any]]: def get_all_scada_info(name: str) -> list[dict[str, Any]]:
return api.get_all_scada_info(name) return api.get_all_scada_info(name)
# DingZQ 2025-03-27
############################################################
# 39 users
############################################################
def get_user_schema(name: str) -> dict[str, dict[str, Any]]:
return api.get_user_schema(name)
def get_user(name: str, user_name: str) -> dict[str, Any]:
return api.get_user(name, user_name=user_name)
def get_all_users(name: str) -> list[dict[str, Any]]:
return api.get_all_users(name)
############################################################ ############################################################
# scheme 40 # scheme 40
############################################################ ############################################################
+1 -1
View File
@@ -3,7 +3,7 @@
"contracts": { "contracts": {
"server": { "server": {
"file": "server-v1.openapi.json", "file": "server-v1.openapi.json",
"sha256": "d80a968d281fdb2953364a5979c2d61fda5151a1e1759c01cc96780b11a6d56c" "sha256": "9cd5b962e9556ec227c52d0dc7d4ef4af562dcea86e16877c923c37de0f4f704"
} }
} }
} }
+1 -334
View File
@@ -19425,108 +19425,6 @@
] ]
} }
}, },
"/api/v1/network-schemas/user": {
"get": {
"description": "获取指定网络的用户模式定义",
"operationId": "get_network_schemas_user",
"parameters": [
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": {
"type": "object"
},
"title": "Response Get Network Schemas User",
"type": "object"
}
}
},
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "获取用户模式",
"tags": [
"Users"
]
}
},
"/api/v1/network-schemas/valve": { "/api/v1/network-schemas/valve": {
"get": { "get": {
"description": "获取指定水网中所有阀门的架构和字段定义", "description": "获取指定水网中所有阀门的架构和字段定义",
@@ -27430,7 +27328,7 @@
}, },
"/api/v1/projects/current/lock/ownership": { "/api/v1/projects/current/lock/ownership": {
"get": { "get": {
"description": "检查指定项目是否被当前客户端 (IP) 锁定。", "description": "检查指定项目是否被当前访问地址 (IP) 锁定。",
"operationId": "get_projects_current_lock_ownership", "operationId": "get_projects_current_lock_ownership",
"parameters": [ "parameters": [
{ {
@@ -47552,237 +47450,6 @@
] ]
} }
}, },
"/api/v1/users": {
"get": {
"description": "获取指定网络的所有用户列表",
"operationId": "get_users",
"parameters": [
{
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 100,
"maximum": 1000,
"minimum": 1,
"title": "Limit",
"type": "integer"
}
},
{
"in": "query",
"name": "offset",
"required": false,
"schema": {
"default": 0,
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Page_dict_Any__Any__"
}
}
},
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "获取所有用户",
"tags": [
"Users"
]
}
},
"/api/v1/users/detail": {
"get": {
"description": "获取指定网络中的单个用户信息",
"operationId": "get_users_detail",
"parameters": [
{
"description": "用户名",
"in": "query",
"name": "user_name",
"required": true,
"schema": {
"description": "用户名",
"title": "User Name",
"type": "string"
}
},
{
"in": "header",
"name": "X-Project-Id",
"required": true,
"schema": {
"title": "X-Project-Id",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"title": "Response Get Users Detail",
"type": "object"
}
}
},
"description": "Successful Response"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Authentication required"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Insufficient permission"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource not found"
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Resource conflict"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Validation error"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
},
"description": "Dependency unavailable"
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
],
"summary": "获取单个用户",
"tags": [
"Users"
]
}
},
"/api/v1/valve-closure-analyses": { "/api/v1/valve-closure-analyses": {
"post": { "post": {
"description": "高级版本的阀门关闭分析,支持同时关闭多个阀门,并在指定持续时间内进行模拟。返回纯文本格式的分析结果。", "description": "高级版本的阀门关闭分析,支持同时关闭多个阀门,并在指定持续时间内进行模拟。返回纯文本格式的分析结果。",
+14
View File
@@ -77,6 +77,20 @@ def test_handler_router_defines_only_the_public_rest_operations() -> None:
assert ("GET", "/getpipeproperties/") not in source_operations assert ("GET", "/getpipeproperties/") not in source_operations
def test_legacy_project_user_operations_are_not_exposed() -> None:
source_paths = {
route.path
for route in source_api_router.routes
if isinstance(route, APIRoute)
}
assert {
"/network-schemas/user",
"/users",
"/users/detail",
}.isdisjoint(source_paths)
def test_rest_openapi_satisfies_contract_invariants() -> None: def test_rest_openapi_satisfies_contract_invariants() -> None:
from fastapi import FastAPI from fastapi import FastAPI