ManageIQ/manageiq-ui-classic

View on GitHub
app/controllers/ops_controller/ops_rbac.rb

Summary

Maintainability
F
1 wk
Test Coverage
F
55%
# Access Control Accordion methods included in OpsController.rb
module OpsController::OpsRbac
  extend ActiveSupport::Concern

  TAG_DB_TO_NAME = {
    'MiqGroup' => 'group',
    'User'     => 'user',
    'Tenant'   => 'tenant'
  }.freeze

  def role_allows?(**options)
    if MiqProductFeature.my_root_tenant_identifier?(options[:feature]) && params.key?(:id) && params[:id] != 'xx-tn'
      if params[:id].to_s.include?('tn')
        _, id, _ = TreeBuilder.extract_node_model_and_id(params[:id].to_s)
      else
        id = params[:id].to_s
      end

      options[:feature] = MiqProductFeature.tenant_identifier(options[:feature], id)
    end

    super(**options)
  end

  # Edit user or group tags
  def rbac_tags_edit
    assert_privileges("rbac_tenant_tags_edit")

    case params[:button]
    when "cancel"
      rbac_edit_tags_cancel
    when "save", "add"
      assert_privileges("rbac_#{TAG_DB_TO_NAME[session[:tag_db]]}_tags_edit")
      rbac_edit_tags_save
    when "reset", nil # Reset or first time in
      nodes = x_node.split('-')
      tagging = if nodes.first == "g" || nodes.last == "g"
                  'MiqGroup'
                elsif nodes.first == "u" || nodes.last == "u"
                  'User'
                elsif nodes.first == "tn" || nodes.last == "tn"
                  'Tenant'
                else
                  params[:tagging]
                end
      rbac_edit_tags_reset(tagging)
    end
  end

  def rbac_user_add
    assert_privileges("rbac_user_add")
    rbac_edit_reset('new', 'user', User)
  end

  def rbac_user_copy
    # get users id either from gtl check or detail id
    user_id = params[:miq_grid_checks].presence || params[:id]
    user = User.find(user_id)
    # check if it is allowed to copy the user
    if rbac_user_copy_restriction?(user)
      rbac_restricted_user_copy_flash(user)
    end
    if @flash_array
      javascript_flash
      return
    end
    assert_privileges("rbac_user_copy")
    rbac_edit_reset('copy', 'user', User)
  end

  def rbac_user_edit
    assert_privileges("rbac_user_edit")
    case params[:button]
    when 'cancel'      then rbac_edit_cancel('user')
    when 'save', 'add' then rbac_edit_save_or_add('user')
    when 'reset', nil  then rbac_edit_reset(params[:typ], 'user', User) # Reset or first time in
    end
  end

  def rbac_group_add
    assert_privileges("rbac_group_add")
    rbac_edit_reset('new', 'group', MiqGroup)
  end

  def rbac_group_edit
    assert_privileges("rbac_group_edit")

    case params[:button]
    when 'cancel'            then rbac_edit_cancel('group')
    when 'save', 'add'       then rbac_edit_save_or_add('group')
    when 'reset', nil        then rbac_edit_reset(params[:typ], 'group', MiqGroup) # Reset or first time in
    end
  end

  def rbac_role_add
    assert_privileges("rbac_role_add")
    rbac_edit_reset('new', 'role', MiqUserRole)
  end

  def rbac_role_copy
    assert_privileges("rbac_role_copy")
    rbac_edit_reset('copy', 'role', MiqUserRole)
  end

  def rbac_role_edit
    assert_privileges("rbac_role_edit")
    case params[:button]
    when 'cancel'      then rbac_edit_cancel('role')
    when 'save', 'add' then rbac_edit_save_or_add('role', 'miq_user_role')
    when 'reset', nil  then rbac_edit_reset(params[:typ], 'role', MiqUserRole) # Reset or form load
    end
  end

  def rbac_tenant_add
    assert_privileges("rbac_tenant_add")
    @_params[:typ] = "new"
    @tenant_type = params[:tenant_type] == "tenant"
    @tenant_parent = Tenant.find(x_node.split('-').last).id
    rbac_tenant_edit
  end
  alias rbac_project_add rbac_tenant_add

  def rbac_tenant_edit_reset
    @tenant = params[:typ] == "new" ? Tenant.new : find_record_with_rbac(Tenant, checked_or_params)

    # This is only because ops_controller tries to set form locals, otherwise we should not use the @edit variable
    @edit = {:tenant_id => @tenant.id}

    # This is a hack to trick the controller into thinking we loaded an edit variable
    session[:edit] = {:key => "tenant_edit__#{@tenant.id || 'new'}"}

    session[:changed] = false

    replace_right_cell(:nodetype => "tenant_edit")
  end

  def rbac_tenant_edit
    assert_privileges("rbac_tenant_edit")
    rbac_tenant_edit_reset
  end

  def rbac_tenant_manage_quotas_cancel
    get_node_info(x_node)
    replace_right_cell(:nodetype => x_node)
  end

  def rbac_tenant_manage_quotas_save_add
    get_node_info(x_node)
    replace_right_cell(:nodetype => "root", :replace_trees => [:rbac])
  end

  def rbac_tenant_manage_quotas_reset
    @tenant = find_record_with_rbac(Tenant, checked_or_params)
    # This is only because ops_controller tries to set form locals, otherwise we should not use the @edit variable
    @edit = {:tenant_id => @tenant.id}
    session[:edit] = {:key => "tenant_manage_quotas__#{@tenant.id}"}
    session[:changed] = false
    replace_right_cell(:nodetype => "tenant_manage_quotas")
  end

  def rbac_tenant_manage_quotas
    assert_privileges("rbac_tenant_manage_quotas")
    case params[:button]
    when "cancel"
      rbac_tenant_manage_quotas_cancel
    when "save", "add"
      rbac_tenant_manage_quotas_save_add
    when "reset", nil # Reset or first time in
      rbac_tenant_manage_quotas_reset
    end
  end

  # Edit user or group tags
  def rbac_tenant_tags_edit
    case params[:button]
    when "cancel"
      rbac_edit_tags_cancel
    when "save", "add"
      assert_privileges("rbac_tenant_tags_edit")
      rbac_edit_tags_save
    when "reset", nil # Reset or first time in
      rbac_edit_tags_reset('Tenant')
    end
  end

  # AJAX driven routines to check for changes in ANY field on the form
  def rbac_user_field_changed
    assert_privileges(params[:id] == "new" ? "rbac_user_add" : "rbac_user_edit")

    rbac_field_changed("user")
  end

  def rbac_group_field_changed
    assert_privileges(params[:id] == "new" ? "rbac_group_add" : "rbac_group_edit")

    rbac_field_changed("group")
  end

  def rbac_role_field_changed
    assert_privileges(params[:id] == "new" ? "rbac_role_add" : "rbac_role_edit")

    rbac_field_changed("role")
  end

  def rbac_user_delete
    assert_privileges("rbac_user_delete")
    users = []
    if params[:id] # showing a list
      if params[:id].nil? || !User.exists?(:id => params[:id])
        add_flash(_("User no longer exists"), :error)
      else
        user = User.find(params[:id])
        if rbac_user_delete_restriction?(user)
          rbac_restricted_user_delete_flash(user)
        else
          users.push(params[:id])
        end
      end
      if @flash_array
        javascript_flash
        return
      end
      process_users(users, "destroy") unless users.empty?
      self.x_node = "xx-u" # reset node to show list
    else # showing 1 user, delete it
      ids = find_checked_items.collect { |r| r.to_s.split("-").last }
      users = User.where(:id => ids).compact
      if users.empty?
        add_flash(_("Default EVM User \"Administrator\" cannot be deleted"), :error)
        javascript_flash
        return
      else
        restricted_users = []
        users.each do |u|
          restricted_users.push(u) if rbac_user_delete_restriction?(u)
        end
        # deleting elements in temporary array, had to create temp array to hold id's to be delete, .each gets confused if i deleted them in above loop
        restricted_users.each do |u|
          rbac_restricted_user_delete_flash(u)
          users.delete(u)
        end
      end
      process_users(users, "destroy") unless users.empty?
    end
    get_node_info(x_node)
    replace_right_cell(:nodetype => x_node, :replace_trees => [:rbac])
  end

  def rbac_role_delete
    assert_privileges("rbac_role_delete")
    roles = []
    if params[:id].nil? # showing a role list
      ids = find_checked_items.collect { |r| r.to_s.split("-").last }
      roles = MiqUserRole.where(:id => ids)
      process_roles(roles, "destroy") unless roles.empty?
    else # showing 1 role, delete it
      roles.push(params[:id])
      process_roles(roles, "destroy") unless roles.empty?
      self.x_node = "xx-ur" unless MiqUserRole.exists?(:id => params[:id]) # reset node to show list
    end
    get_node_info(x_node)
    replace_right_cell(:nodetype => x_node, :replace_trees => [:rbac])
  end

  # Show the main Users/Groups/Roles list view
  def rbac_users_list
    assert_privileges("rbac_user_show_list")

    rbac_list("user")
  end

  def rbac_groups_list
    assert_privileges("rbac_group_show_list")

    rbac_list("group")
  end

  def rbac_roles_list
    assert_privileges("rbac_role_show_list")

    rbac_list("role")
  end

  def rbac_tenants_list
    assert_privileges("rbac_tenant_view")

    rbac_list("tenant")
  end

  def rbac_tenant_delete
    assert_privileges("rbac_tenant_delete")
    tenants = []
    if !params[:id] # showing a tenants list
      tenants = Tenant.where(:id => find_checked_items).reject do |t|
        add_flash(_("Default Tenant \"%{name}\" can not be deleted") % {:name => t.name}, :error) if t.parent.nil?
        t.parent.nil?
      end
    else # showing 1 tenant, delete it
      tenants.push(params[:id])
      parent_id = Tenant.find(params[:id]).parent.id
      self.x_node = "tn-#{parent_id}"
    end

    unless tenants.empty?
      process_tenants(tenants, "destroy")
      MiqProductFeature.invalidate_caches
    end

    get_node_info(x_node)
    replace_right_cell(:nodetype => x_node, :replace_trees => [:rbac])
  end

  def rbac_group_delete
    assert_privileges("rbac_group_delete")
    groups = []
    if params[:id].nil? # showing a list
      ids = find_checked_items.collect { |r| r.to_s.split("-").last }
      groups = MiqGroup.where(:id => ids)
      process_groups(groups, "destroy") unless groups.empty?
      self.x_node = "xx-g" # reset node to show list
    else # showing 1 group, delete it
      groups.push(params[:id])
      process_groups(groups, "destroy") unless groups.empty?
      self.x_node = "xx-g" unless MiqGroup.exists?(:id => params[:id]) # reset node to show list
    end
    get_node_info(x_node)
    replace_right_cell(:nodetype => x_node, :replace_trees => [:rbac])
  end

  def rbac_group_seq_edit
    assert_privileges("rbac_group_seq_edit")
    case params[:button]
    when "cancel"
      @edit = nil
      add_flash(_("Edit Sequence of User Groups was cancelled by the user"))
      get_node_info(x_node)
      replace_right_cell(:nodetype => x_node)
    when "save"
      return unless load_edit("rbac_group_edit__seq", "replace_cell__explorer")

      err = false
      @edit[:new][:ldap_groups_list].each_with_index do |grp, i|
        group = MiqGroup.find_by(:description => grp)
        group.sequence = i + 1
        if group.save
          AuditEvent.success(build_saved_audit(group, @edit))
        else
          group.errors.each do |error|
            add_flash("#{error.attribute.to_s.capitalize} #{error.message}", :error)
          end
          err = true
        end
      end
      if !err
        add_flash(_("User Group Sequence was saved"))
        @_in_a_form = false
        @edit = session[:edit] = nil # clean out the saved info
        get_node_info(x_node)
        replace_right_cell(:nodetype => x_node)
      else
        drop_breadcrumb(:name => _("Edit User Group Sequence"), :url => "/configuration/ldap_seq_edit")
        @in_a_form = true
        replace_right_cell(:nodetype => "group_seq")
      end
    when "reset", nil # Reset or first time in
      rbac_group_seq_edit_screen
      @in_a_form = true
      if params[:button] == "reset"
        add_flash(_("All changes have been reset"), :warning)
      end
      replace_right_cell(:nodetype => "group_seq")
    end
  end

  def rbac_group_seq_edit_screen
    @edit = {}
    @edit[:new] = {}
    @edit[:current] = {}
    @edit[:new][:ldap_groups] = MiqGroup.non_tenant_groups.sort_by(&:sequence) # Get the non-tenant groups from the DB
    @edit[:new][:ldap_groups_list] = []
    @edit[:new][:ldap_groups].each do |g|
      @edit[:new][:ldap_groups_list].push(g.description)
    end
    @edit[:key] = "rbac_group_edit__seq"
    @edit[:current] = copy_hash(@edit[:new])

    @right_cell_text = _("Editing Sequence of User Groups")

    session[:edit] = @edit
    session[:changed] = false
  end

  def move_cols_up
    return unless load_edit("rbac_group_edit__seq", "replace_cell__explorer")

    if params[:seq_fields].blank? || params[:seq_fields][0] == ""
      add_flash(_("No fields were selected to move up"), :error)
      return
    end
    consecutive, first_idx, last_idx = selected_consecutive?
    if !consecutive
      add_flash(_("Select only one or consecutive fields to move up"), :error)
    else
      if first_idx.positive?
        @edit[:new][:ldap_groups_list][first_idx..last_idx].reverse_each do |field|
          pulled = @edit[:new][:ldap_groups_list].delete(field)
          @edit[:new][:ldap_groups_list].insert(first_idx - 1, pulled)
        end
      end
      @refresh_div = "column_lists"
      @refresh_partial = "ldap_seq_form"
    end
    @selected = params[:seq_fields]
  end

  def move_cols_down
    return unless load_edit("rbac_group_edit__seq", "replace_cell__explorer")

    if params[:seq_fields].blank? || params[:seq_fields][0] == ""
      add_flash(_("No fields were selected to move down"), :error)
      return
    end
    consecutive, first_idx, last_idx = selected_consecutive?
    if !consecutive
      add_flash(_("Select only one or consecutive fields to move down"), :error)
    else
      if last_idx < @edit[:new][:ldap_groups_list].length - 1
        insert_idx = last_idx + 1 # Insert before the element after the last one
        insert_idx = -1 if last_idx == @edit[:new][:ldap_groups_list].length - 2 # Insert at end if 1 away from end
        @edit[:new][:ldap_groups_list][first_idx..last_idx].each do |field|
          pulled = @edit[:new][:ldap_groups_list].delete(field)
          @edit[:new][:ldap_groups_list].insert(insert_idx, pulled)
        end
      end
      @refresh_div = "column_lists"
      @refresh_partial = "ldap_seq_form"
    end
    @selected = params[:seq_fields]
  end

  def selected_consecutive?
    first_idx = last_idx = 0
    @edit[:new][:ldap_groups_list].each_with_index do |nf, idx|
      first_idx = idx if nf == params[:seq_fields].first
      if nf == params[:seq_fields].last
        last_idx = idx
        break
      end
    end
    consecutime = last_idx - first_idx + 1 <= params[:seq_fields].length
    [consecutime, first_idx, last_idx]
  end

  def rbac_group_user_lookup_field_changed
    return unless load_edit("rbac_group_edit__#{params[:id]}", "replace_cell__explorer")

    @edit[:new][:user]     = params[:user]     if params[:user]
    @edit[:new][:user_id]  = params[:user_id]  if params[:user_id]
    @edit[:new][:user_pwd] = params[:password] if params[:password]
  end

  def rbac_group_user_lookup
    assert_privileges(params[:id] == "new" ? "rbac_group_add" : "rbac_group_edit")


    rbac_group_user_lookup_field_changed
    add_flash(_("User must be entered to perform LDAP Group Look Up"), :error) if @edit[:new][:user].blank?

    if ::Settings.authentication.mode != "httpd"
      add_flash(_("Username must be entered to perform LDAP Group Look Up"), :error) if @edit[:new][:user_id].blank?
      add_flash(_("User Password must be entered to perform LDAP Group Look Up"), :error) if @edit[:new][:user_pwd].blank?
    end

    unless @flash_array.nil?
      javascript_flash
      return
    end

    @record = MiqGroup.find_by(:id => @edit[:group_id])
    @sb[:roles] = @edit[:roles]
    begin
      @edit[:ldap_groups_by_user] = if ::Settings.authentication.mode == "httpd"
                                      MiqGroup.get_httpd_groups_by_user(@edit[:new][:user])
                                    else
                                      MiqGroup.get_ldap_groups_by_user(@edit[:new][:user],
                                                                       @edit[:new][:user_id],
                                                                       @edit[:new][:user_pwd])
                                    end
    rescue => bang
      @edit[:ldap_groups_by_user] = []
      add_flash(_("Error during 'LDAP Group Look Up': %{message}") % {:message => bang.message}, :error)
      render :update do |page|
        page << javascript_prologue
        page.replace("flash_msg_div", :partial => "layouts/flash_msg")
        page << "miqScrollTop();" if @flash_array.present?
        page.replace("ldap_user_div", :partial => "ldap_auth_users")
      end
    else
      render :update do |page|
        page << javascript_prologue
        page.replace("ldap_user_div", :partial => "ldap_auth_users")
      end
    end
  end

  private ############################

  def tenant_type_title_string(divisible)
    divisible ? _("Tenant") : _("Project")
  end

  # super administrator user with `userid` == "admin" can not be deleted
  # and user can not delete himself
  def rbac_user_delete_restriction?(user)
    user.admin? || User.current_user == user
  end

  def rbac_user_copy_restriction?(user)
    user.super_admin_user?
  end

  def rbac_restricted_user_delete_flash(user)
    msg = if user.super_admin_user?
            _("Default EVM User \"%{name}\" cannot be deleted")
          else
            _("Current EVM User \"%{name}\" cannot be deleted")
          end
    add_flash(msg % {:name => user.name}, :error)
  end

  def rbac_restricted_user_copy_flash(user)
    add_flash(_("Default EVM User \"%{name}\" cannot be copied") % {:name => user.name}, :error)
  end

  def rbac_edit_tags_reset(tagging)
    @object_ids = find_records_with_rbac(tagging.constantize, checked_or_params).ids
    if params[:button] == "reset"
      id = params[:id] if params[:id]
      return unless load_edit("#{session[:tag_db]}_edit_tags__#{id}", "replace_cell__explorer")

      @object_ids = @edit[:object_ids]
      session[:tag_db] = @tagging = @edit[:tagging]
    else
      @object_ids[0] = params[:id] if @object_ids.blank? && params[:id]
      session[:tag_db] = @tagging = tagging
    end

    x_tags_set_form_vars
    @in_a_form = true
    session[:changed] = false
    add_flash(_("All changes have been reset"), :warning) if params[:button] == "reset"
    @sb[:pre_edit_node] = x_node  unless params[:button] # Save active tree node before edit
    @right_cell_text = _("Editing %{model} for \"%{name}\"") % {:name => ui_lookup(:models => @tagging), :model => "#{current_tenant.name} Tags"}
    replace_right_cell(:nodetype => "root")
  end

  def rbac_edit_tags_cancel
    id = params[:id]
    return unless load_edit("#{session[:tag_db]}_edit_tags__#{id}", "replace_cell__explorer")

    add_flash(_("Tag Edit was cancelled by the user"))
    self.x_node = @sb[:pre_edit_node]
    get_node_info(x_node)
    @edit = nil # clean out the saved info
    replace_right_cell(:nodetype => @nodetype)
  end

  def rbac_edit_tags_save
    tagging_edit_tags_save_and_replace_right_cell
  end

  def rbac_edit_cancel(what)
    key = what.to_sym
    id = params[:id] || "new"
    return unless load_edit("rbac_#{what}_edit__#{id}", "replace_cell__explorer")

    case key
    when :role
      record_id = @edit[:role_id]
    when :group
      record_id = @edit[:group_id]
    when :user
      record_id = @edit[:user_id]
    when :tenant
      record_id = id
    end
    add_flash(if record_id
                _("Edit of %{name} was cancelled by the user") % {:name => what.titleize}
              else
                _("Add of new %{name} was cancelled by the user") % {:name => what.titleize}
              end)
    self.x_node = @sb[:pre_edit_node]
    get_node_info(x_node)
    @edit = nil # clean out the saved info
    replace_right_cell(:nodetype => @nodetype)
  end

  def rbac_edit_reset(operation, what, klass)
    key = what.to_sym
    if operation != "new"
      record = find_record_with_rbac(klass, checked_or_params)
      if %i[group role].include?(key) && record && record.read_only && operation != 'copy'
        model, name = if key == :role
                        [_('Role'), record.name]
                      else
                        [_('EVM Group'), record.description]
                      end
        add_flash(_("Read Only %{model} \"%{name}\" can not be edited") % {:model => model, :name => name }, :warning)
        javascript_flash
        return
      end
    end

    case operation
    when "new"
      # create new record
      @record = klass.new
      if key == :role
        @record.miq_product_features = [MiqProductFeature.find_by(:identifier => MiqProductFeature.feature_root)]
      end
    when "copy"
      # copy existing record
      @record = record.clone
      case key
      when :user
        @record.current_group = record.current_group
      when :group
        @record.miq_user_role = record.miq_user_role
      when :role
        @record.miq_product_features = record.miq_product_features
        @record.read_only = false
      end
    else
      # use existing record
      @record = record
    end
    @sb[:typ] = operation

    # set form fields according to what is copied
    case key
    when :user  then rbac_user_set_form_vars
    when :group then rbac_group_set_form_vars
    when :role  then rbac_role_set_form_vars
    end

    @in_a_form = true
    session[:changed] = key == :group ? @deleted_belongsto_filters.present? : false
    add_flash(_("All changes have been reset"), :warning) if params[:button] == "reset"
    @sb[:pre_edit_node] = x_node  unless params[:button] # Save active tree node before edit
    replace_right_cell(:nodetype => x_node)
  end

  def rbac_edit_save_or_add(what, rbac_suffix = what)
    key         = what.to_sym
    id          = params[:id] || "new"
    add_pressed = params[:button] == "add"

    return unless load_edit("rbac_#{what}_edit__#{id}", "replace_cell__explorer")

    case key
    when :user
      record = @edit[:user_id] ? User.find_by(:id => @edit[:user_id]) : User.new
      validated = rbac_user_validate?
      rbac_user_set_record_vars(record)
    when :group then
      record = @edit[:group_id] ? MiqGroup.find_by(:id => @edit[:group_id]) : MiqGroup.new
      validated = rbac_group_validate?
      rbac_group_set_record_description_role(record) # Set new Description, Role and Tenant for a new Group
      rbac_group_set_record_vars(record) if validated
    when :role  then
      record = @edit[:role_id] ? MiqUserRole.find_by(:id => @edit[:role_id]) : MiqUserRole.new
      validated = rbac_role_validate?
      rbac_role_set_record_vars(record)
    end

    if record.valid? && validated && record.save!
      record.update!(:miq_groups => Rbac.filtered(MiqGroup.where(:id => rbac_user_get_group_ids))) if key == :user # only set miq_groups if everything is valid
      populate_role_features(record) if what == "role"
      self.current_user = record if what == 'user' && @edit[:current][:userid] == current_userid
      AuditEvent.success(build_saved_audit(record, @edit))
      subkey = key == :group ? :description : :name
      add_flash(_("%{model} \"%{name}\" was saved") % {:model => what.titleize, :name => @edit[:new][subkey]})
      add_flash(_("Outdated filters were removed from group \"%{name}\"") % {:name => @edit[:new][subkey]}) if what == "group" && @edit[:current][:deleted_belongsto_filters].present?
      @edit = session[:edit] = nil # clean out the saved info
      if add_pressed
        suffix = case rbac_suffix
                 when "group"         then "g"
                 when "miq_user_role" then "ur"
                 when "user"          then "u"
                 end
        self.x_node = "xx-#{suffix}" # reset node to show list
        send("rbac_#{what.pluralize}_list")
      end
      # Get selected Node
      get_node_info(x_node)
      replace_right_cell(:nodetype => x_node, :replace_trees => [:rbac])
      return
    end

    @changed = session[:changed] = (@edit[:new] != @edit[:current])
    record.errors.each { |error| add_flash("#{error.attribute.to_s.capitalize} #{error.message}", :error) }

    render_flash
  end

  # Show the main Users/Gropus/Roles list views
  def rbac_list(rec_type)
    rbac_build_list(rec_type)
    update_gtl_div("rbac_#{rec_type.pluralize}_list") if pagination_or_gtl_request? && @show_list
  end

  # Create the view and associated vars for the rbac list
  def rbac_build_list(rec_type)
    @lastaction = "rbac_#{rec_type}s_list"
    @force_no_grid_xml = true
    if params[:ppsetting]                                             # User selected new per page value
      @items_per_page = params[:ppsetting].to_i                       # Set the new per page value
      @settings.store_path(:perpage, :list, @items_per_page) # Set the per page setting for this gtl type
    end
    @sortcol = session["rbac_#{rec_type}_sortcol"].nil? ? 0 : @sb["rbac_#{rec_type}_sortcol"].to_i
    @sortdir = session["rbac_#{rec_type}_sortdir"].nil? ? "ASC" : @sb["rbac_#{rec_type}_sortdir"]

    # Get the records (into a view) and the paginator
    @view, @pages = case rec_type
                    when "user"
                      get_view(User, :named_scope => :in_my_region)
                    when "group"
                      get_view(MiqGroup, :named_scope => :non_tenant_groups_in_my_region)
                    when "role"
                      get_view(MiqUserRole)
                    when "tenant"
                      get_view(Tenant, :named_scope => :in_my_region)
                    end

    @current_page = @pages[:current] unless @pages.nil? # save the current page number
    @sb["rbac_#{rec_type}_sortcol"] = @sortcol
    @sb["rbac_#{rec_type}_sortdir"] = @sortdir
  end

  # AJAX driven routine to check for changes in ANY field on the form
  def rbac_field_changed(rec_type)
    id = params[:id].split('__').first || 'new' # Get the record id
    id = id unless %w[new seq].include?(id)
    return unless load_edit("rbac_#{rec_type}_edit__#{id}", "replace_cell__explorer")

    case rec_type
    when "user"  then rbac_user_get_form_vars
    when "group" then rbac_group_get_form_vars
    when "role"  then rbac_role_get_form_vars
    end

    @edit[:new][:group] = rbac_user_get_group_ids if rec_type == "user"

    session[:changed] = changed = @edit[:new] != @edit[:current]

    render :update do |page|
      page << javascript_prologue
      if %w[up down].include?(params[:button])
        if @refresh_div
          page.replace("flash_msg_div", :partial => "layouts/flash_msg") if @refresh_div == "column_lists"
          page << "miqScrollTop();" if @flash_array.present?
          page.replace(@refresh_div, :partial => @refresh_partial)
        end
      else
        # only do following for user (adding/editing a user)
        if x_node.split("-").first == "u" || x_node == "xx-u"
          page.replace("group_selected",
                       :partial => "ops/rbac_group_selected")
        end
        # only do following for groups
        if @refresh_div
          page.replace(@refresh_div,
                       :partial => @refresh_partial,
                       :locals  => {:type => "classifications", :action_url => 'rbac_group_field_changed'})
        end

        page.replace("customer_tags_div", :partial => "ops/rbac_group/customer_tags") if params[:use_filter_expression].present?

        # Only update description field value if ldap group user field was selected
        page << "$('#description').val('#{j_str(@edit[:new][:ldap_groups_user])}');" if params[:ldap_groups_user]

        # don't do anything to lookup box when checkboxes on the right side are checked
        page << set_element_visible('group_lookup', @edit[:new][:lookup]) unless params[:check]
      end
      page << javascript_for_miq_button_visibility(changed)
    end
  end

  # Common User button handler routine
  def process_groups(groups, task)
    process_elements(groups, MiqGroup, task)
  end

  # Common User button handler routine
  def process_users(users, task)
    process_elements(users, User, task)
  end

  # Common Role button handler routine
  def process_roles(roles, task)
    process_elements(roles, MiqUserRole, task)
  end

  def process_tenants(tenants, task)
    process_elements(tenants, Tenant, task, _("Tenant"), "name")
  end

  # Get information for an access control node
  def rbac_get_info
    node, id = x_node.split("-")
    case node
    when "xx"
      case id
      when "u"
        @right_cell_text = _("Access Control EVM Users")
        rbac_users_list
      when "g"
        @right_cell_text = _("Access Control EVM Groups")
        rbac_groups_list
      when "ur"
        @right_cell_text = _("Access Control Roles")
        rbac_roles_list
      when "tn"
        @right_cell_text = _("Access Control Tenants")
        rbac_tenants_list
      end
    when "u"
      @right_cell_text = _("EVM User \"%{name}\"") % {:name => User.find(id).name}
      rbac_user_get_details(id)
    when "g"
      @right_cell_text = _("EVM Group \"%{name}\"") % {:name => MiqGroup.find(id).description}
      @edit = nil
      rbac_group_get_details(id)
    when "ur"
      @right_cell_text = _("Role \"%{name}\"") % {:name => MiqUserRole.find(id).name}
      rbac_role_get_details(id)
    when "tn"
      rbac_tenant_get_details(id)
      @right_cell_text = _("%{model} \"%{name}\"") % {:model => tenant_type_title_string(@tenant.divisible),
                                                      :name  => @tenant.name}
    else # Root node
      @right_cell_text = _("Access Control Region \"%{name}\"") %
                         {:name => "#{MiqRegion.my_region.description} [#{MiqRegion.my_region.region}]"}
      @users_count = Rbac.filtered(User.in_my_region).count
      @groups_count = Rbac.filtered(MiqGroup.non_tenant_groups_in_my_region).count
      @roles_count = Rbac.filtered(MiqUserRole).count
      @tenants_count = Rbac.filtered(Tenant.in_my_region).count
    end
  end

  def rbac_user_get_details(id)
    @edit = nil
    @record = @user = User.find(id)
    get_tagdata(@user)
  end

  def rbac_tenant_get_details(id)
    @record = @tenant = find_record_with_rbac(Tenant, id)
    get_tagdata(@tenant)
  end

  def rbac_group_get_details(id)
    @record = @group = MiqGroup.find_by(:id => id)
    @belongsto = {}
    @filters = {}
    @filter_expression = []
    @use_filter_expression = false
    if @record.present?
      get_tagdata(@group)
      @use_filter_expression = @group.entitlement[:filter_expression].kind_of?(MiqExpression)
      # Build the belongsto filters hash
      @group.get_belongsto_filters.each do |b| # Go thru the belongsto tags
        bobj = MiqFilter.belongsto2object(b) # Convert to an object
        if bobj
          @belongsto[bobj.class.to_s + "_" + bobj.id.to_s] = b # Store in hash as <class>_<id> string
        else
          @deleted_belongsto_filters ||= []
          @deleted_belongsto_filters.push(MiqFilter.belongsto2path_human(b))
        end
      end
      # Build the managed filters hash
      [@group.get_managed_filters].flatten.each do |f|
        @filters[f.split("/")[-2] + "-" + f.split("/")[-1]] = f
      end
    end

    rbac_group_right_tree(@belongsto.keys)
  end

  # this causes the correct tree to get instantiated, depending on the active tab
  def rbac_group_right_tree(selected_nodes)
    case @sb[:active_rbac_group_tab]
    when 'rbac_customer_tags'
      cats = Classification.categories.select do |c|
        c.show || !%w[folder_path_blue folder_path_yellow].include?(c.name) && !(c.read_only? || c.entries.empty?)
      end
      cats.sort_by! { |t| t.description.try(:downcase) } # Get the categories, sort by description
      tags = cats.map do |cat|
        {
          :id          => cat.id.to_s,
          :description => cat.description,
          :singleValue => false,
          :values      => cat.entries.sort_by { |e| e[:description.downcase] }.map do |entry|
            { :id => entry.id.to_s, :description => entry.description }
          end
        }
      end

      filters = @edit&.fetch_path(:new, :filters) || @filters
      assigned_tags = Tag.where(:name => filters.flatten).map do |tag|
        {
          :description => tag.category.description,
          :id          => tag.category.id.to_s,
          :values      => [{:id => tag.classification.id.to_s, :description => tag.classification.description}]
        }
      end

      assigned_tags.each_with_object([]) do |tag, arr|
        existing_tag = arr.find { |item| item[:id] == tag[:id] }
        if existing_tag
          existing_tag[:values].push(*tag[:values])
        else
          arr << tag
        end
      end

      assigned_tags.uniq! { |tag| tag[:id] }
      group_id = @group&.id
      @tags = {:tags => tags, :assignedTags => assigned_tags, :affectedItems => [group_id.to_s]}
      @button_urls = {
        :save_url   => url_for_only_path(:action => "rbac_group_edit", :id => group_id, :button => "save"),
        :cancel_url => url_for_only_path(:action => "rbac_group_edit", :id => group_id, :button => "cancel")
      }
    when 'rbac_hosts_clusters'
      @hac_tree = TreeBuilderBelongsToHac.new(:hac_tree,
                                              @sb,
                                              true,
                                              :edit           => @edit,
                                              :group          => @group,
                                              :selected_nodes => selected_nodes)
    when 'rbac_vms_templates'
      @vat_tree = TreeBuilderBelongsToVat.new(:vat_tree,
                                              @sb,
                                              true,
                                              :edit           => @edit,
                                              :group          => @group,
                                              :selected_nodes => selected_nodes)
    end
  end

  def rbac_role_get_details(id)
    @edit = nil
    @record = @role = MiqUserRole.find(id)
    @rbac_menu_tree = build_rbac_feature_tree
  end

  def build_rbac_feature_tree
    @role = @sb[:typ] == "copy" ? @record.dup : @record if @role.nil? # if on edit screen use @record
    @role.miq_product_features = @record.miq_product_features if @sb[:typ] == "copy"
    # The edit/noedit tree should have a different name due to a collision between RJS and Redux
    TreeBuilderOpsRbacFeatures.new(@edit.present? ? "features_tree" : "features_tree_noedit", @sb, true, :role => @role, :editable => @edit.present?)
  end

  # Set form variables for user edit
  def rbac_user_set_form_vars
    copy = @sb[:typ] == "copy"
    # save a shadow copy of the record if record is being copied
    @user = copy ? @record.dup : @record
    @user.miq_groups = @record.miq_groups if copy
    @edit = {:new => {}, :current => {}}
    @edit[:user_id] = @record.id unless copy
    @edit[:key] = "rbac_user_edit__#{@edit[:user_id] || "new"}"
    # prefill form fields for edit and copy action
    @edit[:new].merge!(:name  => @user.name,
                       :email => @user.email,
                       :group => @user.miq_groups ? @user.miq_groups.map(&:id).sort : nil)
    unless copy
      @edit[:new].merge!(:userid   => @user.userid,
                         :password => @user.password,
                         :verify   => @user.password)
    end
    # load all user groups, filter available for tenant
    @edit[:groups] = Rbac.filtered(MiqGroup.non_tenant_groups_in_my_region).sort_by { |g| g.description.downcase }.collect { |g| [g.description, g.id] }
    # store current state of the new users information
    @edit[:current] = copy_hash(@edit[:new])
    @right_cell_text = if @edit[:user_id]
                         _("Editing User \"%{name}\"") % {:name => @record.name}
                       else
                         _('Adding a new User')
                       end
  end

  # Get variables from user edit form
  def rbac_user_get_form_vars
    copy_params_if_present(@edit[:new], params, %i[name password verify])

    @edit[:new][:userid] = params[:userid].strip.presence if params[:userid]
    @edit[:new][:email] = params[:email].strip.presence if params[:email]
    @edit[:new][:group] = params[:chosen_group] if params[:chosen_group]
  end

  # Set user record variables to new values
  def rbac_user_set_record_vars(user)
    user.name       = @edit[:new][:name]
    user.userid     = @edit[:new][:userid]
    user.email      = @edit[:new][:email]
    user.password   = @edit[:new][:password] if @edit[:new][:password]
  end

  # Get array of group ids
  def rbac_user_get_group_ids
    case @edit[:new][:group]
    when 'null', nil
      []
    when String
      @edit[:new][:group].split(',').delete_if(&:blank?).map(&:to_i).sort
    when Array
      @edit[:new][:group].map(&:to_i).sort
    end
  end

  # Validate some of the user fields
  def rbac_user_validate?
    valid = true
    if @edit[:new][:password] != @edit[:new][:verify]
      add_flash(_("Password/Verify Password do not match"), :error)
      valid = false
    end

    new_group_ids = rbac_user_get_group_ids
    new_groups = new_group_ids.present? && MiqGroup.find(new_group_ids).present? ? MiqGroup.find(new_group_ids) : []

    if new_group_ids.blank?
      add_flash(_("A User must be assigned to a Group"), :error)
      valid = false
    elsif Rbac.filtered(new_groups).count != new_group_ids.count
      add_flash(_("A User must be assigned to an allowed Group"), :error)
      valid = false
    end
    valid
  end

  def valid_tenant?(tenant_id)
    Rbac.filtered(Tenant.in_my_region.where(:id => tenant_id)).present?
  end

  def valid_role?(user_role_id)
    Rbac::Filterer.filtered_object(user_role_id, :class => "MiqUserRole").present?
  end

  # Get variables from group edit form
  def rbac_group_get_form_vars
    if %w[up down].include?(params[:button])
      move_cols_up   if params[:button] == "up"
      move_cols_down if params[:button] == "down"
    else
      copy_params_if_present(@edit[:new], params, %i[ldap_groups_user description detailed_description user user_id])

      if params[:group_role]
        if valid_role?(new_role_id = params[:group_role].to_i)
          @edit[:new][:role] = new_role_id
        else
          raise "Invalid role selected."
        end
      end

      if params[:group_tenant]
        if valid_tenant?(new_tenant_id = params[:group_tenant].to_i)
          @edit[:new][:group_tenant] = new_tenant_id
        else
          raise "Invalid tenant selected."
        end
      end

      @edit[:new][:lookup]           = (params[:lookup] == "1")   if params[:lookup]
      @edit[:new][:user_pwd]         = params[:password]          if params[:password]
    end
    if params[:check]                               # User checked/unchecked a tree node
      if params[:tree_typ] == "tags"                # MyCompany tag checked
        cat_name = Classification.find_by(:id => params[:cat]).name
        classification = Classification.find_by(:id => params[:val])
        tag_name = classification ? classification.name : ''
        if params[:check] == "0" #   unchecked
          @edit[:new][:filters].except!("#{cat_name}-#{tag_name}") # Remove the tag from the filters array
        else
          @edit[:new][:filters]["#{cat_name}-#{tag_name}"] = "/managed/#{cat_name}/#{tag_name}" # Put them in the hash
        end
      else # Belongsto tag checked
        class_prefix, id = parse_nodetype_and_id(params[:id])
        klass = TreeBuilder.get_model_for_prefix(class_prefix)
        # If ExtManagementSystem/Host/EmsFolder is returned get specific class
        if %w[ExtManagementSystem Host EmsFolder].include?(klass)
          klass = find_record_with_rbac(klass.constantize, id).class.to_s
        end
        if params[:check] == "0" #   unchecked
          @edit[:new][:belongsto].delete("#{klass}_#{id}") # Remove the tag from the belongsto hash
        else
          object = klass.safe_constantize.find(id)
          # Put the tag into the belongsto hash
          @edit[:new][:belongsto]["#{klass}_#{id}"] = MiqFilter.object2belongsto(object)
        end
      end
    end

    if params[:use_filter_expression]
      @edit[:new][:use_filter_expression] = params[:use_filter_expression]
      @group = MiqGroup.find_by(:id => @edit[:group_id])
      @sb[:active_rbac_group_tab] = 'rbac_customer_tags' # may not be set correctly because of lazy loading
      rbac_group_right_tree(@edit[:new][:belongsto].keys)
      if params[:use_filter_expression] == 'false'
        @edit[:new][:use_filter_expression] = false
      elsif params[:use_filter_expression] == 'true'
        @edit[:use_filter_expression] = true
      end
    end
  end

  # Set form variables for group add/edit
  def rbac_group_set_form_vars
    @assigned_filters = []
    @group = @record
    @edit = {
      :new                 => {
        :filters              => {},
        :filter_expression    => {},
        :belongsto            => {},
        :description          => @group.description,
        :detailed_description => @group.detailed_description,
      },
      :ldap_groups_by_user => [],
      :projects_tenants    => [],
      :roles               => {},
    }
    @edit[:group_id] = @record.id
    @edit[:key] = "rbac_group_edit__#{@edit[:group_id] || "new"}"

    # Build the managed filters hash
    [@group.get_managed_filters].flatten.each do |f|
      @edit[:new][:filters][f.split("/")[-2] + "-" + f.split("/")[-1]] = f
    end
    # Build the belongsto filters hash
    @group.get_belongsto_filters.each do |b| # Go thru the belongsto tags
      bobj = MiqFilter.belongsto2object(b)   # Convert to an object
      if bobj
        @edit[:new][:belongsto][bobj.class.to_s + "_" + bobj.id.to_s] = b # Store in hash as <class>_<id> string
      else
        @deleted_belongsto_filters ||= []
        @deleted_belongsto_filters.push(MiqFilter.belongsto2path_human(b))
      end
    end

    # Build roles hash
    placeholder_text_role = _('Choose a Role')
    @edit[:roles]["<#{placeholder_text_role}>"] = nil if @record.id.nil?

    Rbac::Filterer.filtered(MiqUserRole).each do |r|
      @edit[:roles][r.name] = r.id
    end
    @edit[:new][:role] = if @group.miq_user_role.nil? # If adding, set to first role
                           @edit[:roles][@edit[:roles].keys.min]
                         else
                           @group.miq_user_role.id
                         end

    all_tenants, all_projects = Tenant.tenant_and_project_names
    placeholder_text_tenant = _('Choose a Project/Tenant')
    @edit[:projects_tenants].push(["", [["<#{placeholder_text_tenant}>",
                                         :selected => "<#{placeholder_text_tenant}>",
                                         :disabled => "<#{placeholder_text_tenant}>",
                                         :style    => 'display:none']]])
    @edit[:projects_tenants].push(["Projects", all_projects]) if all_projects.present?
    @edit[:projects_tenants].push(["Tenants", all_tenants]) if all_tenants.present?
    @edit[:new][:group_tenant] = @group.tenant_id

    rbac_group_filter_expression_vars(:filter_expression, :filter_expression_table)
    @edit[:current] = copy_hash(@edit[:new])

    @right_cell_text = if @edit[:group_id]
                         _("Editing Group \"%{name}\"") % {:name => @record.description}
                       else
                         _('Adding a new Group')
                       end

    rbac_group_right_tree(@edit[:new][:belongsto].keys)
    @edit[:current][:deleted_belongsto_filters] = @deleted_belongsto_filters if @deleted_belongsto_filters
    @edit[:new][:belongsto].except!(*@deleted_belongsto_filters)
  end

  def rbac_group_filter_expression_vars(field_expression, field_expression_table)
    @edit[:new][field_expression] = if @group&.entitlement && @group.entitlement[field_expression].kind_of?(MiqExpression)
                                      @group.entitlement[field_expression].exp
                                    else
                                      @edit[:new][field_expression] = nil
                                    end
    @edit[:new][:use_filter_expression] = true
    # Populate exp editor fields for the expression column
    @edit[field_expression] ||= ApplicationController::Filter::Expression.new
    @edit[field_expression][:expression] = [] # Store exps in an array
    if @edit[:new][field_expression].blank?
      @edit[:new][:use_filter_expression] = false
      @edit[field_expression][:expression] = {"???" => "???"} # Set as new exp element
      @edit[:new][field_expression] = copy_hash(@edit[field_expression][:expression]) # Copy to new exp
    else
      @edit[field_expression][:expression] = copy_hash(@edit[:new][field_expression])
    end
    @edit[field_expression_table] = exp_build_table_or_nil(@edit[field_expression][:expression])

    @expkey = field_expression # Set expression key to expression
    @edit[field_expression].history.reset(@edit[field_expression][:expression])
    @edit[field_expression][:exp_table] = exp_build_table(@edit[field_expression][:expression])
    @edit[field_expression][:exp_model] = @group.class.to_s # Set model for the exp editor
  end

  # Set group record variables to new values
  def rbac_group_set_record_vars(group)
    if @edit[:new][:use_filter_expression]
      @edit[:new][:filters].clear
    else
      exp_remove_tokens(@edit[:new][:filter_expression])
      @edit[:new][:filter_expression] = {}
    end

    rbac_group_set_filters(group) # Go set the filters for the group
  end

  # Set group record variables such as Description, Role and Tenant to new values
  def rbac_group_set_record_description_role(group)
    group.description = @edit[:new][:description]
    group.detailed_description = @edit[:new][:detailed_description]
    group.miq_user_role = MiqUserRole.find(@edit[:new][:role]) if @edit[:new][:role]
    group.tenant = Tenant.find(@edit[:new][:group_tenant]) if @edit[:new][:group_tenant]
  end

  # Set filters in the group record from the @edit[:new] hash values
  def rbac_group_set_filters(group)
    group.entitlement ||= Entitlement.new
    if @edit[:new][:use_filter_expression]
      group.entitlement.set_managed_filters(nil) if group.entitlement.get_managed_filters.present?
      group.entitlement.filter_expression = @edit[:new][:filter_expression]["???"] ? nil : MiqExpression.new(@edit[:new][:filter_expression])
    else
      @set_filter_values = []
      @edit[:new][:filters].each_value do |value|
        @set_filter_values.push(value)
      end
      group.entitlement.filter_expression = nil if group.entitlement.filter_expression
      rbac_group_make_subarrays # Need to have category arrays of item arrays for and/or logic
      group.entitlement.set_managed_filters(@set_filter_values)
    end
    group.entitlement.set_belongsto_filters(@edit[:new][:belongsto].values) # Set belongs to to hash values
  end

  # Need to make arrays by category containing arrays of items so the filtering logic can apply
  # AND between the categories, but OR between the items within a category
  def rbac_group_make_subarrays
    # moved into common method used by ops_settings module as well
    rbac_and_user_make_subarrays
  end

  # Set form variables for role edit
  def rbac_role_set_form_vars
    @edit = {}
    @edit[:role_id] = @record.id if @sb[:typ] != "copy"
    @edit[:new] = {}
    @edit[:current] = {}
    @edit[:key] = "rbac_role_edit__#{@edit[:role_id] || "new"}"

    @edit[:new][:name] = @record.name
    vmr = @record.settings.fetch_path(:restrictions, :vms) if @record.settings
    @edit[:new][:vm_restriction] = vmr || :none
    str = @record.settings.fetch_path(:restrictions, :service_templates) if @record.settings
    @edit[:new][:service_template_restriction] = str || :none
    @edit[:new][:features] = rbac_expand_features(@record.miq_product_features.map(&:identifier)).sort

    @edit[:current] = copy_hash(@edit[:new])

    @role_features = @edit[:new][:features]
    @rbac_menu_tree = build_rbac_feature_tree

    @right_cell_text = if @edit[:role_id]
                         _("Editing Role \"%{name}\"") % {:name => @record.name}
                       else
                         _('Adding a new Role')
                       end
  end

  # Get array of total set of features from the children of selected features
  def rbac_expand_features(selected, node = nil)
    node ||= MiqProductFeature.feature_root
    if selected.include?(node)
      [node] + MiqProductFeature.feature_all_children(node)
    else
      MiqProductFeature.feature_children(node).flat_map { |n| rbac_expand_features(selected, n) }
    end
  end

  # Get array of all fully selected parent or leaf node features
  def rbac_compact_features(selected, node = nil)
    node ||= MiqProductFeature.feature_root
    return [node] if selected.include?(node)

    MiqProductFeature.feature_children(node, false).flat_map do |n|
      rbac_compact_features(selected, n)
    end
  end

  # Yield all features for given tree node a section or feature
  #
  # a. special case _tab_all_vm_rules
  # b. section node /^_tab_/
  #   return all features below this section and
  #   recursively below any nested sections
  #   and nested features recursively
  # c. feature node
  #   return nested features recursively
  #
  def recurse_sections_and_features(node)
    if /_tab_all_vm_rules$/.match?(node)
      MiqProductFeature.feature_children('all_vm_rules').each do |feature|
        kids = MiqProductFeature.feature_all_children(feature)
        yield feature, [feature] + kids
      end
    elsif /^_tab_/.match?(node)
      section_id = node.split('_tab_').last.to_sym
      Menu::Manager.section(section_id).features_recursive.each do |f|
        kids = MiqProductFeature.feature_all_children(f)
        yield f, [f] + kids
      end
    else
      kids = MiqProductFeature.feature_all_children(node)
      yield node, [node] + kids
    end
  end

  def rbac_role_get_form_vars
    @edit[:new][:name] = params[:name] if params[:name]
    @edit[:new][:vm_restriction] = params[:vm_restriction].to_sym if params[:vm_restriction]
    @edit[:new][:service_template_restriction] = params[:service_template_restriction].to_sym if params[:service_template_restriction]

    # Add/removed features based on the node that was checked
    if params[:check]
      node = params[:id].split("__").last # Get the feature of the checked node
      if params[:check] == "0" # Unchecked
        recurse_sections_and_features(node) do |feature, all|
          @edit[:new][:features] -= all # remove the feature + children
          rbac_role_remove_parent(feature) # remove all parents above the unchecked tab feature
        end
      else # Checked
        recurse_sections_and_features(node) do |feature, all|
          @edit[:new][:features] += all # remove the feature + children
          rbac_role_add_parent(feature) # remove all parents above the unchecked tab feature
        end
      end
    end
    @edit[:new][:features].uniq!
    @edit[:new][:features].sort!
  end

  # Walk the features tree, removing features up to the top
  def rbac_role_remove_parent(node)
    parent = MiqProductFeature.feature_parent(node)
    return unless parent

    @edit[:new][:features] -= [parent] # Remove the parent from the features array
    rbac_role_remove_parent(parent)    # Remove this nodes parent as well
  end

  # Walk the features tree, adding features up to the top
  def rbac_role_add_parent(node)
    return unless (parent = MiqProductFeature.feature_parent(node)) # Intentional single =, using parent var below

    if MiqProductFeature.feature_children(parent, false) - @edit[:new][:features] == [] # All siblings of node are selected
      @edit[:new][:features] += [parent]  # Add the parent to the features array
      rbac_role_add_parent(parent)        # See if this nodes parent needs to be added
    end
  end

  # Set role record variables to new values
  def rbac_role_set_record_vars(role)
    role.name = @edit[:new][:name]
    role.settings ||= {}
    role.settings[:restrictions] ||= {}
    if @edit[:new][:vm_restriction] == :none
      role.settings[:restrictions].delete(:vms)
    else
      role.settings[:restrictions][:vms] = @edit[:new][:vm_restriction]
    end
    if @edit[:new][:service_template_restriction] == :none
      role.settings[:restrictions].delete(:service_templates)
    else
      role.settings[:restrictions][:service_templates] = @edit[:new][:service_template_restriction]
    end
    role.settings = nil if role.settings[:restrictions].blank?
  end

  def populate_role_features(role)
    role.miq_product_features =
      MiqProductFeature.find_all_by_identifier(rbac_compact_features(@edit[:new][:features]))
    User.current_user.reload
  end

  # Validate some of the role fields
  def rbac_role_validate?
    if @edit[:new][:features].empty?
      add_flash(_("At least one Product Feature must be selected"), :error)
      return false
    end
    true
  end

  # Validate some of the role fields
  def rbac_group_validate?
    return false if @edit[:new][:description].nil?

    @assigned_filters = [] if @edit[:new][:filters].empty? || @edit[:new][:use_filter_expression]
    @filter_expression = [] if @edit[:new][:filter_expression].empty? || @edit[:new][:use_filter_expression] == false
    if @edit[:new][:role].nil? || @edit[:new][:role] == ""
      add_flash(_("A Role must be assigned to this Group"), :error)
      return false
    end
    true
  end
end