Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thanks for contributing such a great piece of work! I'm trying to figure out how modify this to login and logout via a webservice client. I can't use the regular http-based authentication but I can use RESTful URIs. I have a clue that I'll probably need to stick a respond_to block in the sessions_controller.rb but I'm not sure where - maybe the new action?

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

[I'm a newbie so please let me know if I'm contributing something bad/wrong/not optimal]

First, thanks a bunch for this code, it's been working great and has helped me learn about Rails.

As I was testing this out, it bothered me that when you go to see each user's roles, it would not print out "none" or something to tell you that there were no roles assigned to the user under the "Roles assigned" (see below) for the rendered HTML of a user without a role assigned:

                        Roles for <user>
                        Roles assigned:

                        Roles available:

                             * administrator assign role

If you're picky like me, I wanted a way to tell if a user had any role, and print something out if not.  So here's what I changed/added:

Addition to user.rb model:

def has_any_role?
    roles = Role.find(:all)
    roles.each do |role|
        if self.roles.find_by_rolename(role.rolename) # OR it can call has_role?(role.rolename)
            return true
        end
    end
    return false
end

Additions to roles' index.rhtml (lines: 4, 6,7,8, 11, 13, 14, 15)
<h2>Roles for <%=h @user.login.capitalize %></h2>
 
<h3>Roles assigned:</h3>
<% if @user.has_any_role? %>
    <ul><%= render :partial => 'role', :collection => @user.roles %></ul>
<% else %>
    <ul>None</ul>
<% end %>

<h3>Roles available:</h3>
<% if (@all_roles - @user.roles).length > 0 %>
    <ul><%= render :partial => 'role', :collection => (@all_roles - @user.roles) %></ul>
<% else %>
    <ul>None</ul>
<% end %>


Note: I wish I could color lines inside the 'code' tags of BBCode, it would make seeing what I changed/added much easier.

Please let me know if this is good or is there a better way of doing it.

Cheers

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Maybe I'm missing something, being very new to this myself. But couldn't you do this in your view:

<h2>Roles for <%=h @user.login.capitalize %></h2>
 
<h3>Roles assigned:</h3>
<% if @user.roles.empty? %>
    <ul>None</ul>
<% else %> 
    <ul><%= render :partial => 'role', :collection => @user.roles %></ul>
<% end %>
 
<h3>Roles available:</h3>
<% if (@all_roles - @user.roles).length > 0 %>
    <ul><%= render :partial => 'role', :collection => (@all_roles - @user.roles) %></ul>
<% else %>
    <ul>None</ul>
<% end %>

Then there's no need to make changes to the model.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

blueworld wrote:

Maybe I'm missing something, being very new to this myself. But couldn't you do this in your view:

<h2>Roles for <%=h @user.login.capitalize %></h2>
 
<h3>Roles assigned:</h3>
<% if @user.roles.empty? %>
    <ul>None</ul>
<% else %> 
    <ul><%= render :partial => 'role', :collection => @user.roles %></ul>
<% end %>
 
<h3>Roles available:</h3>
<% if (@all_roles - @user.roles).length > 0 %>
    <ul><%= render :partial => 'role', :collection => (@all_roles - @user.roles) %></ul>
<% else %>
    <ul>None</ul>
<% end %>

Then there's no need to make changes to the model.

Without testing anything I think you COULD do that, however that is far too much code to throw into your view (for my taste).

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

jstad wrote:

Without testing anything I think you COULD do that, however that is far too much code to throw into your view (for my taste).

What would you do to simplify it? It does seem like a lot of conditional code for a view.

All I suggested was to use the built in "empty?" method on the user's roles rather than defining a new method in the model. I don't know about the correct architecture overall, although I would definitely not want to put something in the model that would return "None" when a user had no roles. That's really not something the model should have any knowledge about.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thank you for the beautiful tutorial!

Tests for this tutorial:
I was wondering if anyone had catalogued the changes to test code that this tutorial requires, or if there are any testing recommendations specific to this implementation that anyone wants to share.  Thanks!

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thank you for such a great tutorial. I have questions as I try to modify and have this RESTful login to fit in Rails 1.2.6.

After I generate the migration scripts by :

ruby script/generate scaffold_resource Page title:string body:text
ruby script/generate authenticated user sessions --include-activation
ruby script/generate scaffold_resource Role rolename:string
ruby script/generate model Permission

and run the command:

rake db:migrate

the pages, roles,users are successfully created. The table permissions ends  up with the following error:

rake aborted!
No rhtml, rxml, rjs or delegate template found for signup_notification in C:/Wk_area/current/rest2/app/views/user_mailer

I wonder if I need to make up any template ?

any comment or help is appreciated.

thank you
smile

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Great tut! Thanks.  I have a question relating to the general design approach using your example.  How do you tie up the User model with other models containing user details? For example I'm designing a student information system with the following main models,  School, Employee (teacher or other employed by school), Student, Course and Teacher.  There are other models such as , Registration, Admission, Assessment, Achievement and Grade etc. Since my question is about authorization my focus is on the Teacher and Student models. 

Within the context of a school teachers become employees and students become admissions or registrations. For example in School A a school employee or administrator should have permission only to set up class rosters, list of employees, student registrations etc. for that school only.  Furthermore,  teachers employed by School A should have permission only to create assignments, tests etc. for one or more specific courses that they have responsibility for and finally students should have permission only to view their own test scores ,assignments etc.

Since the users of the application exist in separate models, i.e. Teacher and Student, how do you design the authorization model? Do you still need a separate User model or do you build the authentication and authorization into both Teacher and Student models respectively?

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

archie wrote:

Great tut! Thanks.  I have a question relating to the general design approach using your example.  How do you tie up the User model with other models containing user details? For example I'm designing a student information system with the following main models,  School, Employee (teacher or other employed by school), Student, Course and Teacher.  There are other models such as , Registration, Admission, Assessment, Achievement and Grade etc. Since my question is about authorization my focus is on the Teacher and Student models. 

Within the context of a school teachers become employees and students become admissions or registrations. For example in School A a school employee or administrator should have permission only to set up class rosters, list of employees, student registrations etc. for that school only.  Furthermore,  teachers employed by School A should have permission only to create assignments, tests etc. for one or more specific courses that they have responsibility for and finally students should have permission only to view their own test scores ,assignments etc.

Since the users of the application exist in separate models, i.e. Teacher and Student, how do you design the authorization model? Do you still need a separate User model or do you build the authentication and authorization into both Teacher and Student models respectively?

You can use the current setup and add Teacher & Student to roles or the better approach would be to add a Student / Teacher model and set the permissions to user with a belongs_to relationship. Then just check if @user.teacher or @user.student to see if the user is a teacher or student. Should not need much change at all.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

jstad wrote:

You can use the current setup and add Teacher & Student to roles or the better approach would be to add a Student / Teacher model and set the permissions to user with a belongs_to relationship. Then just check if @user.teacher or @user.student to see if the user is a teacher or student. Should not need much change at all.

I have something similar (Buyers, Sellers, Agents, Appraisers, Offices, etc.) but I'm using a Person model (and an Organization model) with the plans to just use roles along with tags (still working on the design). My thought for using a Person model is that they are all humans with the same attributes (name, DOB, gender, etc) and a person could be more than one (Buyer and Seller). My User model has a person_id so then I won't need buyer_id, seller_id etc.
I'm pretty new so I hope I'm on the right track.

Last edited by jdeppen (2008-02-09 23:45:29)

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Hi everyone. I implemented the authentication system pretty easily.  However, I want to be able to authenticate based on owner of the record being requested for. for example, let's say I have Design model that

belongs_to :designer

and Designer:

has many :designs

I want to be able to add something like this in the Design controller:

before_filter :owner_required, :owner_id => "user_id"

Is this possible?  How do you go about this?  It seems like advanced Rails stuff already sad

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I have this bad habit of making things harder than they should be.  After staring at the screen for an hour, I came up with this solution:

In the controller, put two things:

  before_filter :owner_required, :only => [:edit, :update]

  def owner_required
    @idea = Design.find params[:id]
    permission_denied uness @design.designer == current_user
  end

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

ramon.tayag wrote:

Hi everyone. I implemented the authentication system pretty easily.  However, I want to be able to authenticate based on owner of the record being requested for. for example, let's say I have Design model that

belongs_to :designer

and Designer:

has many :designs

I want to be able to add something like this in the Design controller:

before_filter :owner_required, :owner_id => "user_id"

Is this possible?  How do you go about this?  It seems like advanced Rails stuff already sad

I think you could just do something similar to what was done in the users_controller.rb in app/controllers and skip the before_filter

#This show action only allows users to view their own profile
def show
  @user = current_user
end

So maybe yours would look something like this (untested):
#This show action only allows designers to view their own designs
def show
  @design = current_user.designs(params[:id])
end

In your designs table you could have a designer_id (unless other people besides designers can own designs). You could do something like this in the index action if you wanted the current_user to see only his/her designs.
#This index action allows designers to view an array of only their designs
def index
  @designs = current_user.designs
end

I didn't test it but it should be pretty close; you could always play with it in script/console.

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

First of all, thanks for the tutorial and the code.  It's extremely helpful.

The only problem I'm having is in the Session controller functional test.  I'm able to run all the session controller functional tests fine except for test_should_signin_with_cookie (I renamed login to signin in the test name):

  def test_should_signin_with_cookie
    users(:quentin).remember_me
    @request.cookies["auth_token"] = cookie_for(:quentin)
    get :new
    assert @controller.send(:logged_in?)
  end

When I run it, the test fails with:

  1) Error:
test_should_signin_with_cookie(SessionsControllerTest):
TypeError: can't convert nil into String
    /Users/Jonathan/Documents/Web Projects/Ruby on Rails Projects/edb/lib/authenticated_system.rb:97:in `+'
    /Users/Jonathan/Documents/Web Projects/Ruby on Rails Projects/edb/lib/authenticated_system.rb:97:in `permission_denied'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:131:in `call'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:131:in `custom'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:152:in `call'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:152:in `respond'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:150:in `each'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:150:in `respond'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/mime_responds.rb:107:in `respond_to'
    /Users/Jonathan/Documents/Web Projects/Ruby on Rails Projects/edb/lib/authenticated_system.rb:90:in `permission_denied'
    /Users/Jonathan/Documents/Web Projects/Ruby on Rails Projects/edb/lib/authenticated_system.rb:53:in `not_logged_in_required'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:469:in `send!'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:469:in `call'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:441:in `run'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:716:in `run_before_filters'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:695:in `call_filters'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
    /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/benchmark.rb:293:in `measure'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/rescue.rb:199:in `perform_action_without_caching'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:678:in `perform_action'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache'
    /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/query_cache.rb:8:in `cache'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:677:in `perform_action'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `send'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:524:in `process_without_filters'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/filters.rb:685:in `process_without_session_management_support'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/session_management.rb:123:in `process_without_test'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/test_process.rb:15:in `process'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/test_process.rb:393:in `process'
    /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/test_process.rb:364:in `get'
    test/functional/sessions_controller_test.rb:59:in `test_should_signin_with_cookie'

The error message, "can't convert nil into String" isn't very helpful but the error appears to happen on the "get :new" call.  The auth cookie works fine when I try it in a browser (I'm able to log in and click "remember me," close the browser, come back and be automatically logged back in).

Any ideas?  I'm using Rails 2.0.2.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

okay, so I'm pretty new and probably missing something really obvious... but I would appreciate some help as I can't seem to figure this out..

so when I try to navigate to: http://localhost:3000/users or /signup or /anythingElse I get this error:
-----------
Routing Error

No route matches "/signup" with {:method=>:get}
----------
in lieu of the "pages" stuff I had created a model/controller/view for "jobs" outside of the flow of the tutorial and I can get to that fine.
my route.rb is identical to the tutorial, with the exception of "pages" being replaced with "jobs" in each occurrence (throughout all the code as well).

ideas?

THANKS!

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

polomasta wrote:

okay, so I'm pretty new and probably missing something really obvious... but I would appreciate some help as I can't seem to figure this out..

so when I try to navigate to: http://localhost:3000/users or /signup or /anythingElse I get this error:
-----------
Routing Error

No route matches "/signup" with {:method=>:get}
----------
in lieu of the "pages" stuff I had created a model/controller/view for "jobs" outside of the flow of the tutorial and I can get to that fine.
my route.rb is identical to the tutorial, with the exception of "pages" being replaced with "jobs" in each occurrence (throughout all the code as well).

ideas?

THANKS!

Your route.rb file is incorrect. Check for spelling errors. It cant be anything else.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

class AlreadyActivated < StandardError

I don't get under what circumstances this error would ever be raised....

we have first
user = find_by_activation_code(activation_code)
this requires a user with activation_code otherwise we get
raise ActivationCodeNotFound if !user
but then we have:
user.send(:activate!)
where activate! also deletes the activation_code

in the end this means if i try to activate again (why ever)
i will not make it till
raise AlreadyActivated.new(user) if user.active?
because I'm already stopped before when the user is not found by activation_code due to the deletion of the activation_code by my first try to activate...

do I get something totally wrong or is there indeed no conceivable
situation where AlreadyActivated will be raised?

-------------
  def self.find_and_activate!(activation_code)
    raise ArgumentError if activation_code.nil?
    user = find_by_activation_code(activation_code)
    raise ActivationCodeNotFound if !user
    raise AlreadyActivated.new(user) if user.active?
    user.send(:activate!)
    user
  end
  def activate!
    @activated = true
    self.update_attribute(:activated_at, Time.now.utc)
    self.update_attribute(:activation_code, nil)
  end   
-------------

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thanks for the great instructions!

I'm trying to insert an if/then in my views to show in-place text editors for administrators and plain text for everyone else.

<% if @current_user.has_role?('administrator') %>

Works perfectly IF someone is logged in.  When a non-logged in person hits the page an error is thrown because @current_user is nil (also tried current_user), and therefore won't have a has_role? method.  How do I get around this?

Thanks again!

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

RandyInLA wrote:

Thanks for the great instructions!

I'm trying to insert an if/then in my views to show in-place text editors for administrators and plain text for everyone else.

<% if @current_user.has_role?('administrator') %>

Works perfectly IF someone is logged in.  When a non-logged in person hits the page an error is thrown because @current_user is nil (also tried current_user), and therefore won't have a has_role? method.  How do I get around this?

Thanks again!

I had the same problem so here's how I solved it:

<% if logged_in? -%>
  <% if current_user.has_role?('administrator') -%>
    #show this
  <% end -%>
<% end -%>

Typing this made me think that I could probably refactor it with this:

<% if logged_in? && current_user.has_role?('administrator') -%>
  #show this
<% end -%>

This may be too much logic for the view so I may move it into a helper method.

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thanks for that Jason!  It works perfectly.
 
Do you think it would be worth it to change the way the current_user method is configured to create a current_user object with role type 'visitor' instead of nil when someone isn't logged id? 
Then we could go back to just <% if current_user.has_role?('administrator') %>

Last edited by RandyInLA (2008-02-16 14:56:24)