mirror of https://github.com/huashengdun/webssh
Added max_body_size for limiting the size of post form
parent
d38453fd0b
commit
d6de1340c4
|
@ -64,12 +64,13 @@ class Server(paramiko.ServerInterface):
|
||||||
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||||
|
|
||||||
def check_auth_password(self, username, password):
|
def check_auth_password(self, username, password):
|
||||||
|
print('Auth attempt with username: {!r} & password: {!r}'.format(username, password)) # noqa
|
||||||
if (username in ['robey', 'bar']) and (password == 'foo'):
|
if (username in ['robey', 'bar']) and (password == 'foo'):
|
||||||
return paramiko.AUTH_SUCCESSFUL
|
return paramiko.AUTH_SUCCESSFUL
|
||||||
return paramiko.AUTH_FAILED
|
return paramiko.AUTH_FAILED
|
||||||
|
|
||||||
def check_auth_publickey(self, username, key):
|
def check_auth_publickey(self, username, key):
|
||||||
print('Auth attempt with key: ' + u(hexlify(key.get_fingerprint())))
|
print('Auth attempt with username: {!r} & key: {!r}'.format(username, u(hexlify(key.get_fingerprint())))) # noqa
|
||||||
if (username == 'robey') and (key == self.good_pub_key):
|
if (username == 'robey') and (key == self.good_pub_key):
|
||||||
return paramiko.AUTH_SUCCESSFUL
|
return paramiko.AUTH_SUCCESSFUL
|
||||||
return paramiko.AUTH_FAILED
|
return paramiko.AUTH_FAILED
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import json
|
import json
|
||||||
import webssh.handler as handler
|
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
import tornado.gen
|
import tornado.gen
|
||||||
|
import webssh.handler as handler
|
||||||
|
|
||||||
from tornado.testing import AsyncHTTPTestCase
|
from tornado.testing import AsyncHTTPTestCase
|
||||||
|
from tornado.httpclient import HTTPError
|
||||||
from tornado.options import options
|
from tornado.options import options
|
||||||
from webssh.main import make_app, make_handlers
|
from webssh.main import make_app, make_handlers
|
||||||
from webssh.settings import get_app_settings
|
from webssh.settings import get_app_settings, max_body_size
|
||||||
from tests.sshserver import run_ssh_server, banner
|
from tests.sshserver import run_ssh_server, banner
|
||||||
|
from tests.utils import encode_multipart_formdata
|
||||||
|
|
||||||
|
|
||||||
handler.DELAY = 0.1
|
handler.DELAY = 0.1
|
||||||
|
@ -20,6 +22,12 @@ class TestApp(AsyncHTTPTestCase):
|
||||||
running = [True]
|
running = [True]
|
||||||
sshserver_port = 2200
|
sshserver_port = 2200
|
||||||
body = u'hostname=127.0.0.1&port={}&username=robey&password=foo'.format(sshserver_port) # noqa
|
body = u'hostname=127.0.0.1&port={}&username=robey&password=foo'.format(sshserver_port) # noqa
|
||||||
|
body_dict = {
|
||||||
|
'hostname': '127.0.0.1',
|
||||||
|
'port': str(sshserver_port),
|
||||||
|
'username': 'robey',
|
||||||
|
'password': ''
|
||||||
|
}
|
||||||
|
|
||||||
def get_app(self):
|
def get_app(self):
|
||||||
loop = self.io_loop
|
loop = self.io_loop
|
||||||
|
@ -44,6 +52,14 @@ class TestApp(AsyncHTTPTestCase):
|
||||||
cls.running.pop()
|
cls.running.pop()
|
||||||
print('='*20)
|
print('='*20)
|
||||||
|
|
||||||
|
def read_privatekey(self, filename):
|
||||||
|
return open(filename, 'rb').read().decode('utf-8')
|
||||||
|
|
||||||
|
def get_httpserver_options(self):
|
||||||
|
options = super(TestApp, self).get_httpserver_options()
|
||||||
|
options.update(max_body_size=max_body_size)
|
||||||
|
return options
|
||||||
|
|
||||||
def test_app_with_invalid_form(self):
|
def test_app_with_invalid_form(self):
|
||||||
response = self.fetch('/')
|
response = self.fetch('/')
|
||||||
self.assertEqual(response.code, 200)
|
self.assertEqual(response.code, 200)
|
||||||
|
@ -103,6 +119,74 @@ class TestApp(AsyncHTTPTestCase):
|
||||||
self.assertIsNone(msg)
|
self.assertIsNone(msg)
|
||||||
ws.close()
|
ws.close()
|
||||||
|
|
||||||
|
@tornado.testing.gen_test
|
||||||
|
def test_app_auth_with_valid_pubkey_for_user_robey(self):
|
||||||
|
url = self.get_url('/')
|
||||||
|
client = self.get_http_client()
|
||||||
|
response = yield client.fetch(url)
|
||||||
|
self.assertEqual(response.code, 200)
|
||||||
|
|
||||||
|
privatekey = self.read_privatekey('tests/user_rsa_key')
|
||||||
|
files = [('privatekey', 'user_rsa_key', privatekey)]
|
||||||
|
content_type, body = encode_multipart_formdata(self.body_dict.items(),
|
||||||
|
files)
|
||||||
|
headers = {
|
||||||
|
"Content-Type": content_type, 'content-length': str(len(body))
|
||||||
|
}
|
||||||
|
response = yield client.fetch(url, method="POST", headers=headers,
|
||||||
|
body=body)
|
||||||
|
data = json.loads(response.body.decode('utf-8'))
|
||||||
|
self.assertIsNone(data['status'])
|
||||||
|
self.assertIsNotNone(data['id'])
|
||||||
|
self.assertIsNotNone(data['encoding'])
|
||||||
|
|
||||||
|
url = url.replace('http', 'ws')
|
||||||
|
ws_url = url + 'ws?id=' + data['id']
|
||||||
|
ws = yield tornado.websocket.websocket_connect(ws_url)
|
||||||
|
msg = yield ws.read_message()
|
||||||
|
self.assertEqual(msg.decode(data['encoding']), banner)
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
@tornado.testing.gen_test
|
||||||
|
def test_app_auth_with_invalid_pubkey_for_user_robey(self):
|
||||||
|
url = self.get_url('/')
|
||||||
|
client = self.get_http_client()
|
||||||
|
response = yield client.fetch(url)
|
||||||
|
self.assertEqual(response.code, 200)
|
||||||
|
|
||||||
|
privatekey = self.read_privatekey('tests/user_rsa_key')
|
||||||
|
privatekey = privatekey[:100] + u'bad' + privatekey[100:]
|
||||||
|
files = [('privatekey', 'user_rsa_key', privatekey)]
|
||||||
|
content_type, body = encode_multipart_formdata(self.body_dict.items(),
|
||||||
|
files)
|
||||||
|
headers = {
|
||||||
|
"Content-Type": content_type, 'content-length': str(len(body))
|
||||||
|
}
|
||||||
|
response = yield client.fetch(url, method="POST", headers=headers,
|
||||||
|
body=body)
|
||||||
|
data = json.loads(response.body.decode('utf-8'))
|
||||||
|
self.assertIsNotNone(data['status'])
|
||||||
|
self.assertIsNone(data['id'])
|
||||||
|
self.assertIsNone(data['encoding'])
|
||||||
|
|
||||||
|
@tornado.testing.gen_test
|
||||||
|
def test_app_post_form_with_large_body_size(self):
|
||||||
|
url = self.get_url('/')
|
||||||
|
client = self.get_http_client()
|
||||||
|
response = yield client.fetch(url)
|
||||||
|
self.assertEqual(response.code, 200)
|
||||||
|
|
||||||
|
privatekey = u'h' * (2 * max_body_size)
|
||||||
|
files = [('privatekey', 'user_rsa_key', privatekey)]
|
||||||
|
content_type, body = encode_multipart_formdata(self.body_dict.items(),
|
||||||
|
files)
|
||||||
|
headers = {
|
||||||
|
"Content-Type": content_type, 'content-length': str(len(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.assertRaises(HTTPError):
|
||||||
|
yield client.fetch(url, method="POST", headers=headers, body=body)
|
||||||
|
|
||||||
@tornado.testing.gen_test
|
@tornado.testing.gen_test
|
||||||
def test_app_with_correct_credentials_user_robey(self):
|
def test_app_with_correct_credentials_user_robey(self):
|
||||||
url = self.get_url('/')
|
url = self.get_url('/')
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXQIBAAKBgQDI7iK3d8eWYZlYloat94c5VjtFY7c/0zuGl8C7uMnZ3t6i2G99
|
||||||
|
66hEW0nCFSZkOW5F0XKEVj+EUCHvo8koYC6wiohAqWQnEwIoOoh7GSAcB8gP/qaq
|
||||||
|
+adIl/Rvlby/mHakj+y05LBND6nFWHAn1y1gOFFKUXSJNRZPXSFy47gqzwIBIwKB
|
||||||
|
gQCbANjz7q/pCXZLp1Hz6tYHqOvlEmjK1iabB1oqafrMpJ0eibUX/u+FMHq6StR5
|
||||||
|
M5413BaDWHokPdEJUnabfWXXR3SMlBUKrck0eAer1O8m78yxu3OEdpRk+znVo4DL
|
||||||
|
guMeCdJB/qcF0kEsx+Q8HP42MZU1oCmk3PbfXNFwaHbWuwJBAOQ/ry/hLD7AqB8x
|
||||||
|
DmCM82A9E59ICNNlHOhxpJoh6nrNTPCsBAEu/SmqrL8mS6gmbRKUaya5Lx1pkxj2
|
||||||
|
s/kWOokCQQDhXCcYXjjWiIfxhl6Rlgkk1vmI0l6785XSJNv4P7pXjGmShXfIzroh
|
||||||
|
S8uWK3tL0GELY7+UAKDTUEVjjQdGxYSXAkEA3bo1JzKCwJ3lJZ1ebGuqmADRO6UP
|
||||||
|
40xH977aadfN1mEI6cusHmgpISl0nG5YH7BMsvaT+bs1FUH8m+hXDzoqOwJBAK3Z
|
||||||
|
X/za+KV/REya2z0b+GzgWhkXUGUa/owrEBdHGriQ47osclkUgPUdNqcLmaDilAF4
|
||||||
|
1Z4PHPrI5RJIONAx+JECQQC/fChqjBgFpk6iJ+BOdSexQpgfxH/u/457W10Y43HR
|
||||||
|
soS+8btbHqjQkowQ/2NTlUfWvqIlfxs6ZbFsIp/HrhZL
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,38 @@
|
||||||
|
import mimetypes
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
|
def encode_multipart_formdata(fields, files):
|
||||||
|
"""
|
||||||
|
fields is a sequence of (name, value) elements for regular form fields.
|
||||||
|
files is a sequence of (name, filename, value) elements for data to be
|
||||||
|
uploaded as files.
|
||||||
|
Return (content_type, body) ready for httplib.HTTP instance
|
||||||
|
"""
|
||||||
|
boundary = uuid4().hex
|
||||||
|
CRLF = '\r\n'
|
||||||
|
L = []
|
||||||
|
for (key, value) in fields:
|
||||||
|
L.append('--' + boundary)
|
||||||
|
L.append('Content-Disposition: form-data; name="%s"' % key)
|
||||||
|
L.append('')
|
||||||
|
L.append(value)
|
||||||
|
for (key, filename, value) in files:
|
||||||
|
L.append('--' + boundary)
|
||||||
|
L.append(
|
||||||
|
'Content-Disposition: form-data; name="%s"; filename="%s"' % (
|
||||||
|
key, filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
L.append('Content-Type: %s' % get_content_type(filename))
|
||||||
|
L.append('')
|
||||||
|
L.append(value)
|
||||||
|
L.append('--' + boundary + '--')
|
||||||
|
L.append('')
|
||||||
|
body = CRLF.join(L)
|
||||||
|
content_type = 'multipart/form-data; boundary=%s' % boundary
|
||||||
|
return content_type, body
|
||||||
|
|
||||||
|
|
||||||
|
def get_content_type(filename):
|
||||||
|
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
|
@ -5,7 +5,7 @@ import tornado.ioloop
|
||||||
from tornado.options import parse_command_line, options
|
from tornado.options import parse_command_line, options
|
||||||
from webssh.handler import IndexHandler, WsockHandler
|
from webssh.handler import IndexHandler, WsockHandler
|
||||||
from webssh.settings import (get_app_settings, get_host_keys_settings,
|
from webssh.settings import (get_app_settings, get_host_keys_settings,
|
||||||
get_policy_setting)
|
get_policy_setting, max_body_size)
|
||||||
|
|
||||||
|
|
||||||
def make_handlers(loop, options):
|
def make_handlers(loop, options):
|
||||||
|
@ -29,7 +29,7 @@ def main():
|
||||||
parse_command_line()
|
parse_command_line()
|
||||||
loop = tornado.ioloop.IOLoop.current()
|
loop = tornado.ioloop.IOLoop.current()
|
||||||
app = make_app(make_handlers(loop, options), get_app_settings(options))
|
app = make_app(make_handlers(loop, options), get_app_settings(options))
|
||||||
app.listen(options.port, options.address)
|
app.listen(options.port, options.address, max_body_size=max_body_size)
|
||||||
logging.info('Listening on {}:{}'.format(options.address, options.port))
|
logging.info('Listening on {}:{}'.format(options.address, options.port))
|
||||||
loop.start()
|
loop.start()
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ define('version', type=bool, help='Show version information',
|
||||||
|
|
||||||
|
|
||||||
base_dir = os.path.dirname(__file__)
|
base_dir = os.path.dirname(__file__)
|
||||||
|
max_body_size = 1 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
def get_app_settings(options):
|
def get_app_settings(options):
|
||||||
|
|
Loading…
Reference in New Issue