mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-29 22:56:36 +08:00
Add User API Token Management to Admin API and CLI (#12595)
## Summary
This PR extends the RAGFlow Admin API and CLI with comprehensive user
API token management capabilities. Administrators can now generate,
list, and delete API tokens for users through both the REST API and the
Admin CLI interface.
## Changes
### Backend API (`admin/server/`)
#### New Endpoints
- **POST `/api/v1/admin/users/<username>/new_token`** - Generate a new
API token for a user
- **GET `/api/v1/admin/users/<username>/token_list`** - List all API
tokens for a user
- **DELETE `/api/v1/admin/users/<username>/token/<token>`** - Delete a
specific API token for a user
#### Service Layer Updates (`services.py`)
- Added `get_user_api_key(username)` - Retrieves all API tokens for a
user
- Added `save_api_token(api_token)` - Saves a new API token to the
database
- Added `delete_api_token(username, token)` - Deletes an API token for a
user
### Admin CLI (`admin/client/`)
#### New Commands
- **`GENERATE TOKEN FOR USER <username>;`** - Generate a new API token
for the specified user
- **`LIST TOKENS OF <username>;`** - List all API tokens associated with
a user
- **`DROP TOKEN <token> OF <username>;`** - Delete a specific API token
for a user
### Testing
Added comprehensive test suite in `test/testcases/test_admin_api/`:
- **`test_generate_user_api_key.py`** - Tests for API token generation
- **`test_get_user_api_key.py`** - Tests for listing user API tokens
- **`test_delete_user_api_key.py`** - Tests for deleting API tokens
- **`conftest.py`** - Shared test fixtures and utilities
## Technical Details
### Token Generation
- Tokens are generated using `generate_confirmation_token()` utility
- Each token includes metadata: `tenant_id`, `token`, `beta`,
`create_time`, `create_date`
- Tokens are associated with user tenants automatically
### Security Considerations
- All endpoints require admin authentication (`@check_admin_auth`)
- Tokens are URL-encoded when passed in DELETE requests to handle
special characters
- Proper error handling for unauthorized access and missing resources
### API Response Format
All endpoints follow the standard RAGFlow response format:
```json
{
"code": 0,
"data": {...},
"message": "Success message"
}
```
## Files Changed
- `admin/client/admin_client.py` - CLI token management commands
- `admin/server/routes.py` - New API endpoints
- `admin/server/services.py` - Token management service methods
- `docs/guides/admin/admin_cli.md` - CLI documentation updates
- `test/testcases/test_admin_api/conftest.py` - Test fixtures
- `test/testcases/test_admin_api/test_user_api_key_management/*` - Test
suites
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
---------
Co-authored-by: Alexander Strasser <alexander.strasser@ondewo.com>
Co-authored-by: Hetavi Shah <your.email@example.com>
This commit is contained in:
@ -15,8 +15,11 @@
|
||||
#
|
||||
|
||||
import secrets
|
||||
from typing import Any
|
||||
|
||||
from flask import Blueprint, request
|
||||
from common.time_utils import current_timestamp, datetime_format
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, Response, request
|
||||
from flask_login import current_user, login_required, logout_user
|
||||
|
||||
from auth import login_verify, login_admin, check_admin_auth
|
||||
@ -25,19 +28,20 @@ from services import UserMgr, ServiceMgr, UserServiceMgr, SettingsMgr, ConfigMgr
|
||||
from roles import RoleMgr
|
||||
from api.common.exceptions import AdminException
|
||||
from common.versions import get_ragflow_version
|
||||
from api.utils.api_utils import generate_confirmation_token
|
||||
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/api/v1/admin')
|
||||
admin_bp = Blueprint("admin", __name__, url_prefix="/api/v1/admin")
|
||||
|
||||
|
||||
@admin_bp.route('/ping', methods=['GET'])
|
||||
@admin_bp.route("/ping", methods=["GET"])
|
||||
def ping():
|
||||
return success_response('PONG')
|
||||
return success_response("PONG")
|
||||
|
||||
|
||||
@admin_bp.route('/login', methods=['POST'])
|
||||
@admin_bp.route("/login", methods=["POST"])
|
||||
def login():
|
||||
if not request.json:
|
||||
return error_response('Authorize admin failed.' ,400)
|
||||
return error_response("Authorize admin failed.", 400)
|
||||
try:
|
||||
email = request.json.get("email", "")
|
||||
password = request.json.get("password", "")
|
||||
@ -46,7 +50,7 @@ def login():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/logout', methods=['GET'])
|
||||
@admin_bp.route("/logout", methods=["GET"])
|
||||
@login_required
|
||||
def logout():
|
||||
try:
|
||||
@ -58,7 +62,7 @@ def logout():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/auth', methods=['GET'])
|
||||
@admin_bp.route("/auth", methods=["GET"])
|
||||
@login_verify
|
||||
def auth_admin():
|
||||
try:
|
||||
@ -67,7 +71,7 @@ def auth_admin():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users', methods=['GET'])
|
||||
@admin_bp.route("/users", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def list_users():
|
||||
@ -78,18 +82,18 @@ def list_users():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users', methods=['POST'])
|
||||
@admin_bp.route("/users", methods=["POST"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def create_user():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'username' not in data or 'password' not in data:
|
||||
if not data or "username" not in data or "password" not in data:
|
||||
return error_response("Username and password are required", 400)
|
||||
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
role = data.get('role', 'user')
|
||||
username = data["username"]
|
||||
password = data["password"]
|
||||
role = data.get("role", "user")
|
||||
|
||||
res = UserMgr.create_user(username, password, role)
|
||||
if res["success"]:
|
||||
@ -105,7 +109,7 @@ def create_user():
|
||||
return error_response(str(e))
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>', methods=['DELETE'])
|
||||
@admin_bp.route("/users/<username>", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def delete_user(username):
|
||||
@ -122,16 +126,16 @@ def delete_user(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/password', methods=['PUT'])
|
||||
@admin_bp.route("/users/<username>/password", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def change_password(username):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'new_password' not in data:
|
||||
if not data or "new_password" not in data:
|
||||
return error_response("New password is required", 400)
|
||||
|
||||
new_password = data['new_password']
|
||||
new_password = data["new_password"]
|
||||
msg = UserMgr.update_user_password(username, new_password)
|
||||
return success_response(None, msg)
|
||||
|
||||
@ -141,15 +145,15 @@ def change_password(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/activate', methods=['PUT'])
|
||||
@admin_bp.route("/users/<username>/activate", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def alter_user_activate_status(username):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'activate_status' not in data:
|
||||
if not data or "activate_status" not in data:
|
||||
return error_response("Activation status is required", 400)
|
||||
activate_status = data['activate_status']
|
||||
activate_status = data["activate_status"]
|
||||
msg = UserMgr.update_user_activate_status(username, activate_status)
|
||||
return success_response(None, msg)
|
||||
except AdminException as e:
|
||||
@ -158,7 +162,7 @@ def alter_user_activate_status(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/admin', methods=['PUT'])
|
||||
@admin_bp.route("/users/<username>/admin", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def grant_admin(username):
|
||||
@ -173,7 +177,8 @@ def grant_admin(username):
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/users/<username>/admin', methods=['DELETE'])
|
||||
|
||||
@admin_bp.route("/users/<username>/admin", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def revoke_admin(username):
|
||||
@ -188,7 +193,8 @@ def revoke_admin(username):
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/users/<username>', methods=['GET'])
|
||||
|
||||
@admin_bp.route("/users/<username>", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_user_details(username):
|
||||
@ -202,7 +208,7 @@ def get_user_details(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/datasets', methods=['GET'])
|
||||
@admin_bp.route("/users/<username>/datasets", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_user_datasets(username):
|
||||
@ -216,7 +222,7 @@ def get_user_datasets(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<username>/agents', methods=['GET'])
|
||||
@admin_bp.route("/users/<username>/agents", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_user_agents(username):
|
||||
@ -230,7 +236,7 @@ def get_user_agents(username):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/services', methods=['GET'])
|
||||
@admin_bp.route("/services", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_services():
|
||||
@ -241,7 +247,7 @@ def get_services():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/service_types/<service_type>', methods=['GET'])
|
||||
@admin_bp.route("/service_types/<service_type>", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_services_by_type(service_type_str):
|
||||
@ -252,7 +258,7 @@ def get_services_by_type(service_type_str):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/services/<service_id>', methods=['GET'])
|
||||
@admin_bp.route("/services/<service_id>", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_service(service_id):
|
||||
@ -263,7 +269,7 @@ def get_service(service_id):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/services/<service_id>', methods=['DELETE'])
|
||||
@admin_bp.route("/services/<service_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def shutdown_service(service_id):
|
||||
@ -274,7 +280,7 @@ def shutdown_service(service_id):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/services/<service_id>', methods=['PUT'])
|
||||
@admin_bp.route("/services/<service_id>", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def restart_service(service_id):
|
||||
@ -285,38 +291,38 @@ def restart_service(service_id):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles', methods=['POST'])
|
||||
@admin_bp.route("/roles", methods=["POST"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def create_role():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'role_name' not in data:
|
||||
if not data or "role_name" not in data:
|
||||
return error_response("Role name is required", 400)
|
||||
role_name: str = data['role_name']
|
||||
description: str = data['description']
|
||||
role_name: str = data["role_name"]
|
||||
description: str = data["description"]
|
||||
res = RoleMgr.create_role(role_name, description)
|
||||
return success_response(res)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles/<role_name>', methods=['PUT'])
|
||||
@admin_bp.route("/roles/<role_name>", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def update_role(role_name: str):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'description' not in data:
|
||||
if not data or "description" not in data:
|
||||
return error_response("Role description is required", 400)
|
||||
description: str = data['description']
|
||||
description: str = data["description"]
|
||||
res = RoleMgr.update_role_description(role_name, description)
|
||||
return success_response(res)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles/<role_name>', methods=['DELETE'])
|
||||
@admin_bp.route("/roles/<role_name>", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def delete_role(role_name: str):
|
||||
@ -327,7 +333,7 @@ def delete_role(role_name: str):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles', methods=['GET'])
|
||||
@admin_bp.route("/roles", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def list_roles():
|
||||
@ -338,7 +344,7 @@ def list_roles():
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles/<role_name>/permission', methods=['GET'])
|
||||
@admin_bp.route("/roles/<role_name>/permission", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_role_permission(role_name: str):
|
||||
@ -349,54 +355,54 @@ def get_role_permission(role_name: str):
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles/<role_name>/permission', methods=['POST'])
|
||||
@admin_bp.route("/roles/<role_name>/permission", methods=["POST"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def grant_role_permission(role_name: str):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'actions' not in data or 'resource' not in data:
|
||||
if not data or "actions" not in data or "resource" not in data:
|
||||
return error_response("Permission is required", 400)
|
||||
actions: list = data['actions']
|
||||
resource: str = data['resource']
|
||||
actions: list = data["actions"]
|
||||
resource: str = data["resource"]
|
||||
res = RoleMgr.grant_role_permission(role_name, actions, resource)
|
||||
return success_response(res)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/roles/<role_name>/permission', methods=['DELETE'])
|
||||
@admin_bp.route("/roles/<role_name>/permission", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def revoke_role_permission(role_name: str):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'actions' not in data or 'resource' not in data:
|
||||
if not data or "actions" not in data or "resource" not in data:
|
||||
return error_response("Permission is required", 400)
|
||||
actions: list = data['actions']
|
||||
resource: str = data['resource']
|
||||
actions: list = data["actions"]
|
||||
resource: str = data["resource"]
|
||||
res = RoleMgr.revoke_role_permission(role_name, actions, resource)
|
||||
return success_response(res)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<user_name>/role', methods=['PUT'])
|
||||
@admin_bp.route("/users/<user_name>/role", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def update_user_role(user_name: str):
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'role_name' not in data:
|
||||
if not data or "role_name" not in data:
|
||||
return error_response("Role name is required", 400)
|
||||
role_name: str = data['role_name']
|
||||
role_name: str = data["role_name"]
|
||||
res = RoleMgr.update_user_role(user_name, role_name)
|
||||
return success_response(res)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route('/users/<user_name>/permission', methods=['GET'])
|
||||
@admin_bp.route("/users/<user_name>/permission", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_user_permission(user_name: str):
|
||||
@ -406,19 +412,20 @@ def get_user_permission(user_name: str):
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/variables', methods=['PUT'])
|
||||
|
||||
@admin_bp.route("/variables", methods=["PUT"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def set_variable():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data and 'var_name' not in data:
|
||||
if not data and "var_name" not in data:
|
||||
return error_response("Var name is required", 400)
|
||||
|
||||
if 'var_value' not in data:
|
||||
if "var_value" not in data:
|
||||
return error_response("Var value is required", 400)
|
||||
var_name: str = data['var_name']
|
||||
var_value: str = data['var_value']
|
||||
var_name: str = data["var_name"]
|
||||
var_value: str = data["var_value"]
|
||||
|
||||
SettingsMgr.update_by_name(var_name, var_value)
|
||||
return success_response(None, "Set variable successfully")
|
||||
@ -427,7 +434,8 @@ def set_variable():
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/variables', methods=['GET'])
|
||||
|
||||
@admin_bp.route("/variables", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_variable():
|
||||
@ -439,9 +447,9 @@ def get_variable():
|
||||
|
||||
# get var
|
||||
data = request.get_json()
|
||||
if not data and 'var_name' not in data:
|
||||
if not data and "var_name" not in data:
|
||||
return error_response("Var name is required", 400)
|
||||
var_name: str = data['var_name']
|
||||
var_name: str = data["var_name"]
|
||||
res = SettingsMgr.get_by_name(var_name)
|
||||
return success_response(res)
|
||||
except AdminException as e:
|
||||
@ -449,7 +457,8 @@ def get_variable():
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/configs', methods=['GET'])
|
||||
|
||||
@admin_bp.route("/configs", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_config():
|
||||
@ -461,7 +470,8 @@ def get_config():
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/environments', methods=['GET'])
|
||||
|
||||
@admin_bp.route("/environments", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_environments():
|
||||
@ -473,7 +483,69 @@ def get_environments():
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
@admin_bp.route('/version', methods=['GET'])
|
||||
|
||||
@admin_bp.route("/users/<username>/new_token", methods=["POST"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def generate_user_api_key(username: str) -> tuple[Response, int]:
|
||||
try:
|
||||
user_details: list[dict[str, Any]] = UserMgr.get_user_details(username)
|
||||
if not user_details:
|
||||
return error_response("User not found!", 404)
|
||||
tenants: list[dict[str, Any]] = UserServiceMgr.get_user_tenants(username)
|
||||
if not tenants:
|
||||
return error_response("Tenant not found!", 404)
|
||||
tenant_id: str = tenants[0]["tenant_id"]
|
||||
token: str = generate_confirmation_token()
|
||||
obj: dict[str, Any] = {
|
||||
"tenant_id": tenant_id,
|
||||
"token": token,
|
||||
"beta": generate_confirmation_token().replace("ragflow-", "")[:32],
|
||||
"create_time": current_timestamp(),
|
||||
"create_date": datetime_format(datetime.now()),
|
||||
"update_time": None,
|
||||
"update_date": None,
|
||||
}
|
||||
|
||||
if not UserMgr.save_api_token(obj):
|
||||
return error_response("Failed to generate API key!", 500)
|
||||
return success_response(obj, "API key generated successfully")
|
||||
except AdminException as e:
|
||||
return error_response(e.message, e.code)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route("/users/<username>/token_list", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def get_user_api_keys(username: str) -> tuple[Response, int]:
|
||||
try:
|
||||
api_keys: list[dict[str, Any]] = UserMgr.get_user_api_key(username)
|
||||
return success_response(api_keys, "Get user API keys")
|
||||
except AdminException as e:
|
||||
return error_response(e.message, e.code)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route("/users/<username>/token/<token>", methods=["DELETE"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def delete_user_api_key(username: str, token: str) -> tuple[Response, int]:
|
||||
try:
|
||||
deleted = UserMgr.delete_api_token(username, token)
|
||||
if deleted:
|
||||
return success_response(None, "API key deleted successfully")
|
||||
else:
|
||||
return error_response("API key not found or could not be deleted", 404)
|
||||
except AdminException as e:
|
||||
return error_response(e.message, e.code)
|
||||
except Exception as e:
|
||||
return error_response(str(e), 500)
|
||||
|
||||
|
||||
@admin_bp.route("/version", methods=["GET"])
|
||||
@login_required
|
||||
@check_admin_auth
|
||||
def show_version():
|
||||
|
||||
@ -17,14 +17,18 @@
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from werkzeug.security import check_password_hash
|
||||
from common.constants import ActiveEnum
|
||||
from api.db.services import UserService
|
||||
from api.db.joint_services.user_account_service import create_new_user, delete_user_data
|
||||
from api.db.services.canvas_service import UserCanvasService
|
||||
from api.db.services.user_service import TenantService
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.system_settings_service import SystemSettingsService
|
||||
from api.db.services.api_service import APITokenService
|
||||
from api.db.db_models import APIToken
|
||||
from api.utils.crypt import decrypt
|
||||
from api.utils import health_utils
|
||||
|
||||
@ -38,13 +42,15 @@ class UserMgr:
|
||||
users = UserService.get_all_users()
|
||||
result = []
|
||||
for user in users:
|
||||
result.append({
|
||||
'email': user.email,
|
||||
'nickname': user.nickname,
|
||||
'create_date': user.create_date,
|
||||
'is_active': user.is_active,
|
||||
'is_superuser': user.is_superuser,
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"email": user.email,
|
||||
"nickname": user.nickname,
|
||||
"create_date": user.create_date,
|
||||
"is_active": user.is_active,
|
||||
"is_superuser": user.is_superuser,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@ -53,19 +59,21 @@ class UserMgr:
|
||||
users = UserService.query_user_by_email(username)
|
||||
result = []
|
||||
for user in users:
|
||||
result.append({
|
||||
'avatar': user.avatar,
|
||||
'email': user.email,
|
||||
'language': user.language,
|
||||
'last_login_time': user.last_login_time,
|
||||
'is_active': user.is_active,
|
||||
'is_anonymous': user.is_anonymous,
|
||||
'login_channel': user.login_channel,
|
||||
'status': user.status,
|
||||
'is_superuser': user.is_superuser,
|
||||
'create_date': user.create_date,
|
||||
'update_date': user.update_date
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"avatar": user.avatar,
|
||||
"email": user.email,
|
||||
"language": user.language,
|
||||
"last_login_time": user.last_login_time,
|
||||
"is_active": user.is_active,
|
||||
"is_anonymous": user.is_anonymous,
|
||||
"login_channel": user.login_channel,
|
||||
"status": user.status,
|
||||
"is_superuser": user.is_superuser,
|
||||
"create_date": user.create_date,
|
||||
"update_date": user.update_date,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@ -127,8 +135,8 @@ class UserMgr:
|
||||
# format activate_status before handle
|
||||
_activate_status = activate_status.lower()
|
||||
target_status = {
|
||||
'on': ActiveEnum.ACTIVE.value,
|
||||
'off': ActiveEnum.INACTIVE.value,
|
||||
"on": ActiveEnum.ACTIVE.value,
|
||||
"off": ActiveEnum.INACTIVE.value,
|
||||
}.get(_activate_status)
|
||||
if not target_status:
|
||||
raise AdminException(f"Invalid activate_status: {activate_status}")
|
||||
@ -138,6 +146,49 @@ class UserMgr:
|
||||
UserService.update_user(usr.id, {"is_active": target_status})
|
||||
return f"Turn {_activate_status} user activate status successfully!"
|
||||
|
||||
@staticmethod
|
||||
def get_user_api_key(username: str) -> list[dict[str, Any]]:
|
||||
# use email to find user. check exist and unique.
|
||||
user_list: list[Any] = UserService.query_user_by_email(username)
|
||||
if not user_list:
|
||||
raise UserNotFoundError(username)
|
||||
elif len(user_list) > 1:
|
||||
raise AdminException(f"More than one user with username '{username}' found!")
|
||||
|
||||
usr: Any = user_list[0]
|
||||
# tenant_id is typically the same as user_id for the owner tenant
|
||||
tenant_id: str = usr.id
|
||||
|
||||
# Query all API tokens for this tenant
|
||||
api_tokens: Any = APITokenService.query(tenant_id=tenant_id)
|
||||
|
||||
result: list[dict[str, Any]] = []
|
||||
for token_obj in api_tokens:
|
||||
result.append(token_obj.to_dict())
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def save_api_token(api_token: dict[str, Any]) -> bool:
|
||||
return APITokenService.save(**api_token)
|
||||
|
||||
@staticmethod
|
||||
def delete_api_token(username: str, token: str) -> bool:
|
||||
# use email to find user. check exist and unique.
|
||||
user_list: list[Any] = UserService.query_user_by_email(username)
|
||||
if not user_list:
|
||||
raise UserNotFoundError(username)
|
||||
elif len(user_list) > 1:
|
||||
raise AdminException(f"Exist more than 1 user: {username}!")
|
||||
|
||||
usr: Any = user_list[0]
|
||||
# tenant_id is typically the same as user_id for the owner tenant
|
||||
tenant_id: str = usr.id
|
||||
|
||||
# Delete the API token
|
||||
deleted_count: int = APITokenService.filter_delete([APIToken.tenant_id == tenant_id, APIToken.token == token])
|
||||
return deleted_count > 0
|
||||
|
||||
@staticmethod
|
||||
def grant_admin(username: str):
|
||||
# use email to find user. check exist and unique.
|
||||
@ -146,6 +197,7 @@ class UserMgr:
|
||||
raise UserNotFoundError(username)
|
||||
elif len(user_list) > 1:
|
||||
raise AdminException(f"Exist more than 1 user: {username}!")
|
||||
|
||||
# check activate status different from new
|
||||
usr = user_list[0]
|
||||
if usr.is_superuser:
|
||||
@ -172,7 +224,6 @@ class UserMgr:
|
||||
|
||||
|
||||
class UserServiceMgr:
|
||||
|
||||
@staticmethod
|
||||
def get_user_datasets(username):
|
||||
# use email to find user.
|
||||
@ -202,39 +253,43 @@ class UserServiceMgr:
|
||||
tenant_ids = [m["tenant_id"] for m in tenants]
|
||||
# filter permitted agents and owned agents
|
||||
res = UserCanvasService.get_all_agents_by_tenant_ids(tenant_ids, usr.id)
|
||||
return [{
|
||||
'title': r['title'],
|
||||
'permission': r['permission'],
|
||||
'canvas_category': r['canvas_category'].split('_')[0],
|
||||
'avatar': r['avatar']
|
||||
} for r in res]
|
||||
return [{"title": r["title"], "permission": r["permission"], "canvas_category": r["canvas_category"].split("_")[0], "avatar": r["avatar"]} for r in res]
|
||||
|
||||
@staticmethod
|
||||
def get_user_tenants(email: str) -> list[dict[str, Any]]:
|
||||
users: list[Any] = UserService.query_user_by_email(email)
|
||||
if not users:
|
||||
raise UserNotFoundError(email)
|
||||
user: Any = users[0]
|
||||
|
||||
tenants: list[dict[str, Any]] = UserTenantService.get_tenants_by_user_id(user.id)
|
||||
return tenants
|
||||
|
||||
|
||||
class ServiceMgr:
|
||||
|
||||
@staticmethod
|
||||
def get_all_services():
|
||||
doc_engine = os.getenv('DOC_ENGINE', 'elasticsearch')
|
||||
doc_engine = os.getenv("DOC_ENGINE", "elasticsearch")
|
||||
result = []
|
||||
configs = SERVICE_CONFIGS.configs
|
||||
for service_id, config in enumerate(configs):
|
||||
config_dict = config.to_dict()
|
||||
if config_dict['service_type'] == 'retrieval':
|
||||
if config_dict['extra']['retrieval_type'] != doc_engine:
|
||||
if config_dict["service_type"] == "retrieval":
|
||||
if config_dict["extra"]["retrieval_type"] != doc_engine:
|
||||
continue
|
||||
try:
|
||||
service_detail = ServiceMgr.get_service_details(service_id)
|
||||
if "status" in service_detail:
|
||||
config_dict['status'] = service_detail['status']
|
||||
config_dict["status"] = service_detail["status"]
|
||||
else:
|
||||
config_dict['status'] = 'timeout'
|
||||
config_dict["status"] = "timeout"
|
||||
except Exception as e:
|
||||
logging.warning(f"Can't get service details, error: {e}")
|
||||
config_dict['status'] = 'timeout'
|
||||
if not config_dict['host']:
|
||||
config_dict['host'] = '-'
|
||||
if not config_dict['port']:
|
||||
config_dict['port'] = '-'
|
||||
config_dict["status"] = "timeout"
|
||||
if not config_dict["host"]:
|
||||
config_dict["host"] = "-"
|
||||
if not config_dict["port"]:
|
||||
config_dict["port"] = "-"
|
||||
result.append(config_dict)
|
||||
return result
|
||||
|
||||
@ -250,11 +305,11 @@ class ServiceMgr:
|
||||
raise AdminException(f"invalid service_index: {service_idx}")
|
||||
|
||||
service_config = configs[service_idx]
|
||||
service_info = {'name': service_config.name, 'detail_func_name': service_config.detail_func_name}
|
||||
service_info = {"name": service_config.name, "detail_func_name": service_config.detail_func_name}
|
||||
|
||||
detail_func = getattr(health_utils, service_info.get('detail_func_name'))
|
||||
detail_func = getattr(health_utils, service_info.get("detail_func_name"))
|
||||
res = detail_func()
|
||||
res.update({'service_name': service_info.get('name')})
|
||||
res.update({"service_name": service_info.get("name")})
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@ -265,19 +320,21 @@ class ServiceMgr:
|
||||
def restart_service(service_id: int):
|
||||
raise AdminException("restart_service: not implemented")
|
||||
|
||||
|
||||
class SettingsMgr:
|
||||
@staticmethod
|
||||
def get_all():
|
||||
|
||||
settings = SystemSettingsService.get_all()
|
||||
result = []
|
||||
for setting in settings:
|
||||
result.append({
|
||||
'name': setting.name,
|
||||
'source': setting.source,
|
||||
'data_type': setting.data_type,
|
||||
'value': setting.value,
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"name": setting.name,
|
||||
"source": setting.source,
|
||||
"data_type": setting.data_type,
|
||||
"value": setting.value,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@ -287,12 +344,14 @@ class SettingsMgr:
|
||||
raise AdminException(f"Can't get setting: {name}")
|
||||
result = []
|
||||
for setting in settings:
|
||||
result.append({
|
||||
'name': setting.name,
|
||||
'source': setting.source,
|
||||
'data_type': setting.data_type,
|
||||
'value': setting.value,
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"name": setting.name,
|
||||
"source": setting.source,
|
||||
"data_type": setting.data_type,
|
||||
"value": setting.value,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@ -308,8 +367,8 @@ class SettingsMgr:
|
||||
else:
|
||||
raise AdminException(f"No setting: {name}")
|
||||
|
||||
class ConfigMgr:
|
||||
|
||||
class ConfigMgr:
|
||||
@staticmethod
|
||||
def get_all():
|
||||
result = []
|
||||
@ -319,12 +378,13 @@ class ConfigMgr:
|
||||
result.append(config_dict)
|
||||
return result
|
||||
|
||||
|
||||
class EnvironmentsMgr:
|
||||
@staticmethod
|
||||
def get_all():
|
||||
result = []
|
||||
|
||||
env_kv = {"env": "DOC_ENGINE", "value": os.getenv('DOC_ENGINE')}
|
||||
env_kv = {"env": "DOC_ENGINE", "value": os.getenv("DOC_ENGINE")}
|
||||
result.append(env_kv)
|
||||
|
||||
env_kv = {"env": "DEFAULT_SUPERUSER_EMAIL", "value": os.getenv("DEFAULT_SUPERUSER_EMAIL", "admin@ragflow.io")}
|
||||
|
||||
Reference in New Issue
Block a user