Skip to content

Commit a134655

Browse files
committed
Merge branch 'token_redis_improvements' of https://github.com/javicacheiro/websockify
2 parents 7f53e9c + 3d2e93a commit a134655

File tree

4 files changed

+254
-23
lines changed

4 files changed

+254
-23
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
install_requires=[
3333
'numpy', 'requests',
3434
'jwcrypto',
35-
'redis', 'simplejson',
35+
'redis',
3636
],
3737
zip_safe=False,
3838
entry_points={

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mock
22
nose2
33
six
4+
redis
45
wrapt<=1.12.1;python_version<="3.4"

tests/test_token_plugins.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,153 @@ def test_simple(self, mock_redis):
203203
self.assertIsNotNone(result)
204204
self.assertEqual(result[0], 'remote_host')
205205
self.assertEqual(result[1], 'remote_port')
206+
207+
@patch('redis.Redis')
208+
def test_json_token_with_spaces(self, mock_redis):
209+
plugin = TokenRedis('127.0.0.1:1234')
210+
211+
instance = mock_redis.return_value
212+
instance.get.return_value = b' {"host": "remote_host:remote_port"} '
213+
214+
result = plugin.lookup('testhost')
215+
216+
instance.get.assert_called_once_with('testhost')
217+
self.assertIsNotNone(result)
218+
self.assertEqual(result[0], 'remote_host')
219+
self.assertEqual(result[1], 'remote_port')
220+
221+
@patch('redis.Redis')
222+
def test_text_token(self, mock_redis):
223+
plugin = TokenRedis('127.0.0.1:1234')
224+
225+
instance = mock_redis.return_value
226+
instance.get.return_value = b'remote_host:remote_port'
227+
228+
result = plugin.lookup('testhost')
229+
230+
instance.get.assert_called_once_with('testhost')
231+
self.assertIsNotNone(result)
232+
self.assertEqual(result[0], 'remote_host')
233+
self.assertEqual(result[1], 'remote_port')
234+
235+
@patch('redis.Redis')
236+
def test_text_token_with_spaces(self, mock_redis):
237+
plugin = TokenRedis('127.0.0.1:1234')
238+
239+
instance = mock_redis.return_value
240+
instance.get.return_value = b' remote_host:remote_port '
241+
242+
result = plugin.lookup('testhost')
243+
244+
instance.get.assert_called_once_with('testhost')
245+
self.assertIsNotNone(result)
246+
self.assertEqual(result[0], 'remote_host')
247+
self.assertEqual(result[1], 'remote_port')
248+
249+
@patch('redis.Redis')
250+
def test_invalid_token(self, mock_redis):
251+
plugin = TokenRedis('127.0.0.1:1234')
252+
253+
instance = mock_redis.return_value
254+
instance.get.return_value = b'{"host": "remote_host:remote_port" '
255+
256+
result = plugin.lookup('testhost')
257+
258+
instance.get.assert_called_once_with('testhost')
259+
self.assertIsNone(result)
260+
261+
def test_src_only_host(self):
262+
plugin = TokenRedis('127.0.0.1')
263+
264+
self.assertEqual(plugin._server, '127.0.0.1')
265+
self.assertEqual(plugin._port, 6379)
266+
self.assertEqual(plugin._db, 0)
267+
self.assertEqual(plugin._password, None)
268+
269+
def test_src_with_host_port(self):
270+
plugin = TokenRedis('127.0.0.1:1234')
271+
272+
self.assertEqual(plugin._server, '127.0.0.1')
273+
self.assertEqual(plugin._port, 1234)
274+
self.assertEqual(plugin._db, 0)
275+
self.assertEqual(plugin._password, None)
276+
277+
def test_src_with_host_port_db(self):
278+
plugin = TokenRedis('127.0.0.1:1234:2')
279+
280+
self.assertEqual(plugin._server, '127.0.0.1')
281+
self.assertEqual(plugin._port, 1234)
282+
self.assertEqual(plugin._db, 2)
283+
self.assertEqual(plugin._password, None)
284+
285+
def test_src_with_host_port_db_pass(self):
286+
plugin = TokenRedis('127.0.0.1:1234:2:verysecret')
287+
288+
self.assertEqual(plugin._server, '127.0.0.1')
289+
self.assertEqual(plugin._port, 1234)
290+
self.assertEqual(plugin._db, 2)
291+
self.assertEqual(plugin._password, 'verysecret')
292+
293+
def test_src_with_host_empty_port_empty_db_pass(self):
294+
plugin = TokenRedis('127.0.0.1:::verysecret')
295+
296+
self.assertEqual(plugin._server, '127.0.0.1')
297+
self.assertEqual(plugin._port, 6379)
298+
self.assertEqual(plugin._db, 0)
299+
self.assertEqual(plugin._password, 'verysecret')
300+
301+
def test_src_with_host_empty_port_empty_db_empty_pass(self):
302+
plugin = TokenRedis('127.0.0.1:::')
303+
304+
self.assertEqual(plugin._server, '127.0.0.1')
305+
self.assertEqual(plugin._port, 6379)
306+
self.assertEqual(plugin._db, 0)
307+
self.assertEqual(plugin._password, None)
308+
309+
def test_src_with_host_empty_port_empty_db_no_pass(self):
310+
plugin = TokenRedis('127.0.0.1::')
311+
312+
self.assertEqual(plugin._server, '127.0.0.1')
313+
self.assertEqual(plugin._port, 6379)
314+
self.assertEqual(plugin._db, 0)
315+
self.assertEqual(plugin._password, None)
316+
317+
def test_src_with_host_empty_port_no_db_no_pass(self):
318+
plugin = TokenRedis('127.0.0.1:')
319+
320+
self.assertEqual(plugin._server, '127.0.0.1')
321+
self.assertEqual(plugin._port, 6379)
322+
self.assertEqual(plugin._db, 0)
323+
self.assertEqual(plugin._password, None)
324+
325+
def test_src_with_host_empty_port_db_no_pass(self):
326+
plugin = TokenRedis('127.0.0.1::2')
327+
328+
self.assertEqual(plugin._server, '127.0.0.1')
329+
self.assertEqual(plugin._port, 6379)
330+
self.assertEqual(plugin._db, 2)
331+
self.assertEqual(plugin._password, None)
332+
333+
def test_src_with_host_port_empty_db_pass(self):
334+
plugin = TokenRedis('127.0.0.1:1234::verysecret')
335+
336+
self.assertEqual(plugin._server, '127.0.0.1')
337+
self.assertEqual(plugin._port, 1234)
338+
self.assertEqual(plugin._db, 0)
339+
self.assertEqual(plugin._password, 'verysecret')
340+
341+
def test_src_with_host_empty_port_db_pass(self):
342+
plugin = TokenRedis('127.0.0.1::2:verysecret')
343+
344+
self.assertEqual(plugin._server, '127.0.0.1')
345+
self.assertEqual(plugin._port, 6379)
346+
self.assertEqual(plugin._db, 2)
347+
self.assertEqual(plugin._password, 'verysecret')
348+
349+
def test_src_with_host_empty_port_db_empty_pass(self):
350+
plugin = TokenRedis('127.0.0.1::2:')
351+
352+
self.assertEqual(plugin._server, '127.0.0.1')
353+
self.assertEqual(plugin._port, 6379)
354+
self.assertEqual(plugin._db, 2)
355+
self.assertEqual(plugin._password, None)

websockify/token_plugins.py

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
import time
55
import re
6+
import json
67

78
logger = logging.getLogger(__name__)
89

@@ -154,57 +155,136 @@ def lookup(self, token):
154155
logger.error("package jwcrypto not found, are you sure you've installed it correctly?")
155156
return None
156157

157-
class TokenRedis():
158-
"""
159-
The TokenRedis plugin expects the format of the data in a form of json.
158+
159+
class TokenRedis(BasePlugin):
160+
"""Token plugin based on the Redis in-memory data store.
161+
162+
The token source is in the format:
163+
164+
host[:port[:db[:password]]]
165+
166+
where port, db and password are optional. If port or db are left empty
167+
they will take its default value, ie. 6379 and 0 respectively.
168+
169+
If your redis server is using the default port (6379) then you can use:
170+
171+
my-redis-host
172+
173+
In case you need to authenticate with the redis server and you are using
174+
the default database and port you can use:
175+
176+
my-redis-host:::verysecretpass
177+
178+
In the more general case you will use:
179+
180+
my-redis-host:6380:1:verysecretpass
181+
182+
The TokenRedis plugin expects the format of the target in one of these two
183+
formats:
184+
185+
- JSON
186+
187+
{"host": "target-host:target-port"}
188+
189+
- Plain text
190+
191+
target-host:target-port
160192
161193
Prepare data with:
162-
redis-cli set hello '{"host":"127.0.0.1:5000"}'
194+
195+
redis-cli set my-token '{"host": "127.0.0.1:5000"}'
163196
164197
Verify with:
165-
redis-cli --raw get hello
198+
199+
redis-cli --raw get my-token
166200
167201
Spawn a test "server" using netcat
202+
168203
nc -l 5000 -v
169204
170-
Note: you have to install also the 'redis' and 'simplejson' modules
171-
pip install redis simplejson
205+
Note: This Token Plugin depends on the 'redis' module, so you have
206+
to install it before using this plugin:
207+
208+
pip install redis
172209
"""
173210
def __init__(self, src):
174211
try:
175-
# import those ahead of time so we provide error earlier
176212
import redis
177-
import simplejson
178-
self._server, self._port = src.split(":")
213+
except ImportError:
214+
logger.error("Unable to load redis module")
215+
sys.exit()
216+
# Default values
217+
self._port = 6379
218+
self._db = 0
219+
self._password = None
220+
try:
221+
fields = src.split(":")
222+
if len(fields) == 1:
223+
self._server = fields[0]
224+
elif len(fields) == 2:
225+
self._server, self._port = fields
226+
if not self._port:
227+
self._port = 6379
228+
elif len(fields) == 3:
229+
self._server, self._port, self._db = fields
230+
if not self._port:
231+
self._port = 6379
232+
if not self._db:
233+
self._db = 0
234+
elif len(fields) == 4:
235+
self._server, self._port, self._db, self._password = fields
236+
if not self._port:
237+
self._port = 6379
238+
if not self._db:
239+
self._db = 0
240+
if not self._password:
241+
self._password = None
242+
else:
243+
raise ValueError
244+
self._port = int(self._port)
245+
self._db = int(self._db)
179246
logger.info("TokenRedis backend initilized (%s:%s)" %
180247
(self._server, self._port))
181248
except ValueError:
182-
logger.error("The provided --token-source='%s' is not in an expected format <host>:<port>" %
183-
src)
184-
sys.exit()
185-
except ImportError:
186-
logger.error("package redis or simplejson not found, are you sure you've installed them correctly?")
249+
logger.error("The provided --token-source='%s' is not in the "
250+
"expected format <host>[:<port>[:<db>[:<password>]]]" %
251+
src)
187252
sys.exit()
188253

189254
def lookup(self, token):
190255
try:
191256
import redis
192-
import simplejson
193257
except ImportError:
194-
logger.error("package redis or simplejson not found, are you sure you've installed them correctly?")
258+
logger.error("package redis not found, are you sure you've installed them correctly?")
195259
sys.exit()
196260

197261
logger.info("resolving token '%s'" % token)
198-
client = redis.Redis(host=self._server, port=self._port)
262+
client = redis.Redis(host=self._server, port=self._port,
263+
db=self._db, password=self._password)
199264
stuff = client.get(token)
200265
if stuff is None:
201266
return None
202267
else:
203-
responseStr = stuff.decode("utf-8")
268+
responseStr = stuff.decode("utf-8").strip()
204269
logger.debug("response from redis : %s" % responseStr)
205-
combo = simplejson.loads(responseStr)
206-
(host, port) = combo["host"].split(':')
207-
logger.debug("host: %s, port: %s" % (host,port))
270+
if responseStr.startswith("{"):
271+
try:
272+
combo = json.loads(responseStr)
273+
host, port = combo["host"].split(":")
274+
except ValueError:
275+
logger.error("Unable to decode JSON token: %s" %
276+
responseStr)
277+
return None
278+
except KeyError:
279+
logger.error("Unable to find 'host' key in JSON token: %s" %
280+
responseStr)
281+
return None
282+
elif re.match(r'\S+:\S+', responseStr):
283+
host, port = responseStr.split(":")
284+
else:
285+
logger.error("Unable to parse token: %s" % responseStr)
286+
return None
287+
logger.debug("host: %s, port: %s" % (host, port))
208288
return [host, port]
209289

210290

0 commit comments

Comments
 (0)