Commit cbf8e8d0 authored by ibuler's avatar ibuler

[Update] 修改支持复制

parent 8b51e11f
......@@ -11,15 +11,14 @@ from coco.httpd.ws import ProxyNamespace, ElfinderNamespace
logger = get_logger(__file__)
app = Flask(__name__, template_folder='templates',
static_folder='static',
static_url_path='/coco/static')
static_folder='static')
app.config.update(config)
socket_io = SocketIO()
socket_io.on_namespace(ProxyNamespace('/ssh'))
socket_io.on_namespace(ElfinderNamespace('/elfinder'))
# init_kwargs = {'async_mode': 'threading'}
init_kwargs = {'async_mode': 'eventlet',}
init_kwargs = {'async_mode': 'eventlet'}
socket_io.init_app(app, **init_kwargs),
socket_io.on_error_default(lambda x: logger.exception(x))
......
......@@ -23,8 +23,7 @@ class ElFinderConnector:
'mkfile': ('__mkfile', {'target': True, 'name': True}),
'rename': ('__rename', {'target': True, 'name': True}),
'ls': ('__list', {'target': True}),
'paste': ('__paste', {'targets[]': True, 'src': True,
'dst': True, 'cut': True}),
'paste': ('__paste', {'targets[]': True, 'dst': True, 'cut': True}),
'rm': ('__remove', {'targets[]': True}),
'upload': ('__upload', {'target': True}),
'size': ('__size', {'targets[0]': True}),
......@@ -256,14 +255,10 @@ class ElFinderConnector:
def __paste(self):
targets = self.data['targets[]']
source = self.data['src']
dest = self.data['dst']
cut = (self.data['cut'] == '1')
source_volume = self.get_volume(source)
cut = self.data['cut'] == '1'
dest_volume = self.get_volume(dest)
if source_volume != dest_volume:
raise Exception('Moving between volumes is not supported.')
self.response.update(dest_volume.paste(targets, source, dest, cut))
self.response.update(dest_volume.paste(targets, dest, cut))
def __remove(self):
targets = self.data['targets[]']
......
......@@ -186,7 +186,7 @@ class BaseVolume:
"""
raise NotImplementedError
def paste(self, targets, source, dest, cut):
def paste(self, targets, dest, cut):
""" Moves/copies target files/directories from source to dest.
If a file with the same name already exists in the dest directory
......@@ -194,7 +194,6 @@ class BaseVolume:
before sending the request).
:param targets: A list of hashes of files/dirs to move/copy.
:param source: The current parent of the targets.
:param dest: The new parent of the targets.
:param cut: Boolean. If true, move the targets. If false, copy the
targets.
......
......@@ -146,7 +146,9 @@ class SFTPVolume(BaseVolume):
def mkfile(self, name, parent):
""" Creates a new file. """
parent_path = self._path(parent)
remote_path = self._remote_path(parent_path)
path = self._join(parent_path, name)
remote_path = self._remote_path(path)
with self.sftp.open(remote_path, mode='w'):
pass
return self._info(parent_path)
......@@ -163,9 +165,42 @@ class SFTPVolume(BaseVolume):
'removed': [target]
}
def paste(self, targets, source, dest, cut):
def is_exist(self, path):
remote_path = self._remote_path(path)
try:
data = self.sftp.lstat(remote_path)
print(data)
exist = True
except FileNotFoundError:
exist = False
return exist
def paste(self, targets, dest, cut):
""" Moves/copies target files/directories from source to dest. """
return {"error": "Not support paste"}
print("Paste {} {} {}".format(targets, dest, cut))
dest_parent_path = self._path(dest)
added = []
removed = []
for target in targets:
src_path = self._path(target)
dest_path = self._join(dest_parent_path, self._base_name(src_path))
print("Paste {} to => {}".format(src_path, dest_parent_path))
if self.is_exist(dest_path):
print("Exist {}".format(dest_path))
continue
src_remote_path = self._remote_path(src_path)
dest_remote_path = self._remote_path(dest_path)
try:
f = self.sftp.open(src_remote_path, mode='r')
attr = self.sftp.putfo(f, dest_remote_path)
if cut:
removed.append(self.remove(target))
added.append(self._info(dest_path, attr))
finally:
f.close()
return {"added": added, "removed": removed}
def remove(self, target):
""" Delete a File or Directory object. """
......@@ -201,7 +236,6 @@ class SFTPVolume(BaseVolume):
def upload_as_chunk(self, files, chunk_name, parent):
added = []
print("Upload chunk: {}".format(chunk_name))
parent_path = self._path(parent)
item = files.get('upload[]')
__tmp = chunk_name.split('.')
......
......@@ -2,11 +2,11 @@
<body style="margin: 0">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-2.1.1.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui-1.10.4.min.js') }}"></script>
<link rel="stylesheet" type="text/css" media="screen" href="{{ url_for('static', filename='elfinder/css/elfinder.min.css') }}">
<link rel="stylesheet" type="text/css" media="screen" href="{{ url_for('static', filename='elfinder/css/theme-gray.css') }}">
<script type="text/javascript" src="{{ url_for('static', filename='elfinder/elfinder.full.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='elfinder/i18n/elfinder.pl.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='plugins/elfinder/elfinder.full.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='plugins/elfinder/i18n/elfinder.pl.js') }}"></script>
<link rel="stylesheet" type="text/css" media="screen" href="{{ url_for('static', filename='plugins/elfinder/css/elfinder.min.css') }}">
<link rel="stylesheet" type="text/css" media="screen" href="{{ url_for('static', filename='plugins/elfinder/css/theme-gray.css') }}">
<script type="text/javascript" charset="utf-8">
var socket = io.connect('/elfinder');
socket.on('connect', function () {
......@@ -42,10 +42,10 @@
],
cwd: [
'reload', 'back', '|', 'mkdir', 'mkfile', '|',
'upload'
'upload', 'paste'
],
files: [
'rm', 'rename', 'download'
'rm', 'rename', 'download', 'copy', 'cut', 'paste'
]
},
rememberLastDir: false,
......@@ -80,7 +80,7 @@
}
if (lng !== 'en') {
$.ajax({
url : "{{ url_for('static', filename='elfinder/i18n/') }}"+'/elfinder.'+lng+'.js',
url : "{{ url_for('static', filename='plugins/elfinder/i18n/') }}"+'/elfinder.'+lng+'.js',
cache : true,
dataType : 'script'
})
......@@ -101,8 +101,6 @@
if (!$('#elfinder').hasClass('elfinder-fullscreen')) {
resizeTimer = setTimeout(function () {
var h, w;
var isTrue = window == parent;
console.log("Window == parent" + isTrue);
if (window != parent) {
h = parseInt(parent.$('.window.active').height());
w = parseInt(parent.$('.window.active').width());
......@@ -112,7 +110,6 @@
}
var ori_h = parseInt($('#elfinder').height());
var ori_w = parseInt($('#elfinder').width());
console.log("Height: " + h + " Wid: " + w);
if (h !== ori_h || w != ori_w){
elf.resize(w, h);
}
......
import os
import tempfile
import paramiko
import time
from datetime import datetime
from functools import wraps
import traceback
from paramiko.sftp import SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE, \
SFTP_FAILURE, SFTP_EOF, SFTP_CONNECTION_LOST
from coco.utils import get_logger
from .config import config
from .ctx import app_service
from .connection import SSHConnection
CURRENT_DIR = os.path.dirname(__file__)
logger = get_logger(__file__)
def convert_error(func):
@wraps(func)
def wrapper(*args, **kwargs):
error = None
try:
response = func(*args, **kwargs)
except FileNotFoundError as e:
error = e
response = SFTP_NO_SUCH_FILE
except PermissionError as e:
error = e
response = SFTP_PERMISSION_DENIED
except OSError as e:
error = e
response = SFTP_CONNECTION_LOST
except EOFError as e:
error = e
response = SFTP_EOF
# except Exception as e:
# error = e
# response = SFTP_FAILURE
finally:
if isinstance(error, Exception):
logger.error(error)
return response
return wrapper
class SFTPServer(paramiko.SFTPServerInterface):
root = '/tmp'
root = 'home' # Home or /tmp or other path, must exist on all server
def __init__(self, server, **kwargs):
"""
:param server: SSH Interface instance
:param kwargs:
hosts = {
"hostname[.org]": {
"asset": asset_instance,
"system_users": {
system_user_name: system_user_instance,
}
}
"""
super().__init__(server, **kwargs)
self.server = server
self._sftp = {}
self.hosts = self.get_perm_hosts()
self.hosts = self.get_permed_hosts()
def get_permed_hosts(self):
hosts = {}
assets = app_service.get_user_assets(
self.server.connection.user
)
for asset in assets:
value = {}
key = asset.hostname
if asset.org_id:
key = "{}.{}".format(asset.hostname, asset.org_name)
value['asset'] = asset
value['system_users'] = {su.name: su
for su in asset.system_users_granted
if su.protocol == "ssh" and su.login_mode == 'auto'
}
hosts[key] = value
return hosts
def session_ended(self):
super().session_ended()
for _, v in self._sftp.items():
sftp = v['sftp']
sock = v.get('sock')
sftp = v['client']
proxy = v.get('proxy')
chan = sftp.get_channel()
trans = chan.get_transport()
sftp.close()
......@@ -31,82 +99,89 @@ class SFTPServer(paramiko.SFTPServerInterface):
if not active_channels:
print("CLose transport")
trans.close()
if sock:
sock.close()
sock.transport.close()
if proxy:
proxy.close()
proxy.transport.close()
self._sftp = {}
def get_host_sftp(self, host, su):
asset = self.hosts.get(host)
system_user = None
for s in self.get_asset_system_users(host):
if s.name == su:
system_user = s
break
asset = self.hosts.get(host)['asset']
system_user = self.get_host_system_users(host, only_name=False).get(su)
if not asset or not system_user:
raise OSError("No asset or system user explicit")
raise PermissionError("No asset or system user explicit")
if host not in self._sftp:
cache_key = '{}@{}'.format(su, host)
if cache_key not in self._sftp:
ssh = SSHConnection()
sftp, sock, msg = ssh.get_sftp(asset, system_user)
if sftp:
self._sftp[host] = {'sftp': sftp, 'sock': sock}
__sftp, proxy, msg = ssh.get_sftp(asset, system_user)
if __sftp:
sftp = {
'client': __sftp, 'proxy': proxy,
'home': __sftp.normalize('')
}
self._sftp[cache_key] = sftp
return sftp
else:
raise OSError("Can not connect asset sftp server: {}".format(msg))
else:
return self._sftp[host]['sftp']
def get_perm_hosts(self):
hosts = {}
assets = app_service.get_user_assets(
self.server.connection.user
)
for asset in assets:
key = asset.hostname
if asset.org_id:
key = "{}.{}".format(asset.hostname, asset.org_name)
hosts[key] = asset
return hosts
return self._sftp[cache_key]
def host_has_unique_su(self, host):
host_sus = self.get_host_system_users(host, only_name=True)
logger.debug("Host system users: {}".format(host_sus))
unique = False
su = ''
if len(host_sus) == 1:
unique = True
su = host_sus[0]
return unique, su
def parse_path(self, path):
data = path.lstrip('/').split('/')
su = rpath = ''
if len(data) == 1:
host = data[0]
elif len(data) == 2:
host, su = data
rpath = self.root
request = {"host": "", "su": "", "dpath": "", "su_unique": False}
if len(data) == 1 and not data[0]:
return request
host, *path = data
request["host"] = host
unique, su = self.host_has_unique_su(host)
if unique:
request['su'] = su
request['su_unique'] = True
else:
host, su, *rpath = data
rpath = os.path.join(self.root, '/'.join(rpath))
return host, su, rpath
def get_sftp_rpath(self, path):
host, su, rpath = self.parse_path(path)
sftp = self.get_host_sftp(host, su) if host and su else None
return sftp, rpath
def get_asset_system_users(self, host):
asset = self.hosts.get(host)
if not asset:
return []
return [
su for su in asset.system_users_granted
if su.protocol == "ssh" and su.login_mode == 'auto'
]
request['su'] = path.pop() if path else ''
request['dpath'] = '/'.join(path)
return request
def get_sftp_client_rpath(self, request):
if isinstance(request, str):
request = self.parse_path(request)
host, su, dpath = request['host'], request['su'], request['dpath']
if host and su:
sftp = self.get_host_sftp(host, su)
if self.root.lower() in ['~', 'home']:
root = sftp['home']
else:
root = self.root
rpath = os.path.join(root, dpath.lstrip('/'))
return sftp['client'], rpath
else:
raise FileNotFoundError()
def su_in_asset(self, su, host):
system_users = self.get_asset_system_users(host)
if su in [s.name for s in system_users]:
def is_su_in_asset(self, su, host):
system_users = self.get_host_system_users(host, only_name=True)
if su in system_users:
return True
else:
return False
def create_ftp_log(self, path, operate, is_success=True, filename=None):
host, su, rpath = self.parse_path(path)
asset = self.hosts.get(host)
request = self.parse_path(path)
host, su = request['host'], request['su']
c, rpath = self.get_sftp_client_rpath(request)
asset = self.hosts.get(host)['asset']
date_start = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " +0000",
data = {
"user": self.server.connection.user.username,
......@@ -128,67 +203,64 @@ class SFTPServer(paramiko.SFTPServerInterface):
continue
@staticmethod
def stat_host_dir():
tmp = tempfile.TemporaryDirectory()
attr = paramiko.SFTPAttributes.from_stat(os.stat(tmp.name))
tmp.cleanup()
def stat_fake_dir():
s = os.stat(CURRENT_DIR)
attr = paramiko.SFTPAttributes.from_stat(s)
return attr
def get_host_system_users(self, host, only_name=False):
system_users = self.hosts.get(host, {}).get('system_users', {})
if only_name:
system_users = list(system_users.keys())
return system_users
@convert_error
def list_folder(self, path):
output = []
host, su, rpath = self.parse_path(path)
if not host:
request = self.parse_path(path)
logger.debug("List folder: {} => {}".format(path, request))
if not request['host']: # It's root
for hostname in self.hosts:
attr = self.stat_host_dir()
attr = self.stat_fake_dir()
attr.filename = hostname
output.append(attr)
elif not su:
for su in self.get_asset_system_users(host):
attr = self.stat_host_dir()
attr.filename = su.name
elif not request['su']:
for su in self.get_host_system_users(request['host']):
attr = self.stat_fake_dir()
attr.filename = su
output.append(attr)
else:
sftp, rpath = self.get_sftp_rpath(path)
file_list = sftp.listdir(rpath)
for filename in file_list:
attr = sftp.stat(os.path.join(rpath, filename))
attr.filename = filename
output.append(attr)
client, rpath = self.get_sftp_client_rpath(request)
output = client.listdir_attr(rpath)
return output
@convert_error
def stat(self, path):
host, su, rpath = self.parse_path(path)
request = self.parse_path(path)
host, su, dpath, unique = request['host'], request['su'], \
request['dpath'], request['su_unique']
logger.debug("Stat path: {} => {}".format(path, request))
if not host or not su:
stat = self.stat_fake_dir()
stat.filename = host or su or '/'
return stat
e = OSError("Not that dir")
if host and host not in self.hosts:
return paramiko.SFTPServer.convert_errno(e.errno)
if su and not self.su_in_asset(su, host):
return paramiko.SFTPServer.convert_errno(e.errno)
raise PermissionError("Permission deny")
if su and not self.is_su_in_asset(su, host):
raise PermissionError("Permission deny")
if not rpath or rpath == "/":
attr = self.stat_host_dir()
attr.filename = su or host
return attr
else:
sftp = self.get_host_sftp(host, su)
try:
stat = sftp.stat(rpath)
client, rpath = self.get_sftp_client_rpath(request)
logger.debug("Stat path2: {} => {}".format(client, rpath))
stat = client.stat(rpath)
return stat
except FileNotFoundError as e:
return paramiko.SFTPServer.convert_errno(e.errno)
@convert_error
def lstat(self, path):
host, su, rpath = self.parse_path(path)
if not host or not su or not rpath or rpath == "/":
attr = self.stat_host_dir()
attr.filename = su or host
else:
sftp = self.get_host_sftp(host, su)
attr = sftp.stat(rpath)
attr.filename = os.path.basename(path)
return attr
return self.stat(path)
@convert_error
def open(self, path, flags, attr=None):
binary_flag = getattr(os, 'O_BINARY', 0)
flags |= binary_flag
......@@ -207,110 +279,80 @@ class SFTPServer(paramiko.SFTPServerInterface):
else:
mode = 'rb'
sftp, rpath = self.get_sftp_rpath(path)
if 'r' in mode:
operate = "Download"
elif 'a' in mode:
operate = "Append"
else:
operate = "Upload"
result = None
if sftp is not None:
try:
f = sftp.open(rpath, mode, bufsize=4096)
client, rpath = self.get_sftp_client_rpath(path)
f = client.open(rpath, mode, bufsize=4096)
obj = paramiko.SFTPHandle(flags)
obj.filename = rpath
obj.readfile = f
obj.writefile = f
result = obj
success = True
except OSError:
pass
self.create_ftp_log(path, operate, success)
return result
finally:
self.create_ftp_log(path, operate, success)
@convert_error
def remove(self, path):
sftp, rpath = self.get_sftp_rpath(path)
client, rpath = self.get_sftp_client_rpath(path)
success = False
if sftp is not None:
try:
sftp.remove(rpath)
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
client.remove(rpath)
success = True
else:
result = paramiko.SFTP_FAILURE
return paramiko.SFTP_OK
finally:
self.create_ftp_log(path, "Delete", success)
return result
@convert_error
def rename(self, src, dest):
host1, su1, rsrc = self.parse_path(src)
host2, su2, rdest = self.parse_path(dest)
client, rsrc = self.get_sftp_client_rpath(src)
client2, rdest = self.get_sftp_client_rpath(dest)
success = False
filename = "{}=>{}".format(rsrc, rdest)
if host1 == host2 and su1 == su2 and host1:
sftp = self.get_host_sftp(host1, su1)
try:
sftp.rename(rsrc, rdest)
if client == client2:
client.rename(rsrc, rdest)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
return paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
filename = "{}=>{}".format(rsrc, rdest)
raise FileNotFoundError("Can't rename {} => {}".format(src, dest))
finally:
self.create_ftp_log(src, "Rename", success, filename=filename)
return result
@convert_error
def mkdir(self, path, attr=0o755):
sftp, rpath = self.get_sftp_rpath(path)
client, rpath = self.get_sftp_client_rpath(path)
success = False
if sftp is not None and rpath != '/':
try:
sftp.mkdir(rpath)
if rpath == '/':
raise PermissionError("Create '/', are you sure?")
client.mkdir(rpath)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
self.create_ftp_log(path, "Mkdir", success, filename=rpath)
return result
return paramiko.SFTP_OK
finally:
self.create_ftp_log(path, "Mkdir", success)
@convert_error
def rmdir(self, path):
sftp, rpath = self.get_sftp_rpath(path)
client, rpath = self.get_sftp_client_rpath(path)
success = False
if sftp is not None:
try:
sftp.rmdir(rpath)
client.rmdir(rpath)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
return paramiko.SFTP_OK
finally:
self.create_ftp_log(path, "Rmdir", success)
return result
# def chattr(self, path, attr):
# sftp, rpath = self.get_sftp_rpath(path)
# if sftp is not None:
# if attr._flags & attr.FLAG_PERMISSIONS:
# sftp.chmod(rpath, attr.st_mode)
# if attr._flags & attr.FLAG_UIDGID:
# sftp.chown(rpath, attr.st_uid, attr.st_gid)
# if attr._flags & attr.FLAG_AMTIME:
# sftp.utime(rpath, (attr.st_atime, attr.st_mtime))
# if attr._flags & attr.FLAG_SIZE:
# sftp.truncate(rpath, attr.st_size)
# return paramiko.SFTP_OK
class FakeServer:
......@@ -352,22 +394,26 @@ class InternalSFTPClient(SFTPServer):
return self.list_folder(path)
def open(self, path, mode, **kwargs):
sftp, rpath = self.get_sftp_rpath(path)
client, rpath = self.get_sftp_client_rpath(path)
if 'r' in mode:
operate = "Download"
else:
operate = "Upload"
success = False
if sftp is not None:
try:
f = sftp.open(rpath, mode, bufsize=4096)
except OSError:
raise OSError("Open file {} failed".format(rpath))
f = client.open(rpath, mode, bufsize=4096)
success = True
return f
finally:
print("Open success")
self.create_ftp_log(path, operate, success)
return f
def stat(self, path):
attr = super().stat.__wrapped__(self, path)
return attr
def lstat(self, path):
attr = super().lstat.__wrapped__(self, path)
return attr
def get_channel(self):
return FakeChannel.new()
......@@ -375,6 +421,9 @@ class InternalSFTPClient(SFTPServer):
def unlink(self, path):
return self.remove(path)
def putfo(self, f, path, callback=None, confirm=True):
client, rpath = self.get_sftp_client_rpath(path)
return client.putfo(f, rpath, callback=callback, confirm=confirm)
def close(self):
print("End ")
return self.session_ended()
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