commit d6de1340c486b2c424a97e90e93e0aac8753f9a5
parent d38453fd0bc622de940cddd6054995052aff96f8
Author: Sheng <webmaster0115@gmail.com>
Date: Sat, 18 Aug 2018 16:33:21 +0800
Added max_body_size for limiting the size of post form
Diffstat:
6 files changed, 144 insertions(+), 5 deletions(-)
diff --git a/tests/sshserver.py b/tests/sshserver.py
@@ -64,12 +64,13 @@ class Server(paramiko.ServerInterface):
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
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'):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
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):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
diff --git a/tests/test_app.py b/tests/test_app.py
@@ -1,15 +1,17 @@
import json
-import webssh.handler as handler
import random
import threading
import tornado.websocket
import tornado.gen
+import webssh.handler as handler
from tornado.testing import AsyncHTTPTestCase
+from tornado.httpclient import HTTPError
from tornado.options import options
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.utils import encode_multipart_formdata
handler.DELAY = 0.1
@@ -20,6 +22,12 @@ class TestApp(AsyncHTTPTestCase):
running = [True]
sshserver_port = 2200
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):
loop = self.io_loop
@@ -44,6 +52,14 @@ class TestApp(AsyncHTTPTestCase):
cls.running.pop()
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):
response = self.fetch('/')
self.assertEqual(response.code, 200)
@@ -104,6 +120,74 @@ class TestApp(AsyncHTTPTestCase):
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
def test_app_with_correct_credentials_user_robey(self):
url = self.get_url('/')
client = self.get_http_client()
diff --git a/tests/user_rsa_key b/tests/user_rsa_key
@@ -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-----
diff --git a/tests/utils.py b/tests/utils.py
@@ -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'
diff --git a/webssh/main.py b/webssh/main.py
@@ -5,7 +5,7 @@ import tornado.ioloop
from tornado.options import parse_command_line, options
from webssh.handler import IndexHandler, WsockHandler
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):
@@ -29,7 +29,7 @@ def main():
parse_command_line()
loop = tornado.ioloop.IOLoop.current()
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))
loop.start()
diff --git a/webssh/settings.py b/webssh/settings.py
@@ -29,6 +29,7 @@ define('version', type=bool, help='Show version information',
base_dir = os.path.dirname(__file__)
+max_body_size = 1 * 1024 * 1024
def get_app_settings(options):