PhilipL commited on
Commit
e3d438c
·
unverified ·
1 Parent(s): a323e1f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -138
app.py CHANGED
@@ -1,24 +1,94 @@
1
  import gradio as gr
2
  import datetime
3
  import pandas as pd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- # --- 模擬資料庫 ---
6
- # 格式: "帳號": ["密碼", "角色名稱", "顯示姓名"]
7
  USER_DB = {
8
- "student": ["1234", "學生", "林宸平"],
9
- "teacher": ["1234", "老師", "王教授"],
10
  "admin": ["1234", "行政", "行政人員"],
11
- "ta": ["1234", "TA", "系統助教"],
12
  "sysadmin": ["1234", "系統管理員", "管理員"]
13
  }
14
 
15
- # 全域變數來儲存 Log
16
  log_history = []
17
 
18
- def record_log(ip, cookie, account, role, action, status):
19
- """將行為記錄到模擬的資料庫中"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  new_log = {
21
- "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
22
  "ip_address": ip,
23
  "cookie_id": cookie,
24
  "account": account,
@@ -26,168 +96,88 @@ def record_log(ip, cookie, account, role, action, status):
26
  "action": action,
27
  "status": status
28
  }
29
- log_history.append(new_log)
30
- # 返回最新的 DataFrame 更新畫面
31
  return pd.DataFrame(log_history)
32
 
33
- def process_login(ip, cookie, account, password, captcha):
34
- """處理登入邏輯並記錄 Log"""
35
- # 預設驗證碼
36
  valid_captcha = "FEIK"
37
-
38
- # 檢查帳號是否存在
39
- if account not in USER_DB:
40
- df = record_log(ip, cookie, account, "Unknown", "login_attempt", "failed_no_account")
41
- return gr.update(visible=True), gr.update(visible=False), "帳號不存在!", gr.update(), df
42
-
43
- role = USER_DB[account][1]
44
- name = USER_DB[account][2]
45
-
46
- # 檢查驗證碼
47
- if captcha.upper() != valid_captcha:
48
- df = record_log(ip, cookie, account, role, "login_attempt", "failed_captcha")
49
- return gr.update(visible=True), gr.update(visible=False), "驗證碼錯誤!", gr.update(), df
50
-
51
- # 檢查密碼
52
  if USER_DB[account][0] == password:
53
  welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938"
54
- df = record_log(ip, cookie, account, role, "login_attempt", "success")
55
- # 登入成功:隱藏登入區塊,顯示系統區塊
56
  return gr.update(visible=False), gr.update(visible=True), "", welcome_msg, df
57
- else:
58
- df = record_log(ip, cookie, account, role, "login_attempt", "failed_password")
59
- return gr.update(visible=True), gr.update(visible=False), "密碼錯誤!", gr.update(), df
60
 
61
- def simulate_click(ip, cookie, account, button_name):
62
- """模擬登入後點擊系統選單的行為"""
63
  role = USER_DB.get(account, ["", "Unknown"])[1]
64
- action_name = f"click_{button_name}"
65
- df = record_log(ip, cookie, account, role, action_name, "success")
66
- return df
67
 
68
- def logout(ip, cookie, account):
69
- """處理登出並記錄"""
70
  role = USER_DB.get(account, ["", "Unknown"])[1]
71
- df = record_log(ip, cookie, account, role, "logout", "success")
72
- # 登出後清空輸入框
73
  return gr.update(visible=True), gr.update(visible=False), "", "", "", "", df
74
 
75
- # --- UI 介面設計 ---
 
 
 
76
  custom_css = """
77
- .mock-panel { background-color: #f0f4f8; padding: 15px; border-radius: 8px; border: 1px dashed #7b9bb3; }
78
  .login-container { max-width: 500px; margin: 0 auto; padding: 20px; }
79
- .placeholder-box { background-color: #e9ecef; border: 2px dashed #ced4da; text-align: center; color: black; padding: 40px 0; margin-bottom: 15px; border-radius: 5px; }
80
  .ntut-title { color: #d9534f; font-size: 22px; font-weight: bold; text-align: center; margin-bottom: 20px;}
81
  """
82
 
83
- with gr.Blocks(title="模擬校園入口與Log收集系統", css=custom_css) as demo:
84
-
85
  with gr.Row():
86
- # ==========================================
87
- # 左側:後端 Log 與環境模擬區
88
- # ==========================================
89
  with gr.Column(scale=1, elem_classes="mock-panel"):
90
- gr.Markdown("<h3 style='color: black;'>⚙️ 環境變數模擬器 (負責控制右側使用者的連線狀態)</h3>")
91
-
92
- mock_ip = gr.Dropdown(
93
- label="模擬來源 IP",
94
- choices=["140.124.71.55 (校內)", "140.124.18.22 (宿舍)", "211.75.2.11 (校外ISP)", "45.33.22.11 (國外異常IP)"],
95
- value="140.124.71.55 (校內)",
96
- allow_custom_value=True
97
- )
98
- mock_cookie = gr.Textbox(
99
- label="模擬 Cookie Session ID",
100
- value="sess_a1b2c3d4e5",
101
- placeholder="手動輸入或使用預設值"
102
- )
103
-
104
  gr.Markdown("---")
105
- gr.Markdown("<h3 style='color: black;'>📊 即時 Log 收集區</h3>")
106
- log_display = gr.Dataframe(
107
- headers=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"],
108
- datatype=["str", "str", "str", "str", "str", "str", "str"],
109
- interactive=False,
110
- wrap=True
111
- )
112
-
113
- # ==========================================
114
- # 右側:正常使用者介面
115
- # ==========================================
116
  with gr.Column(scale=2):
117
-
118
- # --- 狀態 1: 登入畫面 ---
119
  with gr.Column(visible=True, elem_classes="login-container") as login_view:
120
  gr.HTML("<div class='placeholder-box'>預留位置:台北科大 Logo 與 e化校園圖片</div>")
121
-
122
  gr.Markdown("<div class='ntut-title'>校園入口網站 Taipei Tech Portal</div>")
123
  login_msg = gr.Markdown(value="", visible=True)
124
-
125
- with gr.Row():
126
- acc_input = gr.Textbox(label="使用者帳號 (Account)", placeholder="例如: student, admin...")
127
- with gr.Row():
128
- pwd_input = gr.Textbox(label="使用者密碼 (Password)", type="password", placeholder="密碼皆為 1234")
129
-
130
- with gr.Row():
131
- gr.HTML("<div style='background:#fdfd96; padding:10px; font-weight:bold; letter-spacing: 3px; border:1px solid #ccc; text-align:center; color: black;'>F E I K</div>")
132
- with gr.Row():
133
- captcha_input = gr.Textbox(label="請輸入驗證碼 (Keyin Code)")
134
-
135
  login_btn = gr.Button("登入 Login", variant="primary")
136
- gr.Markdown("*測試帳號:student (學生), teacher (老師), admin (行政), ta (助教), sysadmin (管理員)*")
137
 
138
- # --- 狀態 2: 登入後儀表板 ---
139
  with gr.Column(visible=False) as dashboard_view:
140
- gr.HTML("<div class='placeholder-box'>預留位置:學生學習歷程入口網站橫幅 (Banner)</div>")
141
-
142
  with gr.Row():
143
  gr.Markdown("### 資訊系統")
144
  welcome_text = gr.Markdown(value="", elem_classes="text-right")
145
  logout_btn = gr.Button("登出", size="sm")
146
-
147
  gr.Markdown("---")
148
-
149
- with gr.Accordion("▼ 教務系統", open=True):
150
- with gr.Row():
151
- btn_course_en = gr.Button("▶ 課程系統英文版", size="sm")
152
- btn_course = gr.Button("▶ 課程系統", size="sm")
153
- btn_select = gr.Button("▶ 新生網路選課系統", size="sm")
154
-
155
- with gr.Accordion("▼ 學務系統", open=True):
156
- with gr.Row():
157
- btn_student_query = gr.Button("▶ 學生查詢專區", size="sm")
158
- btn_leave = gr.Button("▶ 學生請假系統", size="sm")
159
-
160
- with gr.Accordion("▼ 總務系統", open=False):
161
- btn_payment = gr.Button("▶ 線上繳費系統", size="sm")
162
-
163
- # --- 綁定事件 ---
164
-
165
- # 登入動作
166
- login_btn.click(
167
- fn=process_login,
168
- inputs=[mock_ip, mock_cookie, acc_input, pwd_input, captcha_input],
169
- outputs=[login_view, dashboard_view, login_msg, welcome_text, log_display]
170
- )
171
-
172
- # 登出動作
173
- logout_btn.click(
174
- fn=logout,
175
- inputs=[mock_ip, mock_cookie, acc_input],
176
- outputs=[login_view, dashboard_view, acc_input, pwd_input, captcha_input, login_msg, log_display]
177
- )
178
-
179
- # 綁定選單點擊動作到 Log 系統
180
- # 使用迴圈與 default argument (n=name) 確保 lambda 綁定正確的值
181
- for btn, name in [
182
- (btn_course_en, "course_en"), (btn_course, "course_system"),
183
- (btn_select, "course_selection"), (btn_student_query, "student_query"),
184
- (btn_leave, "leave_system"), (btn_payment, "payment_system")
185
- ]:
186
- btn.click(
187
- fn=lambda ip, c, acc, n=name: simulate_click(ip, c, acc, n),
188
- inputs=[mock_ip, mock_cookie, acc_input],
189
- outputs=[log_display]
190
- )
191
 
192
  if __name__ == "__main__":
193
  demo.launch()
 
1
  import gradio as gr
2
  import datetime
3
  import pandas as pd
4
+ import joblib
5
+ from sklearn.cluster import HDBSCAN
6
+ import numpy as np
7
+
8
+ # --- 1. 載入模型與歷史資料 (實現動態 Rule 的關鍵) ---
9
+ try:
10
+ le_action = joblib.load('le_action.pkl')
11
+ le_ip = joblib.load('le_ip.pkl')
12
+
13
+ # 🌟 載入歷史資料作為 HDBSCAN 即時比對的基準
14
+ baseline_df = pd.read_csv('baseline_logs.csv')
15
+ baseline_df['hour'] = pd.to_datetime(baseline_df['timestamp']).dt.hour
16
+
17
+ # 預先將歷史資料轉成特徵矩陣
18
+ baseline_X = np.column_stack((
19
+ baseline_df['hour'].values,
20
+ le_ip.transform(baseline_df['ip_address'].values),
21
+ le_action.transform(baseline_df['action'].values)
22
+ ))
23
+
24
+ AI_READY = True
25
+ print("✅ AI 模組與歷史基準資料載入成功!")
26
+ except Exception as e:
27
+ AI_READY = False
28
+ print(f"⚠️ AI 模組載入失敗,請確認 .pkl 與 .csv 檔案是否已上傳。錯誤: {e}")
29
 
 
 
30
  USER_DB = {
31
+ "student": ["1234", "學生", "同學"],
32
+ "teacher": ["1234", "老師", "教授"],
33
  "admin": ["1234", "行政", "行政人員"],
34
+ "ta": ["1234", "TA", "助教"],
35
  "sysadmin": ["1234", "系統管理員", "管理員"]
36
  }
37
 
 
38
  log_history = []
39
 
40
+ # --- 3. 核心邏輯與真實 HDBSCAN 異常偵測 ---
41
+ def check_anomaly(ip, action, log_time):
42
+ if not AI_READY:
43
+ return "success"
44
+
45
+ if action in ["login_attempt", "logout"]:
46
+ return "success"
47
+
48
+ try:
49
+ # 1. 將新進來的行為轉為代碼
50
+ ip_code = le_ip.transform([ip])[0]
51
+ action_code = le_action.transform([action])[0]
52
+
53
+ # 2. 擷取當下行為的時間 (Hour)
54
+ current_hour = pd.to_datetime(log_time).hour
55
+
56
+ # 3. 組合這筆新資料的特徵向量
57
+ new_data_point = np.array([[current_hour, ip_code, action_code]])
58
+
59
+ # 4. 🌟 將新資料與歷史資料合併 (這就是流程圖中的「即時丟入 AI 進行判斷」)
60
+ combined_X = np.vstack((baseline_X, new_data_point))
61
+
62
+ # 5. 執行 HDBSCAN 分群
63
+ model = HDBSCAN(min_cluster_size=5, min_samples=3)
64
+ clusters = model.fit_predict(combined_X)
65
+
66
+ # 6. 檢查最後一筆 (也就是剛剛送進來的新動作) 是否被分類為 -1 (雜訊/異常)
67
+ if clusters[-1] == -1:
68
+ return "⚠️異常_作息或行為不符"
69
+ else:
70
+ return "success"
71
+
72
+ except ValueError:
73
+ # 如果遇到沒看過的 IP 或動作,連算都不用算,絕對是異常
74
+ return "⚠️異常_未知特徵"
75
+
76
+ def record_log(ip, cookie, account, role, action, status, time_mode, custom_time):
77
+ # 決定紀錄的時間
78
+ if time_mode == "自訂時間 (模擬過去/未來)" and custom_time.strip() != "":
79
+ log_time = custom_time
80
+ else:
81
+ tw_tz = datetime.timezone(datetime.timedelta(hours=8))
82
+ log_time = datetime.datetime.now(tw_tz).strftime("%Y-%m-%d %H:%M:%S")
83
+
84
+ # 執行 AI 檢查 (現在把 log_time 也傳進去了)
85
+ if status == "success":
86
+ ai_judgment = check_anomaly(ip, action, log_time)
87
+ if ai_judgment != "success":
88
+ status = ai_judgment
89
+
90
  new_log = {
91
+ "timestamp": log_time,
92
  "ip_address": ip,
93
  "cookie_id": cookie,
94
  "account": account,
 
96
  "action": action,
97
  "status": status
98
  }
99
+ log_history.insert(0, new_log)
 
100
  return pd.DataFrame(log_history)
101
 
102
+ def process_login(ip, cookie, account, password, captcha, time_mode, custom_time):
 
 
103
  valid_captcha = "FEIK"
104
+ if account not in USER_DB: return gr.update(visible=True), gr.update(visible=False), "帳號不存在!", gr.update(), record_log(ip, cookie, account, "Unknown", "login_attempt", "failed_no_account", time_mode, custom_time)
105
+ role, name = USER_DB[account][1], USER_DB[account][2]
106
+ if captcha.upper() != valid_captcha: return gr.update(visible=True), gr.update(visible=False), "驗證碼錯誤!", gr.update(), record_log(ip, cookie, account, role, "login_attempt", "failed_captcha", time_mode, custom_time)
 
 
 
 
 
 
 
 
 
 
 
 
107
  if USER_DB[account][0] == password:
108
  welcome_msg = f"**{name}** 歡迎您 | 線上人數: 938"
109
+ df = record_log(ip, cookie, account, role, "login_attempt", "success", time_mode, custom_time)
 
110
  return gr.update(visible=False), gr.update(visible=True), "", welcome_msg, df
111
+ return gr.update(visible=True), gr.update(visible=False), "密碼錯誤!", gr.update(), record_log(ip, cookie, account, role, "login_attempt", "failed_password", time_mode, custom_time)
 
 
112
 
113
+ def simulate_click(ip, cookie, account, button_name, time_mode, custom_time):
 
114
  role = USER_DB.get(account, ["", "Unknown"])[1]
115
+ return record_log(ip, cookie, account, role, button_name, "success", time_mode, custom_time)
 
 
116
 
117
+ def logout(ip, cookie, account, time_mode, custom_time):
 
118
  role = USER_DB.get(account, ["", "Unknown"])[1]
119
+ df = record_log(ip, cookie, account, role, "logout", "success", time_mode, custom_time)
 
120
  return gr.update(visible=True), gr.update(visible=False), "", "", "", "", df
121
 
122
+ def toggle_time_input(mode):
123
+ return gr.update(visible=(mode == "自訂時間 (模擬過去/未來)"))
124
+
125
+ # --- 4. UI 介面設計 ---
126
  custom_css = """
127
+ .mock-panel { background-color: var(--background-fill-secondary); padding: 15px; border-radius: 8px; border: 1px dashed var(--border-color-primary); }
128
  .login-container { max-width: 500px; margin: 0 auto; padding: 20px; }
129
+ .placeholder-box { background-color: var(--background-fill-secondary); border: 2px dashed var(--border-color-primary); text-align: center; color: var(--body-text-color); padding: 40px 0; margin-bottom: 15px; border-radius: 5px; }
130
  .ntut-title { color: #d9534f; font-size: 22px; font-weight: bold; text-align: center; margin-bottom: 20px;}
131
  """
132
 
133
+ with gr.Blocks(title="模擬校園入口與資安防護系統", css=custom_css) as demo:
 
134
  with gr.Row():
 
 
 
135
  with gr.Column(scale=1, elem_classes="mock-panel"):
136
+ gr.Markdown("### ⚙️ 環境變數模擬器")
137
+ mock_ip = gr.Dropdown(choices=["140.124.71.55 (校內預設)", "140.124.18.22 (宿舍)", "45.33.22.11 (國外異常IP)"], value="140.124.71.55 (校內預設)", allow_custom_value=True, label="模擬來源 IP")
138
+ mock_cookie = gr.Textbox(label="模擬 Cookie Session ID", value="sess_baseline")
139
+ gr.Markdown("---")
140
+ time_mode = gr.Radio(label="時間設定模式", choices=["真實時間 (目前時間)", "自訂時間 (模擬過去/未來)"], value="真實時間 (目前時間)")
141
+ custom_time_input = gr.Textbox(label="輸入自訂時間", placeholder="格式: YYYY-MM-DD HH:MM:SS", value="2026-03-17 03:00:00", visible=False)
 
 
 
 
 
 
 
 
142
  gr.Markdown("---")
143
+ gr.Markdown("### 📊 即時 Log 與 AI 判定區")
144
+ log_display = gr.Dataframe(headers=["timestamp", "ip_address", "cookie_id", "account", "role", "action", "status"], datatype=["str", "str", "str", "str", "str", "str", "str"], interactive=False, wrap=True)
145
+
 
 
 
 
 
 
 
 
146
  with gr.Column(scale=2):
 
 
147
  with gr.Column(visible=True, elem_classes="login-container") as login_view:
148
  gr.HTML("<div class='placeholder-box'>預留位置:台北科大 Logo 與 e化校園圖片</div>")
 
149
  gr.Markdown("<div class='ntut-title'>校園入口網站 Taipei Tech Portal</div>")
150
  login_msg = gr.Markdown(value="", visible=True)
151
+ with gr.Row(): acc_input = gr.Textbox(label="使用者帳號 (Account)", placeholder="例如: student, admin...")
152
+ with gr.Row(): pwd_input = gr.Textbox(label="使用者密碼 (Password)", type="password", placeholder="密碼皆為 1234")
153
+ with gr.Row(): gr.HTML("<div style='background:var(--background-fill-secondary); color:var(--body-text-color); padding:10px; font-weight:bold; letter-spacing: 3px; border:1px solid var(--border-color-primary); text-align:center;'>F E I K</div>")
154
+ with gr.Row(): captcha_input = gr.Textbox(label="請輸入驗證碼 (Keyin Code)")
 
 
 
 
 
 
 
155
  login_btn = gr.Button("登入 Login", variant="primary")
 
156
 
 
157
  with gr.Column(visible=False) as dashboard_view:
158
+ gr.HTML("<div class='placeholder-box'>預留位置:學生學習歷程入口網站橫幅</div>")
 
159
  with gr.Row():
160
  gr.Markdown("### 資訊系統")
161
  welcome_text = gr.Markdown(value="", elem_classes="text-right")
162
  logout_btn = gr.Button("登出", size="sm")
 
163
  gr.Markdown("---")
164
+ with gr.Accordion("▼ 1. 教務系統", open=True):
165
+ with gr.Row(): btn_eval = gr.Button("▶ 期末網路教學評量", size="sm"); btn_pre_select = gr.Button("▶ 期末網路預選系統", size="sm"); btn_add_drop = gr.Button("▶ 開學後加退選系統", size="sm")
166
+ with gr.Row(): btn_istudy = gr.Button("▶ 北科i學園PLUS", size="sm"); btn_card = gr.Button("▶ 學生證掛失及補發系統", size="sm"); btn_course = gr.Button("▶ 課程系統", size="sm")
167
+ with gr.Row(): btn_score = gr.Button("▶ 學業成績查詢系統", size="sm")
168
+ with gr.Accordion("▼ 2. 學務系統", open=True):
169
+ with gr.Row(): btn_leave = gr.Button("▶ 學生請假系統", size="sm"); btn_dorm = gr.Button("▶ 學生宿舍登錄(抽籤)系統", size="sm"); btn_scholarship = gr.Button("▶ 獎助學金申請系統", size="sm")
170
+ with gr.Accordion("▼ 3. 資訊服務", open=True):
171
+ with gr.Row(): btn_webmail = gr.Button("▶ 網路郵局 WebMail", size="sm"); btn_vdesk = gr.Button("▶ 北科軟體雲", size="sm")
172
+ with gr.Accordion("▼ 惡意操作測試區", open=True):
173
+ btn_hack_score = gr.Button("💀 嘗試偷改成績 (SQL Injection)", variant="stop", size="sm")
174
+
175
+ time_mode.change(fn=toggle_time_input, inputs=time_mode, outputs=custom_time_input)
176
+ login_btn.click(fn=process_login, inputs=[mock_ip, mock_cookie, acc_input, pwd_input, captcha_input, time_mode, custom_time_input], outputs=[login_view, dashboard_view, login_msg, welcome_text, log_display])
177
+ logout_btn.click(fn=logout, inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input], outputs=[login_view, dashboard_view, acc_input, pwd_input, captcha_input, login_msg, log_display])
178
+
179
+ for btn, action_name in [(btn_eval, "click_eval_system"), (btn_pre_select, "click_pre_select_system"), (btn_add_drop, "click_add_drop_system"), (btn_istudy, "click_istudy"), (btn_card, "click_card_loss"), (btn_course, "click_course_system"), (btn_score, "click_score_query"), (btn_leave, "click_leave_system"), (btn_dorm, "click_dorm_system"), (btn_scholarship, "click_scholarship"), (btn_webmail, "click_webmail"), (btn_vdesk, "click_vdesk"), (btn_hack_score, "malicious_sql_injection")]:
180
+ btn.click(fn=lambda ip, c, acc, t_mode, t_custom, n=action_name: simulate_click(ip, c, acc, n, t_mode, t_custom), inputs=[mock_ip, mock_cookie, acc_input, time_mode, custom_time_input], outputs=[log_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  if __name__ == "__main__":
183
  demo.launch()