mirror of https://github.com/huashengdun/webssh
Support cross origin connect
parent
b4ca604518
commit
713ae1e8f1
|
@ -449,6 +449,7 @@ class OtherTestBase(AsyncHTTPTestCase):
|
||||||
syshostfile = ''
|
syshostfile = ''
|
||||||
tdstream = ''
|
tdstream = ''
|
||||||
maxconn = 20
|
maxconn = 20
|
||||||
|
origin = 'same'
|
||||||
body = {
|
body = {
|
||||||
'hostname': '127.0.0.1',
|
'hostname': '127.0.0.1',
|
||||||
'port': '',
|
'port': '',
|
||||||
|
@ -467,6 +468,7 @@ class OtherTestBase(AsyncHTTPTestCase):
|
||||||
options.syshostfile = self.syshostfile
|
options.syshostfile = self.syshostfile
|
||||||
options.tdstream = self.tdstream
|
options.tdstream = self.tdstream
|
||||||
options.maxconn = self.maxconn
|
options.maxconn = self.maxconn
|
||||||
|
options.origin = self.origin
|
||||||
app = make_app(make_handlers(loop, options), get_app_settings(options))
|
app = make_app(make_handlers(loop, options), get_app_settings(options))
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -716,3 +718,28 @@ class TestAppWithTooManyConnections(OtherTestBase):
|
||||||
self.assertEqual(data['status'], 'Too many connections.')
|
self.assertEqual(data['status'], 'Too many connections.')
|
||||||
|
|
||||||
ws.close()
|
ws.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAppWithCrossOriginConnect(OtherTestBase):
|
||||||
|
|
||||||
|
origin = 'http://www.example.com'
|
||||||
|
|
||||||
|
@tornado.testing.gen_test
|
||||||
|
def test_app_with_cross_orgin_connect(self):
|
||||||
|
url = self.get_url('/')
|
||||||
|
client = self.get_http_client()
|
||||||
|
body = urlencode(dict(self.body, username='foo', _origin='localhost'))
|
||||||
|
response = yield client.fetch(url, method='POST', body=body,
|
||||||
|
headers=self.headers)
|
||||||
|
data = json.loads(to_str(response.body))
|
||||||
|
self.assertIsNone(data['id'])
|
||||||
|
self.assertIsNone(data['encoding'])
|
||||||
|
self.assertIn('Cross origin frame', data['status'])
|
||||||
|
|
||||||
|
body = urlencode(dict(self.body, username='foo', _origin=self.origin))
|
||||||
|
response = yield client.fetch(url, method='POST', body=body,
|
||||||
|
headers=self.headers)
|
||||||
|
data = json.loads(to_str(response.body))
|
||||||
|
self.assertIsNotNone(data['id'])
|
||||||
|
self.assertIsNotNone(data['encoding'])
|
||||||
|
self.assertIsNone(data['status'])
|
||||||
|
|
|
@ -76,6 +76,28 @@ class MixinHandler(object):
|
||||||
else:
|
else:
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
def check_origin(self, origin):
|
||||||
|
if self.origin_policy == '*':
|
||||||
|
return True
|
||||||
|
|
||||||
|
parsed_origin = urlparse(origin)
|
||||||
|
netloc = parsed_origin.netloc.lower()
|
||||||
|
logging.debug('netloc: {}'.format(netloc))
|
||||||
|
|
||||||
|
host = self.request.headers.get('Host')
|
||||||
|
logging.debug('host: {}'.format(host))
|
||||||
|
|
||||||
|
if netloc == host:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.origin_policy == 'same':
|
||||||
|
return False
|
||||||
|
elif self.origin_policy == 'primary':
|
||||||
|
return is_same_primary_domain(netloc.rsplit(':', 1)[0],
|
||||||
|
host.rsplit(':', 1)[0])
|
||||||
|
else:
|
||||||
|
return origin in self.origin_policy
|
||||||
|
|
||||||
def is_forbidden(self, context, hostname):
|
def is_forbidden(self, context, hostname):
|
||||||
ip = context.address[0]
|
ip = context.address[0]
|
||||||
lst = context.trusted_downstream
|
lst = context.trusted_downstream
|
||||||
|
@ -340,6 +362,13 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
if len(clients.get(self.src_addr[0], {})) >= options.maxconn:
|
if len(clients.get(self.src_addr[0], {})) >= options.maxconn:
|
||||||
raise tornado.web.HTTPError(403, 'Too many connections.')
|
raise tornado.web.HTTPError(403, 'Too many connections.')
|
||||||
|
|
||||||
|
origin = self.get_argument('_origin', u'')
|
||||||
|
if origin:
|
||||||
|
if not self.check_origin(origin):
|
||||||
|
raise tornado.web.HTTPError(
|
||||||
|
403, 'Cross origin frame operation is not allowed.'
|
||||||
|
)
|
||||||
|
|
||||||
future = Future()
|
future = Future()
|
||||||
t = threading.Thread(target=self.ssh_connect_wrapped, args=(future,))
|
t = threading.Thread(target=self.ssh_connect_wrapped, args=(future,))
|
||||||
t.setDaemon(True)
|
t.setDaemon(True)
|
||||||
|
@ -364,28 +393,6 @@ class WsockHandler(MixinHandler, tornado.websocket.WebSocketHandler):
|
||||||
super(WsockHandler, self).initialize(loop)
|
super(WsockHandler, self).initialize(loop)
|
||||||
self.worker_ref = None
|
self.worker_ref = None
|
||||||
|
|
||||||
def check_origin(self, origin):
|
|
||||||
if self.origin_policy == '*':
|
|
||||||
return True
|
|
||||||
|
|
||||||
parsed_origin = urlparse(origin)
|
|
||||||
netloc = parsed_origin.netloc.lower()
|
|
||||||
logging.debug('netloc: {}'.format(netloc))
|
|
||||||
|
|
||||||
host = self.request.headers.get('Host')
|
|
||||||
logging.debug('host: {}'.format(host))
|
|
||||||
|
|
||||||
if netloc == host:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.origin_policy == 'same':
|
|
||||||
return False
|
|
||||||
elif self.origin_policy == 'primary':
|
|
||||||
return is_same_primary_domain(netloc.rsplit(':', 1)[0],
|
|
||||||
host.rsplit(':', 1)[0])
|
|
||||||
else:
|
|
||||||
return origin in self.origin_policy
|
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
self.src_addr = self.get_client_addr()
|
self.src_addr = self.get_client_addr()
|
||||||
logging.info('Connected from {}:{}'.format(*self.src_addr))
|
logging.info('Connected from {}:{}'.format(*self.src_addr))
|
||||||
|
|
|
@ -50,6 +50,7 @@ jQuery(function($){
|
||||||
messages = {1: 'This client is connecting ...', 2: 'This client is already connnected.'},
|
messages = {1: 'This client is connecting ...', 2: 'This client is already connnected.'},
|
||||||
key_max_size = 16384,
|
key_max_size = 16384,
|
||||||
fields = ['hostname', 'port', 'username'],
|
fields = ['hostname', 'port', 'username'],
|
||||||
|
event_origin,
|
||||||
hostname_tester = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/;
|
hostname_tester = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/;
|
||||||
|
|
||||||
|
|
||||||
|
@ -517,6 +518,9 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
data._xsrf = _xsrf.value;
|
data._xsrf = _xsrf.value;
|
||||||
|
if (event_origin) {
|
||||||
|
data._origin = event_origin;
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -569,4 +573,31 @@ jQuery(function($){
|
||||||
connect();
|
connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function cross_origin_connect(event)
|
||||||
|
{
|
||||||
|
console.log(event.origin);
|
||||||
|
var prop = 'connect',
|
||||||
|
args;
|
||||||
|
|
||||||
|
try {
|
||||||
|
args = JSON.parse(event.data);
|
||||||
|
} catch (SyntaxError) {
|
||||||
|
args = event.data.split('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(args)) {
|
||||||
|
args = [args];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
event_origin = event.origin;
|
||||||
|
wssh[prop].apply(wssh, args);
|
||||||
|
} finally {
|
||||||
|
event_origin = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', cross_origin_connect, false);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue