Topic: HOWTO Grant Access based on User/Group permission from scratch

HOWTO Grant Access based on User/Group permission from scratch
On my previous post:
Create a RailsShell Application in 2 minutes & 10 steps
http://railsforum.com/viewtopic.php?id=17700
You could see how easy is to create a web based shell console, as a experienced Network & Linux administrator always like to secure as much as possible my code specially in web-apps. Since I'm not big fan of plugins to solve my life, here is a recipe in 10 steps to enable User/Group permissions from scratch to access some record in your Database.

Our Application will have 6 Tables users,portlets, groups,members, upermits,gpermits.

The idea is to have a way to control who has access to what [ Sarbanes Oxley typical requirement ]. So users can create as many groups as they want, later each groups has many members which are reference from the main users table.
Users can create Portlets and each portlet will have his own permision for Users and for Groups. Each time the Portlet is trying to be accessed the validation will check against this tables and return a resutl to true/false depending if the user which might be located in your session[:username] have access to a particular portlet or if belongs to a group that is being granted to that particular portlet.

So lets get started !!!

1.- rails roles -d mysql
2.- cd roles
3.- edit config/database.yml
4.- rake db:create
5.- Run the following Rails generators or create it in a [Shell Script or Batch File then run it ;-) ]

ruby script\generate scaffold User username:string
ruby script\generate scaffold Portlet user:references portletname:string

ruby script\generate scaffold Group user:references groupname:string
ruby script\generate scaffold Member group:references user:references

ruby script\generate scaffold Upermit portlet:references user:references write:boolean
ruby script\generate scaffold Gpermit portlet:references group:references write:boolean


6.- Lets Edit the Models !!!.

User

  has_many :portlets
  has_many :groups
  has_many :members, :through => :groups

portlet
  belongs_to :user
  has_many :upermits
  has_many :gpermits
  has_many :groups, :through => :gpermits
  has_many :members, :through => :groups

  def self.permit(portlet, user)
    return false if portlet.nil? or user.nil?
    if Upermit.permit_user(portlet,user)
      true
    elsif Member.permit_user(portlet,user)
      true
    else
      false
    end
  end


Group
  belongs_to :user
  belongs_to :gpermit
  has_many :members

Member
  belongs_to :group
  has_many :users

  def self.permit_user(portlet, user)
    return false if portlet.nil? or user.nil?
#    allowed = Member.find_by_sql("select * from members where group_id in (select group_id from gpermits where portlet_id=#{portlet} and 
user_id=#{user})")
    my_groups = Gpermit.find_all_by_portlet_id(portlet)
      lol = Array.new
      my_groups.each_with_index do |group,index|
        lol << my_groups[index].group_id
      end

#    allowed = Member.find_all_by_group_id(lol, :conditions=>{:group_id=>lol, :user_id=>user})
    allowed = Member.find_all_by_group_id(lol, :conditions=>{:user_id=>user})

       if allowed.empty? or allowed.nil?
         false
       else
         true
       end
#    end

  end


Upermit
  belongs_to :portlet
  has_many :users

  def self.permit_user(portlet, user)
    return false if portlet.nil? or user.nil?
    find_by_portlet_id_and_user_id(portlet,user)
  end


Gpermit
  belongs_to :portlet
  belongs_to :group

7.- rake db:migrate

8.- Now Let's Play with the Console
ruby script/console
Lets Create 3 Users:
>> u1 = User.create(:username => "root")
>> u2 = User.create(:username => "admin")
>> u3 = User.create(:username => "foo")

8a.- Lets Create a some Portlets for each user:
>> u1.portlets.create(:portletname => "U1_P1")
>> u2.portlets.create(:portletname => "U2_P1")
>> u3.portlets.create(:portletname => "U3_P1")

8b.- Lets Create some Groups for each user:
>> u1.groups.create(:groupname => "U1_G1")
>> u2.groups.create(:groupname => "U2_G1")
>> u3.groups.create(:groupname => "U3_G1")

8c.- Lets get those groups and asign members to those groups:
>> g1 = Group.find(1)
>> g2 = Group.find(2)
>> g3 = Group.find(3)
Since there is no members assigned to the Groups lets add some Members.
>> g1.members.create(:user_id => 2)
>> g2.members.create(:user_id => 3)
>> g3.members.create(:user_id => 1)

8d.- Lets get our portlets:
>> p1 = Portlet.find(1)
>> p2 = Portlet.find(2)
>> p3 = Portlet.find(3)

8e.- Lets asign permission to access with the Upermit on a user basis and Gpermit in a Group Basis.
>> p1.upermits.create(:user_id => 1, :write => true )
>> p2.upermits.create(:user_id => 2, :write => true )
>> p3.upermits.create(:user_id => 3, :write => true )
This action actually will make sense since we create the Portlet as a After_Filter or After_Save automatically create the right record to allow this user to edit the Portlet.

8f.- Lets assign permision to access the Portlet to a specific Group:
>> p1.upermits.create(:group_id => 1, :write => true )
>> p2.upermits.create(:group_id => 2, :write => true )
>> p3.upermits.create(:group_id => 3, :write => true )

9.- Lets see if we can controll the access to a particular Portlet based on User Permit or Group Permit:

>>Portlet.permit(1,1)
=> true
Portlet.permit(1,2)
=> true
Portlet.permit(1,3)
=> false
Portlet.permit(1,5)
=> false
Portlet.permit(1,50)
=> false

So far so good smile

Lets check for Portlet 3
Portlet.permit(3,0)
=> false
Portlet.permit(3,1)
=> true
Portlet.permit(3,2)
=> false
Portlet.permit(3,3)
=> true
Portlet.permit(3,4)
=> false.

10.- How this works ??? Ok. here is what happens with this experiment:

When we call the Class Method Portlet.permit(X,Y)

In the Portlet model call the self method:

  def self.permit(portlet, user)
    return false if portlet.nil? or user.nil?
    if Upermit.permit_user(portlet,user)
      true
    elsif Member.permit_user(portlet,user)
      true
    else
      false
    end
  end

This will try to validate the Portlet_ID with a particular User via: Upermit.permit_user(portlet,user)

Which in the Upermit Model is:

  def self.permit_user(portlet, user)
    return false if portlet.nil? or user.nil?
    find_by_portlet_id_and_user_id(portlet,user)
  end

Very simple return in case it match the right values.

In case this fails then will try to launch the Group authentication via: Member.permit_user(portlet,user)

Which in the Member Model is:

  def self.permit_user(portlet, user)
    return false if portlet.nil? or user.nil?
#    allowed = Member.find_by_sql("select * from members where group_id in (select group_id from gpermits where portlet_id=#{portlet} and 
user_id=#{user})")
    my_groups = Gpermit.find_all_by_portlet_id(portlet)
      lol = Array.new
      my_groups.each_with_index do |group,index|
        lol << my_groups[index].group_id
      end

#    allowed = Member.find_all_by_group_id(lol, :conditions=>{:group_id=>lol, :user_id=>user})
    allowed = Member.find_all_by_group_id(lol, :conditions=>{:user_id=>user})

       if allowed.empty? or allowed.nil?
         false
       else
         true
       end
#    end

  end


This toke me a while to get it going... first to understand what I wanted to do, later to move into Rails Finders ;-)

NOTE: When you deal with complex models make sure you understand what kind of Query you expect to run, review your development logs, gives you great advise on the kind fo Query that U R doing behing de scenes. Alway can rely on basic SQL the convert it into a Rails Method that is what I did smile

My query is basically a Sub-Query:

select * from members where group_id in (select group_id from gpermits where portlet_id=#{portlet} and user_id=#{user})

Once I got it working I translated it into a Rails Finder:
    allowed = Member.find_all_by_group_id(lol, :conditions=>{:user_id=>user})

But wait there is a little trick here... what it lol... yes I know I know, not what you think... is a way to create an Array with the gorup_id from the first Query.passed by:
    my_groups = Gpermit.find_all_by_portlet_id(portlet)
      lol = Array.new
      my_groups.each_with_index do |group,index|
        lol << my_groups[index].group_id
      end

That will complete my appropiate query to grant or deny access based in a user/group permisions !!!.

I recommend to check:
HowTo - Advanced MD5 database authentication from scratch in 20 steps.
http://railsforum.com/viewtopic.php?id=17512 Or some extra plugin to finish the polished product in your application.


Note this is not a replacement for any of those plugins to generate all the good stuff for user/role authentication, this is just learning purposes and understand what is behind the scenes when you use the rails finder methods.

At the end I show how the rails solution relate to a SQL query which need to use to start in order to get it going finally in rails. As a newbie sometimes is hard to grasp all the information but don't give up, is just a new way of doing things. Remember the only constant in life is change.

Best Regards Din00z.

Re: HOWTO Grant Access based on User/Group permission from scratch

What do you mean by portlet ?

Re: HOWTO Grant Access based on User/Group permission from scratch

Looks good but don't understand the portlet and its function. thanks for clarifying

Re: HOWTO Grant Access based on User/Group permission from scratch

a portlet can be thought of as a page or a fragment of a page - i think.  @dinooz, please correct me if I'm wrong.

Re: HOWTO Grant Access based on User/Group permission from scratch

You R exactly right Phester.

Dinooz.

Re: HOWTO Grant Access based on User/Group permission from scratch

Thank you, Din00z for full description ...

Best reguards,
Alex