feat: add optional dev mode and analyzer tool upload role
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ EXPOSE 8000
|
|||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/', timeout=5)" || exit 1
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8888/', timeout=5)" || exit 1
|
||||||
|
|
||||||
# Set entrypoint
|
# Set entrypoint
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">🏠 Trang chủ</a>
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">🏠 Trang chủ</a>
|
||||||
<a href="{{ url_for('my_files') }}" class="nav-tab"
|
<a href="{{ url_for('my_files') }}" class="nav-tab"
|
||||||
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
|
||||||
{% if user_info and user_info.role == 'admin' %}
|
{% if user_info and user_info.role in ['admin', 'analyzer'] %}
|
||||||
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
|
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
|
||||||
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">⚙️ Quản trị</a>
|
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">⚙️ Quản trị</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -403,11 +403,13 @@
|
|||||||
<div style="display: inline-flex; gap: 6px; align-items: center;">
|
<div style="display: inline-flex; gap: 6px; align-items: center;">
|
||||||
<a href="{{ url_for('download_tool', version_key=tool.version_key) }}"
|
<a href="{{ url_for('download_tool', version_key=tool.version_key) }}"
|
||||||
class="btn btn-secondary" style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">⬇️ Tải</a>
|
class="btn btn-secondary" style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">⬇️ Tải</a>
|
||||||
|
{% if user_info and user_info.role == 'admin' %}
|
||||||
<form action="tools/delete/{{ tool.version_key }}" method="post" style="display: inline; margin: 0;"
|
<form action="tools/delete/{{ tool.version_key }}" method="post" style="display: inline; margin: 0;"
|
||||||
onsubmit="return confirm('Bạn có chắc muốn xóa công cụ này?')">
|
onsubmit="return confirm('Bạn có chắc muốn xóa công cụ này?')">
|
||||||
<button type="submit" class="btn btn-danger"
|
<button type="submit" class="btn btn-danger"
|
||||||
style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">🗑️ Xóa</button>
|
style="padding: 5px 10px; font-size: 12px; white-space: nowrap;">🗑️ Xóa</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="color: var(--text-muted);">-</span>
|
<span style="color: var(--text-muted);">-</span>
|
||||||
@@ -425,6 +427,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if user_info and user_info.role == 'admin' %}
|
||||||
<!-- Upload Documentation Form -->
|
<!-- Upload Documentation Form -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -516,6 +519,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user_info and user_info.role == 'admin' %}
|
||||||
<!-- Email Notification Settings -->
|
<!-- Email Notification Settings -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -583,6 +589,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user_info and user_info.role == 'admin' %}
|
||||||
<!-- User Email Stats -->
|
<!-- User Email Stats -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -623,15 +632,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Drag and drop support
|
// Drag and drop support
|
||||||
const uploadForm = document.getElementById('uploadForm');
|
const uploadForm = document.getElementById('uploadForm');
|
||||||
|
if (uploadForm) {
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
uploadForm.addEventListener(eventName, preventDefaults, false);
|
uploadForm.addEventListener(eventName, preventDefaults, false);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function preventDefaults(e) {
|
function preventDefaults(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -980,7 +980,7 @@
|
|||||||
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">🏠 Trang chủ</a>
|
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">🏠 Trang chủ</a>
|
||||||
<a href="{{ url_for('my_files') }}" class="nav-tab"
|
<a href="{{ url_for('my_files') }}" class="nav-tab"
|
||||||
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
|
||||||
{% if user_info and user_info.role == 'admin' %}
|
{% if user_info and user_info.role in ['admin', 'analyzer'] %}
|
||||||
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
|
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
|
||||||
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">⚙️ Quản trị</a>
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">⚙️ Quản trị</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
+47
-6
@@ -53,6 +53,22 @@ def _email_enabled() -> bool:
|
|||||||
app = FastAPI(title="Audit Hardening Tool", redirect_slashes=False)
|
app = FastAPI(title="Audit Hardening Tool", redirect_slashes=False)
|
||||||
security = HTTPBasic()
|
security = HTTPBasic()
|
||||||
|
|
||||||
|
# Development mode: allows unauthenticated file upload only.
|
||||||
|
# Keep disabled by default; enable explicitly with AUDIT_DEV_MODE=true.
|
||||||
|
DEV_MODE = os.getenv("AUDIT_DEV_MODE", "false").strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
DEV_USER = {
|
||||||
|
"username": "dev_guest",
|
||||||
|
"full_name": "Development Guest",
|
||||||
|
"role": "dev_guest",
|
||||||
|
"unit_id": "",
|
||||||
|
"unit_name": "Development",
|
||||||
|
"auth_method": "dev_mode",
|
||||||
|
"can_view_outputs": True,
|
||||||
|
"can_download": True,
|
||||||
|
}
|
||||||
|
if DEV_MODE:
|
||||||
|
print(Fore.YELLOW + "[DEV MODE] Unauthenticated file upload is ENABLED; admin routes remain protected" + Fore.RESET)
|
||||||
|
|
||||||
# Add middleware to handle X-Forwarded-* headers from reverse proxy (Caddy)
|
# Add middleware to handle X-Forwarded-* headers from reverse proxy (Caddy)
|
||||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
||||||
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
|
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
|
||||||
@@ -1078,10 +1094,16 @@ async def logout(request: Request):
|
|||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse, name="index")
|
@app.get("/", response_class=HTMLResponse, name="index")
|
||||||
async def index(request: Request):
|
async def index(request: Request):
|
||||||
# Kiểm tra đăng nhập bắt buộc
|
|
||||||
session_id = get_or_create_session(request)
|
session_id = get_or_create_session(request)
|
||||||
user_info = sessions.get(session_id, {}).get('user_info')
|
user_info = sessions.get(session_id, {}).get('user_info')
|
||||||
|
|
||||||
|
if not user_info and DEV_MODE:
|
||||||
|
user_info = dict(DEV_USER)
|
||||||
|
sessions[session_id]['username'] = DEV_USER['username']
|
||||||
|
sessions[session_id]['user_info'] = user_info
|
||||||
|
sessions[session_id]['unit_id'] = ''
|
||||||
|
sessions[session_id]['unit_name'] = 'Development'
|
||||||
|
|
||||||
if not user_info:
|
if not user_info:
|
||||||
# Chưa đăng nhập → redirect về trang login
|
# Chưa đăng nhập → redirect về trang login
|
||||||
response = RedirectResponse(url=str(request.url_for('login')), status_code=303)
|
response = RedirectResponse(url=str(request.url_for('login')), status_code=303)
|
||||||
@@ -1639,9 +1661,15 @@ async def api_upload_async(
|
|||||||
os_type: str = Form(...),
|
os_type: str = Form(...),
|
||||||
encrypted_files: List[UploadFile] = File(...)
|
encrypted_files: List[UploadFile] = File(...)
|
||||||
):
|
):
|
||||||
# Bắt buộc đăng nhập để xác định unit_id người upload
|
# Authenticated users and explicit dev mode may upload; admin routes stay protected.
|
||||||
session_id = get_or_create_session(request)
|
session_id = get_or_create_session(request)
|
||||||
user_info = sessions.get(session_id, {}).get('user_info')
|
user_info = sessions.get(session_id, {}).get('user_info')
|
||||||
|
if not user_info and DEV_MODE:
|
||||||
|
user_info = dict(DEV_USER)
|
||||||
|
sessions[session_id]['username'] = DEV_USER['username']
|
||||||
|
sessions[session_id]['user_info'] = user_info
|
||||||
|
sessions[session_id]['unit_id'] = ''
|
||||||
|
sessions[session_id]['unit_name'] = 'Development'
|
||||||
if not user_info:
|
if not user_info:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
@@ -1764,6 +1792,11 @@ async def upload_files(
|
|||||||
|
|
||||||
# Lấy hoặc tạo session
|
# Lấy hoặc tạo session
|
||||||
session_id = get_or_create_session(request, response)
|
session_id = get_or_create_session(request, response)
|
||||||
|
if DEV_MODE and not sessions.get(session_id, {}).get('user_info'):
|
||||||
|
sessions[session_id]['username'] = DEV_USER['username']
|
||||||
|
sessions[session_id]['user_info'] = dict(DEV_USER)
|
||||||
|
sessions[session_id]['unit_id'] = ''
|
||||||
|
sessions[session_id]['unit_name'] = 'Development'
|
||||||
|
|
||||||
# Bắt đầu đo thời gian xử lý
|
# Bắt đầu đo thời gian xử lý
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -2530,16 +2563,24 @@ async def download_doc(doc_key: str):
|
|||||||
# ============== ADMIN ROUTES ==============
|
# ============== ADMIN ROUTES ==============
|
||||||
|
|
||||||
def require_admin_session(request: Request):
|
def require_admin_session(request: Request):
|
||||||
"""Check session auth and return admin user or None"""
|
"""Check session auth and return admin user or None."""
|
||||||
user = get_session_user(request)
|
user = get_session_user(request)
|
||||||
if user and user.get('role') == 'admin':
|
if user and user.get('role') == 'admin':
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def require_tool_manager_session(request: Request):
|
||||||
|
"""Allow admin or analyzer to access the tool upload page."""
|
||||||
|
user = get_session_user(request)
|
||||||
|
if user and user.get('role') in {'admin', 'analyzer'}:
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
@app.get("/admin/tools", response_class=HTMLResponse, name="admin_tools")
|
@app.get("/admin/tools", response_class=HTMLResponse, name="admin_tools")
|
||||||
async def admin_tools_page(request: Request, msg: str = None, msg_type: str = "success"):
|
async def admin_tools_page(request: Request, msg: str = None, msg_type: str = "success"):
|
||||||
"""Admin page for managing tools - requires session authentication"""
|
"""Admin page for managing tools - requires session authentication"""
|
||||||
user = require_admin_session(request)
|
user = require_tool_manager_session(request)
|
||||||
if not user:
|
if not user:
|
||||||
return RedirectResponse(url=request.url_for('login'), status_code=303)
|
return RedirectResponse(url=request.url_for('login'), status_code=303)
|
||||||
|
|
||||||
@@ -2789,8 +2830,8 @@ async def admin_upload_tool(
|
|||||||
tool_name: str = Form(...),
|
tool_name: str = Form(...),
|
||||||
tool_file: UploadFile = File(...)
|
tool_file: UploadFile = File(...)
|
||||||
):
|
):
|
||||||
"""Upload a new tool - requires admin session authentication"""
|
"""Upload a new tool - requires admin or analyzer session."""
|
||||||
user = require_admin_session(request)
|
user = require_tool_manager_session(request)
|
||||||
if not user:
|
if not user:
|
||||||
return RedirectResponse(url=request.url_for('login'), status_code=303)
|
return RedirectResponse(url=request.url_for('login'), status_code=303)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Ho_Chi_Minh
|
- TZ=Asia/Ho_Chi_Minh
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
|
# Dev mode: unauthenticated upload; admin routes remain protected.
|
||||||
|
# Set to false for production.
|
||||||
|
- AUDIT_DEV_MODE=${AUDIT_DEV_MODE:-false}
|
||||||
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user