Archive | Programming RSS feed for this section

A RESTful password locker with Django and backbone.js part 6

29 May

This series has explained how to create a RESTful web application using Django REST framework and Backbone.js. The code is available for you to explore and play with.

To conclude, I’d like to discuss what other additions we could make to make the application more secure, performant and robust.

Security

Storing passwords in plain text in a database is a Bad Idea. Django hashes user passwords for us, so those credentials are fine. But how could we secure the passwords users want to put in our password locker?

If we didn’t have the requirement to allow passwords to be shared, we could use a symmetric key encryption algorithm with the user’s raw authentication password as a secret key – possibly munged with some extra data. This would mean that passwords would only be able to be decrypted once a user logged in and would make large-scale brute-forcing of the database unfeasible if we chose our algorithm carefully since every user’s password would need to be cracked to decrypt their data. We would be storing user passwords in memory and it’s possible they could leak to some degree, but it’d be safer if a hacker was only able to download a dump of the database.

One possibility for supporting sharing and making the stored data more secure would be to use public key cryptography. The private key could require the user’s password to decrypt data. If a user shares a  password, we could encrypt it with the recipient’s public key and they’d be able to decrypt it with their private key when they log in.

Cryptography is computationally expensive, and since our code is in python we may find it better to code these modules in a compiled language. Some python libraries implement their encryption routines in C, so we could use these. However, if we were interested in scalability, we may find it more performant to use dedicated servers to handle the cryptography. In this scenario, we could use an RPC framework such as Apache Thrift to handle communication between the front-end web nodes and Java/C backends.

Also, the entire application must run over SSL for the site to be secure, and ideally not contain any third-party content (such as adverts) to make sure that there’s no possibility for some kind of cross-domain Javascript bug to steal user passwords.

Performance and robustness

Before putting this code into production, we should create a full suite of unit & functional tests. It also needs testing cross-browser testing to make sure there are no quirks in different browsers.

Because the majority of the application is loaded via AJAX, we can cache web templates to a large degree which will reduce load on the web nodes. Of course, we should also combine and minify Javascript and CSS.

That’s all. I hope you’ve found this tutorial useful.

Advertisements

A RESTful password locker with Django and backbone.js part 5

29 May

In this penultimate part of this series, we’re going to add the ability to share passwords between users. The spec for sharing passwords is as follows:

  1. Users should be able to maintain a contact list of other users with whom they can share passwords. Users must be able to search for other users by user name, and be able to view their first and last name to confirm the user is who they think they are.
  2. If a user removes another user from their contact list, all passwords shared with that user should stop being shared with the removed contact.
  3. Passwords must be able to be shared with multiple users in the creator’s contact list.
  4. Only the creator of a password is allowed to modify data. Users with whom it’s shared have read-only access.
  5. Only the creator of a password may share a password (i.e. if User A has shared a password with User B, User B may not share that password with anyone else).

Since this series is primarily about Backbone.js, we’ll implement the above in a single paged web app.

The contact list

To allow users to share passwords, we’ll create a many-to-many relationship to a new model. Update `apps/passwords/models.py` to the following:

from django.db import models
from django.contrib.auth.models import User

class Password(models.Model):
    """
    Represents a username and password together with several other fields
    """
    created_by = models.ForeignKey(User, related_name='+', editable=False)
    title = models.CharField(max_length=200)
    username = models.CharField(max_length=200,
        blank=True)
    password = models.CharField(max_length=200)
    url = models.URLField(max_length=500,
        blank=True,
        verbose_name='Site URL')
    notes = models.CharField(
        max_length=500,
        blank=True)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)
    shares = models.ManyToManyField('PasswordContact',
        verbose_name='Share with', blank=True)

    def __unicode__(self):
        return self.title

class PasswordContact(models.Model):
    """
    Someone with whom a user can share a Password
    """
    from_user = models.ForeignKey(User, related_name="passwordcontactfrom", editable=False)
    to_user = models.ForeignKey(User, related_name="passwordcontactto")
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)

    def __unicode__(self):
        return "%s %s (%s)" % (self.to_user.first_name, self.to_user.last_name, self.to_user.username)

This is standard Django. Migrate with south and apply it: `./manage.py schemamigration passwords –auto && ./manage.py migrate passwords`.

We’ll create 2 new APIs – one for the PasswordContact resource, and another for User objects which will allow members to search for other users.

Create `apps/users/resources.py` and enter the following:

from djangorestframework.resources import ModelResource

from django.contrib.auth.models import User

class UserResource(ModelResource):
    """
    Lets users search for other users by username.
    """
    model = User
    fields = ('id', 'first_name', 'last_name', 'username', 'url')

    def validate_request(self, data, files=None):
        """
        Backbone.js will submit all fields in the model back to us, but
        some fields are set as uneditable in our Django model. So we need
        to remove those extra fields before performing validation.
        """
        for key in self.ignore_fields:
            if key in data:
                del data[key]

        return super(UserResource, self).validate_request(data, files)

Update `apps/passwords/resources.py` to the following:

from djangorestframework.resources import ModelResource
from djangorestframework.serializer import Serializer
from django.core.urlresolvers import reverse

from apps.users.resources import UserResource
from models import Password, PasswordContact

class PasswordContactResource(ModelResource):
    model = PasswordContact
    ordering = ('to_user__first_name',)
    fields = ('id', 'url', ('to_user', 'UserResource'), ('from_user', 'UserResource'))
    ignore_fields = ('id',)

    def validate_request(self, data, files=None):
        """
        Backbone.js will submit all fields in the model back to us, but
        some fields are set as uneditable in our Django model. So we need
        to remove those extra fields before performing validation.
        """
        for key in self.ignore_fields:
            if key in data:
                del data[key]

        return super(PasswordContactResource, self).validate_request(data, files)

class CurrentUserSingleton(object):
    """
    Literally the only way I can find to give the PasswordResource access
    to the current user object.
    """
    user = None

    @classmethod
    def set_user(cls, user):
        cls.user = user

class PasswordResource(ModelResource):
    model = Password
    # by default, django rest framework won't return the ID - backbone.js
    # needs it though, so don't exclude it
    exclude = ('created_by',)
    ordering = ('-title',)
    # django rest framework will overwrite our 'url' attribute with its own
    # that points to the resource, so we need to provide an alternative.
    include = ('resource_url',)
    ignore_fields = ('created_at', 'updated_at', 'id', 'maskedPassword',
        'resource_url', 'is_owner')
    fields = ('id', 'title', 'username', 'password', 'url', 'notes',
        'resource_url', 'shares', 'is_owner')

    related_serializer = PasswordContactResource

    def is_owner(self, instance):
        """
        Returns True if this resource was created by the current user.
        """
        return instance.created_by == CurrentUserSingleton.user

    def url(self, instance):
        """
        Return the instance URL. If we don't specify this, django rest
        framework will return a generated URL to the resource
        """
        return instance.url

    def resource_url(self, instance):
        """
        An alternative to the 'url' attribute django rest framework will
        add to the model.
        """
        return reverse('passwords_api_instance',
                       kwargs={'id': instance.id})

    def validate_request(self, data, files=None):
        """
        Backbone.js will submit all fields in the model back to us, but
        some fields are set as uneditable in our Django model. So we need
        to remove those extra fields before performing validation.
        """
        for key in self.ignore_fields:
            if key in data:
                del data[key]

        return super(PasswordResource, self).validate_request(data, files)

Change `apps/api/urls.py` to the following to wire up URLs:

from django.conf.urls.defaults import patterns, url

from views import PasswordListView, PasswordInstanceView
from views import PasswordContactListView, PasswordContactReadOrDeleteInstanceView
from views import UserView

urlpatterns = patterns('',
    url(r'^passwords/$', PasswordListView.as_view(), name='passwords_api_root'),
    url(r'^passwords/(?P[0-9]+)$', PasswordInstanceView.as_view(), name='passwords_api_instance'),
    url(r'^passwordcontacts/$', PasswordContactListView.as_view(),
        name='password_contacts_api_root'),
    url(r'^passwordcontacts/(?P[0-9]+)$', PasswordContactReadOrDeleteInstanceView.as_view(),
        name='password_contacts_api_instance'),
    url(r'^user/(?P.+)$', UserView.as_view(), name='user_api'),
)

We also need to update the views in `apps/api/views.py`:

from django.db.models import Q
from djangorestframework.mixins import ModelMixin, InstanceMixin, \
ReadModelMixin, DeleteModelMixin
from djangorestframework.permissions import IsAuthenticated
from djangorestframework.response import ErrorResponse
from djangorestframework import status
from djangorestframework.views import ListOrCreateModelView, InstanceModelView, ModelView

from apps.passwords.models import PasswordContact
from apps.passwords.resources import PasswordResource, PasswordContactResource, \
CurrentUserSingleton
from apps.users.resources import UserResource

class RestrictPasswordToUserMixin(ModelMixin):
    """
    Mixin that restricts users to working with their own data
    """
    def get_queryset(self):
        """
        Only return objects created by, or shared with, the currently
        authenticated user.
        """
        return self.resource.model.objects.filter(Q(created_by=self.user) |
            Q(shares__to_user=self.user)).distinct()

    def get_instance_data(self, model, content, **kwargs):
        """
        Set the created_by field to the currently authenticated user.
        """
        content['created_by'] = self.user
        return super(RestrictPasswordToUserMixin, self).get_instance_data(model, content, **kwargs)

    def initial(self, request, *args, **kwargs):
        """
        Set the currently authenticated user on the resource
        """
        CurrentUserSingleton.set_user(request.user)
        return super(ModelMixin, self).initial(request, *args, **kwargs)

    def final(self, request, response, *args, **kargs):
        """
        Clear the current user singleton to make sure it doesn't leak
        """
        CurrentUserSingleton.set_user(None)
        return super(ModelMixin, self).final(request, response, *args, **kargs)

class PasswordListView(RestrictPasswordToUserMixin, ListOrCreateModelView):
    """
    List view for Password objects.
    """
    resource = PasswordResource
    permissions = (IsAuthenticated, )

class PasswordInstanceView(RestrictPasswordToUserMixin, InstanceModelView):
    """
    View for individual Password instances
    """
    resource = PasswordResource
    permissions = (IsAuthenticated, )

    def put(self, request, *args, **kwargs):
        """
        Only allow the creating user to modify an instance.
        """
        model = self.resource.model
        query_kwargs = self.get_query_kwargs(request, *args, **kwargs)

        try:
            self.model_instance = self.get_instance(**query_kwargs)

            if self.model_instance.created_by == self.user:
                return super(RestrictPasswordToUserMixin, self).put(request, *args, **kwargs)
        except model.DoesNotExist:
            pass

        raise ErrorResponse(status.HTTP_401_UNAUTHORIZED, None, {})

    def delete(self, request, *args, **kwargs):
        """
        Only the creator should be able to delete an instance.
        """
        model = self.resource.model
        query_kwargs = self.get_query_kwargs(request, *args, **kwargs)

        try:
            instance = self.get_instance(**query_kwargs)
        except model.DoesNotExist:
            raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})

        if instance.created_by == self.user:
            instance.delete()
        else:
            raise ErrorResponse(status.HTTP_401_UNAUTHORIZED, None, {})

class PasswordContactListView(ListOrCreateModelView):
    """
    List view for PasswordContact objects.
    """
    resource = PasswordContactResource
    permissions = (IsAuthenticated, )

    def get_queryset(self):
        """
        Only return objects where the from_user is the currently authenticated user.
        """
        return self.resource.model.objects.filter(from_user=self.user)

    def get_instance_data(self, model, content, **kwargs):
        """
        Set the from_user field to the currently authenticated user.
        """
        content['from_user'] = self.user
        return super(PasswordContactListView, self).get_instance_data(model, content, **kwargs)

class ReadOnlyInstanceModelView(InstanceMixin, ReadModelMixin, ModelView):
    """
    A view which provides default operations for read/delete against a model instance
    but that prevents updates.
    """
    _suffix = 'Instance'

class PasswordContactReadOrDeleteInstanceView(ReadOnlyInstanceModelView):
    """
    View for individual PasswordContact instances
    """
    resource = PasswordContactResource
    permissions = (IsAuthenticated, )

    def delete(self, request, *args, **kwargs):
        """
        Deletes shares from Passwords when a PasswordContact is deleted
        """
        model = self.resource.model
        query_kwargs = self.get_query_kwargs(request, *args, **kwargs)

        try:
            instance = self.get_instance(**query_kwargs)
        except model.DoesNotExist:
            raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})

        # remove any shares from any passwords shared with this contact
        password_contacts = PasswordContact.objects.filter(from_user=self.user,
            to_user=instance.to_user)

        for password_contact in password_contacts:
            password_contact.delete()

        instance.delete()
        return

class UserView(InstanceMixin, ReadModelMixin, ModelView):
    """
    View for individual Users lets users find other users by username
    """
    resource = UserResource
    permissions = (IsAuthenticated, )

    def get_queryset(self):
        """
        Filter the current user from search results to prevent them sharing
        with themselves.
        """
        return self.resource.model.objects.filter(~Q(id=self.user.id))

Finally,

There’s quite a lot going on in the above code:

  • We’re restricting users to only viewing those objects for which they’re the creator or a recipient of a share.
  • A singleton is used to enable the PasswordResource to determine whether the currently authenticated user created a resource or not, and this is returned as the `is_owner` property we added to the PasswordResource.
  • We restrict CRUD operations on password instances so they can only be performed by the creator of a password.
  • Users can only create or delete password contacts, they can’t update them. When a password contact is deleted, we remove all shares associated with that user. So when a user removes someone from their contact list, access to all shared passwords is automatically revoked.
  • Finally we prevent the UserView from returning the current user. This view only support GET preventing users from browsing all members.

One last thing we need before we can hook things up on the front-end is to update the PasswordForm so it allows users to share their passwords with users in their contact list. Update `apps/passwords/forms.py` with the following:

from django.forms import ModelForm
from django.forms import widgets
from django.forms.models import ModelMultipleChoiceField
from django.utils.translation import ugettext_lazy as _

from models import Password, PasswordContact

class PasswordForm(ModelForm):
    class Meta:
        model = Password
        widgets = {
            'shares': widgets.CheckboxSelectMultiple
        }

    def __init__(self, user, *args, **kwargs):
        super(PasswordForm, self).__init__(*args, **kwargs)
        remove_message = unicode(_('Hold down "Control", or "Command" on a Mac, to select more than one.'))

        for field in self.fields:
            if remove_message in self.fields[field].help_text:
                self.fields[field].help_text = self.fields[field].help_text.replace(remove_message, '').strip()

        # restrict the choice of users to share passwords with to a
        # user's PasswordContacts
        self.fields['shares'] = ModelMultipleChoiceField(
            queryset=PasswordContact.objects.filter(from_user=user) \
                .order_by('to_user__first_name'),
            widget=widgets.CheckboxSelectMultiple())

Backbone.js

Let’s create a separate application for handling contacts, although we’ll load it all on the same page.

First, update `templates/passwords/password_list.html` as follows:

{% extends "base.html" %}

{% load sekizai_tags %}
{% load bootstrap_toolkit %}

{% block content %}
    {% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}bootstrap/js/bootstrap.min.js"></script>

    <!-- backbone --><script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/ICanHaz.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/json2.js"></script><script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/underscore-min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/backbone-min.js"></script><script type="text/javascript" src="{{ STATIC_URL }}/js/passwords.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}/js/contacts.js"></script>
    {% endaddtoblock %}</pre>

&nbsp;

<ul class="nav nav-tabs">
<ul class="nav nav-tabs">
	<li class="active"><a href="#passwordPanel" data-toggle="tab">Passwords</a></li>
</ul>
</ul>

&nbsp;

<ul class="nav nav-tabs">
<ul class="nav nav-tabs">
	<li><a href="#contactPanel" data-toggle="tab">Contacts</a></li>
</ul>
</ul>

&nbsp;

<pre>

</pre>
<div class="tab-content">

<div id="passwordPanel" class="tab-pane active">

<h1 class="page-header">Passwords</h1>

Move your mouse over a password to reveal it. You can only edit your own passwords, not those that have been shared with you.

<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>User name</th>
<th>Password</th>
<th>Notes</th>
<th>Actions</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="5"><button class="btn btn-primary" data-toggle="modal">Add new password</button></td>
</tr>
</tfoot>
</table>

<div id="passwordModal" class="modal hide fade">
<form id="passwordForm" method="post">

<div class="modal-header"><button class="close" data-dismiss="modal">×</button>

<h3>Password Details</h3>
</div>

<div class="modal-body">{{ form|as_bootstrap }} {% csrf_token %}</div>

<div class="modal-footer"><a class="btn" href="#" data-dismiss="modal">Cancel</a> <input class="btn btn-primary" type="submit" value="Save" /></div>
</form></div>
</div>

<div id="contactPanel" class="tab-pane">

<h1 class="page-header">Manage contacts</h1>

<div class="well">

<h3>Add new contacts</h3>

To find other users to share passwords with, enter their username below:

<form class="form-search"><input id="userSearch" class="search-query" type="text" /> <input class="btn" type="submit" value="Add user" /></form></div>

<h3>My contacts</h3>

You can share passwords with the following users. If you want to stop sharing all passwords with a certain user, simply delete them from your list by clicking on the icon.

<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>User name</th>
<th>Actions</th>
</tr>
</thead>
</table>
</div>
</div>
<pre>
    {% load verbatim %}

    <!-- ICanHaz templates -->{% comment %}
    Mustache and django both use {{}} tags for templates, so we need to use
    a custom template tag to output the mustache template exactly as it is.
    {% endcomment %}
    {% verbatim %}<script id="passwordRowTpl" type="text/html">// <![CDATA[
<td>
            <a href="{{ url }}" target="_blank">
                {{ title }}
            </a></td>

<td>{{ username }}</td>

<td class="password">{{ maskedPassword }}</td>

<td>{{ notes }}</td>

<td>
            {{#is_owner}}
            <a href="#" class="edit" title="Edit this entry"><i class="icon-pencil"></i></a>
            <a href="#" class="destroy" title="Delete this entry"><i class="icon-remove"></i></a>
            {{/is_owner}}</td>

// ]]></script>

<script id="contactRowTpl" type="text/html">// <![CDATA[
<td>
            {{ to_user.first_name }} {{ to_user.last_name }}</td>

<td>{{ to_user.username }}</td>

<td>
            <a href="#" class="destroy" title="Delete this contact"><i class="icon-remove"></i></a></td>

// ]]></script>

<script id="shareOption" type="text/html">// <![CDATA[
        <label class="checkbox">
            <input type="checkbox" value="{{ id }}" name="shares">
            {{ to_user.first_name }} {{ to_user.last_name }} ({{ to_user.username }})
        </label>

// ]]></script>
    {% endverbatim %}
{% endblock %}

We’ve added a few new templates and have created a nice tabbed interface courtesy of Twitter Bootstrap.

So, to create our contact application, enter the following into a new file, `staticfiles/js/contacts.js`:

// load the following using JQuery's document ready function
$(function(){

    // Contact model
    var Contact = Backbone.Model.extend({
        remove: function(options) {
            mergedOptions = {wait: true};
            $.extend(mergedOptions, options);
            this.destroy(mergedOptions);
        }
    });

    // set up the view for a contact
    var ContactView = Backbone.View.extend({
        tagName: 'tr',

        events: {
            "click a.destroy" : "remove"
        },

        remove: function(event) {
            event.stopImmediatePropagation();
            event.preventDefault();
            if (confirm("Are you sure you want to delete this contact?"))
            {
                var that = this;

                this.model.remove({error: function(model, response) {
                        if (response.status == 403) {
                            alert("You don't have permission to delete that data");
                        }
                        else {
                            alert("Unable to delete that data");
                        }
                    },
                    success: function() {
                        // update the form options - a little hacky, but oh well
                        $('#passwordForm').find(':checkbox').remove();
                        $('#passwordForm').find('.checkbox').remove();

                        var shareOptions = new Array();

                        that.options.collection.each(function(data){
                            shareOptions.push(ich.shareOption(data.toJSON(), true));
                        });

                        $(shareOptions.join('')).insertAfter('#id_notes');
                    }
                });
            }
        },

        render: function () {
            // template with ICanHaz.js (ich)
            $(this.el).html(ich.contactRowTpl(this.model.toJSON()));
            return this;
        }
    });

    // define the collection of contacts
    var ContactCollection = Backbone.Collection.extend({
        model: Contact,
        url: '/api/1.0/passwordcontacts/',

        // maintain ordering by first_name
        comparator: function(obj1, obj2) {
            return obj1.get('to_user').first_name.localeCompare(obj2.get('to_user').first_name);
        }
    });

    /**
     * Manages the list of contacts.
     */
    var ContactListView = Backbone.View.extend({
        tagName: 'tbody',

        /**
         * Constructor. Takes a reference to the parent view so we can invoke
         * methods on it.
         */
        initialize: function(options) {
            // instantiate a password collection
            this.collection = new ContactCollection();

            this.collection.bind('all', this.render, this);
            this.collection.fetch();
        },

        addOne: function(contact) {
            this.$el.append(new ContactView({model: contact, collection: this.collection}).render().el);
            return this;
        },

        addNew: function(data, options) {
            mergedOptions = {wait: true};
            $.extend(mergedOptions, options);

            var contact = {
                to_user: data.id
            };

            this.collection.create(contact, mergedOptions);
            return this;
        },

        render: function() {
            this.$el.html('');
            this.collection.each(this.addOne, this);
            return this;
        }
    });

    /**
     * View for the overall application. We need this because backbone can only
     * bind events for children of 'el'.
     *
     * In our template our modal is inside #app, so this class handles
     * interaction at the application level rather than strictly with a
     * collection of Passwords (that's the job of the PasswordListView).
     */
    var ContactPanelView = Backbone.View.extend({
        el: '#contactPanel',
        events: {
            "click #contactPanel :submit": "handleSearch",
            "keydown #contactPanel :input[type=text]": "handleSearchOnEnter"
        },

        initialize: function() {
            this.dataList = new ContactListView({app: this});
        },

        displayError: function(model, response) {
            if (response.status == 403) {
                alert("You don't have permission to edit that data");
            }
            else {
                alert("Unable to create or edit that data. Please make sure you entered valid data.");
            }
        },

        render: function() {
            this.$el.find('table').append(this.dataList.render().el);
        },

        handleSearch: function(event) {
            event.preventDefault();
            event.stopImmediatePropagation();

            var username = $('#userSearch').val();

            var that = this;

            // perform a GET request to the userSearch service and if it
            // returns a user, create a new PasswordContact
            $.ajax({
                url: '/api/1.0/user/' + username,
                dataType: 'json',
                success: function(data, textStatus, jqXHR) {
                    that.dataList.addNew(data, {success: function() {
                        $('#userSearch').val('');

                        // update the form options
                        $('#passwordForm').find(':checkbox').remove();
                        $('#passwordForm').find('.checkbox').remove();

                        var shareOptions = new Array();

                        that.dataList.collection.each(function(data){
                            shareOptions.push(ich.shareOption(data.toJSON(), true));
                        });

                        $(shareOptions.join('')).insertAfter('#id_notes');
                    }});
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    if (jqXHR.status) {
                        alert("Sorry, we couldn't find that user");
                    }
                    else {
                        alert("There was a problem searching for that user.");
                    }
                }
            });

            return this;
        },

        handleSearchOnEnter: function(event) {
            // process the modal if the user pressed the ENTER key
            if (event.keyCode == 13)
            {
                return this.handleSearch(event);
            }
        }
    });

    var contactPanel = new ContactPanelView();
    contactPanel.render();
});

The above is quite similar to passwords.js, but generally simpler. About the only complex code is to do with updating the list of checkboxes on the password form when users add or delete contacts. It’s not ideal having selectors in the view like that, but we’ve managed to keep it to a minimum, so I can live with it here.

The `handleSearch` method uses the `user` API – if it successfully receives a response it creates a new PasswordContact and updates the user’s contact list.

Try it out

This post is a bit like a shopping list, but hopefully it’ll help make the repository more accessible. If you open two different browsers you’ll be able to create two different users, add them to each others’ contact lists and share and revoke passwords between them.

We’ll finish off this series with a discussion of the current architecture and what could be done to make the application more secure.

A RESTful password locker with Django and backbone.js part 4

28 May

So far we’ve got a single-user password storing application. That’s not very secure or useful 🙂 So now we’re going to support multiple users and lock down the app so users must be authenticated. We’ll also lock down the REST API.

There’s now sufficient code that I’ll point you in the direction of certain files in the repository for some of the more standard Django code for things such as user registration.

Supporting multiple users

I created a registration form in `apps/users/forms.py` and updated `urls.py` so the django registration app would use it. The registration form asks for users first and last names, email address, a user name and their password (twice). This is pretty standard stuff.

Now users can register, we need to update the Password model in `apps/passwords/models.py` so passwords are associated with the user that created them. Add a new foreign key to django.contrib.auth.models.User:

from django.db import models
from django.contrib.auth.models import User

class Password(models.Model):
    """
    Represents a username and password together with several other fields
    """
    created_by = models.ForeignKey(User, related_name='+', editable=False)
    title = models.CharField(max_length=200)
    username = models.CharField(max_length=200,
        blank=True)
    password = models.CharField(max_length=200)
    url = models.URLField(max_length=500,
        blank=True,
        verbose_name='Site URL')
    notes = models.CharField(
        max_length=500,
        blank=True)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)

    def __unicode__(self):
        return self.title

We’ll use South to create a migration with `./manage.py schemamigration passwords –auto`. When it asks what to do about defaults for the created_by column, just make it set the column to ‘1’ (or any number you like). Since we’re developing, and we don’t have legacy data to support, we can don’t need to maintain the integrity of the database at this point.

Apply the migration with `./manage.py migrate passwords`.

Locking down the REST API

We need to do two things with the API:

  1. Require users to be authenticated to access it,
  2. Restrict data users can work with to only that which they have created.

Because Django REST API uses generic class-based views, it’s very simple to add these constraints. Simply create `apps/api/views.py` and add the following:

from djangorestframework.mixins import ModelMixin
from djangorestframework.permissions import IsAuthenticated
from djangorestframework.views import ListOrCreateModelView, InstanceModelView

from apps.passwords.resources import PasswordResource

class RestrictToUserMixin(ModelMixin):
    """
    Mixin that restricts users to working with their own data
    """
    def get_queryset(self):
        """
        Only return objects created by the currently authenticated user.
        """
        return self.resource.model.objects.filter(created_by=self.user)

    def get_instance_data(self, model, content, **kwargs):
        """
        Set the created_by field to the currently authenticated user.
        """
        content['created_by'] = self.user
        return super(RestrictToUserMixin, self).get_instance_data(model, content, **kwargs)

class PasswordListView(RestrictToUserMixin, ListOrCreateModelView):
    """
    List view for Password objects.
    """
    resource = PasswordResource
    permissions = (IsAuthenticated, )

class PasswordInstanceView(RestrictToUserMixin, InstanceModelView):
    """
    View for individual Password instances
    """
    resource = PasswordResource
    permissions = (IsAuthenticated, )

We’ve created a mixin to add the same logic to both classes. The mixin filters the queryset used by the API methods so that the `created_by` field is the currently authenticated user. This prevents users from accessing data belonging to other users. It also sets the `created_by` field to the currently authenticated user for CREATE (and UPDATE) operations. It’s very simple and very elegant.

The `permissions` tuple instructs Django REST API to require that users are authenticated in order to access those resources.

And now update `apps/api/urls.py` to use these views instead of the others we had configured:

from django.conf.urls.defaults import patterns, url

from views import PasswordListView, PasswordInstanceView

urlpatterns = patterns('',
    url(r'^passwords/$', PasswordListView.as_view(), name='passwords_api_root'),
    url(r'^passwords/(?P<id>[0-9]+)$', PasswordInstanceView.as_view(), name='passwords_api_instance'),
)

If you check out the API browser at http://localhost:8000/api/1.0/passwords/ you should notice you need to be logged in to access anything. Once you register and log in, you can use the API, but you can only view data associated with the account you’ve logged in as. However, if you try to use the front-end application, it will load the list of passwords, but CREATE, UPDATE and DELETE API access will be refused. You can see this if you have firebug open, or if you reload the page. This is because Django is now enforcing CSRF tokens.

Before we fix this, lets go off on a little tangent. Unless you had firebug open, you may not have spotted that the API was refusing your access – the app appeared to work correctly. So let’s add error handling to the backbone.js application so it’ll be easier for us to know when we’ve fixed this issue.

To be alerted when deletions fail, update `staticfiles/js/passwords.js` as follows:

    var Password = Backbone.Model.extend({
        ...
        remove: function(options) {
            mergedOptions = {wait: true};
            $.extend(mergedOptions, options);
            this.destroy(mergedOptions);
        },
    ...
    });

    var PasswordView = Backbone.View.extend({
        ...
        remove: function(event) {
            event.stopImmediatePropagation();
            event.preventDefault();
            if (confirm("Are you sure you want to delete this entry?"))
            {
                this.model.remove({error: function(model, response) {
                        if (response.status == 403) {
                            alert("You don't have permission to delete that data");
                        }
                        else {
                            alert("Unable to delete that data");
                        }
                    }
                });
            }
        },
        ...
    });

    var PasswordListView = Backbone.View.extend({
        ...
        addNew: function(password, options) {
            mergedOptions = {wait: true};
            $.extend(mergedOptions, options);
            this.passwords.create(password, mergedOptions);
            return this;
        },

        updatePassword: function(passwordData, options) {
            options = options || {};
            var password = this.passwords.get(passwordData.id);
            if (_.isObject(password))
            {
                // iterate through all the data in passwordData, setting it
                // to the password model
                for (var key in passwordData)
                {
                    // ignore the ID attribute
                    if (key != 'id')
                    {
                        password.set(key, passwordData[key]);
                    }
                }

                // persist the change
                password.save({}, options);
                this.passwords.sort();
            }
        },
        ...
    });

    var AppView = Backbone.View.extend({
        ...
        displayError: function(model, response) {
            if (response.status == 403) {
                alert("You don't have permission to edit that data");
            }
            else {
                alert("Unable to create or edit that data. Please make sure you entered valid data.");
            }
        },

        handleModal: function(event) {
            event.preventDefault();
            event.stopImmediatePropagation();
            var form = $('#passwordForm');

            var passwordData = {
                title: $(form).find('#id_title').val(),
                username: $(form).find('#id_username').val(),
                password: $(form).find('#id_password').val(),
                url: $(form).find('#id_url').val(),
                notes: $(form).find('#id_notes').val()
            };

            if ($('#passwordModal').data('passwordId'))
            {
                passwordData.id = $('#passwordModal').data('passwordId');
                this.passwordList.updatePassword(passwordData, { error: this.displayError });
            }
            else
            {
                // add or update the password
                this.passwordList.addNew(passwordData, { error: this.displayError });
            }

            // hide the modal
            $('#passwordModal').modal('hide');

            return this;
        },
        ...
    });

We’re passing an error handler through the code to the CRUD methods. The handler checks the status code and displays an appropriate message. Now when we try to use the javascript app, we at least have some feedback that things are failing.

We’ve also made backbone.js wait until it receives a response from the server before firing a ‘change’ event and updating the UI (with the `{wait: true}` option), so the UI will only update on success.

Finally, to fix this CSRF issue, we need to add the `{% csrf_token %}` template tag to `templates/passwords/password_list.html`. Just add it after the `{{ form }}` tag. This adds the actual token to the template.

Then we need to tweak `staticfiles/js/passwords.js` so jQuery will send the token as a header with each AJAX request.

Add the following at the end of the javascript code, just inside the final closing braces of the `$(function() {})` function:

// Setup $.ajax to always send an X-CSRFToken header:
    var csrfToken = $('input[name=csrfmiddlewaretoken]').val();
    $(document).ajaxSend(function(e, xhr, settings) {
        xhr.setRequestHeader('X-CSRFToken', csrfToken);
    });

Now, try creating several different users, create, edit and delete some data, and try to access data owned by other users. You should find that the API correctly restricts your access to data, and that the front-end javascript app works correctly too.

Masking passwords

For added security, we’ll mask the passwords in the UI, and only reveal them when the user moves their mouse over them. This stops people being able to see your complete list of credentials if they can see your screen.

First, we’ll update the backbone.js app in `staticfiles/js/passwords.js` to set a new property on the model called ‘maskedPassword’ which will just be a string of asterisks. We’ll also add some events for displaying and hiding the clear passwords:

    var Password = Backbone.Model.extend({
        initialize: function() {
            this.hidePassword();
        },

        // display the password
        showPassword: function() {
            this.set({"maskedPassword": this.get('password')});
        },

        // hide the password
        hidePassword: function() {
            this.set({"maskedPassword": '********'});
        },
        ...
    });

    var PasswordView = Backbone.View.extend({
        ...
        events: {
            "mouseover .password": "showPassword",
            "mouseout .password": "hidePassword",
            "click a.edit" : "editPassword",
            "click a.destroy" : "remove"
        },

        showPassword: function(event) {
            event.stopImmediatePropagation();
            this.model.showPassword();
        },

        hidePassword: function(event) {
            event.stopImmediatePropagation();
            this.model.hidePassword();
        },
        ...
    });

Now update the ICanHaz template in `templates/passwords/password_list.html` to populate the password field using `maskedPassword` instead of `password`:

    <script id="passwordRowTpl" type="text/html">
        <td>
            <a href="{{ url }}" target="_blank">
                {{ title }}
            </a>
        </td>
        <td>{{ username }}</td>
        <td class="password">{{ maskedPassword }}</td>
        <td>{{ notes }}</td>
        <td>
            <a href="#" class="edit" title="Edit this entry"><i class="icon-pencil"></i></a>
            <a href="#" class="destroy" title="Delete this entry"><i class="icon-remove"></i></a>
        </td>
    </script>

Reload the front-end app and it should load the list correctly with the passwords masked. It should also display the clear password when you hover the mouse over the asterisks. However, CRUD operations will fail because backbone.js will submit the extra `maskedPassword` field to the API, and Django REST framework will complain.

We’ve already got a way of ignoring certain fields submitted to the API, so just add `maskedPassword` to the `ignore_fields` tuple in `apps/passwords/resources.py`:

class PasswordResource(ModelResource):
    ...
    ignore_fields = ('created_at', 'updated_at', 'id', 'maskedPassword')
    ...

Summary

We’re done for this iteration. The app now supports multiple users, displays error messages to users and masks passwords.

In the penultimate part of this series, we’ll add the ability to share passwords between users.

A RESTful password locker with Django and backbone.js part 3

26 May

We left our application loading data via the API. Now we need to support CRUD operations on it.

To support deletions, we need to tweak one setting in our Django settings.py file so. Bootstrap will perform CRUD operations against a URL without a trailing slash, but by default, Django will add a trailing slash to any URLs without one. To disable this behaviour, set `APPEND_SLASH = False` in settings.py. Also update your `apps/api/urls.py` file to remove the trailing slash. It should look like this:

from django.conf.urls.defaults import patterns, url

from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from apps.passwords.resources import PasswordResource

password_list = ListOrCreateModelView.as_view(resource=PasswordResource)
password_instance = InstanceModelView.as_view(resource=PasswordResource)

urlpatterns = patterns('',
    url(r'^passwords/$', password_list, name='passwords_api_root'),
    url(r'^passwords/(?P[0-9]+)$', password_instance, name='passwords_api_instance'),
)

We need to update the template to include an ‘actions’ column to let users edit and delete rows. We’ll also add a modal using Twitter bootstrap that will contain a form to let users add new entries or update existing ones.

Update `templates/passwords/password_list.html` as follows:

{% extends "base.html" %}

{% block content %}</pre>
<h1 class="page-header">Passwords</h1>
<div id="app">
<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>User name</th>
<th>Password</th>
<th>Notes</th>
<th>Actions</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="5"><button class="btn btn-primary" data-toggle="modal">Add new password</button></td>
</tr>
</tfoot>
</table>
<div id="passwordModal" class="modal hide fade"><form id="passwordForm" method="post">
<div class="modal-header"><button class="close" data-dismiss="modal">×</button>
<h3>Password Details</h3>
</div>
<div class="modal-body">{{ form }}</div>
<div class="modal-footer"><a class="btn" href="#" data-dismiss="modal">Cancel</a>
 <input class="btn btn-primary" type="submit" value="Save" /></div>
</form></div>
</div>
<pre>
    {% load verbatim %}

    <!-- ICanHaz templates -->
    {% comment %}
    Mustache and django both use {{}} tags for templates, so we need to use
    a custom template tag to output the mustache template exactly as it is.
    {% endcomment %}
    {% verbatim %}
<script id="passwordRowTpl" type="text/html">// <![CDATA[
<td>
            <a href="{{ url }}" target="_blank">
                {{ title }}
            </a></td>


<td>{{ username }}</td>



<td class="password">{{ password }}</td>


<td>{{ notes }}</td>


<td>
            <a href="#" class="edit" title="Edit this entry"><i class="icon-pencil"></i></a>
            <a href="#" class="destroy" title="Delete this entry"><i class="icon-remove"></i></a></td>

    
// ]]></script>
    {% endverbatim %}
{% endblock %}

Since we want to include a form in the template, we need to create a Django view for this page so we can include it.

Edit `apps/passwords/url.py` as follows:

from django.conf.urls.defaults import patterns, url

from models import Password

urlpatterns = patterns('apps.passwords.views',
    url(r'^$', 'password_list', name='password_list'),
)

And create a simple view in `apps/passwords/view.py`:

from django.shortcuts import render_to_response
from django.template import RequestContext

from forms import PasswordForm

def password_list(request):
    context = RequestContext(request)
    form = PasswordForm()
    context.update({'form': form})
    return render_to_response('passwords/password_list.html', context)

Now we need to create the form – just a standard model form will do. Create `apps/passwords/forms.py` and add the following:

from django.forms import ModelForm

from models import Password

class PasswordForm(ModelForm):
    class Meta:
        model = Password

Now we can create our application using backbone.js. Update `staticfiles/js/passwords.js` to contain the following:

// load the following using JQuery's document ready function
$(function(){

    // Password model
    var Password = Backbone.Model.extend({
        remove: function() {
            this.destroy();
        },

        validate: function(attrs) {
            if (attrs.title.length == 0 || attrs.password.length == 0)
            {
                return "Please enter a title and a password";
            }

            if (attrs.url)
            {
                var re = /^(http[s]?:\/\/){0,1}(www\.){0,1}[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,5}[\.]{0,1}/;
                if (!re.test(attrs.url))
                {
                    return "Please enter a valid URL";
                }
            }
        }
    });

    // set up the view for a password
    var PasswordView = Backbone.View.extend({
        tagName: 'tr',

        events: {
            "click a.edit" : "editPassword",
            "click a.destroy" : "remove"
        },

        editPassword: function(event) {
            event.preventDefault();
            event.stopImmediatePropagation();
            // call back up to the main app passing the current model for it
            // to allow a user to update the details
            this.options.app.editPassword(this.model);
        },

        remove: function(event) {
            event.stopImmediatePropagation();
            event.preventDefault();
            if (confirm("Are you sure you want to delete this entry?"))
            {
                this.model.remove();
            }
        },

        render: function () {
            // template with ICanHaz.js (ich)
            $(this.el).html(ich.passwordRowTpl(this.model.toJSON()));
            return this;
        }

    });

    // define the collection of passwords
    var PasswordCollection = Backbone.Collection.extend({
        model: Password,
        url: '/api/1.0/passwords/',

        // maintain ordering by password title
        comparator: function(obj1, obj2) {
            return obj1.get('title').localeCompare(obj2.get('title'));
        }
    });

    /**
     * Manages the list of passwords and related data. Events are only for
     * child nodes of the generated element.
     */
    var PasswordListView = Backbone.View.extend({
        tagName: 'tbody',

        /**
         * Constructor. Takes a reference to the parent view so we can invoke
         * methods on it.
         */
        initialize: function(options) {
            // instantiate a password collection
            this.passwords = new PasswordCollection();

            this.passwords.bind('all', this.render, this);
            this.passwords.fetch();
        },

        addOne: function(password) {
            // pass a reference to the main application into the password view
            // so it can call methods on it
            this.$el.append(new PasswordView({model: password, app: this.options.app}).render().el);
            return this;
        },

        addNew: function(password) {
            this.passwords.create(password);
            return this;
        },

        updatePassword: function(passwordData) {
            var password = this.passwords.get(passwordData.id);
            if (_.isObject(password))
            {
                // iterate through all the data in passwordData, setting it
                // to the password model
                for (var key in passwordData)
                {
                    // ignore the ID attribute
                    if (key != 'id')
                    {
                        password.set(key, passwordData[key]);
                    }
                }

                // persist the change
                password.save();
                this.passwords.sort();
            }
        },

        render: function() {
            this.$el.html('');
            this.passwords.each(this.addOne, this);
            return this;
        }
    });

    /**
     * View for the overall application. We need this because backbone can only
     * bind events for children of 'el'.
     *
     * In our template our modal is inside #app, so this class handles
     * interaction at the application level rather than strictly with a
     * collection of Passwords (that's the job of the PasswordListView).
     */
    var AppView = Backbone.View.extend({
        el: '#app',
        events: {
            "click #passwordForm :submit": "handleModal",
            "keydown #passwordForm": "handleModalOnEnter",
            "hidden #passwordModal": "prepareForm"
        },

        initialize: function() {
            this.passwordList = new PasswordListView({app: this});
        },

        render: function() {
            this.$el.find('table').append(this.passwordList.render().el);
        },

        /**
         * Allows users to update an existing password
         *
         * @param Password password: A Password Model of the password to edit.
         */
        editPassword: function(password) {
            this.prepareForm(password.toJSON());
            // store the password ID as data on the modal itself
            $('#passwordModal').data('passwordId', password.get('id'));
            $('#passwordModal').modal('show');
        },

        /**
         * Sets up the password form.
         *
         * @param object passwordData: An object containing data to use for the
         * form values. Any fields not present will be set to defaults.
         */
        prepareForm: function(passwordData) {
            passwordData = passwordData || {};

            var data = {
                'title': '',
                'username': '',
                'password': '',
                'url': '',
                'notes': ''
            };

            $.extend(data, passwordData);

            var form = $('#passwordForm');
            $(form).find('#id_title').val(data.title);
            $(form).find('#id_username').val(data.username);
            $(form).find('#id_password').val(data.password);
            $(form).find('#id_url').val(data.url);
            $(form).find('#id_notes').val(data.notes);

            // clear any previous references to passwordId in case the user
            // clicked the cancel button
            $('#passwordModal').data('passwordId', '');
        },

        handleModal: function(event) {
            event.preventDefault();
            event.stopImmediatePropagation();
            var form = $('#passwordForm');

            var passwordData = {
                title: $(form).find('#id_title').val(),
                username: $(form).find('#id_username').val(),
                password: $(form).find('#id_password').val(),
                url: $(form).find('#id_url').val(),
                notes: $(form).find('#id_notes').val()
            };

            if ($('#passwordModal').data('passwordId'))
            {
                passwordData.id = $('#passwordModal').data('passwordId');
                this.passwordList.updatePassword(passwordData);
            }
            else
            {
                // add or update the password
                this.passwordList.addNew(passwordData);
            }

            // hide the modal
            $('#passwordModal').modal('hide');

            return this;
        },

        handleModalOnEnter: function(event) {
            // process the modal if the user pressed the ENTER key
            if (event.keyCode == 13)
            {
                return this.handleModal(event);
            }
        }
    });

    var app = new AppView();
    app.render();
});

Explanation of the backbone.js code

We’ve added validation to the model although we don’t currently display the error messages to the user. We’ll address this at a future stage.

We’ve also added a method that allows us to delete instances.

The PasswordView listens to events for the  ‘actions’ we added to the password template and allows objects to be edited and deleted.

We’ve added a comparator to the PasswordCollection so it stays nicely ordered by password title.

The PasswordListView handles updates to passwords as well as adding new ones.

Finally, the AppView listens to events related to submitting the password form and resetting the form when the modal is closed.

When passwords are edited, we keep track of which password is being edited by setting a data attribute on the modal itself with the ID of the model to edit. This isn’t set if we need to add a new password. Every time the modal is hidden, we reset the form and remove this ID data attribute in case users cancel the modal.

The modal is displayed and hidden thanks to Twitter Bootstrap purely due to classes and data attributes in the HTML.

One final tweak

Run the server with `./manage.py runserver` and everything should work – except updates. Bootstrap submits the complete model, but in our Django model we’ve defined several fields as uneditable. So the final thing we need to do is to edit `apps/passwords/resources.py` and make it drop the uneditable fields (created_at, updated_at and id) before validating the form, otherwise Django REST framework will complain that extra fields have been submitted. Edit the file so it contains the following:

from djangorestframework.resources import ModelResource
from django.core.urlresolvers import reverse

from models import Password

class PasswordResource(ModelResource):
    model = Password
    # by default, django rest framework won't return the ID - backbone.js
    # needs it though, so don't exclude it
    exclude = None
    ordering = ('-title',)
    # django rest framework will overwrite our 'url' attribute with its own
    # that points to the resource, so we need to provide an alternative.
    include = ('resource_url',)
    ignore_fields = ('created_at', 'updated_at', 'id')

    def url(self, instance):
        """
        Return the instance URL. If we don't specify this, django rest
        framework will return a generated URL to the resource
        """
        return instance.url

    def resource_url(self, instance):
        """
        An alternative to the 'url' attribute django rest framework will
        add to the model.
        """
        return reverse('passwords_api_instance',
                       kwargs={'id': instance.id})

    def validate_request(self, data, files=None):
        """
        Backbone.js will submit all fields in the model back to us, but
        some fields are set as uneditable in our Django model. So we need
        to remove those extra fields before performing validation.
        """
        for key in self.ignore_fields:
            if key in data:
                del data[key]

        return super(PasswordResource, self).validate_request(data, files)

Editing data now works:

Summary

At this point our app supports the following:

  • When we load the page http://localhost:8000/passwords/ backbone.js loads our passwords via the API and renders a table containing the data.
  • We’re able to add new entries with AJAX via our API.
  • We can update entries
  • We can delete entries

What’s not supported:

  • We are validating our backbone.js model on the client side and server side, but if validation fails we don’t inform the user. We should provide them with this feedback.
  • There’s no authentication and passwords aren’t associated with specific users.
  • We need to support sharing passwords between users since that’s the purpose of this app.
  • It’d be nice to be able to put passwords into categories or to tag them.
  • It’d also be nice to mask passwords, and only reveal them when users hover over them.
  • Users’ saved passwords are stored in plain text in the database. This is really bad, but if we encrypt them, how do we decrypt them when they’re shared between users? We’ll solve this later.

In our next iteration we’ll relate passwords with users and add authentication to our API.

A RESTful password locker with Django and backbone.js part 2

25 May

In the first part of this series, we set up Django, created a basic Password model and created a RESTful interface for it. In this post, we’re going to set up backbone.js to load our data from our API.

Get the code

Don’t forget you can browse or clone the code from the github repo.

Javascript dependencies

Download the following dependencies and put them into a subdirectory inside a directory managed by Django’s staticfiles app. I’ve used `contrib/backbone`.

If you don’t have jQuery or Zepto installed, you’ll need one of those too.

Also create a file called `contrib/js/passwords.js` – this is where we’ll actually create our backbone application. Add all of these to your template:

    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

    <!-- backbone -->
    <script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/ICanHaz.min.js"></script>
    <script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/json2.js"></script>
    <script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/underscore-min.js"></script>
    <script type="text/javascript" src="{{ STATIC_URL }}contrib/backbone/backbone-min.js"></script>

    <script type="text/javascript" src="{{ STATIC_URL }}/js/passwords.js"></script>

Now we’re ready to start building the backbone part of the application. My goal for this stage is just to get something up and running where it’s pulling data in via the API.

Open http://localhost:8000/passwords/ to view what we’ve currently got – data loaded via Django. We want to recreate this, but loading data using backbone.

Add the following to `contrib/js/passwords.js`:

// load the following using JQuery's document ready function
$(function(){

    // Password model
    var Password = Backbone.Model.extend({});

    // set up the view for a password
    var PasswordView = Backbone.View.extend({
        render: function () {
            // template with ICanHaz.js (ich)
            this.el = ich.passwordRowTpl(this.model.toJSON());
            return this;
        }
    });

    // define the collection of passwords
    var PasswordCollection = Backbone.Collection.extend({
        model: Password,
        url: '/api/1.0/passwords/'
    });

    // main app
    var AppView = Backbone.View.extend({
        tagName: 'tbody',

        initialize: function() {
            // instantiate a password collection
            this.passwords = new PasswordCollection();
            this.passwords.bind('all', this.render, this);
            this.passwords.fetch();
        },

        render: function () {
            // template with ICanHaz.js (ich)
            this.passwords.each(function (password) {
                $(this.el).append(new PasswordView({model: password}).render().el);
            }, this);

            return this;
        }
    });

    var app = new AppView();
    $('#app').append(app.render().el);
});

There’s not much to it. Backbone.js represents models as key-value objects to which you can add methods. We don’t need anything fancy at this stage, so we just extend the default class. Collections contain the logic for interacting with an endpoint via REST which we’ve already set up. We’ve also created a view that can render individual password objects, and one that renders the collection by delegating to the password object view.

One point worth noting in the above code is that there are no selectors in the backbone classes. This makes them reusable and more robust. The only line that contains a reference to a selector is the very last line which appends the output of the rendering of the whole application to a specific element.

The last thing to do is to create some templates. We’re using ICanHaz which includes Mustache. Update `templates/passwords/password_list.html` so it contains the following:

{% extends "base.html" %}

{% block content %}
    <h1 class="page-header">Passwords</h1>
    <table class="table table-striped" id="app">
        <thead>
          <tr>
            <th>Title</th>
            <th>User name</th>
            <th>Password</th>
            <th>Notes</th>
          </tr>
        </thead>
    </table>

    {% load verbatim %}

    <!-- ICanHaz templates -->
    {% comment %}
    Mustache and django both use {{}} tags for templates, so we need to use
    a custom template tag to output the mustache template exactly as it is.
    {% endcomment %}
    {% verbatim %}
    <script id="passwordRowTpl" type="text/html">
        <tr>
            <td>
                <a href="{{ url }}" target="_blank">
                    {{ title }}
                </a>
            </td>
            <td>{{ username }}</td>
            <td class="password">{{ password }}</td>
            <td>{{ notes }}</td>
        </tr>
    </script>
    {% endverbatim %}
{% endblock %}

Save everything and reload the page and you should see your page loading as before. If you’ve got firebug installed, open it, switch to the Net tab and reload the page to make sure that the data is being loaded remotely.

Summary

Now we’ve got a read-only application using backbone and Django, it’s time to move on and add CRUD support to the app.

A RESTful password locker with Django and backbone.js

25 May

In this series I’m going to show you how to use backbone.js with Django. We’re going to be creating a password locker – a site that will let you keep track of your passwords and share them with colleagues. My development environment is Fedora 16 so shell scripts are in bash.

Disclaimer: The site will evolve and to begin with will be very naive with passwords stored unencrypted in the database. Don’t use it in production 🙂

To implement a RESTful interface in Django, we’ll use Django REST framework which makes creating RESTful interfaces from Django models super easy, and gives you a nice little browser so the APIs are self-describing.

Get the code

You can browse or clone the source code for this application from github. It’s open-sourced under the MIT licence. I’ve also added tags for most of the pages so you can follow along with the tutorial if you wish.

Set up

Let’s set up the django installation quickly. I cloned my standard foundational Django project and tweaked it. It’s just a basic Django package but with a few fabric scripts that make building the project easy. It also includes user registration and authentication which we’ll need later as well as Twitter bootstrap.

You can check out my code, or install Django yourself. Once you’re set up, create a ‘passwords’ application inside an ‘apps’ directory with `mkdir apps/passwords && manage.py startapp passwords apps/passwords` – I do this so code is better namespaced instead of having application packages and other packages all mixed together:

The model

To begin with we’ll create a simple model without any user authentication. Add the following to apps/passwords/models.py:

from django.db import models

class Password(models.Model):
    """
    Represents a username and password together with several other fields
    """
    title = models.CharField(max_length=200)
    username = models.CharField(max_length=200,
        blank=True)
    password = models.CharField(max_length=200)
    url = models.URLField(max_length=500,
        blank=True,
        verbose_name='Site URL')
    notes = models.TextField(
        max_length=500,
        blank=True,
        help_text='Any extra notes')
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)

    def __unicode__(self):
        return self.title

Run `./manage.py syncdb` to create your model in your database. We’ll use South to manage migrations, so convert your new password app to south with `./manage.py convert_to_south passwords`.

Now we can start the server with `./manage.py runserver`.

Now we need to create a resource for this model so Django REST framework knows how to serve it up. This is pretty simple when using a basic model. Create apps/passwords/resources.py and add the following:

from djangorestframework.resources import ModelResource
from django.core.urlresolvers import reverse
from models import Password

class PasswordResource(ModelResource):
    model = Password
    # by default, django rest framework won't return the ID - backbone.js
    # needs it though, so don't exclude it
    exclude = None
    ordering = ('-created_at',)
    # django rest framework will overwrite our 'url' attribute with its own
    # that points to the resource, so we need to provide an alternative.
    include = ('resource_url',)

    def url(self, instance):
        """
        Return the instance URL. If we don't specify this, django rest
        framework will return a generated URL to the resource
        """
        return instance.url

    def resource_url(self, instance):
        """
        An alternative to the 'url' attribute django rest framework will
        add to the model.
        """
        return reverse('passwords_api_instance',
                       kwargs={'id': instance.id})

Finally, we need to set up some URLs. To namespace all the APIs together, create an API app with `mkdir apps/api && ./manage.py startapp api apps/api` and then wire up Django REST framework by editing apps/api/urls.py:

from django.conf.urls.defaults import patterns, url

from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from apps.passwords.resources import PasswordResource

my_model_list = ListOrCreateModelView.as_view(resource=PasswordResource)
my_model_instance = InstanceModelView.as_view(resource=PasswordResource)

urlpatterns = patterns('',
    url(r'^passwords/$', my_model_list, name='passwords_api_root'),
    url(r'^passwords/(?P<id>[0-9]+)/$', my_model_instance, name='passwords_api_instance'),
)

Also add the following to apps/passwords/urls.py to use Django’s class-based generic views to create a list view of passwords:

from django.conf.urls.defaults import patterns, url
from django.views.generic import ListView

from models import Password

urlpatterns = patterns('',
    url(r'^$', ListView.as_view(model=Password), name='password_list'),
)

Finally we need to include these two url.py files into the main urls.py file in your project root directory. Add the following to it inside your urlpatterns:

    url(r'^passwords/', include('apps.passwords.urls')),
    url(r'^api/1.0/', include('apps.api.urls')),

Now, we’ve got a nice browser for the API available at http://localhost:8000/api/1.0/passwords/. Open it up and check it out, and add some entries.

That’s really cool. Not only has it saved us the repetitive effort of creating a REST interface ourselves, but we can get on and populate it without using any browser extensions. It’s also validating our input using a default model form for our model, so for example it’s ensuring that input in the site URL field validates as a URL.

Finally, before we can browse the list of passwords we need to create a template in templates/passwords/password_list.html so the generic view can render the list:

{% extends "base.html" %}

{% block content %}
    <h1 class="page-header">Passwords</h1>
    <table class="table table-striped">
        <thead>
          <tr>
            <th>Title</th>
            <th>User name</th>
            <th>Password</th>
            <th>Notes</th>
          </tr>
        </thead>
        <tbody>
        {% for password in object_list %}
        <tr>
            <td>{% if password.site_url %}
                <a href="{{ password.site_url }}" target="_blank">
                    {{ password.title }}
                </a>
                {% else %}
                    {{ password.title }}
                {% endif %}
            </td>
            <td>{{ password.username }}</td>
            <td>{{ password.password }}</td>
            <td>{{ password.notes }}</td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

Now, you can browse your passwords at http://localhost:8000/passwords/

Now we’ve got some data in the database, have a RESTful interface to access it and have a list of passwords on the front end, we’re ready to start ajaxing it with backbone.js. That’s in part 2.