Add files functions to API v2

This commit is contained in:
amcmanu3 2023-09-03 16:15:38 -04:00
parent 90905a70f5
commit afb7b91b7a
3 changed files with 544 additions and 137 deletions

View File

@ -34,6 +34,8 @@ from app.classes.web.routes.api.servers.server.backups.backup.index import (
)
from app.classes.web.routes.api.servers.server.files import (
ApiServersServerFilesIndexHandler,
ApiServersServerFilesCreateHandler,
ApiServersServerFilesZipHandler,
)
from app.classes.web.routes.api.servers.server.tasks.task.children import (
ApiServersServerTasksTaskChildrenHandler,
@ -154,6 +156,16 @@ def api_handlers(handler_args):
ApiServersServerFilesIndexHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/files/create/?",
ApiServersServerFilesCreateHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/files/zip/?",
ApiServersServerFilesZipHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/tasks/?",
ApiServersServerTasksIndexHandler,

View File

@ -15,12 +15,52 @@ files_get_schema = {
"type": "object",
"properties": {
"page": {"type": "string", "minLength": 1},
"path": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
}
files_patch_schema = {
"type": "object",
"properties": {
"path": {"type": "string"},
"contents": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
}
files_unzip_schema = {
"type": "object",
"properties": {
"folder": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
}
files_create_schema = {
"type": "object",
"properties": {
"parent": {"type": "string"},
"name": {"type": "string"},
"directory": {"type": "boolean"},
},
"additionalProperties": False,
"minProperties": 1,
}
files_rename_schema = {
"type": "object",
"properties": {
"path": {"type": "string"},
"new_name": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
}
file_delete_schema = {
"type": "object",
"properties": {
@ -73,7 +113,7 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
)
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
data["folder"],
data["path"],
):
return self.finish_json(
400,
@ -83,60 +123,71 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
"error_data": str(e),
},
)
# TODO: limit some columns for specific permissions?
folder = data["folder"]
return_json = {
"root_path": {
"path": folder,
"top": data["folder"]
== self.controller.servers.get_server_data_by_id(server_id)["path"],
if os.path.isdir(data["path"]):
# TODO: limit some columns for specific permissions?
folder = data["path"]
return_json = {
"root_path": {
"path": folder,
"top": data["path"]
== self.controller.servers.get_server_data_by_id(server_id)["path"],
}
}
}
dir_list = []
unsorted_files = []
file_list = os.listdir(folder)
for item in file_list:
if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item)
else:
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
)
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
server_id
):
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": True,
}
dir_list = []
unsorted_files = []
file_list = os.listdir(folder)
for item in file_list:
if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item)
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": True,
}
else:
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": False,
}
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
)
for raw_filename in file_list:
filename = html.escape(raw_filename)
rel = os.path.join(folder, raw_filename)
dpath = os.path.join(folder, filename)
if str(dpath) in self.controller.management.get_excluded_backup_dirs(
server_id
):
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": True,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": True,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": False,
}
self.finish_json(200, {"status": "ok", "data": return_json})
if os.path.isdir(rel):
return_json[filename] = {
"path": dpath,
"dir": True,
"excluded": False,
}
else:
return_json[filename] = {
"path": dpath,
"dir": False,
"excluded": False,
}
self.finish_json(200, {"status": "ok", "data": return_json})
else:
try:
with open(data["path"], encoding="utf-8") as file:
file_contents = file.read()
except UnicodeDecodeError as ex:
self.finish_json(
400,
{"status": "error", "error": "DECODE_ERROR", "error_data": str(ex)},
)
self.finish_json(200, {"status": "ok", "data": file_contents})
def delete(self, server_id: str):
auth_data = self.authenticate_user()
@ -190,3 +241,315 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
else:
FileHelpers.del_file(data["filename"])
return self.finish_json(200, {"status": "ok"})
def patch(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, files_patch_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
data["path"],
):
return self.finish_json(
400,
{
"status": "error",
"error": "TRAVERSAL DETECTED",
"error_data": str(e),
},
)
file_path = Helpers.get_os_understandable_path(data["path"])
file_contents = data["contents"]
# Open the file in write mode and store the content in file_object
with open(file_path, "w", encoding="utf-8") as file_object:
file_object.write(file_contents)
return self.finish_json(200, {"status": "ok"})
def put(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, files_create_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
path = os.path.join(data["parent"], data["name"])
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
path,
):
return self.finish_json(
400,
{
"status": "error",
"error": "TRAVERSAL DETECTED",
"error_data": str(e),
},
)
if Helpers.check_path_exists(os.path.abspath(path)):
return self.finish_json(
400,
{
"status": "error",
"error": "FILE EXISTS",
"error_data": str(e),
},
)
if data["directory"]:
os.mkdir(path)
else:
# Create the file by opening it
with open(path, "w", encoding="utf-8") as file_object:
file_object.close()
return self.finish_json(200, {"status": "ok"})
class ApiServersServerFilesCreateHandler(BaseApiHandler):
def patch(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, files_rename_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
path = data["path"]
new_item_name = data["new_name"]
new_item_path = os.path.join(os.path.split(path)[0], new_item_name)
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
path,
) and not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
new_item_path,
):
return self.finish_json(
400,
{
"status": "error",
"error": "TRAVERSAL DETECTED",
"error_data": str(e),
},
)
if Helpers.check_path_exists(os.path.abspath(new_item_path)):
return self.finish_json(
400,
{
"status": "error",
"error": "FILE EXISTS",
"error_data": {},
},
)
os.rename(path, new_item_path)
return self.finish_json(200, {"status": "ok"})
def put(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, files_create_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
path = os.path.join(data["parent"], data["name"])
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
path,
):
return self.finish_json(
400,
{
"status": "error",
"error": "TRAVERSAL DETECTED",
"error_data": str(e),
},
)
if Helpers.check_path_exists(os.path.abspath(path)):
return self.finish_json(
400,
{
"status": "error",
"error": "FILE EXISTS",
"error_data": str(e),
},
)
if data["directory"]:
os.mkdir(path)
else:
# Create the file by opening it
with open(path, "w", encoding="utf-8") as file_object:
file_object.close()
return self.finish_json(200, {"status": "ok"})
class ApiServersServerFilesZipHandler(BaseApiHandler):
def post(self, server_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
EnumPermissionsServer.FILES
not in self.controller.server_perms.get_user_id_permissions_list(
auth_data[4]["user_id"], server_id
)
):
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e:
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
try:
validate(data, files_unzip_schema)
except ValidationError as e:
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
"error_data": str(e),
},
)
folder = data["folder"]
user_id = auth_data[4]["user_id"]
if not Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"],
folder,
):
return self.finish_json(
400,
{
"status": "error",
"error": "TRAVERSAL DETECTED",
"error_data": str(e),
},
)
if Helpers.check_file_exists(folder):
folder = self.file_helper.unzip_file(folder, user_id)
else:
if user_id:
return self.finish_json(
400,
{
"status": "error",
"error": "FILE_DOES_NOT_EXIST",
"error_data": str(e),
},
)
return self.finish_json(200, {"status": "ok"})

View File

@ -398,32 +398,36 @@
},
];
let filePath = '', serverFileContent = '';
let path = '', serverFileContent = '';
function clickOnFile(event) {
filePath = event.target.getAttribute('data-path');
$.ajax({
type: 'GET',
url: "/files/get_file?id=" + serverId + "&file_path=" + encodeURIComponent(filePath),
dataType: 'text',
success: function (data) {
async function clickOnFile(event) {
var token = getCookie("_xsrf");
path = event.target.getAttribute('data-path');
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
method: 'POST',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"page": "files", "path": path}),
});
let responseData = await res.json();
console.log(responseData)
if (responseData.status === "ok") {
console.log('Got File Contents From Server');
json = JSON.parse(data)
if (json.error) {
$('#editorParent').toggle(false) // hide
$('#fileError').toggle(true) // show
$('#fileError').text("{{ translate('serverFiles', 'fileReadError', data['lang']) }}: " + json.error) // show error
editor.blur()
} else {
$('#editorParent').toggle(true) // show
$('#fileError').toggle(false) // hide
setFileName(event.target.innerText);
editor.session.setValue(json.content);
serverFileContent = json.content;
editor.session.setValue(responseData.data);
serverFileContent = responseData.data;
setSaveStatus(true);
}
},
});
else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
function setFileName(name) {
@ -578,74 +582,96 @@
function save() {
async function save() {
let text = editor.session.getValue();
var token = getCookie("_xsrf")
$.ajax({
type: "PUT",
headers: { 'X-XSRFToken': token },
url: "/files/save_file?id=" + serverId,
data: {
file_contents: text,
file_path: filePath
},
success: (data) => {
let res = await fetch(`/api/v2/servers/${serverId}/files`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"path": path, "contents": text}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
serverFileContent = text;
setSaveStatus(true)
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
});
}
function createFile(parent, name, callback) {
async function createFile(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: "/files/create_file?id=" + serverId,
data: {
file_parent: parent,
file_name: name
},
success: function (data) {
console.log("got response:");
callback();
},
});
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
method: 'PUT',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"parent": parent, "name": name, "directory": false}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
function createDir(parent, name, callback) {
async function createDir(parent, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: "/files/create_dir?id=" + serverId,
data: {
dir_parent: parent,
dir_name: name
},
success: function (data) {
console.log("got response:");
callback();
},
});
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
method: 'PUT',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"parent": parent, "name": name, "directory": true}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
function renameItem(path, name, callback) {
async function renameItem(path, name, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "PATCH",
headers: { 'X-XSRFToken': token },
url: "/files/rename_file?id=" + serverId,
data: {
item_path: path,
new_item_name: name
},
success: function (data) {
console.log("got response:");
callback();
},
});
let res = await fetch(`/api/v2/servers/${serverId}/files/create/`, {
method: 'PATCH',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"path": path, "new_name": name}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
async function deleteItem(path, el, callback) {
@ -671,20 +697,26 @@
}
}
function unZip(path, callback) {
console.log('path: ', path)
async function unZip(path, callback) {
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: { 'X-XSRFToken': token },
url: "/files/unzip_file?id=" + serverId,
data: {
path: path
},
success: function (data) {
window.location.href = "/panel/server_detail?id=" + serverId + "&subpage=files";
},
});
let res = await fetch(`/api/v2/servers/${serverId}/files/zip/`, {
method: 'POST',
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"folder": path}),
});
let responseData = await res.json();
if (responseData.status === "ok") {
getTreeView($('#root_dir').data('path'));
setTreeViewContext();
} else {
bootbox.alert({
title: responseData.status,
message: responseData.error
});
}
}
async function sendFile(file, path, serverId, left, i, onProgress) {
@ -888,7 +920,7 @@
headers: {
'X-XSRFToken': token
},
body: JSON.stringify({"page": "files", "folder": path}),
body: JSON.stringify({"page": "files", "path": path}),
});
let responseData = await res.json();
if (responseData.status === "ok") {