Commit 1dc91cfd by Nate Hardison

Add utilities to Jabber app to help connecting

Add utilities to the Jabber app to parse the Jabber settings and
build the URLs, chat room names, and user passwords for connecting
to ejabberd. This is a WIP commit since it still needs tests (in
progress...stubbed out with comments).
parent 9048630d
......@@ -34,9 +34,8 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
from xmodule.modulestore.search import path_to_location
from jabber.models import JabberUser
import comment_client
import jabber.utils
log = logging.getLogger("mitx.courseware")
......@@ -235,36 +234,6 @@ def update_timelimit_module(user, course_id, model_data_cache, timelimit_descrip
return context
def chat_settings(course, user):
"""
Returns a dict containing the settings required to connect to a
Jabber chat server and room.
"""
domain = getattr(settings, "JABBER_DOMAIN", None)
if domain is None:
raise ImproperlyConfigured("Missing JABBER_DOMAIN in the settings")
# username/password from somewhere
jabber_user, created = JabberUser.objects.get_or_create(username=user.username,
defaults={'password' : 'bobobo'})
if created:
log.info("CREATED PASSWORD FOR USER %s\n" % user.username)
return {
'domain': domain,
# Jabber doesn't like slashes, so replace with dashes
'room': "{ID}_class".format(ID=course.id.replace('/', '-')),
'username': "{USER}@{DOMAIN}".format(
USER=jabber_user.username, DOMAIN=domain
),
'password': jabber_user.password
}
@login_required
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
......@@ -333,7 +302,12 @@ def index(request, course_id, chapter=None, section=None,
# by the course.
context['show_chat'] = course.show_chat and settings.MITX_FEATURES.get('ENABLE_CHAT')
if context['show_chat']:
context['chat'] = chat_settings(course, user)
context['chat'] = {
'bosh_url': jabber.utils.get_bosh_url(),
'course_room': jabber.utils.get_room_name_for_course(course.id),
'username': "%s@%s" % (user.username, settings.JABBER.get('HOST')),
'password': jabber.utils.get_password_for_user(user.username)
}
chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter)
if chapter_descriptor is not None:
......
from django.test import TestCase
from django.test.utils import override_settings
from django.conf import settings
from mock import patch
from nose.plugins.skip import SkipTest
import jabber.utils
class JabberSettingsTests(TestCase):
@override_settings()
def test_valid_settings(self):
pass
def test_missing_settings(self):
pass
class UtilsTests(TestCase):
def test_get_bosh_url(self):
# USE_SSL present (True/False) and absent
# HOST present (something/empty) and absent
# PORT present (int/str) and absent
# PATH present (something/empty) and absent
pass
def test_get_password_for_user(self):
# Test JabberUser present/absent
pass
def test_get_room_name_for_course(self):
# HOST present (something/empty) and absent
# Test course_id parsing
pass
"""
Various utilities for working with Jabber chat. Includes helper
functions to parse the settings, create and retrieve chat-specific
passwords for users, etc.
"""
import base64
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from .models import JabberUser
# The default length of the Jabber passwords we create. We set a
# really long default since we're storing these passwords in
# plaintext (ejabberd implementation detail).
DEFAULT_PASSWORD_LENGTH = 256
def get_bosh_url():
"""
Build a "Bidirectional-streams Over Synchronous HTTP" (BOSH) URL
for connecting to a Jabber server. It has the following format:
<protocol>://<host>:<port>/<path>
The Candy.js widget connects to ejabberd at this endpoint.
By default, the BOSH URL uses HTTP, not HTTPS. The port and the
path are optional; only the host is required.
"""
__validate_settings()
protocol = "http"
use_ssl = settings.JABBER.get("USE_SSL", False)
if use_ssl:
protocol = "https"
host = settings.JABBER.get("HOST")
bosh_url = "%s://%s" % (protocol, host)
# The port is an optional setting
port = settings.JABBER.get("PORT")
if port is not None:
# Convert port to a string in case it's specified as a number
bosh_url += ":%s" % str(port)
# Also optional is the "path", which could possibly use a better
# name...help @jrbl?
path = settings.JABBER.get("PATH")
if path is not None:
bosh_url += "/%s" % path
return bosh_url
def get_password_for_user(username):
"""
Retrieve the password for the user with the given username. If
a password doesn't exist, then we'll create one by generating a
random string.
"""
try:
jabber_user = JabberUser.objects.get(username=username)
except JabberUser.DoesNotExist:
password = __generate_random_string(DEFAULT_PASSWORD_LENGTH)
jabber_user = JabberUser(username=username,
password=password)
jabber_user.save()
return jabber_user.password
def get_room_name_for_course(course_id):
"""
Build a Jabber chat room name given a course ID with format:
<room>@<domain>
The room name will just be the course name (parsed from the
course_id), and the domain will be the Jabber host with the
optional multi-user chat (MUC) subdomain.
"""
__validate_settings()
host = settings.JABBER.get("HOST")
# The "multi-user chat" subdomain is a convention in Jabber to
# keep chatroom traffic from blowing up your one-to-one traffic.
# This is an optional setting.
muc_subdomain = settings.JABBER.get("MUC_SUBDOMAIN")
if muc_subdomain is not None:
host = "%s.%s" % (muc_subdomain, host)
# Rather than using the whole course ID, which is rather ugly for
# display, we'll just grab the name portion.
# TODO: is there a better way to just grab the name out?
org, num, name = course_id.split('/')
return "%s_class@%s" % (name, host)
def __generate_random_string(length):
"""
Generate a Base64-encoded random string of the specified length,
suitable for a password that can be stored in a database.
"""
# Base64 encoding gives us 4 chars for every 3 bytes we give it,
# so figure out how many random bytes we need to get a string of
# just the right length
num_bytes = length / 4 * 3
with open("/dev/random", "rb") as random:
return base64.b64encode(random.read(num_bytes))
def __validate_settings():
"""
Ensure that the Jabber settings are properly configured. This
is intended for internal use only to prevent code duplication.
"""
if getattr(settings, "JABBER") is None:
raise ImproperlyConfigured("Missing Jabber dict in settings")
host = settings.JABBER.get("HOST")
if host is None or host == "":
raise ImproperlyConfigured("Missing Jabber HOST in settings")
......@@ -123,8 +123,8 @@
<script type="text/javascript">
// initialize the Candy.js plugin
$(document).ready(function() {
Candy.init("http://${chat['domain']}:5280/http-bind/", {
core: { debug: true, autojoin: ["${chat['room']}@conference.${chat['domain']}"] },
Candy.init("${chat['bosh_url']}", {
core: { debug: true, autojoin: ["${chat['course_room']}"] },
view: { resources: "${static.url('candy_res/')}"}
});
Candy.Core.connect("${chat['username']}", "${chat['password']}");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment