# Copyright (c) 2005 James Adam
#
# This is the MIT license, the license Ruby on Rails itself is licensed 
# under.
#
# Permission is hereby granted, free of charge, to any person obtaining 
# a copy of this software and associated documentation files (the 
# "Software"), to deal in the Software without restriction, including 
# without limitation the rights to use, copy, modify, merge, publish, 
# distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the 
# following conditions:
#
# The above copyright notice and this permission notice shall be included 
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 



# Methods for manipulating and querying roles from a User object
module UserEngine
  
  # This module defines a number of methods to be included into a User model object and class
  # to enable the manipulation and determination of permissions, based on the relationship of
  # User objects to Roles.
  #
  # To use the UserEngine, you must ensure that this module is included in your User model
  # object., e.g.
  # 
  #   class User < ActiveRecord::Base
  #     include LoginEngine::AuthenticatedUser # to do login stuff
  #     include UserEngine::AuthorizedUser     # to ensure authorization for actions
  #       ...
  #   end
  module AuthorizedUser
  
    def self.included(base)
      base.extend(ClassMethods)
      base.class_eval {
        has_and_belongs_to_many :roles, :join_table => UserEngine.config(:user_role_table)
        
        # ensure that all users recieve the 'user' role
        before_create :add_user_role
      }
    end
  
    # Returns the full name of this user.
    def fullname
      "#{self.firstname} #{self.lastname}"
    end

    # This module defines methods to be attached to the User class itself.
    module ClassMethods
  
      # Check if the requested controller/action is available for guest users
      # i.e. anyone who isn't logged in.
      # The 'Guest' user is actually a Role object held my no user. The name of this
      # Role object is defined in UserEngine.config(:guest_role_name), and defaults
      # to "Guest". To control which actions are available to site users who are 
      # *not* logged in, you should modify the permissions for this role.
      def guest_user_authorized?(controller, action="index")
        query = <<-EOS
SELECT DISTINCT #{UserEngine.config(:permission_table)}.* 
FROM #{UserEngine.config(:permission_table)}, #{UserEngine.config(:role_table)}, 
     #{UserEngine.config(:permission_role_table)}
WHERE #{UserEngine.config(:role_table)}.name = :role
AND #{UserEngine.config(:role_table)}.id = #{UserEngine.config(:permission_role_table)}.role_id
AND #{UserEngine.config(:permission_role_table)}.permission_id = #{UserEngine.config(:permission_table)}.id
AND #{UserEngine.config(:permission_table)}.controller = :controller
AND #{UserEngine.config(:permission_table)}.action = :action
EOS

        result = Permission.find_by_sql([query, {:role => UserEngine.config(:guest_role_name), 
                                                 :controller => controller.to_s, :action => action.to_s}])    
  
        return (result != nil) && (!result.empty?)       
      end
    end


    # Returns true if this user is authorised to perform the given action. A
    # user is authorized if one or more of the Roles which this user holds is
    # associated with a Permission object which matches the current controller and
    # action.
    def authorized?(controller, action="index")

      return true if self.admin?

      query = <<-EOS
SELECT DISTINCT #{UserEngine.config(:permission_table)}.* 
FROM #{UserEngine.config(:permission_table)}, #{UserEngine.config(:role_table)}, 
     #{UserEngine.config(:permission_role_table)}, #{UserEngine.config(:user_role_table)},
     #{LoginEngine.config(:user_table)}
WHERE #{LoginEngine.config(:user_table)}.id = :person
AND #{LoginEngine.config(:user_table)}.id = #{UserEngine.config(:user_role_table)}.user_id
AND #{UserEngine.config(:user_role_table)}.role_id = #{UserEngine.config(:role_table)}.id
AND #{UserEngine.config(:role_table)}.id = #{UserEngine.config(:permission_role_table)}.role_id
AND #{UserEngine.config(:permission_role_table)}.permission_id = #{UserEngine.config(:permission_table)}.id
AND #{UserEngine.config(:permission_table)}.controller = :controller
AND #{UserEngine.config(:permission_table)}.action = :action
EOS

      result = Permission.find_by_sql([query, {:person => self.id, 
                                               :controller => controller.to_s, :action => action.to_s}])    

      return (result != nil) && (!result.empty?)   
    end  

    # Returns true if this user is has the 'admin' role
    def admin?()
      roles.each { |r| return true if r.omnipotent? }
      false
    end
    
    private
      # This method is called before a User object is saved to ensure that *all* users are
      # given the default 'user' role. The name of this role is defined in 
      # UserEngine.config(:user_role_name).
      def add_user_role
        user_role = Role.find_by_name(UserEngine.config(:user_role_name))
        if user_role
          self.roles << user_role
        else
          raise "Cannot find user-level role '#{UserEngine.config(:user_role_name)}'!"
        end
      end

  end  
end
