Commit 510513c1 authored by ibuler's avatar ibuler

Merge branch 'dev' of github.com:jumpserver/coco into dev

parents a2592688 61bae649
...@@ -376,6 +376,8 @@ defaults = { ...@@ -376,6 +376,8 @@ defaults = {
'SFTP_SHOW_HIDDEN_FILE': False, 'SFTP_SHOW_HIDDEN_FILE': False,
'UPLOAD_FAILED_REPLAY_ON_START': True, 'UPLOAD_FAILED_REPLAY_ON_START': True,
'REUSE_CONNECTION': True, 'REUSE_CONNECTION': True,
'FORCE_REMOVE_FOLDER': False,
'TELNET_TTYPE': 'XTERM-256COLOR',
} }
......
...@@ -300,25 +300,31 @@ class TelnetConnection: ...@@ -300,25 +300,31 @@ class TelnetConnection:
logger.info(msg) logger.info(msg)
return None, msg return None, msg
if data.startswith(telnetlib.IAC): # 将数据以 \r\n 进行分割
self.option_negotiate(data) _data_list = data.split(b'\r\n')
else: for _data in _data_list:
result = self.login_auth(data) if not _data:
if result:
msg = 'Successful asset connection.<{}>/<{}>/<{}>.'.format(
self.client.user, self.system_user.username,
self.asset.hostname
)
logger.info(msg)
return self.sock, None
elif result is False:
self.sock.close()
msg = 'Authentication failed.\r\n'
logger.info(msg)
return None, msg
elif result is None:
continue continue
if _data.startswith(telnetlib.IAC):
self.option_negotiate(_data)
else:
result = self.login_auth(_data)
if result:
msg = 'Successful asset connection.<{}>/<{}>/<{}>.'.format(
self.client.user, self.system_user.username,
self.asset.hostname
)
logger.info(msg)
return self.sock, None
elif result is False:
self.sock.close()
msg = 'Authentication failed.\r\n'
logger.info(msg)
return None, msg
elif result is None:
continue
def option_negotiate(self, data): def option_negotiate(self, data):
""" """
Telnet server option negotiate before connection Telnet server option negotiate before connection
...@@ -338,7 +344,8 @@ class TelnetConnection: ...@@ -338,7 +344,8 @@ class TelnetConnection:
elif x == telnetlib.DO + telnetlib.TTYPE: elif x == telnetlib.DO + telnetlib.TTYPE:
new_data_list.append(telnetlib.WILL + telnetlib.TTYPE) new_data_list.append(telnetlib.WILL + telnetlib.TTYPE)
elif x == telnetlib.SB + telnetlib.TTYPE + b'\x01': elif x == telnetlib.SB + telnetlib.TTYPE + b'\x01':
new_data_list.append(telnetlib.SB + telnetlib.TTYPE + b'\x00' + b'XTERM-256COLOR') terminal_type = bytes(config.TELNET_TTYPE, encoding='utf-8')
new_data_list.append(telnetlib.SB + telnetlib.TTYPE + b'\x00' + terminal_type)
elif telnetlib.DO in x: elif telnetlib.DO in x:
new_data_list.append(x.replace(telnetlib.DO, telnetlib.WONT)) new_data_list.append(x.replace(telnetlib.DO, telnetlib.WONT))
elif telnetlib.WILL in x: elif telnetlib.WILL in x:
......
...@@ -31,7 +31,7 @@ class ElFinderConnector: ...@@ -31,7 +31,7 @@ class ElFinderConnector:
_allowed_args = [ _allowed_args = [
'cmd', 'target', 'targets[]', 'current', 'tree', 'cmd', 'target', 'targets[]', 'current', 'tree',
'name', 'content', 'src', 'dst', 'cut', 'init', 'name', 'content', 'src', 'dst', 'cut', 'init',
'type', 'width', 'height', 'upload[]', 'dirs[]', 'type', 'width', 'height', 'upload[]', 'upload_path[]', 'dirs[]',
'targets', "chunk", "range", "cid", 'reload', 'targets', "chunk", "range", "cid", 'reload',
] ]
...@@ -125,7 +125,7 @@ class ElFinderConnector: ...@@ -125,7 +125,7 @@ class ElFinderConnector:
# Copy allowed parameters from the given request's GET to self.data # Copy allowed parameters from the given request's GET to self.data
for field in self._allowed_args: for field in self._allowed_args:
if field in request_data: if field in request_data:
if field in ["targets[]", "targets", "dirs[]"]: if field in ["targets[]", "targets", "dirs[]", 'upload_path[]']:
self.data[field] = request_data.getlist(field) self.data[field] = request_data.getlist(field)
else: else:
self.data[field] = request_data[field] self.data[field] = request_data[field]
...@@ -270,20 +270,21 @@ class ElFinderConnector: ...@@ -270,20 +270,21 @@ class ElFinderConnector:
parent = self.data['target'] parent = self.data['target']
volume = self.get_volume(parent) volume = self.get_volume(parent)
upload = self.data.get('upload[]') upload = self.data.get('upload[]')
upload_paths = self.data.get('upload_path[]')
if self.data.get('chunk') and self.data.get('cid'): if self.data.get('chunk') and self.data.get('cid'):
self.response.update( self.response.update(
volume.upload_as_chunk( volume.upload_as_chunk(
self.request.files, self.data.get('chunk'), parent self.request.files, self.data.get('chunk'), parent, upload_paths
) )
) )
elif self.data.get('chunk'): elif self.data.get('chunk'):
self.response.update( self.response.update(
volume.upload_chunk_merge(parent, self.data.get('chunk')) volume.upload_chunk_merge(parent, self.data.get('chunk'), upload_paths),
) )
elif isinstance(upload, str): elif isinstance(upload, str):
self.response.update(volume.upload_as_url(upload, parent)) self.response.update(volume.upload_as_url(upload, parent, upload))
else: else:
self.response.update(volume.upload(self.request.files, parent)) self.response.update(volume.upload(self.request.files, parent, upload_paths))
def __size(self): def __size(self):
target = self.data['targets[]'] target = self.data['targets[]']
......
...@@ -214,23 +214,25 @@ class BaseVolume: ...@@ -214,23 +214,25 @@ class BaseVolume:
""" """
raise NotImplementedError raise NotImplementedError
def upload(self, files, parent): def upload(self, files, parent, upload_path):
""" Uploads one or more files in to the parent directory. """ Uploads one or more files in to the parent directory.
:param files: A list of uploaded file objects, as described here: :param files: A list of uploaded file objects, as described here:
https://docs.djangoproject.com/en/dev/topics/http/file-uploads/ https://docs.djangoproject.com/en/dev/topics/http/file-uploads/
:param parent: The hash of the directory in which to create the :param parent: The hash of the directory in which to create the
new files. new files.
:param upload_path:
:returns: TODO :returns: TODO
""" """
raise NotImplementedError raise NotImplementedError
def upload_as_chunk(self, files, chunk_name, parent): def upload_as_chunk(self, files, chunk_name, parent, upload_path):
""" """
Upload a large file as chunk Upload a large file as chunk
:param files: :param files:
:param chunk_name: :param chunk_name:
:param cid: :param cid:
:param parent: :param parent:
:param upload_path:
:return: :return:
""" """
...@@ -4,7 +4,7 @@ import stat ...@@ -4,7 +4,7 @@ import stat
import threading import threading
from flask import send_file from flask import send_file
import requests import os
from coco.utils import get_logger from coco.utils import get_logger
from .base import BaseVolume from .base import BaseVolume
...@@ -222,10 +222,10 @@ class SFTPVolume(BaseVolume): ...@@ -222,10 +222,10 @@ class SFTPVolume(BaseVolume):
self.sftp.unlink(remote_path) self.sftp.unlink(remote_path)
return target return target
def upload_as_url(self, url, parent): def upload_as_url(self, url, parent, upload_path):
raise PermissionError("Not support upload from url") raise PermissionError("Not support upload from url")
def upload(self, files, parent): def upload(self, files, parent, upload_path):
""" For now, this uses a very naive way of storing files - the entire """ For now, this uses a very naive way of storing files - the entire
file is read in to the File model's content field in one go. file is read in to the File model's content field in one go.
...@@ -234,20 +234,23 @@ class SFTPVolume(BaseVolume): ...@@ -234,20 +234,23 @@ class SFTPVolume(BaseVolume):
""" """
added = [] added = []
parent_path = self._path(parent) parent_path = self._path(parent)
item = files.get('upload[]') for i, item in enumerate(files.getlist("upload[]")):
path = self._join(parent_path, item.filename) if upload_path and (parent != upload_path[i]) and (item.filename in upload_path[i]):
remote_path = self._remote_path(path) path = self._join(parent_path, upload_path[i].lstrip(self.path_sep))
infos = self._list(parent_path) else:
files_exist = [d['name'] for d in infos] path = self._join(parent_path, item.filename)
if item.filename in files_exist: remote_path = self._remote_path(path)
raise OSError("File {} exits".format(remote_path)) infos = self._list(os.path.dirname(path))
with self.sftp.open(remote_path, 'w') as rf: files_exist = [d['name'] for d in infos]
for data in item: if item.filename in files_exist:
rf.write(data) raise OSError("File {} exits".format(remote_path))
added.append(self._info(path)) with self.sftp.open(remote_path, 'w') as rf:
for data in item:
rf.write(data)
added.append(self._info(path))
return {'added': added} return {'added': added}
def upload_as_chunk(self, files, chunk_name, parent): def upload_as_chunk(self, files, chunk_name, parent, upload_path):
added = [] added = []
parent_path = self._path(parent) parent_path = self._path(parent)
item = files.get('upload[]') item = files.get('upload[]')
...@@ -255,11 +258,13 @@ class SFTPVolume(BaseVolume): ...@@ -255,11 +258,13 @@ class SFTPVolume(BaseVolume):
filename = '.'.join(__tmp[:-2]) filename = '.'.join(__tmp[:-2])
num, total = __tmp[-2].split('_') num, total = __tmp[-2].split('_')
num, total = int(num), int(total) num, total = int(num), int(total)
if len(upload_path) == 1 and (parent != upload_path[0]) and (filename in upload_path[0]):
path = self._join(parent_path, filename) path = self._join(parent_path, upload_path[0].lstrip(self.path_sep))
else:
path = self._join(parent_path, upload_path[0].lstrip(self.path_sep), filename)
remote_path = self._remote_path(path) remote_path = self._remote_path(path)
if num == 0: if num == 0:
infos = self._list(parent_path) infos = self._list(os.path.dirname(path))
files_exist = [d['name'] for d in infos] files_exist = [d['name'] for d in infos]
if item.filename in files_exist: if item.filename in files_exist:
raise OSError("File {} exits".format(remote_path)) raise OSError("File {} exits".format(remote_path))
...@@ -271,9 +276,12 @@ class SFTPVolume(BaseVolume): ...@@ -271,9 +276,12 @@ class SFTPVolume(BaseVolume):
else: else:
return {'added': added, '_chunkmerged': filename, '_name': filename} return {'added': added, '_chunkmerged': filename, '_name': filename}
def upload_chunk_merge(self, parent, chunk): def upload_chunk_merge(self, parent, chunk, upload_path):
parent_path = self._path(parent) parent_path = self._path(parent)
path = self._join(parent_path, chunk) if len(upload_path) == 1 and (parent != upload_path[0]):
path = self._join(parent_path, upload_path[0].lstrip(self.path_sep))
else:
path = self._join(parent_path, chunk)
return {"added": [self._info(path)]} return {"added": [self._info(path)]}
def size(self, target): def size(self, target):
......
...@@ -422,7 +422,7 @@ ...@@ -422,7 +422,7 @@
'minsLeft' : '剩余 $1 分钟', // from v2.1.17 added 13.11.2016 'minsLeft' : '剩余 $1 分钟', // from v2.1.17 added 13.11.2016
'openAsEncoding' : '使用所选编码重新打开', // from v2.1.19 added 2.12.2016 'openAsEncoding' : '使用所选编码重新打开', // from v2.1.19 added 2.12.2016
'saveAsEncoding' : '使用所选编码保存', // from v2.1.19 added 2.12.2016 'saveAsEncoding' : '使用所选编码保存', // from v2.1.19 added 2.12.2016
'selectFolder' : '选择目录(暂不支持)', // from v2.1.20 added 13.12.2016 'selectFolder' : '选择目录', // from v2.1.20 added 13.12.2016
'firstLetterSearch': '首字母搜索', // from v2.1.23 added 24.3.2017 'firstLetterSearch': '首字母搜索', // from v2.1.23 added 24.3.2017
'presets' : '预置', // from v2.1.25 added 26.5.2017 'presets' : '预置', // from v2.1.25 added 26.5.2017
'tooManyToTrash' : '项目太多,不能移动到回收站.', // from v2.1.25 added 9.6.2017 'tooManyToTrash' : '项目太多,不能移动到回收站.', // from v2.1.25 added 9.6.2017
......
import os import os
import stat
import paramiko import paramiko
import time import time
from datetime import datetime from datetime import datetime
...@@ -47,6 +48,7 @@ def convert_error(func): ...@@ -47,6 +48,7 @@ def convert_error(func):
if isinstance(error, Exception): if isinstance(error, Exception):
logger.error(error) logger.error(error)
return response return response
return wrapper return wrapper
...@@ -74,6 +76,7 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -74,6 +76,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
self._sftp = {} self._sftp = {}
self.hosts = self.get_permed_hosts() self.hosts = self.get_permed_hosts()
self.is_finished = False self.is_finished = False
self.force_rm_folder = config.FORCE_REMOVE_FOLDER
def get_user_assets(self): def get_user_assets(self):
user_id = self.server.connection.user.id user_id = self.server.connection.user.id
...@@ -256,7 +259,7 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -256,7 +259,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
def stat(self, path): def stat(self, path):
request = self.parse_path(path) request = self.parse_path(path)
host, su, dpath, unique = request['host'], request['su'], \ host, su, dpath, unique = request['host'], request['su'], \
request['dpath'], request['su_unique'] request['dpath'], request['su_unique']
logger.debug("Stat path: {} => {}".format(path, request)) logger.debug("Stat path: {} => {}".format(path, request))
if not host or not su: if not host or not su:
...@@ -392,12 +395,24 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -392,12 +395,24 @@ class SFTPServer(paramiko.SFTPServerInterface):
success = False success = False
try: try:
client.rmdir(rpath) if self.force_rm_folder:
self._rmdir(client, rpath)
else:
client.rmdir(rpath)
success = True success = True
return paramiko.SFTP_OK return paramiko.SFTP_OK
finally: finally:
self.create_ftp_log(path, "Rmdir", success) self.create_ftp_log(path, "Rmdir", success)
def _rmdir(self, sftp_client, path):
for item in list(sftp_client.listdir_iter(path)):
filepath = "/".join([path, item.filename])
if stat.S_IFMT(item.st_mode) == stat.S_IFDIR:
self._rmdir(sftp_client, filepath)
continue
sftp_client.remove(filepath)
sftp_client.rmdir(path)
class FakeServer: class FakeServer:
pass pass
......
...@@ -60,3 +60,9 @@ BOOTSTRAP_TOKEN: <PleasgeChangeSameWithJumpserver> ...@@ -60,3 +60,9 @@ BOOTSTRAP_TOKEN: <PleasgeChangeSameWithJumpserver>
# 是否复用和用户后端资产已建立的连接(用户不会复用其他用户的连接) # 是否复用和用户后端资产已建立的连接(用户不会复用其他用户的连接)
# REUSE_CONNECTION: true # REUSE_CONNECTION: true
# 是否强制删除文件夹:(default false)
# FORCE_REMOVE_FOLDER: false
# Telnet连接协商使用的终端类型
# TELNET_TTYPE: XTERM-256COLOR
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment