Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added django authentication plugins and the option to set the target inside an auth plugin #195

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
144 changes: 144 additions & 0 deletions websockify/django_auth_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
'''
Django authentication plugins for Python WebSocket library.
Copyright 2015 Luca Capacci
Licensed under LGPL version 3


**************************************** 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 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 = {'john': ('localhost', 5900),
'bob': ('localhost', 5901)}

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()
25 changes: 20 additions & 5 deletions websockify/websocketproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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:]
Expand All @@ -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:
Expand Down