require 'og' require 'glue/logger' module Auth # Represents a user or account. # Has a many-to-many relationship with Auth::Role. # # This class can be either extended or related to in order to # create application users. This allows a 'user' to own other # application objects, for example, without polluting the basic # authentication/authorization code. # # Your application's user object's #create method should: # * Have a signature like create(login, password, parameters = {}) # Note that parameters is essentially request.parameters from the # registration form, and thus should be treated as tainted. # * Call super(login, password, parameters) if it extends Auth::User # * Call Auth::User.create(login, password, parameters) if it relates # to Auth::User rather than extending it. class User #-- # These are done as attrs and then as props, so that we # can get them to show up in rdoc. Ugly but it seems to work. #++ # The user's login name. attr :login # The user's salted and hashed password. attr :hashed_password # The last salt used, for later password checks. attr :salt # The security session key, which can be stored in a browser cookie # to allow later password-less login. # # Getting the session key will create a new session key if the # old one has expired (and thus cookie checks will fail, # as they should.) attr :session_key # Time when the session key expires. attr :session_key_expires prop_reader :login, String, :unique => true, :sql => 'varchar(255) not null unique' prop_reader :hashed_password, String, :sql => 'char(40) null' prop :session_key, String, :sql_index => true, :sql => 'char(40) null', :reader => false, :writer => false prop_reader :session_key_expires, Time prop :salt, String, :sql => 'char(40) null', :reader => false, :writer => false many_to_many Role schema_inheritance # Set the raw password. Will appropriately hash it and store it # in +hashed_password+. def password=(new_password) if not new_password @salt = nil @hashed_password = nil else @salt = Crypt.make_salt @hashed_password = Crypt.salt_password @salt, new_password end update if @oid end def session_key # :nodoc: if session_key_expired? @session_key = Crypt.make_session_key hashed_password @session_key_expires = Time.now + Auth.session_key_expiration update if @oid end @session_key end # Has the session key expired? def session_key_expired? not @session_key or (session_key_expires and Time.now > session_key_expires) end # Convenience method. Does this user have this role? # # Can take either a Auth::Role object or a symbol/string role name. def has_role?(role) if role.is_a? Role roles.include? role else # This is the canonical implementation # roles.include? Role.find_one(:where => "name = '#{role.to_s}'") # This is sort of a hack, but turns two queries into one. not find_roles(:extra => "AND #{Role.table}.name = '#{role.to_s}'").empty? end end # Creates a new user. Login is required, password is optional. # Currently, parameters is not used by this implementation. # Auth::AuthController passes in request.params, though, # so future implementations could get further information there. # Subclasses can use it and should pass it along. def initialize(login, password = nil, parameters = {}) @login = login self.password = password end end # Represents a (simple) security role. # # Many-to-many with Auth::User. An Auth::User can have many # +Roles+ and an Auth::Role can be had by many +Users+. class Role #-- # These are done as attrs and then as props, so that we # can get them to show up in rdoc. Ugly but it seems to work. #++ # The role name. (Must be unique.) attr :name property :name, String, :sql => 'varchar(255) not null unique', :unique => true many_to_many User schema_inheritance post "create_roles", :on => :og_create_schema # Note that if you subclass Auth::Role, you need to allow just # passing in a role name and nothing else in your initialize # (and your schema), or you need to override the create_roles # method. def initialize(name) @name = name end # Create the admin and user roles. def create_roles # I believe that because this is called in an advice, # that self.class will be the right class for the entity # that actually got created, even if it's not us. self.class.create(Auth.admin_role) if 0 == self.class.count(:condition => "name = '#{Auth.admin_role}'") self.class.create(Auth.user_role) if 0 == self.class.count(:condition => "name = '#{Auth.user_role}'") end end end