Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use post and add ip addr and user agent #719

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions fileserver/fileop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
}
Expand Down Expand Up @@ -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},
}
Expand All @@ -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)
Expand Down Expand Up @@ -3719,20 +3726,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}
Expand Down Expand Up @@ -3765,7 +3785,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
}
Expand Down
14 changes: 12 additions & 2 deletions server/access-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1605,6 +1609,7 @@ access_v2_cb(evhtp_request_t *req, void *arg)
g_free (rpath);
g_free (filename);
g_free (file_id);
g_free (ip_addr);
if (repo != NULL)
seaf_repo_unref (repo);
if (key != NULL)
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion server/http-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions server/http-server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
43 changes: 32 additions & 11 deletions server/http-tx-mgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -599,13 +599,16 @@ parse_error_message (const char *rsp_content, int rsp_size)
}

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;
Expand All @@ -625,16 +628,28 @@ 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);
headers = curl_slist_append (headers, cookie_header);
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;
Expand All @@ -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);
Expand Down Expand Up @@ -687,7 +705,8 @@ parse_file_access_info (const char *rsp_content, int rsp_size)

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;
Expand All @@ -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 ();
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion server/http-tx-mgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
51 changes: 34 additions & 17 deletions tests/test_gc/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand Down
Loading
Loading