require 'uri' require 'glue/configuration' module Auth # Include this class in any controller that you want to have # authentication and/or authorization on. module Controller # The Auth::User object for the currently logged-in user. # Will be +nil+ if no user is logged in. def user # If we don't have a user yet, see if we can get one via # the session key cookie. if not @user session_key = request.cookies['login_session_key'] if session_key @user = User.find_one(:where => "session_key = '#{session_key}'") end end # If we already had a user, or managed to find one above, # check for session expiration. if @user if @user.session_key_expired? @expired_login = @user.login @user = nil else @expired_login = nil end end @user end @user = nil # Is the current user an administrator? def administrator? user and user.has_role? Auth.admin_role end # Is the current user allowed to execute the current action? def allowed? required = required_roles[action_name.intern] not required or (user and user.has_role? required) end # Checks the current user's permission to run the current action, # and redirects to the appropriate auth action if a login is needed # or if the current user doesn't have sufficient permissions. def check_permissions if not allowed? store_location redirect "/auth/access_denied" if user redirect URI.escape("/auth/login?login=#{@expired_login}") raise RenderExit end end # Stores the current location, so that we can redirect the user # to a login page but get back to where they originally wanted to go. def store_location session["prelogin_uri"] = request.uri session["prelogin_referer"] = request.referer end # Spits out a link to the login page if there is no current user, # or to the logout page if there is one. def login_link unless user body.a "Login", :href => "/auth/login" else body.a "Logout", :href => "/auth/logout" end end private # If the user had an expired session key, this was their login # name. Allows being a little friendlier when redirecting and # forcing a new login. @expired_login = nil def self.append_features(base) base.module_eval { private # Make sure that @@required_roles is a class variable # on the controller class, and not on Auth::Controller. @@required_roles = Hash.new def self.required_roles @@required_roles end def required_roles @@required_roles end # @@protected_methods also needs to be a class variable # on the controller class. @@protected_methods = Array.new def self.protected_methods @@protected_methods end # Make sure that protected methods don't get included # in the list of action methods. def self.action_methods am = super am.reject { |method| @@protected_methods.include? method } end } super base.extend(ClassMethods) end module ClassMethods # Protects the given action. # Any requests to call it will require login # and will check permissions automatically. # The original implementation will be "hidden" # from Nitro so that it cannot be called directly. # # [+action+] The action to protect. # [+:role+] The required role name. # (Defaults to Auth.user_role.) # # The default role is essentially equivalent to # "must be authenticated", as all users have the # user role by default. def protect(action, options = {}) role = options[:role] || Auth.user_role required_role action, role, options end # Protects the given action and requires # administrative privileges to call it. def administrative(action, options = {}) required_role action, Auth.admin_role, options end # Sets the role required to run the given action, # and makes the action protected, so that any requests # to call it will require login and check permissions # automatically. # # [+action+] The action to protect. # [+role+] The required role name. def required_role(action, role, options = {}) action = action.intern if action.is_a? String role = role.to_s required_roles[action] = role unprot_action = "unprotected_" + action.to_s protected_methods << unprot_action alias_method unprot_action, action class_eval %{ def #{action} check_permissions #{unprot_action} end } end end end end