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

On Oct 13, 1:47 am, “[email protected]
[email protected] wrote:

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.

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 . Do you really want to
do this?”

On Oct 14, 2007, at 12:45 , Michael G. wrote:

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.

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 G.
grzm seespotcode net

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

class Role < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :rights
validates_uniqueness_of :name

def has_right_for?(action_name, controller_name)
rights.detect{ |right| right.has_right_for?(action_name,
controller_name) }
end
end

right.rb

class Right < ActiveRecord::Base
has_and_belongs_to_many :roles

def has_right_for?(action_name, controller_name)
action == action_name && controller == controller_name
end
end


user.rb

class User < ActiveRecord::Base
has_and_belongs_to_many :roles

def has_right_for?(action_name, controller_name)
roles.detect{ |role| role.has_right_for?(action_name,
controller_name) }
end

end

application.rb

class ApplicationController < ActionController::Base
before_filter :check_authorization

def check_authorization
if session[:user].blank?
flash[:error] = “You must login to view the page you requested”
redirect_to :action => “login”,
:controller => “user”
return false
end

# logged in users can view the following pages with out an

explicit right
unless action_name == “logout” || action_name =~ %r{my_}
unless has_right?(action_name, self.class.controller_path)
flash[:error] = “You are not authorized to view the page you
requested”
redirect_to :action => “index”, :controller => “home”
return false
end
end
end

def has_right?(action_name, controller_name)
unless session[:user].blank?
user = User.find(session[:user])
user.has_right_for?(action_name, controller_name)
end
end

end

Just use skip_before_filter on any actions you want open to users who
are not logged in (don’t forget to do this on the login page!)

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:

<% 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 %>

  1. 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

  1. 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

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

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?

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?

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:

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

def total
cart_items.inject(0) {|sum, n| n.price * n.amount + sum}
end

def add(product_id)
items = cart_items.find_all_by_product_id(product_id)
product = Product.find(product_id)
if items.size < 1
ci = cart_items.create(:product_id => product_id,
:amount => 1,
:price => product.price)
else
ci = items.first
ci.update_attribute(:amount, ci.amount + 1)
end
ci
end

def remove(product_id)
ci = cart_items.find_by_product_id(product_id)

if ci.amount > 1
  ci.update_attribute(:amount, ci.amount - 1)
else
  CartItem.destroy(ci.id)
end
return ci

end

def update(product_id)
ci = cart_items.find_by_product_id(product_id)
ci.update_attribue(:amount => new_qty.to_i )
end

end

My cart_controller:

class CartController < 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 :action => "view_cart"
    end
else
  # otherwise report an error
  flash[:notice] = "Invalid Login"
  redirect_to :controller => "catalog"
end

end

def logout
@session[‘customer’] = nil
redirect_to :controller => “catalog”
end

def index
end

def add
@product = Product.find(params[:id])
if request.post?
@item = @cart.add(params[:id])
flash[:notice] = “Added #{@item.product.title} to
cart.”
redirect_to :controller => “catalog”
else
render
end
end

def update
@product = Product.find(params[:id])
new_qty = params[:new_qty]
if request.post?
@item = @cart.update(params[:id], new_qty)
flash[:notice] = “Updated #{@item.product.title}'s
quantity.”
redirect_to :action => “view_cart”
else
render
end
end

def remove
@product = Product.find(params[:id])
if request.post?
@item = @cart.remove(params[:id])
flash[:notice] = “Removed #{@item.product.title}
redirect_to :action => “view_cart”
else
render
end
end

def clear
if request.post?
@cart.cart_items.destroy_all
flash[:notice] = “Cleared the cart”
redirect_to :controller => “catalog”
else
render
end
end

def view_cart
@page_title = “Shopping Cart for #{@c.name}”
@order = Order.new
if @cart.products.empty?
flash[:notice] = “Your shopping cart is empty! Please add
something to it before proceeding to checkout.”
redirect_to :controller => ‘catalog’
end
end

def checkout
@page_title = “Checkout”
@order = Order.new(params[:order])
@order.customer_ip = request.remote_ip
@order.customer_id = @session[‘customer’]
populate_order

if @order.save
  if @order.process
    flash[:notice] = 'Your order has been submitted, and will be

processed immediately.’
session[:order_id] = @order.id
# Empty the cart
@cart.cart_items.destroy_all
redirect_to :action => ‘thank_you’
else
flash[:notice] = “Error while placing order.
‘#{@order.error_message}’”
render :action => ‘view_cart’
end
else
render :action => ‘view_cart’
end
end

def thank_you
@page_title = ‘Thank You!’
end

def populate_order
for cart_item in @cart.cart_items
order_item = OrderItem.new(
:product_id => cart_item.product_id,
:price => cart_item.price,
:amount => cart_item.amount
)
@order.order_items << order_item
end
end

end

And _item.rhtml which part of the view_cart.rhtml:

<%=h item.product.sku %> <%= link_to item.product.title, :action => "show", :controller => "catalog", :id => item.product.id %> <%= pluralize(item.amount, "pc", "pcs") %> $<%= two_dec(item.price * item.amount) %> <%= form_tag :controller => "cart", :action => "update", :id => item.product, :with => "new_qty" %> <%= text_field_tag "new_qty", "", "size" => 4 %> <%= end_form_tag %> <%= button_to "x", :controller => "cart", :action => "remove", :id => item.product %>

Now tell me the truth, how messy is my code? where are my mistakes?

Thanks,
Elle

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

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]?

On Oct 20, 6:33 am, “randomutterings…” [email protected]

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

On Oct 24, 1:49 am, “randomutterings…” [email protected]