Introduction

This is the third tutorial in my series of building applications using the Streamlined Framework. These are simple step by step procedures that are designed to show you (and remind me!) how to make this all work day in and day out. This tutorial adds authentication and authorization into the mix, showing you how to include this vital pieces into your application. We will be using the following two frameworks:

http://technoweenie.stikipad.com/plugins/show/Acts+as+Authenticated

and

http://www.writertopia.com/developers/authorization

Both of which seem to be stable and well written, but admittedly both have poor tutorials explaining how to use them effectively, especially for those of us who are NOT programmers by day, but just hacking this stuff for the fun of it.

NOTE\'''

As of October 1st, 2007, this is a bare bones tutorial which still needs work, especially since I haven't gone through and verified all my steps yet. Please feel free to make changes, but please also let me know you have made them so I can learn from them and incorporate them into other tutorials.

Thanks, John john@…

Requirements

I'm going to assume you did the first two tutorials and I'll just start by copying over the SimpleLetterAuth tutorial in it's entirety to use as the base to build upon.

> cp -rp SimpleLetterMenu SimpleLetterAuth

> cd SimpleLetterAuth

Now we're going to start installing our plugins that we need, starting with the Acts_As_Authenticated.

> script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_authenticated

> script/generate authenticated user account

> rake db:migrate

Add to the Letter controller (app/controllers/letter_controller.rb)

  before_filter :login_required

Add to the Person controller (app/controllers/person_controller.rb)

  before_filter :login_required

Edit app/controllers/account_controler.rb and remove the top two lines:

  # Be sure to include AuthenticationSystem in Application Controller instead
  include AuthenticatedSystem

Then edit app/controllers/application.rb to have:

    # Filters added to this controller apply to all controllers in the application.
    # Likewise, all the methods added will be available for all controllers.

    class ApplicationController < ActionController::Base
      include AuthenticatedSystem
      # Pick a unique cookie name to distinguish our session data from others'
      session :session_key => '_SimpleLetter_session_id'
    end
> script/server
  • login and create an account.
    • friggin ugly login screen.
  • now, how do you logout?
  • roles?
  • ok, kill the server and let's starting hacking

Now we want to change the default silly installed index.rhtml form to actually have some useful info, such as who we are logged in as, and the ability to logout:

> edit app/views/account/index.rhtml
<% if not logged_in? %>
  <%= form_tag :controller => 'account', :action => 'login' %>
    <p><label for="login">Login</label><br/>
    <%= text_field_tag 'login' %></p>
    <p><label for="password">Password</label><br/>
    <%= password_field_tag 'password' %></p>
    <p><%= submit_tag 'Log in' %></p>
  <%= end_form_tag %>
  <%= link_to("Register for an Account", :controller => 'account', :action => 'signup') %>
  <%= link_to("Forgot your password?", :controller => 'account', :action => 'forgot_password') %>
<% else %>             
  <%= form_tag :controller => 'account', :action => 'logout' %>
    Logged in as: <br/>
    <%= current_user.login %> <br/>
    <%= submit_tag 'Log out' %></p>
  <%= end_form_tag %>
<% end %>

Now we can login/logout properly. But how do we wrap this into the streamlined framework and CSS stuff? That's the tricky thing...

So once we're authenticated, we need authorization for various parts of our application. So now we get and install the authorization plugin to handle this aspect.

But first let's setup a default route to our 'letter' controller, which is allowed access by all users. This will serve to show us how to tie in authorization and authentication into an app nicely, and will also redirect our web site from it's base URL of http://localhost:3000/ to our letter controller.

> edit config/routes.rb

map.connect , :controller => "letter"

> rm public/index.html

And of course we want to have different roles for different users.

The 'admin' role can do anything, such as adding users, etc. The 'editor' role can edit Letters The 'reader' role can have Letters emailed to themselves. The 'guest' can only search for Letters from the main page.

We need to get the authorization plugin from http://rubyforge.net

> script/plugin install http://svn.writertopia.com/svn/plugins/authorization

> edit config/environment.rb
  • add to the end of the file
        AUTHORIZATION_MIXIN = 'hardwired'

edit app/models/letter.rb, person.rb and add the following at the

top of the class definition. Note, you can't just add this to your app/models/application.rb file, it breaks for some reason.

    acts_as_authorizable

edit app/models/user.rb

  • add these two lines at the top, just under class definition:
    acts_as_authorized_user
    acts_as_authorizable

Now where the hell do we pre-define the various user roles? I guess

we could go back and setup a roles table and pre-populate it with a migration? Maybe that's easiest...

> script/generate role_model role
> script/generate migration setup_base_roles
> script/generate migration setup_base_user
> edit config/environment.rb and comment out the AUTHORIZATION_MIXIN line:

> edit db/migrate/005_setup_base_roles.rb

    class SetupBaseRoles < ActiveRecord::Migration
      def self.up
        # Purge any/all roles...
        down

        # setup our roles
        role = Role.create(:name => "admin")
        role.save!

        role = Role.create(:name => "editor")
        role.save!

        role = Role.create(:name => "reader")
        role.save!

        role = Role.create(:name => "guest")
        role.save!
      end

      def self.down
      end
    end

> rake db:migrate
> script/console
  • generate a user account by hand using the console for the initial testing.
    >> u=User.create(:login => "admin", :password => "admin",
                     :password_confirmation => "admin", :email => "admin")
  • note the values for salt & crypted_password
  • exit the console
> edit db/migrate/006_setup_base_user.rb


   class SetupBaseUser < ActiveRecord::Migration
     def self.up
       # Purge any and all users by default
       down

       # Setup the 'admin' user with a default password of 'admin', and 
       # a role of 'admin'.  This is the user who can CRUD other users.

       user = User.create(:login => "admin",
                          :email => "admin",
                          :password => "admin",
                          :password_confirmation => "admin")
       user.save!
     end

     def self.down
     end
   end

Now let's do some playing to better understand how the Authorizable plugin actually works. The docs are terse and assume you're a Ruby god already, which I most certainly am not.

> script/console

  >> u=User.create(:login => "foo", :email => "foo", :password =>
                            "foofoo", :password_confirmation => "foofoo")
  ...
  >> u.is_admin?
  false
  >> u.is_admin
  >> u.is_admin?
  true
  >> u.is_not_admin
  >> i.is_admin?
  false

  >> u.is_gargoyle
  ...
  >> u.is_gargoyle?
  true

This is pretty neat how you can assign roles to a user and then check them.

> edit app/views/account/index.rhtml

<% if not logged_in? %>
  <%= form_tag :controller => 'account', :action => 'login' %>
    <p><label for="login">Login</label><br/>
    <%= text_field_tag 'login' %></p>
    <p><label for="password">Password</label><br/>
    <%= password_field_tag 'password' %></p>
    <p><%= submit_tag 'Log in' %></p>
  <%= end_form_tag %>
  <%= link_to("Register for an Account", :controller => 'account',
:action => 'signup') %>
  <%= link_to("Forgot your password?", :controller => 'account',
:action => 'forgot_password') %>
<% else %>             
  <%= form_tag :controller => 'account', :action => 'logout' %>
    Logged in as: <br/>
    <%= current_user.login %> <br/>
        <% who = User.find_by_login(current_user.login) %>
        <% if who.is_admin? %>
          You have admin privs!<br>
    <% end %>
    <%= submit_tag 'Log out' %></p>
  <%= end_form_tag %>
<% end %>

script/server

  • http://localhost:3000/account
    • login as admin as a test...
      • logout, then register and login again, note how you don't have admin privs now.
      • logout

- Now we want to add in a menu header at the top to either

login/logout for the default streamlined stuff.

edit app/helpers/application_helper.rb

# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  def streamlined_top_menus
    [
      [ "New Letter", {:action=>"new"}],
      if logged_in?
        [ "Logout", {:controller => 'account', :action => 'logout' } ]
      else
        ["Login", {:controller => 'account', :action => 'login' } ]
      end
    ]
  end
end

But now we want to change how we filter accounts, since we really just want to assign people by default to the guest account, which only has privs to see records. You need to login to get more privs.

edit the Letter controller (app/controllers/letter_controller.rb) to modify the before_filter, since we now want to be able to allow access for guests who don't need to login at all. But to get more privs, you need to login.

edit app/controllers/letter_controller.rb

  before_filter :login_required, :except => [:index]

Now we want to put in some more dynamic menus, depending on whether the user has certain privs or not. So we got back and edit:

edit app/helpers/application_helper.rb

And we'll change the previous streamlined_top_menus to look more like this:

# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  def streamlined_top_menus
    menu = [ "New Letter", {:action=>"new"} ]
    
    if logged_in?
      if current_user.has_role? 'admin'
        menu << [ "Manage Users", {:controller => 'users', :action => 'list'} ]
      end
      
      menu << [ "Logout", {:controller => 'account', :action => 'logout' } ]
    else
      menu << [ "Login", {:controller => 'account', :action => 'login' } ]
    end
    return menu
  end
end

I find this easier and neater to parse, and means it's also easier to keep track of trailing commas, since I tend to still use them like I do in perl scripts, since trailing commands in array definitions don't count, while in Ruby they're an error.

Now we'd also like to be able to edit users and their roles, so we'll now generate a controller and put it under streamlined controller.

> script/generate controller users
> edit app/controllers/users_controller.rb
class UsersController < ApplicationController
  layout "streamlined"
  acts_as_streamlined

  before_filter :login_required
end

script/server

We also want to make it so that only those users who have admin privs can access this page at all.

There's another couple of problems we need to address. When you logout, you're taken back by default to the login screen. And if you are at that screen and login, you also are left at the default Acts As Authenticated screen. We don't want that, we'd instead like to go back to the index of the Letters, so that users can view letters no matter what permissions they have.

edit app/controllers/account_controller.rb

We need to re-define the index from looking like this:

        def index
          redirect_to(:action => 'signup') unless logged_in? || User.count > 0
        end

To instead look like this:

        def index
          redirect_to(:controller => '/letter', :action => 'index') unless 
           logged_in? || User.count > 0
        end

And we modify the login method from:

      redirect_back_or_default(:controller => '/account', :action => 'index')

to:

      redirect_back_or_default(:controller => '/letter', :action => 'index')

We also need to lock down the streamlined_side_menus as defined in app/helpers/letter_helper.rb, and since we want to make this global, let's move this function to the app/helpers/application_helper.rb instead.

edit app/helpers/letter_helper.rb

  • delete the def streamlined_side_menus

edit app/helpers/application_helper.rb

  • add in the deleted streamlined_side_menus from letter_helper.rb and change it to look like this:

The next thing is that when editing users, we don't want to see or even be able to touch the encrypted password, or the Salt in the views. So we need to modify the following:

> edit app/streamlined/users_ui.rb
        Streamlined.ui_for(User) do
          user_columns :login, :email, :roles, :created_at, :updated_at
        end

Note how this is different from the person_ui.rb file, which uses the older (supported for how long?) Streamlined Framework UI customization via inheritance.

The issue we see here is that the :roles are just returning the number of the role the user has, which isn't very helpful. We'd like to instead link through to the roles table, like this:

Streamlined.ui_for(User) do
  user_columns :login, 
               :email, 
               :roles, { 
                  :show_view => 
                    [ :list, { :fields => [:name]} ]
                       }, 
               :created_at, 
               :updated_at
end

This makes it much nicer, and it's now even possible to edit a user's roles quickly and easily. It's not amazingly pretty, but it's decent. Note, see the Wiki page on #Relationships for more details.

TODO

- Cleanup the login page, or use a JavaScript? login popup.

- Keep writing and testing this tutorial

- Put in more on how to keep the Admin user from being deleted by accident

- Add in editing of user passwords (crib from another tutorial I found).

- Action Mailer setup should be covered, or maybe punted for now?