Commit b1637632 authored by 广宏伟's avatar 广宏伟

Merged in test (pull request #19)

Test
parents 21efdc90 35d8896f
...@@ -3,12 +3,10 @@ ...@@ -3,12 +3,10 @@
# #
import os import os
import socket import socket
from flask_socketio import SocketIO, Namespace, emit, join_room, leave_room
from flask import Flask, send_from_directory, render_template, request, jsonify
import uuid import uuid
from flask_socketio import SocketIO, Namespace, join_room, leave_room
from flask import Flask, request, current_app, redirect
# Todo: Remove for future
from jms.models import User
from .models import Request, Client, WSProxy from .models import Request, Client, WSProxy
from .proxy import ProxyServer from .proxy import ProxyServer
from .utils import get_logger from .utils import get_logger
...@@ -19,31 +17,31 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) ...@@ -19,31 +17,31 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
logger = get_logger(__file__) logger = get_logger(__file__)
class BaseWebSocketHandler: class BaseNamespace(Namespace):
clients = None clients = None
current_user = None current_user = None
def app(self, app): @property
self.app = app def app(self):
return self app = current_app.config['coco']
return app
def prepare(self, request): def on_connect(self):
# self.app = self.settings["app"] self.current_user = self.get_current_user()
x_forwarded_for = request.headers.get("X-Forwarded-For", '').split(',') if self.current_user is None:
if x_forwarded_for and x_forwarded_for[0]: return redirect(current_app.config['LOGIN_URL'])
remote_ip = x_forwarded_for[0] logger.debug("{} connect websocket".format(self.current_user))
else:
remote_ip = request.remote_addr
req = Request((remote_ip, 0))
req.user = self.current_user
req.meta = {
"width": self.clients[request.sid]["cols"],
"height": self.clients[request.sid]["rows"]
}
self.clients[request.sid]["request"] = req
def check_origin(self, origin): def get_current_user(self):
return True session_id = request.cookies.get('sessionid', '')
csrf_token = request.cookies.get('csrftoken', '')
token = request.headers.get("Authorization")
user = None
if session_id and csrf_token:
user = self.app.service.check_user_cookie(session_id, csrf_token)
if token:
user = self.app.service.check_user_with_token(token)
return user
def close(self): def close(self):
try: try:
...@@ -52,99 +50,164 @@ class BaseWebSocketHandler: ...@@ -52,99 +50,164 @@ class BaseWebSocketHandler:
pass pass
class SSHws(Namespace, BaseWebSocketHandler): class ProxyNamespace(BaseNamespace):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.clients = dict() self.clients = dict()
self.rooms = dict() self.rooms = dict()
super().__init__(*args, **kwargs)
def on_connect(self): def new_client(self):
room = str(uuid.uuid4()) room = str(uuid.uuid4())
self.clients[request.sid] = { client = {
"cols": int(request.cookies.get('cols', 80)), "cols": int(request.cookies.get('cols', 80)),
"rows": int(request.cookies.get('rows', 24)), "rows": int(request.cookies.get('rows', 24)),
"room": room, "room": room,
# "chan": dict(),
"proxy": dict(), "proxy": dict(),
"client": dict(), "client": dict(),
"forwarder": dict(), "forwarder": dict(),
"request": None, "request": self.make_coco_request()
} }
self.rooms[room] = { return client
def make_coco_request(self):
x_forwarded_for = request.headers.get("X-Forwarded-For", '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
remote_ip = x_forwarded_for[0]
else:
remote_ip = request.remote_addr
width_request = request.cookies.get('cols')
rows_request = request.cookies.get('rows')
if width_request and width_request.isdigit():
width = int(width_request)
else:
width = 80
if rows_request and rows_request.isdigit():
rows = int(rows_request)
else:
rows = 24
req = Request((remote_ip, 0))
req.user = self.current_user
req.meta = {
"width": width,
"height": rows,
}
return req
def on_connect(self):
logger.debug("On connect event trigger")
super().on_connect()
client = self.new_client()
self.clients[request.sid] = client
self.rooms[client['room']] = {
"admin": request.sid, "admin": request.sid,
"member": [], "member": [],
"rw": [] "rw": []
} }
join_room(room) join_room(client['room'])
self.current_user = self.app.service.check_user_cookie(
session_id=request.cookies.get('sessionid', ''),
csrf_token=request.cookies.get('csrftoken', '')
)
self.prepare(request)
def on_data(self, message): def on_data(self, message):
if message['room'] and self.clients[request.sid]["proxy"][message['room']]: """
self.clients[request.sid]["proxy"][message['room']].send({"data": message['data']}) 收到浏览器请求
:param message: {"data": "xxx", "room": "xxx"}
:return:
"""
room = message.get('room')
if not room:
return
room_proxy = self.clients[request.sid]['proxy'].get(room)
if room_proxy:
room_proxy.send({"data": message['data']})
def on_host(self, message): def on_host(self, message):
# 此处获取主机的信息 # 此处获取主机的信息
logger.debug("On host event trigger")
connection = str(uuid.uuid4()) connection = str(uuid.uuid4())
asset_id = message.get('uuid', None) asset_id = message.get('uuid', None)
user_id = message.get('userid', None) user_id = message.get('userid', None)
self.emit('room', {'room': connection, 'secret': message['secret']}) secret = message.get('secret', None)
if asset_id and user_id: self.emit('room', {'room': connection, 'secret': secret})
asset = self.app.service.get_asset(asset_id)
system_user = self.app.service.get_system_user(user_id) if not asset_id or not user_id:
# self.on_connect()
if system_user: return
child, parent = socket.socketpair()
self.clients[request.sid]["client"][connection] = Client( asset = self.app.service.get_asset(asset_id)
parent, self.clients[request.sid]["request"] system_user = self.app.service.get_system_user(user_id)
)
self.clients[request.sid]["proxy"][connection] = WSProxy( if not asset or not system_user:
self, child, self.clients[request.sid]["room"], connection self.on_connect()
) return
self.clients[request.sid]["forwarder"][
connection] = ProxyServer( child, parent = socket.socketpair()
self.app, self.clients[request.sid]["client"][connection] self.clients[request.sid]["client"][connection] = Client(
) parent, self.clients[request.sid]["request"]
self.app.clients.append(self.clients[request.sid]["client"][connection]) )
self.socketio.start_background_task( self.clients[request.sid]["proxy"][connection] = WSProxy(
self.clients[request.sid]["forwarder"][connection].proxy, self, child, self.clients[request.sid]["room"], connection
asset, system_user )
) self.clients[request.sid]["forwarder"][connection] = ProxyServer(
# self.forwarder.proxy(self.asset, system_user) self.app, self.clients[request.sid]["client"][connection]
else: )
self.on_disconnect() self.socketio.start_background_task(
else: self.clients[request.sid]["forwarder"][connection].proxy,
self.on_disconnect() asset, system_user
)
def on_token(self, message):
# 此处获取token含有的主机的信息
logger.debug("On token trigger")
token = message.get('token', None)
secret = message.get('secret', None)
host = self.app.service.get_token_asset(token).json()
logger.debug(host)
# {
# "user": {UUID},
# "asset": {UUID},
# "system_user": {UUID}
# }
self.on_host({'secret': secret, 'uuid': host['asset'], 'userid': host['system_user']})
def on_resize(self, message): def on_resize(self, message):
if self.clients[request.sid]["request"]: cols = message.get('cols')
self.clients[request.sid]["request"].meta['width'] = message.get('cols', 80) rows = message.get('rows')
self.clients[request.sid]["request"].meta['height'] = message.get('rows', 24) logger.debug("On resize event trigger: {}*{}".format(cols, rows))
if cols and rows and self.clients[request.sid]["request"]:
self.clients[request.sid]["request"].meta['width'] = cols
self.clients[request.sid]["request"].meta['height'] = rows
self.clients[request.sid]["request"].change_size_event.set() self.clients[request.sid]["request"].change_size_event.set()
def on_room(self, sessionid): def on_room(self, session_id):
if sessionid not in self.clients.keys(): logger.debug("On room event trigger")
self.emit('error', "no such session", room=self.clients[request.sid]["room"]) if session_id not in self.clients.keys():
self.emit(
'error', "no such session",
room=self.clients[request.sid]["room"]
)
else: else:
self.emit('room', self.clients[sessionid]["room"], room=self.clients[request.sid]["room"]) self.emit(
'room', self.clients[session_id]["room"],
room=self.clients[request.sid]["room"]
)
def on_join(self, room): def on_join(self, room):
logger.debug("On join room event trigger")
self.on_leave(self.clients[request.id]["room"]) self.on_leave(self.clients[request.id]["room"])
self.clients[request.sid]["room"] = room self.clients[request.sid]["room"] = room
self.rooms[room]["member"].append(request.sid) self.rooms[room]["member"].append(request.sid)
join_room(room=room) join_room(room=room)
def on_leave(self, room): def on_leave(self, room):
logger.debug("On leave room event trigger")
if self.rooms[room]["admin"] == request.sid: if self.rooms[room]["admin"] == request.sid:
self.emit("data", "\nAdmin leave", room=room) self.emit("data", "\nAdmin leave", room=room)
del self.rooms[room] del self.rooms[room]
leave_room(room=room) leave_room(room=room)
def on_disconnect(self): def on_disconnect(self):
logger.debug("On disconnect event trigger")
self.on_leave(self.clients[request.sid]["room"]) self.on_leave(self.clients[request.sid]["room"])
try: try:
for connection in self.clients[request.sid]["client"]: for connection in self.clients[request.sid]["client"]:
...@@ -154,10 +217,11 @@ class SSHws(Namespace, BaseWebSocketHandler): ...@@ -154,10 +217,11 @@ class SSHws(Namespace, BaseWebSocketHandler):
pass pass
def on_logout(self, connection): def on_logout(self, connection):
logger.debug("{} logout".format(connection)) logger.debug("On logout event trigger")
if connection: if connection:
if connection in self.clients[request.sid]["proxy"].keys(): if connection in self.clients[request.sid]["proxy"].keys():
self.clients[request.sid]["proxy"][connection].close() self.clients[request.sid]["proxy"][connection].close()
del self.clients[request.sid]['proxy'][connection]
def logout(self, connection): def logout(self, connection):
if connection and (request.sid in self.clients.keys()): if connection and (request.sid in self.clients.keys()):
...@@ -171,28 +235,31 @@ class SSHws(Namespace, BaseWebSocketHandler): ...@@ -171,28 +235,31 @@ class SSHws(Namespace, BaseWebSocketHandler):
class HttpServer: class HttpServer:
# prepare may be rewrite it # prepare may be rewrite it
settings = { config = {
'cookie_secret': '', 'SECRET_KEY': '',
'app': None, 'coco': None,
'login_url': '/login' 'LOGIN_URL': '/login'
} }
async_mode = "threading"
def __init__(self, app): def __init__(self, coco):
self.app = app config = coco.config
# self.settings['cookie_secret'] = self.app.config['SECRET_KEY'] config.update(self.config)
# self.settings['app'] = self.app config['coco'] = coco
self.flask_app = Flask(__name__, template_folder='dist')
self.flask_app.config.update(config)
self.socket_io = SocketIO()
self.register_routes()
self.flask = Flask(__name__, template_folder='dist') def register_routes(self):
self.flask.config['SECRET_KEY'] = self.app.config['SECRET_KEY'] self.socket_io.on_namespace(ProxyNamespace('/ssh'))
self.socketio = SocketIO()
def run(self): def run(self):
host = self.app.config["BIND_HOST"] host = self.flask_app.config["BIND_HOST"]
port = self.app.config["HTTPD_PORT"] port = self.flask_app.config["HTTPD_PORT"]
print('Starting websocket server at {}:{}'.format(host, port)) print('Starting websocket server at {}:{}'.format(host, port))
self.socketio.on_namespace(SSHws('/ssh').app(self.app)) self.socket_io.init_app(self.flask_app, async_mode=self.async_mode)
self.socketio.init_app(self.flask, async_mode="threading") self.socket_io.run(self.flask_app, port=port, host=host, debug=False)
self.socketio.run(self.flask, port=port, host=host)
def shutdown(self): def shutdown(self):
pass pass
...@@ -82,6 +82,7 @@ class InteractiveServer: ...@@ -82,6 +82,7 @@ class InteractiveServer:
self.client.send(wr(prompt, before=1, after=0)) self.client.send(wr(prompt, before=1, after=0))
while True: while True:
data = self.client.recv(10) data = self.client.recv(10)
logger.debug(data)
if len(data) == 0: if len(data) == 0:
self.app.remove_client(self.client) self.app.remove_client(self.client)
break break
...@@ -96,6 +97,15 @@ class InteractiveServer: ...@@ -96,6 +97,15 @@ class InteractiveServer:
self.client.send(data) self.client.send(data)
continue continue
if data.startswith(b'\x03'):
# Ctrl-C
self.client.send(b'^C\r\nOpt> ')
input_data = []
continue
elif data.startswith(b'\x04'):
# Ctrl-D
return 'q'
# Todo: Move x1b to char # Todo: Move x1b to char
if data.startswith(b'\x1b') or data in char.UNSUPPORTED_CHAR: if data.startswith(b'\x1b') or data in char.UNSUPPORTED_CHAR:
self.client.send(b'') self.client.send(b'')
...@@ -119,11 +129,12 @@ class InteractiveServer: ...@@ -119,11 +129,12 @@ class InteractiveServer:
input_data.append(data) input_data.append(data)
def dispatch(self, opt): def dispatch(self, opt):
print(repr(opt))
if opt is None: if opt is None:
return self._sentinel return self._sentinel
elif opt.startswith("/"): elif opt.startswith("/"):
self.search_and_display(opt.lstrip("/")) self.search_and_display(opt.lstrip("/"))
elif opt in ['p', 'P']: elif opt in ['p', 'P', '']:
self.display_assets() self.display_assets()
elif opt in ['g', 'G']: elif opt in ['g', 'G']:
self.display_asset_groups() self.display_asset_groups()
...@@ -181,7 +192,7 @@ class InteractiveServer: ...@@ -181,7 +192,7 @@ class InteractiveServer:
amount_max_length = max(len(str(max([group.assets_amount for group in self.asset_groups]))), 10) amount_max_length = max(len(str(max([group.assets_amount for group in self.asset_groups]))), 10)
header = '{1:>%d} {0.name:%d} {0.assets_amount:<%s} ' % (id_max_length, name_max_length, amount_max_length) header = '{1:>%d} {0.name:%d} {0.assets_amount:<%s} ' % (id_max_length, name_max_length, amount_max_length)
comment_length = self.request.meta["width"] - len(header.format(fake_group, id_max_length)) comment_length = self.request.meta["width"] - len(header.format(fake_group, id_max_length))
line = header + '{0.comment:%s}' % (comment_length//2) # comment中可能有中文 line = header + '{0.comment:%s}' % (comment_length // 2) # comment中可能有中文
header += "{0.comment:%s}" % comment_length header += "{0.comment:%s}" % comment_length
self.client.send(title(header.format(fake_group, "ID"))) self.client.send(title(header.format(fake_group, "ID")))
for index, group in enumerate(self.asset_groups, 1): for index, group in enumerate(self.asset_groups, 1):
...@@ -194,12 +205,13 @@ class InteractiveServer: ...@@ -194,12 +205,13 @@ class InteractiveServer:
self.display_asset_groups() self.display_asset_groups()
return return
self.search_result = self.asset_groups[_id-1].assets_granted self.search_result = self.asset_groups[_id - 1].assets_granted
self.display_search_result() self.display_search_result()
def display_search_result(self): def display_search_result(self):
self.search_result = sort_assets(self.search_result, self.app.config["ASSET_LIST_SORT_BY"]) self.search_result = sort_assets(self.search_result, self.app.config["ASSET_LIST_SORT_BY"])
fake_asset = Asset(hostname=_("Hostname"), ip=_("IP"), _system_users_name_list=_("LoginAs"), comment=_("Comment")) fake_asset = Asset(hostname=_("Hostname"), ip=_("IP"), _system_users_name_list=_("LoginAs"),
comment=_("Comment"))
id_max_length = max(len(str(len(self.search_result))), 3) id_max_length = max(len(str(len(self.search_result))), 3)
hostname_max_length = max(max([len(asset.hostname) for asset in self.search_result + [fake_asset]]), 15) hostname_max_length = max(max([len(asset.hostname) for asset in self.search_result + [fake_asset]]), 15)
sysuser_max_length = max([len(asset.system_users_name_list) for asset in self.search_result + [fake_asset]]) sysuser_max_length = max([len(asset.system_users_name_list) for asset in self.search_result + [fake_asset]])
......
...@@ -30,5 +30,5 @@ tornado==4.5.2 ...@@ -30,5 +30,5 @@ tornado==4.5.2
urllib3==1.22 urllib3==1.22
wcwidth==0.1.7 wcwidth==0.1.7
werkzeug==0.12.2 werkzeug==0.12.2
jumpserver-python-sdk==0.0.27 jumpserver-python-sdk==0.0.28
jms-es-sdk jms-es-sdk
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