diff --git a/fileserver/fileop.go b/fileserver/fileop.go index dbac6fa..0c53c60 100644 --- a/fileserver/fileop.go +++ b/fileserver/fileop.go @@ -241,8 +241,6 @@ func accessV2CB(rsp http.ResponseWriter, r *http.Request) *appError { msg := "No file path\n" return &appError{nil, msg, http.StatusBadRequest} } - // filePath will be unquote by mux, we need to escape filePath before calling check file access. - escPath := url.QueryEscape(filePath) rpath := getCanonPath(filePath) fileName := filepath.Base(rpath) @@ -260,7 +258,9 @@ func accessV2CB(rsp http.ResponseWriter, r *http.Request) *appError { return &appError{nil, msg, http.StatusBadRequest} } - user, appErr := checkFileAccess(repoID, token, cookie, escPath, "download") + ipAddr := getClientIPAddr(r) + userAgent := r.Header.Get("User-Agent") + user, appErr := checkFileAccess(repoID, token, cookie, filePath, "download", ipAddr, userAgent) if appErr != nil { return appErr } @@ -318,13 +318,13 @@ type UserInfo struct { User string `json:"user"` } -func checkFileAccess(repoID, token, cookie, filePath, op string) (string, *appError) { +func checkFileAccess(repoID, token, cookie, filePath, op, ipAddr, userAgent string) (string, *appError) { tokenString, err := utils.GenSeahubJWTToken() if err != nil { err := fmt.Errorf("failed to sign jwt token: %v", err) return "", &appError{err, "", http.StatusInternalServerError} } - url := fmt.Sprintf("%s/repos/%s/check-access/?path=%s", option.SeahubURL, repoID, filePath) + url := fmt.Sprintf("%s/repos/%s/check-access/", option.SeahubURL, repoID) header := map[string][]string{ "Authorization": {"Token " + tokenString}, } @@ -333,9 +333,16 @@ func checkFileAccess(repoID, token, cookie, filePath, op string) (string, *appEr } req := make(map[string]string) req["op"] = op + req["path"] = filePath if token != "" { req["token"] = token } + if ipAddr != "" { + req["ip_addr"] = ipAddr + } + if userAgent != "" { + req["user_agent"] = userAgent + } msg, err := json.Marshal(req) if err != nil { err := fmt.Errorf("failed to encode access token: %v", err) @@ -3713,20 +3720,33 @@ type ShareLinkInfo struct { ShareType string `json:"share_type"` } -func queryShareLinkInfo(token, cookie, opType string) (*ShareLinkInfo, *appError) { +func queryShareLinkInfo(token, cookie, opType, ipAddr, userAgent string) (*ShareLinkInfo, *appError) { tokenString, err := utils.GenSeahubJWTToken() if err != nil { err := fmt.Errorf("failed to sign jwt token: %v", err) return nil, &appError{err, "", http.StatusInternalServerError} } - url := fmt.Sprintf("%s?token=%s&type=%s", option.SeahubURL+"/check-share-link-access/", token, opType) + url := fmt.Sprintf("%s?type=%s", option.SeahubURL+"/check-share-link-access/", opType) header := map[string][]string{ "Authorization": {"Token " + tokenString}, } if cookie != "" { header["Cookie"] = []string{cookie} } - status, body, err := utils.HttpCommon("GET", url, header, nil) + req := make(map[string]string) + req["token"] = token + if ipAddr != "" { + req["ip_addr"] = ipAddr + } + if userAgent != "" { + req["user_agent"] = userAgent + } + msg, err := json.Marshal(req) + if err != nil { + err := fmt.Errorf("failed to encode access token: %v", err) + return nil, &appError{err, "", http.StatusInternalServerError} + } + status, body, err := utils.HttpCommon("POST", url, header, bytes.NewReader(msg)) if err != nil { if status != http.StatusInternalServerError { return nil, &appError{nil, string(body), status} @@ -3759,7 +3779,9 @@ func accessLinkCB(rsp http.ResponseWriter, r *http.Request) *appError { } token := parts[1] cookie := r.Header.Get("Cookie") - info, appErr := queryShareLinkInfo(token, cookie, "file") + ipAddr := getClientIPAddr(r) + userAgent := r.Header.Get("User-Agent") + info, appErr := queryShareLinkInfo(token, cookie, "file", ipAddr, userAgent) if appErr != nil { return appErr } diff --git a/server/access-file.c b/server/access-file.c index 0672add..9308bde 100644 --- a/server/access-file.c +++ b/server/access-file.c @@ -1488,12 +1488,14 @@ access_v2_cb(evhtp_request_t *req, void *arg) char *rpath = NULL; char *filename = NULL; char *file_id = NULL; + char *ip_addr = NULL; const char *repo_id = NULL; const char *path = NULL; const char *operation = NULL; const char *byte_ranges = NULL; const char *auth_token = NULL; const char *cookie = NULL; + const char *user_agent = NULL; int error_code = EVHTP_RES_BADREQ; SeafileCryptKey *key = NULL; @@ -1533,11 +1535,13 @@ access_v2_cb(evhtp_request_t *req, void *arg) auth_token = evhtp_kv_find (req->headers_in, "Authorization"); token = seaf_parse_auth_token (auth_token); cookie = evhtp_kv_find (req->headers_in, "Cookie"); + ip_addr = get_client_ip_addr (req); + user_agent = evhtp_header_find (req->headers_in, "User-Agent"); if (!token && !cookie) { error_str = "Both token and cookie are not set\n"; goto out; } - if (http_tx_manager_check_file_access (repo_id, token, cookie, dec_path, "download", &user) < 0) { + if (http_tx_manager_check_file_access (repo_id, token, cookie, dec_path, "download", ip_addr, user_agent, &user) < 0) { error_str = "No permission to access file\n"; error_code = EVHTP_RES_FORBIDDEN; goto out; @@ -1605,6 +1609,7 @@ out: g_free (rpath); g_free (filename); g_free (file_id); + g_free (ip_addr); if (repo != NULL) seaf_repo_unref (repo); if (key != NULL) @@ -1828,10 +1833,13 @@ access_link_cb(evhtp_request_t *req, void *arg) token = parts[1]; + char *ip_addr = get_client_ip_addr (req); + const char *user_agent = evhtp_header_find (req->headers_in, "User-Agent"); + const char *cookie = evhtp_kv_find (req->headers_in, "Cookie"); int status = HTTP_OK; char *err_msg = NULL; - info = http_tx_manager_query_share_link_info (token, cookie, "file", &status, &err_msg); + info = http_tx_manager_query_share_link_info (token, cookie, "file", ip_addr, user_agent, &status, &err_msg); if (!info) { g_strfreev (parts); if (status != HTTP_OK) { @@ -1843,9 +1851,11 @@ access_link_cb(evhtp_request_t *req, void *arg) evbuffer_add_printf(req->buffer_out, "%s\n", error_str); evhtp_send_reply(req, error_code); } + g_free (ip_addr); g_free (err_msg); return; } + g_free (ip_addr); repo_id = seafile_share_link_info_get_repo_id (info); file_path = seafile_share_link_info_get_file_path (info); diff --git a/server/http-server.c b/server/http-server.c index 148612e..2930764 100644 --- a/server/http-server.c +++ b/server/http-server.c @@ -609,8 +609,9 @@ send_statistic_msg (const char *repo_id, char *user, char *operation, guint64 by } char * -get_client_ip_addr (evhtp_request_t *req) +get_client_ip_addr (void *data) { + evhtp_request_t *req = data; const char *xff = evhtp_kv_find (req->headers_in, "X-Forwarded-For"); if (xff) { struct in_addr addr; diff --git a/server/http-server.h b/server/http-server.h index ecd4246..d9eb3a9 100644 --- a/server/http-server.h +++ b/server/http-server.h @@ -37,6 +37,9 @@ seaf_http_server_invalidate_tokens (HttpServerStruct *htp_server, void send_statistic_msg (const char *repo_id, char *user, char *operation, guint64 bytes); + +char * +get_client_ip_addr (void *data); #endif #endif diff --git a/server/http-tx-mgr.c b/server/http-tx-mgr.c index 73a6021..6eda970 100644 --- a/server/http-tx-mgr.c +++ b/server/http-tx-mgr.c @@ -504,7 +504,7 @@ http_tx_manager_get_nickname (const char *modifier) url = g_strdup_printf("%s/user-list/", seaf->seahub_url); ret = http_post_common (curl, url, &headers, jwt_token, req_content, strlen(req_content), - &rsp_status, &rsp_content, &rsp_size, TRUE, 1); + &rsp_status, &rsp_content, &rsp_size, TRUE, 45); if (ret < 0) { conn->release = TRUE; goto out; @@ -599,13 +599,16 @@ out: } SeafileShareLinkInfo * -http_tx_manager_query_share_link_info (const char *token, const char *cookie, const char *type, int *status, char **err_msg) +http_tx_manager_query_share_link_info (const char *token, const char *cookie, const char *type, + const char *ip_addr, const char *user_agent, int *status, char **err_msg) { Connection *conn = NULL; char *cookie_header; struct curl_slist *headers = NULL; int ret = 0; CURL *curl; + json_t *content = NULL; + char *req_content = NULL; int rsp_status; char *jwt_token = NULL; char *rsp_content = NULL; @@ -625,6 +628,18 @@ http_tx_manager_query_share_link_info (const char *token, const char *cookie, co return NULL; } + content = json_object (); + json_object_set_new (content, "token", json_string(token)); + if (ip_addr) + json_object_set_new (content, "ip_addr", json_string(ip_addr)); + if (user_agent) + json_object_set_new (content, "user_agent", json_string(user_agent)); + req_content = json_dumps (content, JSON_COMPACT); + if (!req_content) { + seaf_warning ("Failed to dump json request.\n"); + goto out; + } + curl = conn->curl; if (cookie) { cookie_header = g_strdup_printf ("Cookie: %s", cookie); @@ -632,9 +647,9 @@ http_tx_manager_query_share_link_info (const char *token, const char *cookie, co g_free (cookie_header); } - url = g_strdup_printf("%s/check-share-link-access/?token=%s&type=%s", seaf->seahub_url, token, type); - ret = http_get_common (curl, url, &headers, jwt_token, &rsp_status, - &rsp_content, &rsp_size, NULL, NULL, TRUE); + url = g_strdup_printf("%s/check-share-link-access/?type=%s", seaf->seahub_url, type); + ret = http_post_common (curl, url, &headers, jwt_token, req_content, strlen(req_content), + &rsp_status, &rsp_content, &rsp_size, TRUE, 45); if (ret < 0) { conn->release = TRUE; goto out; @@ -649,8 +664,11 @@ http_tx_manager_query_share_link_info (const char *token, const char *cookie, co info = parse_share_link_info (rsp_content, rsp_size); out: + if (content) + json_decref (content); g_free (url); g_free (jwt_token); + g_free (req_content); g_free (rsp_content); curl_slist_free_all (headers); connection_pool_return_connection (seaf->seahub_conn_pool, conn); @@ -687,7 +705,8 @@ out: int http_tx_manager_check_file_access (const char *repo_id, const char *token, const char *cookie, - const char *path, const char *op, char **user) + const char *path, const char *op, const char *ip_addr, + const char *user_agent, char **user) { Connection *conn = NULL; char *cookie_header; @@ -700,7 +719,6 @@ http_tx_manager_check_file_access (const char *repo_id, const char *token, const char *jwt_token = NULL; char *rsp_content = NULL; gint64 rsp_size; - char *esc_path = NULL; char *url = NULL; jwt_token = gen_jwt_token (); @@ -720,6 +738,11 @@ http_tx_manager_check_file_access (const char *repo_id, const char *token, const if (token) { json_object_set_new (content, "token", json_string(token)); } + json_object_set_new (content, "path", json_string(path)); + if (ip_addr) + json_object_set_new (content, "ip_addr", json_string(ip_addr)); + if (user_agent) + json_object_set_new (content, "user_agent", json_string(user_agent)); req_content = json_dumps (content, JSON_COMPACT); if (!req_content) { ret = -1; @@ -734,10 +757,9 @@ http_tx_manager_check_file_access (const char *repo_id, const char *token, const g_free (cookie_header); } - esc_path = g_uri_escape_string(path, NULL, FALSE); - url = g_strdup_printf("%s/repos/%s/check-access/?path=%s", seaf->seahub_url, repo_id, esc_path); + url = g_strdup_printf("%s/repos/%s/check-access/", seaf->seahub_url, repo_id); ret = http_post_common (curl, url, &headers, jwt_token, req_content, strlen(req_content), - &rsp_status, &rsp_content, &rsp_size, TRUE, 1); + &rsp_status, &rsp_content, &rsp_size, TRUE, 45); if (ret < 0) { conn->release = TRUE; goto out; @@ -757,7 +779,6 @@ http_tx_manager_check_file_access (const char *repo_id, const char *token, const out: if (content) json_decref (content); - g_free (esc_path); g_free (url); g_free (jwt_token); g_free (req_content); diff --git a/server/http-tx-mgr.h b/server/http-tx-mgr.h index 75bf091..55851b6 100644 --- a/server/http-tx-mgr.h +++ b/server/http-tx-mgr.h @@ -51,9 +51,11 @@ http_tx_manager_get_nickname (const char *modifier); SeafileShareLinkInfo * http_tx_manager_query_share_link_info (const char *token, const char *cookie, const char *type, + const char *ip_addr, const char *user_agent, int *status, char **err_msg); int http_tx_manager_check_file_access (const char *repo_id, const char *token, const char *cookie, - const char *path, const char *op, char **user); + const char *path, const char *op, const char *ip_addr, + const char *user_agent, char **user); #endif diff --git a/tests/test_gc/test_gc.py b/tests/test_gc/test_gc.py index ca086ba..e0e0d1f 100644 --- a/tests/test_gc/test_gc.py +++ b/tests/test_gc/test_gc.py @@ -146,16 +146,39 @@ def test_gc_partial_history(repo, rm_fs): del_local_files() -def upload_file(url, m): - start = 0 - end = large_file_size - 1 - total = large_file_size - response = requests.post(url, - data = m, headers = {'Content-Type': m.content_type, - 'Content-Range': f"bytes {start}-{end}/{total}", - 'Content-Disposition': 'attachment; filename="large.txt"'}) - return response.status_code, response.text +def upload_file_in_chunks(url, file_path, chunk_size=8*1024*1024): + status = 200 + rsp = '' + with open(file_path, 'rb') as file: + chunk_num = 0 + while True: + chunk = file.read(chunk_size) + if not chunk: + break + start = chunk_num * chunk_size + end = min((chunk_num + 1) * chunk_size - 1, large_file_size - 1) + + m = MultipartEncoder( + fields={ + 'parent_dir': '/', + 'file': (os.path.basename(file_path), chunk, 'application/octet-stream') + }) + + headers = { + 'Content-Range': f"bytes {start}-{end}/{large_file_size}", + 'Content-Type': m.content_type, + 'Content-Disposition': 'attachment; filename="large.txt"' + } + + response = requests.post(url, data = m, headers=headers) + status = response.status_code + rsp = response.text + if status != 200: + break + + chunk_num += 1 + return status, rsp @pytest.mark.parametrize('rm_fs', ['', '--rm-fs']) def test_gc_on_upload(repo, rm_fs): @@ -165,16 +188,10 @@ def test_gc_on_upload(repo, rm_fs): obj_id = '{"parent_dir":"/"}' token = api.get_fileserver_access_token(repo.id, obj_id, 'upload', USER, False) upload_url_base = 'http://127.0.0.1:8082/upload-aj/'+ token - m = MultipartEncoder( - fields={ - 'parent_dir': '/', - 'file': (large_file_name, open(large_file_path, 'rb'), 'application/octet-stream') - }) - status_code = 200 executor = ThreadPoolExecutor() - future = executor.submit(upload_file, upload_url_base, m) + future = executor.submit(upload_file_in_chunks, upload_url_base, large_file_path) while True: offset = api.get_upload_tmp_file_offset(repo.id, "/" + large_file_name) diff --git a/tests/test_share_and_perm/test_shared_repo_perm.py b/tests/test_share_and_perm/test_shared_repo_perm.py index d8a2465..90757bf 100644 --- a/tests/test_share_and_perm/test_shared_repo_perm.py +++ b/tests/test_share_and_perm/test_shared_repo_perm.py @@ -115,7 +115,7 @@ def test_share_dir_to_user(repo, permission): assert api.del_file(repo.id, '/', '[\"dir1\"]', USER) == 0 assert api.unshare_subdir_for_user(repo.id, '/dir2', USER, USER2) == 0 - time.sleep(2) + time.sleep(2.5) assert api.get_shared_repo_by_path(repo.id, '/dir1', USER2) is None assert api.get_shared_repo_by_path(repo.id, '/dir2', USER2) is None @@ -138,7 +138,7 @@ def test_share_dir_to_group(repo, group, permission): assert api.del_file(repo.id, '/', '[\"dir1\"]', USER) == 0 assert api.unshare_subdir_for_group(repo.id, '/dir2', USER, group.id) == 0 - time.sleep(2) + time.sleep(2.5) assert api.check_permission(v_repo_id_1, USER2) is None assert api.check_permission(v_repo_id_2, USER2) is None