<commit_before>class User < ActiveRecord::Base
  include RelationshipMixin
  acts_as_miq_taggable
  include RegionMixin
  has_secure_password
  include CustomAttributeMixin
  include ActiveVmAggregationMixin
  include TimezoneMixin

  belongs_to :role, :class_name => "UiTaskSet", :foreign_key => :ui_task_set_id
  has_many   :miq_approvals, :as => :approver
  has_many   :miq_approval_stamps,  :class_name => "MiqApproval", :foreign_key => :stamper_id
  has_many   :miq_requests, :foreign_key => :requester_id
  has_many   :vms,           :foreign_key => :evm_owner_id
  has_many   :miq_templates, :foreign_key => :evm_owner_id
  has_many   :miq_widgets
  has_many   :miq_widget_contents, :dependent => :destroy
  has_many   :miq_widget_sets, :as => :owner, :dependent => :destroy
  has_many   :miq_reports, :dependent => :nullify
  belongs_to :current_group, :class_name => "MiqGroup"
  has_and_belongs_to_many :miq_groups
  scope      :admin, where(:userid => "admin")

  virtual_has_many :active_vms, :class_name => "VmOrTemplate"

  delegate   :miq_user_role, :to => :current_group, :allow_nil => true

  validates_presence_of   :name, :userid, :region
  validates_uniqueness_of :userid, :scope => :region
  validates_format_of     :email, :with => %r{\A([\w\.\-\+]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z}i,
    :allow_nil => true, :message => "must be a valid email address"
  validates_inclusion_of  :current_group, :in => proc { |u| u.miq_groups }, :allow_nil => true

  attr_accessor :ldaphost
  attr_accessor :basedn

  # use authenticate_bcrypt rather than .authenticate to avoid confusion
  # with the class method of the same name (User.authenticate)
  alias_method :authenticate_bcrypt, :authenticate
  serialize :filters

  include ReportableMixin

  @@ldaphost = ""
  @@basedn   = ""
  @@role_ns  = "/managed/user"
  @@role_cat = "role"

  @role_changed = false

  EVMROLE_SELF_SERVICE_ROLE_NAME         = "EvmRole-user_self_service"
  EVMROLE_LIMITED_SELF_SERVICE_ROLE_NAME = "EvmRole-user_limited_self_service"
  EVMROLE_SUPER_ADMIN_ROLE_NAME          = "EvmRole-super_administrator"
  EVMROLE_ADMIN_ROLE_NAME                = "EvmRole-administrator"

  DEFAULT_GROUP_MEMBERSHIPS_MAX_DEPTH    = 2

  serialize     :settings, Hash   #Implement settings column as a hash
  default_value_for(:settings) { Hash.new }

  def self.in_region
    where(:region => my_region_number)
  end

  def self.in_my_region
    where(:id => region_to_range(my_region_number))
  end

  def self.find_by_userid(userid)
    self.in_region.find(:first, :conditions => {:userid => userid})
  end

  def self.find_by_email(email)
    self.in_region.find(:first, :conditions => {:email  => email})
  end

  virtual_column :ldap_group, :type => :string, :uses => :current_group
  # FIXME: amazon_group too?
  virtual_column :miq_group_description, :type => :string, :uses => :current_group
  virtual_column :miq_user_role_name, :type => :string, :uses => {:current_group => :miq_user_role}

  def validate
    errors.add(:userid, "'system' is reserved for EVM internal operations") unless (self.userid =~ /^system$/i).nil?
  end

  before_validation :nil_email_field_if_blank
  before_validation :dummy_password_for_external_auth
  before_save :on_changed_admin_password
  before_destroy :destroy_subscribed_widget_sets

  def miq_group
    unless Rails.env.production?
      msg = "[DEPRECATION] miq_group accessor is deprecated.  Please use current_group instead.  At #{caller[0]}"
      $log.warn msg
      warn msg
    end

    current_group
  end

  def miq_group=(group)
    unless Rails.env.production?
      msg = "[DEPRECATION] miq_group= accessor is deprecated.  Please use current_group= instead.  At #{caller[0]}"
      $log.warn msg
      warn msg
    end

    self.current_group = group
  end

  def nil_email_field_if_blank
    self.email = nil if self.email.blank?
  end

  def dummy_password_for_external_auth
    if self.password.blank? && self.password_digest.blank? &&
        self.class.mode != "database"
      self.password = "<PASSWORD>"
    end
  end

  def change_password(oldpwd, <PASSWORD>)
    mode = self.class.mode
    raise MiqException::MiqEVMLoginError, "password change not allowed when authentication mode is #{mode}" unless mode == "database"
    raise MiqException::MiqEVMLoginError, "old password does not match current password" unless User.authenticate(self.userid, oldpwd)

    self.password = <PASSWORD>
    self.save!
  end

  def get_filters
    filters = self.current_group.get_filters if self.current_group
    filters || {"managed" => [], "belongsto" => []}
  end

  def has_filters?
    !(self.get_managed_filters.blank? && self.get_belongsto_filters.blank?)
  end

  def get_managed_filters
    self.get_filters["managed"]
  end

  def get_belongsto_filters
    self.get_filters["belongsto"]
  end

  def self_service_user?
    return false if self.current_group.nil?
    self.current_group.self_service_group?
  end
  alias_method :self_service?, :self_service_user?

  def limited_self_service_user?
    return false if self.current_group.nil?
    self.current_group.limited_self_service_group?
  end
  alias_method :limited_self_service?, :limited_self_service_user?

  def super_admin_user?
    self.miq_user_role_name == EVMROLE_SUPER_ADMIN_ROLE_NAME
  end

  def admin_user?
    # Check for admin or super_admin
    role_name = self.miq_user_role_name
    role_name == EVMROLE_SUPER_ADMIN_ROLE_NAME || role_name == EVMROLE_ADMIN_ROLE_NAME
  end

  def ldap_group
    self.current_group ? self.current_group.description : nil
  end
  alias miq_group_description ldap_group

  def role_allows?(options={})
    return false if self.miq_user_role.nil?
    feature = MiqProductFeature.find_by_identifier(options[:identifier])
    identifiers = {:identifiers => [options[:identifier]]}
    feature.try(:hidden) ? miq_user_role.allows_any?(identifiers) : miq_user_role.allows?(options)
  end

  def role_allows_any?(options={})
    return false if self.miq_user_role.nil?
    self.miq_user_role.allows_any?(options)
  end

  def role_allows_all?(options={})
    return false if self.miq_user_role.nil?
    self.miq_user_role.allows_all?(options)
  end

  def miq_user_role_name
    self.miq_user_role.try(:name)
  end

  def self.mode
    VMDB::Config.new("vmdb").config[:authentication][:mode]
  end

  def self.ldaphost(host=nil)
    @@ldaphost = host unless host == nil
    return @@ldaphost
  end

  def self.basedn(dn=nil)
    @@basedn = dn unless dn == nil
    return @@basedn
  end

  def self.verify_ldap_credentials(username, password)
    ldap = MiqLdap.new
    fq_user = ldap.normalize(ldap.fqusername(username))
    raise MiqException::MiqEVMLoginError, "authentication failed" unless ldap.bind(fq_user, password)
  end

  def self.authenticate(username, password, request = nil, options = {})
    options = options.dup
    options[:require_user] ||= false
    fail_message = "Authentication failed"
    mode = self.mode

    begin
      user_or_taskid = if mode == "database" || username == "admin"
        self.authenticate_database(username, password)
      elsif mode == "ldap" || mode == "ldaps"
        self.authenticate_ldap(username, password)
      elsif mode == "amazon"
        self.authenticate_amazon(username, password)
      elsif mode == "httpd"
        authenticate_httpd(username, request)
      end

      raise MiqException::MiqEVMLoginError, fail_message if user_or_taskid.nil?
    rescue MiqException::MiqEVMLoginError => err
      $log.warn err.message
      raise
    rescue Exception => err
      $log.log_backtrace(err)
      raise MiqException::MiqEVMLoginError, err.message
    end

    if options[:require_user] && !user_or_taskid.kind_of?(self)
      task = MiqTask.wait_for_taskid(user_or_taskid, options)
      if task.nil? || MiqTask.status_error?(task.status) || MiqTask.status_timeout?(task.status)
        raise MiqException::MiqEVMLoginError, fail_message
      end
      user_or_taskid = find_by_userid(task.userid)
    end

    if user_or_taskid.kind_of?(self)
      user_or_taskid.lastlogon = Time.now.utc
      user_or_taskid.save!
    end
    return user_or_taskid
  end

  def self.authenticate_database(username, password)
    audit = {:event => "authenticate_database", :message => "Authentication failed for user #{username}", :userid => username}
    user = self.find_by_userid(username)

    if user.nil? || !(user.authenticate_bcrypt(password))
      AuditEvent.failure(audit)
      return nil
    end
    AuditEvent.success(audit.merge(:message => "Authentication successful for user #{username}"))

    return user
  end

  def self.authenticate_amazon(username, password)
    auth = VMDB::Config.new("vmdb").config[:authentication]
    audit = {:event => "authenticate_amazon", :userid => username}
    if password.blank?
      AuditEvent.failure(audit.merge(:message => "Authentication failed for user #{username}"))
      return nil
    end

    amazon_auth = AmazonAuth.new
    if amazon_auth.iam_authenticate(username, password)
      AuditEvent.success(audit.merge(:message => "User #{username} successfully validated as Amazon IAM user"))

      if auth[:amazon_role] == true
        user = self.authorize_amazon_queue(username)
      else
        # If role_mode == database we will only use amazon for authentication. Also, the user must exist in our database
        # otherwise we will fail authentication
        user = self.find_by_userid(username)
        unless user
          AuditEvent.failure(audit.merge(:message => "User #{username} authenticated but not defined in EVM"))
          raise MiqException::MiqEVMLoginError, "User authenticated but not defined in EVM, please contact your EVM administrator"
        end
        return nil unless user
      end

      AuditEvent.success(audit.merge(:message => "Authentication successful for user #{username}"))
      return user
    else
      AuditEvent.failure(audit.merge(:message => "Authentication failed for userid #{username}"))
      return nil
    end

    return user
  end

  def self.authorize_amazon_queue(username)
    task = MiqTask.create(:name => "Amazon IAM User Authorization of '#{username}'", :userid => username)
    unless MiqEnvironment::Process.is_ui_worker_via_command_line?
      cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
      MiqQueue.put(
        :queue_name   => "generic",
        :class_name   => self.to_s,
        :method_name  => "authorize_amazon",
        :args         => [task.id, username],
        :server_guid  => MiqServer.my_guid,
        :priority     => MiqQueue::HIGH_PRIORITY,
        :miq_callback => cb
      )
    else
      self.authorize_amazon(task.id, username)
    end

    return task.id
  end

  def self.authorize_amazon(taskid, username)
    log_prefix = "MIQ(User.authorize_amazon):"
    audit = {:event => "authorize_amazon", :userid => username}

    task = MiqTask.find_by_id(taskid)
    if task.nil?
      message = "#{log_prefix} Unable to find task with id: [#{taskid}]"
      $log.error(message)
      raise message
    end
    task.update_status("Active", "Ok", "Authorizing")

    begin
      auth = VMDB::Config.new("vmdb").config[:authentication]
      # Amazon IAM will be used for authentication and role assignment
      $log.info("#{log_prefix} AWS key: [#{auth[:amazon_key]}]")
      amazon_auth = AmazonAuth.new(:auth=>auth)
      $log.info("#{log_prefix}  User: [#{username}]")
      amazon_user = amazon_auth.iam_user(username)
      $log.debug("#{log_prefix} User obj from Amazon: #{amazon_user.inspect}")
      unless amazon_user
        msg = "Authentication failed for userid #{username}, unable to find IAM user object in Amazon"
        $log.warn("#{log_prefix}: #{msg}")
        AuditEvent.failure(audit.merge(:message => msg))
        task.error(msg)
        task.state_finished
        return nil
      end

      matching_groups = match_groups(amazon_auth.get_memberships(amazon_user))
      user   = self.find_by_userid(username) || self.new(:userid => username)
      user.update_attrs_from_iam(amazon_auth, amazon_user, username)
      user.save_successful_logon(matching_groups, audit, task)
    rescue Exception => err
      $log.log_backtrace(err)
      task.error(err.message)
      AuditEvent.failure(audit.merge(:message=> err.message))
      task.state_finished
      raise
    end
  end

  def self.authenticate_ldap(username, password)
    auth = VMDB::Config.new("vmdb").config[:authentication]
    audit = {:event => "authenticate_ldap", :userid => username}
    if password.blank?
      AuditEvent.failure(audit.merge(:message => "Authentication failed for user #{username}"))
      return nil
    end

    ldap = MiqLdap.new
    fq_user = ldap.normalize(ldap.fqusername(username))

    if ldap.bind(fq_user, password)
      AuditEvent.success(audit.merge(:message => "User #{fq_user} successfully binded to LDAP directory"))

      if auth[:ldap_role] == true
        user = self.authorize_ldap_queue(fq_user)
      else
        # If role_mode == database we will only use ldap for authentication. Also, the user must exist in our database
        # otherwise we will fail authentication
        user = self.find_by_userid(fq_user)
        unless user
          AuditEvent.failure(audit.merge(:message => "User #{fq_user} authenticated but not defined in EVM"))
          raise MiqException::MiqEVMLoginError, "User authenticated but not defined in EVM, please contact your EVM administrator"
        end
        return nil unless user
      end

      AuditEvent.success(audit.merge(:message => "Authentication successful for user #{fq_user}"))
      return user
    else
      AuditEvent.failure(audit.merge(:message => "Authentication failed for userid #{fq_user}"))
      return nil
    end

    return user
  end

  def self.authorize_ldap_queue(fq_user)
    task = MiqTask.create(:name => "LDAP User Authorization of '#{fq_user}'", :userid => fq_user)
    unless MiqEnvironment::Process.is_ui_worker_via_command_line?
      cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
      MiqQueue.put(
        :queue_name   => "generic",
        :class_name   => self.to_s,
        :method_name  => "authorize_ldap",
        :args         => [task.id, fq_user],
        :server_guid  => MiqServer.my_guid,
        :priority     => MiqQueue::HIGH_PRIORITY,
        :miq_callback => cb
      )
    else
      self.authorize_ldap(task.id, fq_user)
    end

    return task.id
  end

  def self.authorize_ldap(taskid, fq_user)
    log_prefix = "MIQ(User.authorize_ldap):"
    audit = {:event => "authorize_ldap", :userid => fq_user}
<commit_msg>Support Default Group for Users with LDAP authentication.<commit_after>class User < ActiveRecord::Base
  include RelationshipMixin
  acts_as_miq_taggable
  include RegionMixin
  has_secure_password
  include CustomAttributeMixin
  include ActiveVmAggregationMixin
  include TimezoneMixin

  belongs_to :role, :class_name => "UiTaskSet", :foreign_key => :ui_task_set_id
  has_many   :miq_approvals, :as => :approver
  has_many   :miq_approval_stamps,  :class_name => "MiqApproval", :foreign_key => :stamper_id
  has_many   :miq_requests, :foreign_key => :requester_id
  has_many   :vms,           :foreign_key => :evm_owner_id
  has_many   :miq_templates, :foreign_key => :evm_owner_id
  has_many   :miq_widgets
  has_many   :miq_widget_contents, :dependent => :destroy
  has_many   :miq_widget_sets, :as => :owner, :dependent => :destroy
  has_many   :miq_reports, :dependent => :nullify
  belongs_to :current_group, :class_name => "MiqGroup"
  has_and_belongs_to_many :miq_groups
  scope      :admin, where(:userid => "admin")

  virtual_has_many :active_vms, :class_name => "VmOrTemplate"

  delegate   :miq_user_role, :to => :current_group, :allow_nil => true

  validates_presence_of   :name, :userid, :region
  validates_uniqueness_of :userid, :scope => :region
  validates_format_of     :email, :with => %r{\A([\w\.\-\+]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z}i,
    :allow_nil => true, :message => "must be a valid email address"
  validates_inclusion_of  :current_group, :in => proc { |u| u.miq_groups }, :allow_nil => true

  attr_accessor :ldaphost
  attr_accessor :basedn

  # use authenticate_bcrypt rather than .authenticate to avoid confusion
  # with the class method of the same name (User.authenticate)
  alias_method :authenticate_bcrypt, :authenticate
  serialize :filters

  include ReportableMixin

  @@ldaphost = ""
  @@basedn   = ""
  @@role_ns  = "/managed/user"
  @@role_cat = "role"

  @role_changed = false

  EVMROLE_SELF_SERVICE_ROLE_NAME         = "EvmRole-user_self_service"
  EVMROLE_LIMITED_SELF_SERVICE_ROLE_NAME = "EvmRole-user_limited_self_service"
  EVMROLE_SUPER_ADMIN_ROLE_NAME          = "EvmRole-super_administrator"
  EVMROLE_ADMIN_ROLE_NAME                = "EvmRole-administrator"

  DEFAULT_GROUP_MEMBERSHIPS_MAX_DEPTH    = 2

  serialize     :settings, Hash   #Implement settings column as a hash
  default_value_for(:settings) { Hash.new }

  def self.in_region
    where(:region => my_region_number)
  end

  def self.in_my_region
    where(:id => region_to_range(my_region_number))
  end

  def self.find_by_userid(userid)
    self.in_region.find(:first, :conditions => {:userid => userid})
  end

  def self.find_by_email(email)
    self.in_region.find(:first, :conditions => {:email  => email})
  end

  virtual_column :ldap_group, :type => :string, :uses => :current_group
  # FIXME: amazon_group too?
  virtual_column :miq_group_description, :type => :string, :uses => :current_group
  virtual_column :miq_user_role_name, :type => :string, :uses => {:current_group => :miq_user_role}

  def validate
    errors.add(:userid, "'system' is reserved for EVM internal operations") unless (self.userid =~ /^system$/i).nil?
  end

  before_validation :nil_email_field_if_blank
  before_validation :dummy_password_for_external_auth
  before_save :on_changed_admin_password
  before_destroy :destroy_subscribed_widget_sets

  def miq_group
    unless Rails.env.production?
      msg = "[DEPRECATION] miq_group accessor is deprecated.  Please use current_group instead.  At #{caller[0]}"
      $log.warn msg
      warn msg
    end

    current_group
  end

  def miq_group=(group)
    unless Rails.env.production?
      msg = "[DEPRECATION] miq_group= accessor is deprecated.  Please use current_group= instead.  At #{caller[0]}"
      $log.warn msg
      warn msg
    end

    self.current_group = group
  end

  def nil_email_field_if_blank
    self.email = nil if self.email.blank?
  end

  def dummy_password_for_external_auth
    if self.password.blank? && self.password_digest.blank? &&
        self.class.mode != "database"
      self.password = "<PASSWORD>"
    end
  end

  def change_password(oldpwd, newpwd)
    mode = self.class.mode
    raise MiqException::MiqEVMLoginError, "password change not allowed when authentication mode is #{mode}" unless mode == "database"
    raise MiqException::MiqEVMLoginError, "old password does not match current password" unless User.authenticate(self.userid, oldpwd)

    self.password = <PASSWORD>
    self.save!
  end

  def get_filters
    filters = self.current_group.get_filters if self.current_group
    filters || {"managed" => [], "belongsto" => []}
  end

  def has_filters?
    !(self.get_managed_filters.blank? && self.get_belongsto_filters.blank?)
  end

  def get_managed_filters
    self.get_filters["managed"]
  end

  def get_belongsto_filters
    self.get_filters["belongsto"]
  end

  def self_service_user?
    return false if self.current_group.nil?
    self.current_group.self_service_group?
  end
  alias_method :self_service?, :self_service_user?

  def limited_self_service_user?
    return false if self.current_group.nil?
    self.current_group.limited_self_service_group?
  end
  alias_method :limited_self_service?, :limited_self_service_user?

  def super_admin_user?
    self.miq_user_role_name == EVMROLE_SUPER_ADMIN_ROLE_NAME
  end

  def admin_user?
    # Check for admin or super_admin
    role_name = self.miq_user_role_name
    role_name == EVMROLE_SUPER_ADMIN_ROLE_NAME || role_name == EVMROLE_ADMIN_ROLE_NAME
  end

  def ldap_group
    self.current_group ? self.current_group.description : nil
  end
  alias miq_group_description ldap_group

  def role_allows?(options={})
    return false if self.miq_user_role.nil?
    feature = MiqProductFeature.find_by_identifier(options[:identifier])
    identifiers = {:identifiers => [options[:identifier]]}
    feature.try(:hidden) ? miq_user_role.allows_any?(identifiers) : miq_user_role.allows?(options)
  end

  def role_allows_any?(options={})
    return false if self.miq_user_role.nil?
    self.miq_user_role.allows_any?(options)
  end

  def role_allows_all?(options={})
    return false if self.miq_user_role.nil?
    self.miq_user_role.allows_all?(options)
  end

  def miq_user_role_name
    self.miq_user_role.try(:name)
  end

  def self.mode
    VMDB::Config.new("vmdb").config[:authentication][:mode]
  end

  def self.ldaphost(host=nil)
    @@ldaphost = host unless host == nil
    return @@ldaphost
  end

  def self.basedn(dn=nil)
    @@basedn = dn unless dn == nil
    return @@basedn
  end

  def self.verify_ldap_credentials(username, password)
    ldap = MiqLdap.new
    fq_user = ldap.normalize(ldap.fqusername(username))
    raise MiqException::MiqEVMLoginError, "authentication failed" unless ldap.bind(fq_user, password)
  end

  def self.authenticate(username, password, request = nil, options = {})
    options = options.dup
    options[:require_user] ||= false
    fail_message = "Authentication failed"
    mode = self.mode

    begin
      user_or_taskid = if mode == "database" || username == "admin"
        self.authenticate_database(username, password)
      elsif mode == "ldap" || mode == "ldaps"
        self.authenticate_ldap(username, password)
      elsif mode == "amazon"
        self.authenticate_amazon(username, password)
      elsif mode == "httpd"
        authenticate_httpd(username, request)
      end

      raise MiqException::MiqEVMLoginError, fail_message if user_or_taskid.nil?
    rescue MiqException::MiqEVMLoginError => err
      $log.warn err.message
      raise
    rescue Exception => err
      $log.log_backtrace(err)
      raise MiqException::MiqEVMLoginError, err.message
    end

    if options[:require_user] && !user_or_taskid.kind_of?(self)
      task = MiqTask.wait_for_taskid(user_or_taskid, options)
      if task.nil? || MiqTask.status_error?(task.status) || MiqTask.status_timeout?(task.status)
        raise MiqException::MiqEVMLoginError, fail_message
      end
      user_or_taskid = find_by_userid(task.userid)
    end

    if user_or_taskid.kind_of?(self)
      user_or_taskid.lastlogon = Time.now.utc
      user_or_taskid.save!
    end
    return user_or_taskid
  end

  def self.authenticate_database(username, password)
    audit = {:event => "authenticate_database", :message => "Authentication failed for user #{username}", :userid => username}
    user = self.find_by_userid(username)

    if user.nil? || !(user.authenticate_bcrypt(password))
      AuditEvent.failure(audit)
      return nil
    end
    AuditEvent.success(audit.merge(:message => "Authentication successful for user #{username}"))

    return user
  end

  def self.authenticate_amazon(username, password)
    auth = VMDB::Config.new("vmdb").config[:authentication]
    audit = {:event => "authenticate_amazon", :userid => username}
    if password.blank?
      AuditEvent.failure(audit.merge(:message => "Authentication failed for user #{username}"))
      return nil
    end

    amazon_auth = AmazonAuth.new
    if amazon_auth.iam_authenticate(username, password)
      AuditEvent.success(audit.merge(:message => "User #{username} successfully validated as Amazon IAM user"))

      if auth[:amazon_role] == true
        user = self.authorize_amazon_queue(username)
      else
        # If role_mode == database we will only use amazon for authentication. Also, the user must exist in our database
        # otherwise we will fail authentication
        user = self.find_by_userid(username)
        unless user
          AuditEvent.failure(audit.merge(:message => "User #{username} authenticated but not defined in EVM"))
          raise MiqException::MiqEVMLoginError, "User authenticated but not defined in EVM, please contact your EVM administrator"
        end
        return nil unless user
      end

      AuditEvent.success(audit.merge(:message => "Authentication successful for user #{username}"))
      return user
    else
      AuditEvent.failure(audit.merge(:message => "Authentication failed for userid #{username}"))
      return nil
    end

    return user
  end

  def self.authorize_amazon_queue(username)
    task = MiqTask.create(:name => "Amazon IAM User Authorization of '#{username}'", :userid => username)
    unless MiqEnvironment::Process.is_ui_worker_via_command_line?
      cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
      MiqQueue.put(
        :queue_name   => "generic",
        :class_name   => self.to_s,
        :method_name  => "authorize_amazon",
        :args         => [task.id, username],
        :server_guid  => MiqServer.my_guid,
        :priority     => MiqQueue::HIGH_PRIORITY,
        :miq_callback => cb
      )
    else
      self.authorize_amazon(task.id, username)
    end

    return task.id
  end

  def self.authorize_amazon(taskid, username)
    log_prefix = "MIQ(User.authorize_amazon):"
    audit = {:event => "authorize_amazon", :userid => username}

    task = MiqTask.find_by_id(taskid)
    if task.nil?
      message = "#{log_prefix} Unable to find task with id: [#{taskid}]"
      $log.error(message)
      raise message
    end
    task.update_status("Active", "Ok", "Authorizing")

    begin
      auth = VMDB::Config.new("vmdb").config[:authentication]
      # Amazon IAM will be used for authentication and role assignment
      $log.info("#{log_prefix} AWS key: [#{auth[:amazon_key]}]")
      amazon_auth = AmazonAuth.new(:auth=>auth)
      $log.info("#{log_prefix}  User: [#{username}]")
      amazon_user = amazon_auth.iam_user(username)
      $log.debug("#{log_prefix} User obj from Amazon: #{amazon_user.inspect}")
      unless amazon_user
        msg = "Authentication failed for userid #{username}, unable to find IAM user object in Amazon"
        $log.warn("#{log_prefix}: #{msg}")
        AuditEvent.failure(audit.merge(:message => msg))
        task.error(msg)
        task.state_finished
        return nil
      end

      matching_groups = match_groups(amazon_auth.get_memberships(amazon_user))
      user   = self.find_by_userid(username) || self.new(:userid => username)
      user.update_attrs_from_iam(amazon_auth, amazon_user, username)
      user.save_successful_logon(matching_groups, audit, task)
    rescue Exception => err
      $log.log_backtrace(err)
      task.error(err.message)
      AuditEvent.failure(audit.merge(:message=> err.message))
      task.state_finished
      raise
    end
  end

  def self.authenticate_ldap(username, password)
    auth = VMDB::Config.new("vmdb").config[:authentication]
    audit = {:event => "authenticate_ldap", :userid => username}
    if password.blank?
      AuditEvent.failure(audit.merge(:message => "Authentication failed for user #{username}"))
      return nil
    end

    ldap = MiqLdap.new
    fq_user = ldap.normalize(ldap.fqusername(username))

    if ldap.bind(fq_user, password)
      AuditEvent.success(audit.merge(:message => "User #{fq_user} successfully binded to LDAP directory"))

      if auth[:ldap_role] == true
        user = self.authorize_ldap_queue(fq_user)
      else
        # If role_mode == database we will only use ldap for authentication. Also, the user must exist in our database
        # otherwise we will fail authentication
        user = self.find_by_userid(fq_user)

        default_group = MiqGroup.where(:description => auth[:default_group_for_users]).first if auth[:default_group_for_users]
        if user.nil? && default_group
          # when default group for ldap users is enabled, create the user
          user = new
          lobj = ldap.get_user_object(fq_user)
          user.update_attrs_from_ldap(ldap, lobj)
          user.save_successful_logon([default_group], audit)
          $log.info("MIQ(User.authenticate_ldap): Created User: [#{user.userid}]")
        end

        unless user
          AuditEvent.failure(audit.merge(:message => "User #{fq_user} authenticated but not defined in EVM"))
          raise MiqException::MiqEVMLoginError, "User authenticated but not defined in EVM, please contact your EVM administrator"
        end
      end

      AuditEvent.success(audit.merge(:message => "Authentication successful for user #{fq_user}"))
      return user
    else
      AuditEvent.failure(audit.merge(:message => "Authentication failed for userid #{fq_user}"))
      return nil
    end
  end

  def self.authorize_ldap_queue(fq_user)
    task = MiqTask.create(:name => "LDAP User Authorization of '#{fq_user}'", :userid => fq_user)
    unless MiqEnvironment::Process.is_ui_worker_via_command_line?
      cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
      MiqQueue.put(
        :queue_name   => "generic",
        :class_name   => self.to_s,
        :method_name  => "authorize_ldap",
        :args         => [task.id, fq_user],
        :server_guid  => MiqServer.my_guid,
        :priority     => MiqQueue::HIGH_PRIORITY,
        :miq_callback => cb
      )
    else
      self.authorize_ldap(task.id, fq_user)
    end

    return task.id
  end

  def self.authorize_ldap(taskid, fq_user)
    log_prefix = "MIQ(User.authorize_ldap):"
    audit = {:event => "authorize_ldap", :userid => fq_user}

{'fraction_non_alphanumeric': 0.10684002261164499, 'fraction_numerical': 0.0003990290293618861, 'mean_word_length': 3.617534162444342, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 197, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3184869', 'n_tokens_mistral': 10018, 'n_tokens_neox': 9798, 'n_words': 2123}