Class: Incline::UserManager

Inherits:
AuthEngineBase show all
Defined in:
lib/incline/user_manager.rb

Overview

Handles the user management tasks between an authentication system and the database.

The default authentication system is the database, but other systems are supported. Out of the box we support LDAP, but the class can be extended to add other functionality.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ UserManager

Creates a new user manager.

The user manager itself takes no options, however options will be passed to any registered authentication engines when they are instantiated.

The options can be used to pre-register engines and provide configuration for them. The engines will have specific configurations, but the UserManager class recognizes the ‘engines’ key.

{
  :engines => {
    'example.com' => {
      :engine => MySuperAuthEngine.new(...)
    },
    'example.org' => {
      :engine => 'incline_ldap/auth_engine',
      :config => {
        :host => 'ldap.example.org',
        :port => 636,
        :base_dn => 'DC=ldap,DC=example,DC=org'
      }
    }
  }
}

When an ‘engines’ key is processed, the configuration options for the engines are pulled from the subkeys. Once the processing of the ‘engines’ key is complete, it will be removed from the options hash so any engines registered in the future will not receive the extra options.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/incline/user_manager.rb', line 41

def initialize(options = {})
  @options = (options || {}).deep_symbolize_keys
  Incline::User.ensure_admin_exists!

  if @options[:engines].is_a?(::Hash)
    @options[:engines].each do |domain_name, domain_config|
      if domain_config[:engine].blank?
        ::Incline::Log::info "Domain #{domain_name} is missing an engine definition and will not be registered."
      elsif domain_config[:engine].is_a?(::Incline::AuthEngineBase)
        ::Incline::Log::info "Using supplied auth engine for #{domain_name}."
        register_auth_engine domain_config[:engine], domain_name
      else
        engine =
            begin
              domain_config[:engine].to_s.classify.constantize
            rescue NameError
              nil
            end

        if engine
          engine = engine.new(domain_config[:config] || {})
          if engine.is_a?(::Incline::AuthEngineBase)
            ::Incline::Log::info "Using newly created auth engine for #{domain_name}."
            register_auth_engine engine, domain_name
          else
            ::Incline::Log::warn "Object created for #{domain_name} does not inherit from Incline::AuthEngineBase."
          end
        else
          ::Incline::Log::warn "Failed to create auth engine for #{domain_name}."
        end
      end
    end
  end

  @options.delete(:engines)

end

Class Method Details

.authenticate(email, password, client_ip) ⇒ Object

Attempts to authenticate the user and returns the model on success.



145
146
147
# File 'lib/incline/user_manager.rb', line 145

def self.authenticate(email, password, client_ip)
  default.authenticate email, password, client_ip
end

.begin_external_authentication(request) ⇒ Object

Returns a URL if an external login is to be used, or nil to use local authentication.



151
152
153
# File 'lib/incline/user_manager.rb', line 151

def self.begin_external_authentication(request)
  default.begin_external_authentication request
end

.clear_auth_engine(*domains) ⇒ Object

Clears any registered authentication engine for one or more domains.



226
227
228
# File 'lib/incline/user_manager.rb', line 226

def self.clear_auth_engine(*domains)
  default.clear_auth_engine(*domains)
end

.end_external_authentication(request) ⇒ Object

Returns a URL if an external logout is to be used, or nil to use local authentication.



157
158
159
# File 'lib/incline/user_manager.rb', line 157

def self.end_external_authentication(request)
  default.end_external_authentication request
end

.register_auth_engine(engine, *domains) ⇒ Object

Registers an authentication engine for one or more domains.

The engine passed in should take an options hash as the only argument to initialize and should provide an authenticate method that takes the email, password, and client_ip.

The authenticate method of the engine should return an Incline::User object on success or nil on failure.



214
215
216
# File 'lib/incline/user_manager.rb', line 214

def self.register_auth_engine(engine, *domains)
  default.register_auth_engine(engine, *domains)
end

Instance Method Details

#authenticate(email, password, client_ip) ⇒ Object

Attempts to authenticate the user and returns the model on success.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/incline/user_manager.rb', line 81

def authenticate(email, password, client_ip)
  return nil unless Incline::EmailValidator.valid?(email)
  email = email.downcase

  # If an engine is registered for the email domain, then use it.
  engine = get_auth_engine(email)
  if engine
    return engine.authenticate(email, password, client_ip)
  end

  # Otherwise we will be using the database.
  user = User.find_by(email: email)
  if user
    # user must be enabled and the password must match.
    unless user.enabled?
      add_failure_to user, '(DB) account disabled', client_ip
      return nil
    end
    if user.authenticate(password)
      add_success_to user, '(DB)', client_ip
      return user
    else
      add_failure_to user, '(DB) invalid password', client_ip
      return nil
    end
  end
  add_failure_to email, 'invalid email', client_ip
  nil
end

#begin_external_authentication(request) ⇒ Object

The begin_external_authentication method takes a request object to determine if it should process a login or return nil. If it decides to process authentication, it should return a URL to redirect to.



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/incline/user_manager.rb', line 114

def begin_external_authentication(request)
  # We don't have an email domain to work from.
  # Instead, we'll call each engine's authenticate_external method.
  # If one of them returns a user, then we return that value and skip further processing.
  auth_engines.each do |dom,engine|
    unless engine.nil?
      url = engine.begin_external_authentication(request)
      return url unless url.blank?
    end
  end
  nil
end

#clear_auth_engine(*domains) ⇒ Object

Clears any registered authentication engine for one or more domains.



220
221
222
# File 'lib/incline/user_manager.rb', line 220

def clear_auth_engine(*domains)
  register_auth_engine(nil, *domains)
end

#end_external_authentication(request) ⇒ Object

The end_external_authentication method takes a request object to determine if it should process a logout or return nil. If it decides to process authentication, it should return a URL to redirect to.



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/incline/user_manager.rb', line 130

def end_external_authentication(request)
  # We don't have an email domain to work from.
  # Instead, we'll call each engine's authenticate_external method.
  # If one of them returns a user, then we return that value and skip further processing.
  auth_engines.each do |dom,engine|
    unless engine.nil?
      url = engine.end_external_authentication(request)
      return url unless url.blank?
    end
  end
  nil
end

#register_auth_engine(engine, *domains) ⇒ Object

Registers an authentication engine for one or more domains.

The engine passed in should take an options hash as the only argument to initialize and should provide an authenticate method that takes the email, password, and client_ip. You can optionally define an authenticate_external method that takes the current request as the only parameter.

The authenticate method of the engine should return an Incline::User object on success or nil on failure. The begin_external_authentication method of the engine should return a URL to redirect to on success or nil on failure.

class MyAuthEngine
  def initialize(options = {})
    ...
  end

  def authenticate(email, password, client_ip)
    ...
  end

  def begin_external_authentication(request)
    ...
  end
end

Incline::UserManager.register_auth_engine(MyAuthEngine, 'example.com', 'example.net', 'example.org')


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/incline/user_manager.rb', line 189

def register_auth_engine(engine, *domains)
  unless engine.nil?
    unless engine.is_a?(::Incline::AuthEngineBase)
      raise ArgumentError, "The 'engine' parameter must be an instance of an auth engine or a class defining an auth engine." unless engine.is_a?(::Class)
      engine = engine.new(@options)
      raise ArgumentError, "The 'engine' parameter must be an instance of an auth engine or a class defining an auth engine." unless engine.is_a?(::Incline::AuthEngineBase)
    end
  end
  domains.map do |dom|
    dom = dom.to_s.downcase.strip
    raise ArgumentError, "The domain #{dom.inspect} does not appear to be a valid domain." unless dom =~ /\A[a-z0-9]+(?:[-.][a-z0-9]+)*\.[a-z]+\Z/
    dom
  end.each do |dom|
    auth_engines[dom] = engine
  end
end