From 7e05c61e1177d609e4acb5886d752fd5953bfbf1 Mon Sep 17 00:00:00 2001 From: Luca Capacci Date: Mon, 28 Sep 2015 10:58:05 +0200 Subject: [PATCH 1/4] Added django authentication plugin --- websockify/django_auth_plugins.py | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 websockify/django_auth_plugins.py diff --git a/websockify/django_auth_plugins.py b/websockify/django_auth_plugins.py new file mode 100644 index 00000000..fb475a20 --- /dev/null +++ b/websockify/django_auth_plugins.py @@ -0,0 +1,77 @@ +from auth_plugins import AuthenticationError + + +class SessionIdAuth(object): + def __init__(self, src=None): + init_django() + + def authenticate(self, headers, target_host, target_port): + try: + cookies = headers.get('Cookie').split("; ") + for cookie in cookies: + if cookie.startswith("sessionid"): + session_token = cookie.split("=")[1] + current_user = user_from_session_key(session_token) + from django.contrib.auth.models import AnonymousUser + if type(current_user) is AnonymousUser: + raise AuthenticationError(response_code=403) + except: + raise AuthenticationError(response_code=403) + + +class SessionIdAuthAndHostPort(object): + def __init__(self, src=None): + init_django() + + def authenticate(self, headers, target_host, target_port): + try: + cookies = headers.get('Cookie').split("; ") + for cookie in cookies: + if cookie.startswith("sessionid"): + session_token = cookie.split("=")[1] + current_user = user_from_session_key(session_token) + from django.contrib.auth.models import AnonymousUser + if type(current_user) is AnonymousUser: + raise AuthenticationError(response_code=403) + return get_host_port(current_user) + except: + raise AuthenticationError(response_code=403) + + +def get_host_port(current_user): + host_port_dict = {'pippo': ('localhost', 5900), + 'luca': ('localhost', 9001)} + + if current_user.username in host_port_dict: + return host_port_dict[current_user.username] + else: + raise AuthenticationError(response_code=403) + + +def user_from_session_key(session_key): + from django.conf import settings + from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY, load_backend + from django.contrib.auth.models import AnonymousUser + + session_engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) + session_wrapper = session_engine.SessionStore(session_key) + session = session_wrapper.load() + user_id = session.get(SESSION_KEY) + backend_id = session.get(BACKEND_SESSION_KEY) + if user_id and backend_id: + auth_backend = load_backend(backend_id) + user = auth_backend.get_user(user_id) + if user: + return user + return AnonymousUser() + + +def init_django(): + import sys + import os + current_path = os.path.dirname(os.path.abspath(__file__)) + django_app_path = os.path.abspath(os.path.join(current_path, os.pardir, os.pardir)) + sys.path.insert(0, django_app_path) + os.environ['DJANGO_SETTINGS_MODULE'] = u'{0}.settings'.format(os.path.split(django_app_path)[1]) + import django + django.setup() From 83a38d960d34bc347bfc82d0dea9592204f8c4cb Mon Sep 17 00:00:00 2001 From: Luca Capacci Date: Mon, 28 Sep 2015 12:08:26 +0200 Subject: [PATCH 2/4] Added django authentication plugin --- websockify/django_auth_plugins.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/websockify/django_auth_plugins.py b/websockify/django_auth_plugins.py index fb475a20..d3536c24 100644 --- a/websockify/django_auth_plugins.py +++ b/websockify/django_auth_plugins.py @@ -1,3 +1,15 @@ +''' +Django authentication plugins for Python WebSocket library. +Copyright 2015 Luca Capacci +Licensed under LGPL version 3 + +- SessionIdAuth grants access to the target only to the users authenticated in a django web app. + +- SessionIdAuthAndHostPort determines the target based on the authenticated user. Edit get_host_port(current_user) to determine a target and a host for each user. + +''' + + from auth_plugins import AuthenticationError @@ -39,8 +51,8 @@ def authenticate(self, headers, target_host, target_port): def get_host_port(current_user): - host_port_dict = {'pippo': ('localhost', 5900), - 'luca': ('localhost', 9001)} + host_port_dict = {'john': ('localhost', 5900), + 'bob': ('localhost', 5901)} if current_user.username in host_port_dict: return host_port_dict[current_user.username] From 8f05e984c7dc19544459709092e418f3fa2a0a9b Mon Sep 17 00:00:00 2001 From: Luca Capacci Date: Mon, 28 Sep 2015 12:53:03 +0200 Subject: [PATCH 3/4] Added django authentication plugin --- websockify/django_auth_plugins.py | 61 +++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/websockify/django_auth_plugins.py b/websockify/django_auth_plugins.py index d3536c24..04e8ab52 100644 --- a/websockify/django_auth_plugins.py +++ b/websockify/django_auth_plugins.py @@ -3,9 +3,64 @@ Copyright 2015 Luca Capacci Licensed under LGPL version 3 -- SessionIdAuth grants access to the target only to the users authenticated in a django web app. - -- SessionIdAuthAndHostPort determines the target based on the authenticated user. Edit get_host_port(current_user) to determine a target and a host for each user. + +**************************************** SessionIdAuth **************************************** + + SessionIdAuth grants access to the target only to the users authenticated in a django web app. + + Usage: put the websockify folder inside the django project, as shown below: + + django_project + |__________django_project + | |__________ settings.py + | | + | |__________ urls.py + | | + | |__________ wsgi.py + | + | + |__________ websockify + | |__________ some files... + | | + | |__________ websockify + | |_____________ django_auth_plugins.py + | + | + |__________ other files and folders... + + Right after starting the django web app, run websockify with the --auth-plugin option (Example: ./websockify/run 6080 localhost:5900 --auth-plugin="websockify.django_auth_plugins.SessionIdAuth") + + +**************************************** SessionIdAuthAndHostPort **************************************** + + SessionIdAuthAndHostPort determines the target based on the authenticated user. + + Usage: put the websockify folder inside the django project, as shown below: + + django_project + |__________django_project + | |__________ settings.py + | | + | |__________ urls.py + | | + | |__________ wsgi.py + | + | + |__________ websockify + | |__________ some files... + | | + | |__________ websockify + | |_____________ django_auth_plugins.py + | + | + |__________ other files and folders... + + + Edit get_host_port(current_user) to implement an algorithm to determine a target and a host for each user. + + Right after starting the django web app, run websockify with the --auth-plugin and the --auth-host-port options (Example: ./websockify/run 6080 --auth-plugin="websockify.django_auth_plugins.SessionIdAuthAndHostPort" --auth-host-port) + + For a complete example: https://github.com/lucacapacci/noVncDjangoPoC ''' From df1aa028da122acd5ea4474182dfda567d1c839a Mon Sep 17 00:00:00 2001 From: Luca Capacci Date: Mon, 28 Sep 2015 13:00:55 +0200 Subject: [PATCH 4/4] Added --auth-host-port option --- websockify/websocketproxy.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 46ab5459..36515560 100755 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -53,9 +53,16 @@ def validate_connection(self): if self.server.auth_plugin: try: - self.server.auth_plugin.authenticate( - headers=self.headers, target_host=self.server.target_host, - target_port=self.server.target_port) + if self.server.auth_host_port: + server_target_host, server_target_port = self.server.auth_plugin.authenticate(headers=self.headers, + target_host=self.server.target_host, + target_port=self.server.target_port) + self.server.target_host = server_target_host + self.server.target_port = server_target_port + else: + self.server.auth_plugin.authenticate( + headers=self.headers, target_host=self.server.target_host, + target_port=self.server.target_port) except auth.AuthenticationError: ex = sys.exc_info()[1] self.send_auth_error(ex) @@ -229,6 +236,7 @@ def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs): self.token_plugin = kwargs.pop('token_plugin', None) self.auth_plugin = kwargs.pop('auth_plugin', None) + self.auth_host_port = kwargs.pop('auth_host_port', False) # Last 3 timestamps command was run self.wrap_times = [0, 0, 0] @@ -288,6 +296,9 @@ def started(self): if self.token_plugin: msg = " - proxying from %s:%s to targets generated by %s" % ( self.listen_host, self.listen_port, type(self.token_plugin).__name__) + elif self.auth_host_port: + msg = " - proxying from %s:%s to targets generated by %s" % ( + self.listen_host, self.listen_port, type(self.auth_plugin).__name__) else: msg = " - proxying from %s:%s to %s" % ( self.listen_host, self.listen_port, dst_string) @@ -407,6 +418,8 @@ def websockify_init(): parser.add_option("--auth-source", default=None, metavar="ARG", help="an argument to be passed to the auth plugin" "on instantiation") + parser.add_option("--auth-host-port", action="store_true", + help="let the auth plugin set host and port") parser.add_option("--auto-pong", action="store_true", help="Automatically respond to ping frames with a pong") parser.add_option("--heartbeat", type=int, default=0, @@ -423,6 +436,8 @@ def websockify_init(): if opts.auth_source and not opts.auth_plugin: parser.error("You must use --auth-plugin to use --auth-source") + if opts.auth_host_port and not opts.auth_plugin: + parser.error("You must use --auth-plugin to use --auth-host-port") # Transform to absolute path as daemon may chdir if opts.target_cfg: @@ -435,7 +450,7 @@ def websockify_init(): del opts.target_cfg # Sanity checks - if len(args) < 2 and not (opts.token_plugin or opts.unix_target): + if len(args) < 2 and not (opts.token_plugin or opts.unix_target or opts.auth_host_port): parser.error("Too few arguments") if sys.argv.count('--'): opts.wrap_cmd = args[1:] @@ -460,7 +475,7 @@ def websockify_init(): try: opts.listen_port = int(opts.listen_port) except: parser.error("Error parsing listen port") - if opts.wrap_cmd or opts.unix_target or opts.token_plugin: + if opts.wrap_cmd or opts.unix_target or opts.token_plugin or opts.auth_host_port: opts.target_host = None opts.target_port = None else: