Classifying Users

Most applications have at least two categories of users—administrators and ordinary users. Many applications have finer-grained permissions, but this is a good place to start. Code for this is available in ch14/students008. The first step toward creating an extra category of users is to create a migration with a suitable name:

script/generate migration AddAdminFlagToUsers

In the newly created migration, under db/migrate, change the migration file with the name ending in add_admin_flag_to_users so that it looks like Example 14-4.

Example 14-4. A migration for adding a boolean administration flag to the users table

class AddAdminFlagToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :admin, :boolean, :default => false, :null => false
  end

  def self.down
    remove_column :users, :admin
  end
end

This adds one column to the users table, a boolean named admin. It defaults to false—most users will not be administrators—and can’t have a null value. Run rake db:migrate to run the migration and add the column.

Next, there’s a small challenge. The system really should have at least one administrator, but building a complete user administration facility means building another whole set of forms and controller methods. In addition, it probably makes sense to be able to designate an initial user as administrator before locking all nonadministrators out of the user management system.

For this demonstration, there’s an easy answer—the Rails console. In addition to its use in debugging, demonstrated in Chapter 11, it’s also a great tool for making small direct modifications to databases. Run script/console and at the prompt, enter:

User.find(:all)

Rails will report a list of user records. You need to find the id of the one you want to give administrative privileges and assign them with something like:

User.find(1).update_attribute('admin', true)

This will find the user with the id value of 1—probably the first user created—and update the admin attribute for that record to true. You can, of course, pick any id value you need for this. If you’re feeling paranoid, you can call User.find(:all) again to make sure the change is reflected in the record, or just exit the console.

Now that users have an administrative flag, and now that there is at least one user with the admin flag set to true, it’s time to make that flag matter. One of the nicest features of restful_authentication is that you can centralize many authentication tasks in the app/controllers/application.rb file. Doing that generally requires overriding the authorized? method, which is defined in lib/authenticated_system.rb quite simply:

def authorized?
  logged_in?
end

In the examples earlier in this chapter, this was the method that kept out users who hadn’t created accounts, thanks to the before_filter :login_required at the top of most controllers. (login_required is also defined in lib/authenticated_system.rb.)

It’s time to set this to something stronger. An easy approach would be to add a condition, moving from logged_in? to logged_in? && current_user.admin?, locking out everyone who isn’t an administrator. (It’s always best to check first that the user is logged in—otherwise, current_user will be null, producing errors when you try to call methods on it.)

Most authentication systems don’t limit content to administrators while giving users only the privilege of logging in to a useless system, however. More typically, ordinary users have read access while more privileged users, like administrators, can make changes. In a RESTful application environment, this can be easily implemented by letting users GET anything they want, while only privileged users can use POST, PUT, or DELETE. Rails has a request.get? method that returns true for GET and false for everything else. That makes it possible to write “must be logged and either GETting something or having admin privileges” as:

logged_in? && (request.get? || current_user.admin?)

First, this checks to see if the user is logged in. If not, Ruby won’t bother evaluating the righthand side of the && expression, the part in parentheses, and Rails will reject the request. If the user is logged in, Ruby next evaluates whether the incoming request was a GET. If it was, great—there’s no need to evaluate the other side of the || expression, and the user is granted access. If it wasn’t, then Ruby checks to see whether the current_user object has its admin flag set. If yes, then permission is granted, because admins can make any kind of HTTP call they want. If not, then permission is denied, because some evil user is trying to make changes they’re not allowed to make.

Overriding the method call should look like:

protected
   def authorized?
      logged_in? && (request.get? || current_user.admin?)
   end

The protected label lets objects of classes that descend from ApplicationController (all your controllers) call the authorized? method, but prohibits other objects from calling it. This can go near the bottom of the app/controllers/application.rb file, just above the final end statement. Before closing the file, it also makes sense to uncomment a line just above this, removing the # from:

# filter_parameter_logging :password

Chapter 11 explained filter_parameter_logging’s blocking of certain parameters from the logs, and password information is a good choice for blocking.

At this point you can save the files and call script/server. If you log in with the account you set to be administrator (or don’t log in at all), the application works just like it did before. The more interesting case, of course, is to create a new user with fewer permissions, and examine the new user’s experience. Figures 14-8 to 14-11 show how a newly created user steps through the application.

A new user, freshly welcomed

Figure 14-8. A new user, freshly welcomed

The students page works as planned for the new user

Figure 14-9. The students page works as planned for the new user

The editing page also works for the new user

Figure 14-10. The editing page also works for the new user

The new user’s attempt to edit a document is finally blocked and they face a new login screen

Figure 14-11. The new user’s attempt to edit a document is finally blocked and they face a new login screen

The beginning of this user’s journey goes as planned. Zoid gets a screen name (14-8), then visits the students page (14-9) where all the data is visible. Feeling curious, though, Zoid tries the Edit link for one of the students and is rewarded with the editing page (14-10). It’s only when Zoid tries to submit the edits that the authorization? method decides it’s time to lock him out (14-11).

From a strictly data security point of view, this is fine. The user can’t change data without proper authorization. From the user’s point of view, though, the interface is lying, offering opportunities that look like they should be available. As an interface issue, this is a problem with the views and can be solved by checking whether the current user is an administrator before presenting those options. This needs to be checked in each of the index.html.erb files for students, courses, and awards—a little repetition is necessary. The changes, though, are just a pair of if statements, highlighted in Example 14-5.

Example 14-5. Removing inappropriate choices from a user with limited powers

<h1>Listing students</h1>

<table>
  <tr>
    <th>Given name</th>
    <th>Middle name</th>
    <th>Family name</th>
    <th>DOB</th>
    <th>GPA</th>
    <th>Start date</th>
    <th>Courses</th>
    <th>Awards</th>
  </tr>

<% for student in @students %>
  <tr>
    <td><%=h student.given_name %></td>
    <td><%=h student.middle_name %></td>
    <td><%=h student.family_name %></td>
    <td><%=h student.date_of_birth %></td>
    <td><%=h student.grade_point_average %></td>
    <td><%=h student.start_date %></td>
    <td><%=h student.courses.count %></td>
    <td><%=h student.awards.count %></td>
    <td><%= link_to 'Show', student %></td>
    <td><%= link_to 'Awards', student_awards_path(student) %></td>
<% if current_user.admin? %>
    <td><%= link_to 'Edit', edit_student_path(student) %></td>
    <td><%= link_to 'Destroy', student, :confirm => 'Are you sure?', :method =>
:delete %></td>
<% end%>
  </tr>
<% end %>
</table>

<br />

<% if current_user.admin? %>
<%= link_to 'New student', new_student_path %>
<% end%>

This just removes the Edit, Destroy, and New options. (The Awards entry moves up a line, above Edit, to reduce the number of if statements needed.) Now, when Zoid logs in and visits the students page, he’ll see Figure 14-12: just the options he’s allowed to use.

A more limited array of options appropriate to an ordinary user

Figure 14-12. A more limited array of options appropriate to an ordinary user

There are a few other features that need the same treatment, like the link to the form for enrolling students in courses from the app/views/students/show.html.erb file. Every reasonably sophisticated application that has moved beyond the basic CRUD interface will have a few of these cases.

Note

It’s convenient to check results by keeping multiple browser windows open, logged into different user accounts. Remember, though, that Rails is tracking authentication status through sessions, which use cookies, that apply to the whole browser and not just a single window.

The easy way to deal with this is to open two different browsers and log in to the application separately in each of those, rather than in two different windows in the same browser.

There’s still one leftover that may be worth addressing, depending on your security needs. The authorization? method has secured the data, and the view no longer shows the user options they can’t really use, but if a user knows the URL for the edit form, it will still open. It’s a GET request, after all. This is a good reason to make sure that these forms don’t display any information that isn’t publicly available through other means. If this is an issue, it may be worth the effort of adding authorization checks to every controller method that could spring a leak.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset