Authentication Help

This is the first time I'm developing a site with rails (online shop) and I really would like to ask for help with authetication.

I've installed acts_as_autheticated and I set up that certain pages can only be viewed if the user logs in (for example editing or adding new products). I am just trying to get my head around certain roles for certain users. How should I go around creating different roles for different users? what if I want to make it mandatory for customers to log in in order to complete a transaction?

I also discovered RoleRequirement yesterday on Google code. Should I use this plugin? Would anyone know any good tutorail to teach me how to accomplish this?

Please help.

Elle

The trick I did was to add flags to the user table, which is what acts_as_authenticated is pointed at in my case.

These flags:

    superadmin, a boolean, defaults to false     forumadmin, a boolean, defaults to false     banned, a boolean, defaults to false

I modified lib/authenticated_system.rb to teach it that I ALWAYS want a current_user, and that it can never be nil. I can send you some diffs if you want. The purpose in this is so I can write code that reads like:

  current_user.superadmin?

without having to write:

  if !current_user.nil? && current_user.superadmin?

--Michael

Thanks Michael. Would appreciate anything you can send me about this.

This might seem like a really silly question: Do I use the same users model for both admins and cutomers? Because the way, my logic works is to create another table of customers and have it under admin control. My logic also says that I will have the following relationships: Customer has many orders Order belongs to one customer

But then, can I use the same acts_as_authenticated to get the cutomers to login? And how do I do this? And am I missing something with my relationships?

Many questions, I know :-/

Thanks, Elle

I took the approach of using roles and rights. A user has and belongs to many roles and a role has many rights. A right would be to a specific action on a specific controller. Then just use a before filter in your application.rb to check if the user has a role that has the right to access the action in the controller he is trying to access. It sounds more complicated than it is but it works great and its very flexible. Here is some code to get you started if you're interested in doing it this way.

role.rb

Thanks Michael. I really wanted to have a separate model for Customer (bacause I have about 3 admins, 4 staff and 150 customers). I also wanted to have a relationship between customers and orders (to show orders per customer). But i've spend all yesterday and today on it and I just don't know how to implement it.

Any advice, before I give in and just use one User model for everyone?

Elle

In nearly all my applications, I do something far more complicated. But to answer your question, I use one table for "things that can log in" and have a default-false "is admin" flag. I want to write the roles/permissions model thing, and intend to do it eventually, so if you can that's the most flexible but most complicated to get started on method. No matter what you choose, I would suggest having an is_admin? method that you can use in your checks. Then should you change your method from a simple flag to a full-blown roles/rights system, you don't have to change as much code.

The more complicated thing I do is this: Since most users eventually report a problem, I have a way for an admin to set themselves up as an "effective user" -- that is, they can become that user for all purposes, and see exactly what the user would see. The only difference is they cannot complete certain operations without a small dialog box saying something like: "You are in administrative mode, and are making this change as user <username>. Do you really want to do this?"

This is on my list of features to implement on a current project. I haven't yet done any research for how others have done this (which I intend to do when I get to that feature), but since you brought it up I was wondering if you'd care to share how you did this.

Michael Glaesemann grzm seespotcode net

I ended up with the following: 1) For admin pages: I used acts_as_authenticated plugin for users (and by users I mean admins).

My aaplication.rb controller has: class ApplicationController < ActionController::Base   include AuthenticatedSystem ...

My base_controller.rb for the admin area inherits from the ApplicationController and also has a before_filter: class Admin::BaseController < ApplicationController   before_filter :login_required end

And all my admin pages inherit from the base controller by using: class Admin::UserController < Admin::BaseController ... Which means, they all require login to be viewed.

In my views/layout/application.rhtml I have: <p id="adminlogin">     <% if logged_in? %>       Logged in as <%= current_user.login %>       (<%= link_to "Logout", :controller => "/admin", :action => "logout" %>)     <% else %>       <%= link_to "Admin Login", :controller => "/admin", :action => "login" %>     <% end %>   </p>

2) Next, I added another column in my users table called 'role' (:integer, :limit => 1) to identify 1 for manager and 0 for staff. Only managers can add admins. Staff can do everything else in the admin area besides administering admins. So, in my user_controller.rb I added another before_filter:

before_filter :check_authorization

  def check_authorization     user = User.find(session[:user])       unless user.role?        flash[:notice] = "You are not authorized to view the page requested"        redirect_to :controller => 'product', :action => 'index'        return false       end   end

3) Lastly, I created a customer model which admins can create, edit.... Now, only logged customers can checkout. So, in my checkout controller I added all my customer's login methods, like so:

class CheckoutController < ApplicationController   before_filter :initialize_cart   before_filter :authorize, :except => ["login"]

  def authorize     return true if @c     flash[:notice] = "To place your order please login"     redirect_to :controller => "catalog"   end

  def login     # examine the form data for "name" and "password"     pw,name = params[:customer].values_at(*%w{password name})     # Retrieve the customer record for the name and store it in a variable 'c'     c = Customer.find_by_name(name)     # if such record exists, and it's password matches the password from the form     if c && Digest::SHA1.hexdigest(pw) == c.password       # start a session with the customer id       @session['customer'] = c.id         if @cart.products.empty?         redirect_to :controller => "catalog"         else         redirect_to :controller => "checkout", :action => "index"         end     else       # otherwise report an error       flash[:notice] = "Invalid Login"     end   end

  def logout     @session['customer'] = nil     redirect_to :controller => "catalog"   end

And in my application.rb controller: # register the method get_customer as a filter   before_filter :get_customer

  def get_customer     # sets an instance variable @c to the customer object     # drawn from the db record of the customer who's logged in     # else the method is not assigned to @c and @c defaults to nil     if @session['customer']       @c = Customer.find(@session['customer'])     end   end

This login method is taken from "Ruby from Rails" book, Part 4, which I am going to use next to change my current cart. I am going to change it that both cart and checkout are in the same controller because I need the cart to belongs_to :customer. The reason is that I need to apply the customer's discount level and I also want to record the customer's id in the orders table. (optional later, I will add the option to remember customer's shipping address.)

This has been a huge learning curve for me. I'm happy (and tired) that it finally works. I'm off to re-do my cart. If you have any suggestions/improvements to my code or way of thinking, please share your thoughts.

Just one last question, when I create a session, does the information for session['customer'] and session[:cart_id] get stored in the same file? and How do I call on the customer_id from the session? Have run into trouble with this, where the order recorded the order_id in the customer_id column. Not sure where is my mistake yet.

Cheers, Elle

Each session is stored in a single file in binary format. It looks like you are storing the customer id as session["customer"] so you can just reference it like that. Can you post your code with the order_id problem?

The basics are:

(1) When an admin requests to become another user, I set a session variable called:       session[:effective_user] = User.find_by_login("whatever")

(2) When they stop being that user, I delete that entry from the session hash.

(3) When I set up my current_user pseudo-global, I set it to the effective user if it is set, or to the real user (in session[:user]) if it is not set.

(4) I set real_user to session[:user]

(5) Once those are set up, I verify that this is true:

    current_user == real_user || real_user.is_site_admin?

This prevents a mistake from letting someone become another user where they should not.

(6) In most places, I use current_user for all access checking, display, and database updates.

(7) In specific places where I want the admin to be warned (deleting data, changing data in some cases, and also on a "WARNING: you are logged in as ..." on the sidebar menu) I check to see if real_user != current_user && current_user.is_site_admin?

Since I use current_user nearly everywhere, this was the path of least rototill for me :slight_smile:

--Michael

This is slightly different but still on the same subject. So, I got the customer to log in, but.... he doesn't have a cart that belongs to him. Which means if another customer logs in, they see the other's cart. I want to associate the cart with a customer to also be able to record the customer's id in the orders table. but.... I don't know how to do that.

Other problems that I have with my cart are: 1) Update doesn't happen 2) checkout page suddenly doesn't appear

Will you have a look at how I can associate the cart with the logged in customer? (And if you can at the other questions/problems)

My cart.rb:

You have a belongs_to :customer in your cart.rb, make sure you have a customer_id field integer in your carts table. Where is the initialize_cart method?

Should I start a session[:cart] and add the customer_id to it? would rails then see all the columns in the cart_items table? Or should I leave it as session[:customer]?

I don't think that would help. Your cart model has a before_filter called initialize_cart. I'm assuming that is what creates the cart and stores it in the database as I cannot see where that is happening. I would assume that initialize_cart should look something like this.

def initialize_cart   cart = Cart.new   cart.customer_id = session[:customer_id]   cart.save end

I should have posted my solution before. I ended up creating a new [:customer] session at login as well as a session{:cart]. In the carts table, I inserted the customer_id. The relationships are:

class Customer < ActiveRecord::Base    has_many :orders,      :dependent => true,      :order => "created_at ASC"    has_one :cart

class Cart < ActiveRecord::Base    has_many :cart_items    has_many :products, :through => :cart_items    belongs_to :customer

Then when the customer logs off, I set: session[:customer] = nil session[:cart] = nil

Works great. The app remembers customer's cart when he logs in next time. I got help with this as well (wish I knew the person's name to give credit).

Cheers, Elle