CS142 Project #5: Forms, Sessions, and Validation

In this project you will extend your work on Project #4 by adding forms for logging in, commenting on existing photos, and uploading new photos. In order to implement these new features you will need to use the Rails facilities for sessions and validation. To get started, copy the directory tree for Project #4 to a new directory named project5. Do all of your work for this project in the new directory.

Problem 1: Simple Login (10 points)

Write a database migration that will add a new attribute login to the User model. This attribute is a string containing the identifier the user will type when logging in (their "login name"). Include code in your migration to initialize the login attribute for each existing user to the users's last name, converted to lower case.

Create a new controller that implements 3 new URLs:

In addition to implementing these URLs, include support for login/logout in the standard layout used for all of your application's pages. If there is no user logged in, the banner at the top of each page should include a small "Login" link to the login page. If there is a user logged in, the banner should include a small message "Hi Alice! Logout", where "Alice" is the first name of the logged-in user and "Logout" is a link to the logout page.

Problem 2: New Comments (10 points)

Once you have implemented user login, the next step is to implement a form for adding comments to existing photos. Implement a URL /pics/comment/id that displays a form where a user can add a comment for the photo whose primary key is id. You should also display the photo on this page so the user can see it while he/she is typing the comment. The form should post to the URL /pics/post_comment/id; your implementation for this URL should create a new comment in the database using the Rails models. The comment must include the identifier of the logged in user and the time when the comment was created. Make sure that new comments can be viewed in the same way as pre-existing comments.

Once you've implemented the form for new comments, modify the page /pics/user/id to display a "New Comment" link next to each photo, which will go to the new-comment form for that photo.

Your implementation must handle the following errors:

Problem 3: Photo Uploading (10 points)

Allow users to add new photos. To do this, implement a URL /pics/photo, which displays a form where the user can select a photo file for upload. The form should post to the URL /pics/post_photo, which copies the incoming photo data to a file in the directory project5/public/images and creates a new record in the database containing the name of the photo file, the creation time, and the identifier of the user. Also, add a "New Photo" link at an appropriate place in one of your existing pages, which users can click to go to the photo upload form.

Your implementation should check to make sure that a user is logged in and prevent photo uploading if not.

Problem 4: Registration and Passwords (10 points)

Enhance the login mechanism with support for new-user registration and passwords.

Your password mechanism must implement salting, which is a way of storing passwords securely. The salting mechanism is described in the next few paragraphs. One not-very-secure approach would be to store passwords directly in the database. However, if someone is able to read the database (for example, a rogue system administrator) they can easily retrieve all of the passwords for all users.

A better approach is to apply a message digest function such as SHA-1 to each password, and store only the message digest in the database. SHA-1 takes a string such as a password as input and produces a 40-character string of hex digits (called a message digest) as output. The output has two interesting properties: first, the digest provides a unique signature for the input string (there is no known way to produce two different strings with the same digest); second, given a message digest, there is no known way to produce a string that will generate that digest. When a user sets their password, you must invoke Digest::SHA1.hexdigest to generate the SHA-1 digest corresponding to that password, and store only the digest in the database; once this is done you can discard the password. When a user enters a password to login, invoke Digest::SHA1.hexdigest to compute the digest, and compare that digest to what is stored in the database. With this approach, you can make sure that a user types the correct password when logging in, but if someone reads the digests from the database they cannot use that information to log in.

However, the approach of the previous paragraph has one remaining flaw. Suppose an attacker gets a copy of the database containing the digests. Since the SHA-1 function is public, they can employ a fast dictionary attack to guess common passwords. To do this, the attacker takes each word from a dictionary and computes its digest using SHA-1. Then the attacker checks each digest in the database against the digests in the dictionary (this can be done very quickly by putting all of the dictionary digests in a hash table). If any user has chosen a simple dictionary word as their password, the attacker can guess it quickly.

In order to make dictionary attacks more difficult, you must use password salting. When a user sets their password, compute a random number and concatenate it with the password before computing the SHA-1 digest (the rand method will return a suitable random number). The random number is called a salt. Then store both the salt and the digest in the database. When checking passwords during login, retrieve the salt from the database, concatenate it to the password typed by the user, and compute the digest of this string for comparison with the digest in the database. With this approach an attacker who has gained access to the login database cannot use the simple dictionary attack described above; the digest of a dictionary word would need to include the salt for a particular account, which means that the attacker would need to recompute all of the dictionary digests for every distinct account in the database. This makes dictionary attacks more expensive.

This salting approach requires one small twist in your code. The password value that is posted in forms never gets stored in the database (so it is not specified in migrations, for example). In order to handle the password correctly, you must define it as a virtual attribute of the User model. This simply means that you must define by hand the two standard accessor methods password and password= in the user model (if you used a migration to create those accessors, they would automatically read and write from the database record; your methods will not do that). When password= is invoked, it must compute the salt and the password digest and set the corresponding attributes of the User model (which are part of the database).

Style Points (5 points)

These points will be awarded if your problem solutions have proper MVC decomposition, follow the Rails conventions, and generate valid XHTML. In addition, your code and templates must be clean and readable, and your Web pages must be at least "reasonably nice" in appearance and convenience.

Hints

Deliverables

Use the standard class submission mechanism to submit the entire application (everything in the project5 directory). Please indicate in a README file whether you developed on Windows or a Macintosh (we may need this information in order to test your solution). Note: If you have added more than a couple of new images, please clean up your data before submitting, as described above. Be sure to test your project one more time after resetting the database, just to make sure everything is still OK.