[PATCH conductor] Tasks 3387/3388 UI for user groups implementation (revised)

Scott Seago sseago at redhat.com
Mon Jul 2 15:05:57 UTC 2012


UI for managing local user  groups and for assigning group permissions.
When testing, note that after adding/removing a user from a group
the user must log out and in again to see the effects.
---
 aeolus-conductor.spec.in                           |    1 +
 src/app/controllers/application_controller.rb      |    7 +-
 src/app/controllers/permissions_controller.rb      |   13 +-
 src/app/controllers/user_groups_controller.rb      |  238 ++++++++++++++++++++
 .../user_groups_helper.rb}                         |   23 +--
 src/app/models/common_filter_methods.rb            |    8 +-
 src/app/models/derived_permission.rb               |   23 ++
 src/app/models/entity.rb                           |   19 ++
 src/app/models/permission.rb                       |   23 ++
 src/app/models/user_group.rb                       |   12 +
 src/app/views/permissions/_form.html.haml          |   23 ++-
 src/app/views/permissions/_permissions.html.haml   |   20 ++
 src/app/views/user_groups/_form.html.haml          |   26 +++
 src/app/views/user_groups/_list.html.haml          |   41 ++++
 src/app/views/user_groups/add_members.html.haml    |   25 ++
 src/app/views/user_groups/edit.html.haml           |   14 ++
 src/app/views/user_groups/index.html.haml          |    2 +
 src/app/views/user_groups/new.html.haml            |   13 +
 src/app/views/user_groups/show.html.haml           |   71 ++++++
 src/config/locales/en.yml                          |   63 +++++-
 src/config/navigation.rb                           |    2 +-
 src/config/routes.rb                               |   15 ++
 src/features/step_definitions/user_group_steps.rb  |   56 +++++
 src/features/support/paths.rb                      |    6 +
 src/features/user_group.feature                    |   72 ++++++
 .../controllers/user_groups_controller_spec.rb     |   75 ++++++
 src/spec/factories/permission.rb                   |    7 +
 .../factories/user_group.rb}                       |   23 +-
 src/spec/models/permission_spec.rb                 |   19 ++
 src/spec/models/user_group_spec.rb                 |   43 ++++
 30 files changed, 948 insertions(+), 35 deletions(-)
 create mode 100644 src/app/controllers/user_groups_controller.rb
 copy src/app/{models/common_filter_methods.rb => helpers/user_groups_helper.rb} (55%)
 create mode 100644 src/app/views/user_groups/_form.html.haml
 create mode 100644 src/app/views/user_groups/_list.html.haml
 create mode 100644 src/app/views/user_groups/add_members.html.haml
 create mode 100644 src/app/views/user_groups/edit.html.haml
 create mode 100644 src/app/views/user_groups/index.html.haml
 create mode 100644 src/app/views/user_groups/new.html.haml
 create mode 100644 src/app/views/user_groups/show.html.haml
 create mode 100644 src/features/step_definitions/user_group_steps.rb
 create mode 100644 src/features/user_group.feature
 create mode 100644 src/spec/controllers/user_groups_controller_spec.rb
 copy src/{app/models/common_filter_methods.rb => spec/factories/user_group.rb} (56%)
 create mode 100644 src/spec/models/user_group_spec.rb

diff --git a/aeolus-conductor.spec.in b/aeolus-conductor.spec.in
index 6186651..30e6f43 100644
--- a/aeolus-conductor.spec.in
+++ b/aeolus-conductor.spec.in
@@ -209,6 +209,7 @@ haml="app/views/hardware_profiles app/views/realm_mappings \
       app/views/api/hooks \
       app/views/api \
       app/views/images \
+      app/views/user_groups \
       app/views/api/builds app/views/api/provider_images \
       app/views/api/target_images app/views/api/entrypoint \
       app/views/api/environments \
diff --git a/src/app/controllers/application_controller.rb b/src/app/controllers/application_controller.rb
index 1907d23..d5da863 100644
--- a/src/app/controllers/application_controller.rb
+++ b/src/app/controllers/application_controller.rb
@@ -282,7 +282,7 @@ class ApplicationController < ActionController::Base
 
   def set_admin_users_tabs(tab)
     @tabs = [{:name => t('application_controller.admin_tabs.users'), :url => users_url, :id => 'users'},
-             #{:name => t('application_controller.admin_tabs.groups'), :url => groups_url, :id => 'groups'},
+             {:name => t('application_controller.admin_tabs.user_groups'), :url => user_groups_url, :id => 'user_groups'},
              {:name => t('application_controller.admin_tabs.permissions'), :url => permissions_url, :id => 'permissions'},
     ]
     unless @details_tab = @tabs.find {|t| t[:id] == tab}
@@ -352,7 +352,10 @@ class ApplicationController < ActionController::Base
     else
       local_perms = @permission_object.permissions
     end
-    @permissions = paginate_collection(local_perms, params[:page])
+    @permissions = paginate_collection(local_perms.
+      apply_filters(:preset_filter_id => params[:permissions_preset_filter],
+                    :search_filter => params[:permissions_search]),
+                                       params[:page])
 
     @permission_list_header = []
     unless (@show_inherited or @show_global)
diff --git a/src/app/controllers/permissions_controller.rb b/src/app/controllers/permissions_controller.rb
index c4d1371..41cad43 100644
--- a/src/app/controllers/permissions_controller.rb
+++ b/src/app/controllers/permissions_controller.rb
@@ -165,11 +165,22 @@ class PermissionsController < ApplicationController
                 :permission_object_id => p.permission_object_id
   end
 
+  def filter
+    redirect_to_original({"permissions_preset_filter" => params[:permissions_preset_filter], "permissions_search" => params[:permissions_search]})
+  end
+
+  def filter_entities
+    redirect_to_original({"entities_preset_filter" => params[:entities_preset_filter], "entities_search" => params[:entities_search]})
+  end
+
   private
 
   def load_entities
     sort_order = params[:sort_by].nil? ? "name" : params[:sort_by]
-    @entities = paginate_collection(Entity.all(:order => sort_column(Entity, sort_order)),
+    @entities = paginate_collection(Entity.
+      all(:order => sort_column(Entity, sort_order)).
+      apply_filters(:preset_filter_id => params[:entities_preset_filter],
+                    :search_filter => params[:entities_search]),
                                     params[:page])
   end
 
diff --git a/src/app/controllers/user_groups_controller.rb b/src/app/controllers/user_groups_controller.rb
new file mode 100644
index 0000000..59b8665
--- /dev/null
+++ b/src/app/controllers/user_groups_controller.rb
@@ -0,0 +1,238 @@
+#
+#   Copyright 2011 Red Hat, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+
+class UserGroupsController < ApplicationController
+  before_filter :require_user, :except => [:new, :create]
+
+  def index
+    require_privilege(Privilege::VIEW, User)
+    @title = t'user_groups.groups'
+    clear_breadcrumbs
+    save_breadcrumb(user_groups_path)
+    set_admin_users_tabs 'user_groups'
+    @params = params
+    load_headers
+    load_user_groups
+    respond_to do |format|
+      format.html
+      format.js { render :partial => 'list' }
+    end
+  end
+
+  def show
+    @user_group = UserGroup.find(params[:id])
+    require_privilege(Privilege::VIEW, User)
+    @title = @user_group.name
+    save_breadcrumb(user_group_path(@user_group), @user_group.name)
+    @tab_captions = ['Properties']
+    @details_tab = params[:details_tab].blank? ? 'properties' : params[:details_tab]
+    @members = paginate_collection(@user_group.members, params[:page])
+    respond_to do |format|
+      format.html
+      format.js do
+        if params.delete :details_pane
+          render :partial => 'layouts/details_pane' and return
+        end
+        render :partial => @details_tab
+      end
+    end
+  end
+
+  def new
+    require_privilege(Privilege::CREATE, User)
+    @title = t'user_groups.new.new_user_group'
+    @user_group = UserGroup.new
+    # remove when we enable ldap
+    @user_group.membership_source = UserGroup::MEMBERSHIP_SOURCE_LOCAL
+  end
+
+  def create
+    if params[:commit] == "Reset"
+      redirect_to :action => 'new' and return
+    end
+
+    require_privilege(Privilege::MODIFY, User)
+    @user_group = UserGroup.new(params[:user_group])
+    unless @user_group.save
+      render :action => 'new' and return
+    end
+
+    respond_to do |format|
+      if @user_group.save
+        flash[:notice] = t "user_groups.flash.notice.added"
+        format.html { redirect_to user_groups_path }
+        format.json { render :json => @user_group, :status => :created }
+      else
+        flash.now[:warning] = t "user_groups.flash.warning.creation_failed"
+        format.html { render :new }
+        format.js { render :partial => 'new' }
+        format.json { render :json => @user_group.errors, :status => :unprocessable_entity }
+      end
+    end
+  end
+
+  def edit
+    @user_group = UserGroup.find(params[:id])
+    require_privilege(Privilege::MODIFY, User)
+    @title = t'user_groups.edit.edit_user_group'
+  end
+
+  def update
+    @user_group = UserGroup.find(params[:id])
+    require_privilege(Privilege::MODIFY, User)
+
+    if params[:commit] == "Reset"
+      redirect_to edit_user_group_url(@user_group) and return
+    end
+
+    redirect_to root_url and return unless @user_group
+
+    unless @user_group.update_attributes(params[:user_group])
+      render :action => 'edit' and return
+    else
+      flash[:notice] = t"user_groups.flash.notice.updated"
+      redirect_to user_group_path(@user_group)
+    end
+  end
+
+  def multi_destroy
+    require_privilege(Privilege::MODIFY, User)
+    deleted_user_groups = []
+
+    begin
+      UserGroup.transaction do
+        UserGroup.find(params[:user_group_selected]).each do |user_group|
+          if user_group.destroy
+            deleted_user_groups << user_group.name
+          end
+        end
+      end
+
+      unless deleted_user_groups.empty?
+        flash[:notice] =  "#{t('user_groups.flash.notice.more_deleted', :count => deleted_user_groups.length)} #{deleted_user_groups.join(', ')}"
+      end
+
+    rescue => ex
+      flash[:warning] = t('user_groups.flash.warning.not_delete', :reason => ex.message)
+    end
+
+    redirect_to user_groups_url
+  end
+
+  def destroy
+    require_privilege(Privilege::MODIFY, User)
+    user_group = UserGroup.find(params[:id])
+    if user_group.destroy
+      flash[:notice] = t"user_groups.flash.notice.deleted"
+    else
+      flash[:notice] = t"user_groups.flash.warning.delete_failed"
+    end
+
+    respond_to do |format|
+      format.html { redirect_to user_groups_path }
+    end
+  end
+
+  def add_members
+    @user_group = UserGroup.find(params[:id])
+    require_privilege(Privilege::MODIFY, User)
+
+    unless @user_group.membership_source == UserGroup::MEMBERSHIP_SOURCE_LOCAL
+      flash[:error] = t('user_groups.flash.error.not_a_local_group')
+      redirect_to user_group_path(@user_group) and return
+    end
+
+    @users = paginate_collection(User.where('users.id not in (?)',
+                                            @user_group.members.empty? ?
+                                            0 : @user_group.members.map(&:id)),
+                                 params[:page])
+
+    added = []
+    not_added = []
+
+    if params[:members_selected].blank?
+      flash[:error] = t"user_groups.flash.error.select_to_add_members" if request.post?
+    else
+      User.find(params[:members_selected]).each do |member|
+        if !@user_group.members.include?(member) and
+            @user_group.members << member
+          added << member.name
+        else
+          not_added << member.name
+        end
+      end
+      unless added.empty?
+        flash[:notice] = "#{t('user_groups.flash.notice.members_added')}: #{added.join(', ')}"
+      end
+      unless not_added.empty?
+        flash[:error] = "#{t('user_groups.flash.error.members_not_added')}: #{not_added.join(', ')}"
+      end
+      respond_to do |format|
+        format.html { redirect_to user_group_path(@user_group) }
+      end
+    end
+  end
+
+  def remove_members
+    @user_group = UserGroup.find(params[:id])
+    require_privilege(Privilege::MODIFY, User)
+
+    unless @user_group.membership_source == UserGroup::MEMBERSHIP_SOURCE_LOCAL
+      flash[:error] = t('user_groups.flash.error.not_a_local_group')
+      redirect_to user_group_path(@user_group) and return
+    end
+
+    removed=[]
+    not_removed=[]
+
+    if params[:members_selected].blank?
+      if request.post?
+        flash[:error] = t"user_groups.flash.error.select_to_remove_members"
+      end
+    else
+      User.find(params[:members_selected]).each do |member|
+        if @user_group.members.delete member
+          removed << member.name
+        else
+          not_removed << member.name
+        end
+      end
+      unless removed.empty?
+        flash[:notice] = "#{t('user_groups.flash.notice.members_removed')}: #{removed.join(', ')}"
+      end
+      unless not_removed.empty?
+        flash[:error] = "#{t('user_groups.flash.error.members_not_removed')}: #{not_removed.join(', ')}"
+      end
+    end
+    respond_to do |format|
+      format.html { redirect_to user_group_path(@user_group) }
+    end
+  end
+
+  def load_user_groups
+    sort_order = params[:sort_by].nil? ? "name" : params[:sort_by]
+    @user_groups = UserGroup.apply_filters(:preset_filter_id => params[:users_preset_filter], :search_filter => params[:user_groups_search]).order(sort_order)
+  end
+
+  def load_headers
+    @header = [
+      { :name => 'checkbox', :class => 'checkbox', :sortable => false },
+      { :name => t('user_groups.index.name'), :sortable => false },
+      { :name => t('user_groups.index.type'), :sortable => false },
+    ]
+  end
+
+end
diff --git a/src/app/models/common_filter_methods.rb b/src/app/helpers/user_groups_helper.rb
similarity index 55%
copy from src/app/models/common_filter_methods.rb
copy to src/app/helpers/user_groups_helper.rb
index 7bb2be2..85ad81f 100644
--- a/src/app/models/common_filter_methods.rb
+++ b/src/app/helpers/user_groups_helper.rb
@@ -1,5 +1,5 @@
 #
-#   Copyright 2011 Red Hat, Inc.
+#   Copyright 2012 Red Hat, Inc.
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -13,19 +13,12 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 #
-#this module contains functions for filtering table data
-module CommonFilterMethods
-  def apply_filters(options = {})
-    apply_preset_filter(options[:preset_filter_id]).apply_search_filter(options[:search_filter])
-  end
-
-  private
-
-  def apply_preset_filter(preset_filter_id)
-    if preset_filter_id.present?
-      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] == preset_filter_id}.first[:query]
-    else
-      scoped
-    end
+module UserGroupsHelper
+  def members_header
+    [
+      { :name => 'checkbox', :class => 'checkbox', :sortable => false },
+      { :name => t('users.index.username'), :sortable => false },
+      { :name => t('user_groups.index.name'), :sortable => false },
+    ]
   end
 end
diff --git a/src/app/models/common_filter_methods.rb b/src/app/models/common_filter_methods.rb
index 7bb2be2..9491ecb 100644
--- a/src/app/models/common_filter_methods.rb
+++ b/src/app/models/common_filter_methods.rb
@@ -23,7 +23,13 @@ module CommonFilterMethods
 
   def apply_preset_filter(preset_filter_id)
     if preset_filter_id.present?
-      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] == preset_filter_id}.first[:query]
+      option = self::PRESET_FILTERS_OPTIONS.select { |item|
+        item[:id] == preset_filter_id}.first
+      if option[:query]
+        option[:query]
+      else
+        includes(option[:includes]).where(option[:where])
+      end
     else
       scoped
     end
diff --git a/src/app/models/derived_permission.rb b/src/app/models/derived_permission.rb
index b7a2530..80d055d 100644
--- a/src/app/models/derived_permission.rb
+++ b/src/app/models/derived_permission.rb
@@ -15,6 +15,9 @@
 #
 
 class DerivedPermission < ActiveRecord::Base
+  class << self
+    include CommonFilterMethods
+  end
   # the source permission for the denormalized object
   belongs_to :permission
   validates_presence_of :permission_id
@@ -54,4 +57,24 @@ class DerivedPermission < ActiveRecord::Base
   validates_uniqueness_of :permission_id, :scope => [:permission_object_id,
                                                      :permission_object_type]
 
+  # :query is handled differently for permission
+  PRESET_FILTERS_OPTIONS = [
+    {:title => "permissions.preset_filters.user_permissions",
+     :id => "user_permissions",
+     :includes => :entity,
+     :where => {"entities.entity_target_type" => "User"}},
+    {:title => "permissions.preset_filters.group_permissions",
+     :id => "group_permissions",
+     :includes => :entity,
+     :where => {"entities.entity_target_type" => "UserGroup"}}
+  ]
+
+  def self.apply_search_filter(search)
+    if search
+      includes("entity").where("lower(entities.name) LIKE :search", :search => "%#{search.downcase}%")
+    else
+      scoped
+    end
+  end
+
 end
diff --git a/src/app/models/entity.rb b/src/app/models/entity.rb
index bbc56fe..db88a5e 100644
--- a/src/app/models/entity.rb
+++ b/src/app/models/entity.rb
@@ -15,6 +15,9 @@
 #
 
 class Entity < ActiveRecord::Base
+  class << self
+    include CommonFilterMethods
+  end
   belongs_to :entity_target, :polymorphic => true
   validates_presence_of :entity_target_id
   has_many :session_entities, :dependent => :destroy
@@ -26,4 +29,20 @@ class Entity < ActiveRecord::Base
   belongs_to :user_group, :class_name => "UserGroup",
                           :foreign_key => "entity_target_id"
 
+  PRESET_FILTERS_OPTIONS = [
+    {:title => "user",
+     :id => "users",
+     :where => {"entities.entity_target_type" => "User"}},
+    {:title => "user_group",
+     :id => "user_groups",
+     :where => {"entities.entity_target_type" => "UserGroup"}}
+  ]
+
+  def self.apply_search_filter(search)
+    if search
+      where("lower(entities.name) LIKE :search", :search => "%#{search.downcase}%")
+    else
+      scoped
+    end
+  end
 end
diff --git a/src/app/models/permission.rb b/src/app/models/permission.rb
index 048f7a5..9b4c0ac 100644
--- a/src/app/models/permission.rb
+++ b/src/app/models/permission.rb
@@ -30,6 +30,9 @@
 #
 
 class Permission < ActiveRecord::Base
+  class << self
+    include CommonFilterMethods
+  end
   belongs_to :role
   belongs_to :entity
 
@@ -67,6 +70,17 @@ class Permission < ActiveRecord::Base
 
   after_save :update_derived_permissions
 
+  # :query is handled differently for permission
+  PRESET_FILTERS_OPTIONS = [
+    {:title => "permissions.preset_filters.user_permissions",
+     :id => "user_permissions",
+     :includes => :entity,
+     :where => {"entities.entity_target_type" => "User"}},
+    {:title => "permissions.preset_filters.group_permissions",
+     :id => "group_permissions",
+     :includes => :entity,
+     :where => {"entities.entity_target_type" => "UserGroup"}}
+  ]
   def user
     entity.user
   end
@@ -100,4 +114,13 @@ class Permission < ActiveRecord::Base
       end
     end
   end
+
+  def self.apply_search_filter(search)
+    if search
+      includes("entity").where("lower(entities.name) LIKE :search", :search => "%#{search.downcase}%")
+    else
+      scoped
+    end
+  end
+
 end
diff --git a/src/app/models/user_group.rb b/src/app/models/user_group.rb
index 293d0db..dcb2f63 100644
--- a/src/app/models/user_group.rb
+++ b/src/app/models/user_group.rb
@@ -15,6 +15,9 @@
 #
 
 class UserGroup < ActiveRecord::Base
+  class << self
+    include CommonFilterMethods
+  end
   # name will correspond to the group name if we're using LDAP, otherwise it's
   # entered by the admin creating the group
 
@@ -42,4 +45,13 @@ class UserGroup < ActiveRecord::Base
     self.entity.name = "#{self.name} (#{self.membership_source})"
     self.entity.save!
   end
+
+  def self.apply_search_filter(search)
+    if search
+      where("lower(name) LIKE :search", :search => "%#{search.downcase}%")
+    else
+      scoped
+    end
+  end
+
 end
diff --git a/src/app/views/permissions/_form.html.haml b/src/app/views/permissions/_form.html.haml
index 95aa05d..39b1865 100644
--- a/src/app/views/permissions/_form.html.haml
+++ b/src/app/views/permissions/_form.html.haml
@@ -1,3 +1,23 @@
+- content_for :entities_filter_controls do
+  %li
+    = label_tag :entities_preset_filter, t('filter_table.viewing')
+    = select_tag(:entities_preset_filter, preset_filters_options_for_select(Entity::PRESET_FILTERS_OPTIONS, params[:entities_preset_filter]), :include_blank => t("permissions.preset_filters.all_entities"), :disabled => false)
+    = hidden_field_tag :current_path, request.fullpath
+    = restful_submit_tag t("filter_table.apply_filters"), "filter", filter_entities_permissions_path, 'POST', :class => 'button', :id => 'apply_entities_preset_filter'
+    %span.label.badge.dark= @entities.count
+  %li.table-search-filter
+    = text_field_tag :entities_search, params[:entities_search], :placeholder => t("filter_table.search")
+    = restful_submit_tag "", "search", filter_entities_permissions_path, 'POST', :class => 'button', :id => 'apply_entities_search'
+
+  :javascript
+    $(document).ready(function(){
+      $("#apply_entities_preset_filter").hide();
+      $("#entities_preset_filter").change(function() {
+        $("#apply_entities_preset_filter").click();
+      });
+      Conductor.clickOnEnterKeypress($('#entities_search'), $('#apply_entities_search'));
+    });
+
 - content_for :form_footer do
   = hidden_field_tag :permission_object_type, @permission_object.class.name
   = hidden_field_tag :permission_object_id, @permission_object.id
@@ -5,7 +25,8 @@
   = hidden_field_tag :use_tabs, @use_tabs
   = link_to t('cancel'), @return_path, :class => 'button danger'
   = restful_submit_tag t('permissions.form.grant_access'), "create", permissions_path, 'POST', :id => 'save_button', :class => 'button'
-= filter_table(@header, @entities) do |entity|
+= filter_table(@header, @entities,
+               :filter_controls => :entities_filter_controls) do |entity|
   %tr{:class => cycle('nostripe','stripe')}
     %td
       -# - selected = params[:select] == 'all'
diff --git a/src/app/views/permissions/_permissions.html.haml b/src/app/views/permissions/_permissions.html.haml
index b1e18f7..56a0c8e 100644
--- a/src/app/views/permissions/_permissions.html.haml
+++ b/src/app/views/permissions/_permissions.html.haml
@@ -24,6 +24,26 @@
         %li= link_to t('permissions.inherited_access'), params.merge(:show_inherited => true, :show_global => false, :page => 1), { :class => 'button primary', :id => 'inherited_permission_button'}
         %li= link_to t('permissions.global_access'), params.merge(:show_inherited => false, :show_global => true, :page => 1), { :class => 'button primary', :id => 'global_permission_button'}
 
+- content_for :permissions_filter_controls do
+  %li
+    = label_tag :permissions_preset_filter, t('filter_table.viewing')
+    = select_tag(:permissions_preset_filter, preset_filters_options_for_select(Permission::PRESET_FILTERS_OPTIONS, params[:permissions_preset_filter]), :include_blank => t("permissions.preset_filters.all_permissions"), :disabled => false)
+    = hidden_field_tag :current_path, request.fullpath
+    = restful_submit_tag t("filter_table.apply_filters"), "filter", filter_permissions_path, 'POST', :class => 'button', :id => 'apply_permissions_preset_filter'
+    %span.label.badge.dark= @permissions.count
+  %li.table-search-filter
+    = text_field_tag :permissions_search, params[:permissions_search], :placeholder => t("filter_table.search")
+    = restful_submit_tag "", "search", filter_permissions_path, 'POST', :class => 'button', :id => 'apply_permissions_search'
+
+  :javascript
+    $(document).ready(function(){
+      $("#apply_permissions_preset_filter").hide();
+      $("#permissions_preset_filter").change(function() {
+        $("#apply_permissions_preset_filter").click();
+      });
+      Conductor.clickOnEnterKeypress($('#permissions_search'), $('#apply_permissions_search'));
+    });
+
 = filter_table(@permission_list_header,
                @permissions,
                :form_header => :permissions_form_header,
diff --git a/src/app/views/user_groups/_form.html.haml b/src/app/views/user_groups/_form.html.haml
new file mode 100644
index 0000000..1db6a7a
--- /dev/null
+++ b/src/app/views/user_groups/_form.html.haml
@@ -0,0 +1,26 @@
+- if @user_group.errors.any?
+  = render 'layouts/error_messages', :object => @user_group
+
+%fieldset
+  .field
+    = form.label :type
+    .input
+      - if @user_group.new_record?
+        = form.select(:membership_source, UserGroup::MEMBERSHIP_SOURCES.collect {|x| [t("user_groups.#{x.downcase}"), x]}, {}, {:disabled => true})
+        = form.hidden_field :membership_source
+      - else
+        = t("user_groups.#{@user_group.membership_source.downcase}")
+  .field
+    = form.label :name
+    .input
+      - if @user_group.new_record? or @user_group.membership_source == UserGroup::MEMBERSHIP_SOURCE_LOCAL
+        = form.text_field :name
+      - else
+        = @user_group.name
+  .field
+    = form.label :description
+    .input
+      = form.text_area :description, :class => 'long'
+
+%fieldset.options
+  = form.submit "#{t'user_groups.form.save_user_group'}",  :class => "submit button pill"
diff --git a/src/app/views/user_groups/_list.html.haml b/src/app/views/user_groups/_list.html.haml
new file mode 100644
index 0000000..5c99b39
--- /dev/null
+++ b/src/app/views/user_groups/_list.html.haml
@@ -0,0 +1,41 @@
+%header
+  %h2#user_groups.groups= @title
+
+- content_for :form_header do
+  %li= restful_submit_tag "#{t'user_groups.list.delete_selected'}", "destroy", multi_destroy_user_groups_path, 'DELETE', :id => 'delete_button', :class => 'button danger'
+  %li= link_to t("user_groups.list.add_user_group"), new_user_group_path, :class => 'button', :id => 'add_user_group_button'
+
+- content_for :filter_controls do
+  %li
+    = label_tag :user_groups_preset_filter, t('filter_table.viewing')
+    = hidden_field_tag :current_path, request.fullpath
+    = select_tag(:user_groups_preset_filter, preset_filters_options_for_select(User::PRESET_FILTERS_OPTIONS, params[:user_groups_preset_filter]), :include_blank => t("user_groups.preset_filters.all_user_groups"), :disabled => true)
+    = restful_submit_tag t("filter_table.apply_filters"), "index", filter_user_groups_path, 'POST', :class => 'button', :id => 'apply_user_groups_preset_filter'
+    %span.label.badge.dark= @user_groups.count
+  /  = link_to "Filter", "#", :class => 'button pill'
+  /%li
+  /%li.more_actions
+  /  %span= image_tag "button-userdrop.png"
+  /  %ul
+  /    %li= link_to "Add/Remove Columns", "#"
+  /    %li= link_to "Freeze Column", "#"
+  %li.table-search-filter
+    = text_field_tag :user_groups_search, params[:user_groups_search], :placeholder => t("filter_table.search")
+    = restful_submit_tag "", "index", filter_user_groups_path, 'POST', :class => 'button', :id => 'apply_user_groups_search'
+
+  :javascript
+    $(document).ready(function(){
+      $("#apply_user_groups_preset_filter").hide();
+      $("#user_groups_preset_filter").change(function() {
+        $("#apply_user_groups_preset_filter").click();
+      });
+      Conductor.clickOnEnterKeypress($('#user_groups_search'), $('#apply_user_groups_search'));
+    });
+
+= filter_table(@header, @user_groups) do |user_group|
+  %tr{:class => cycle('nostripe','stripe')}
+    %td{:class => 'checkbox'}
+      - selected = params[:select] == 'all'
+      = check_box_tag "user_group_selected[]", user_group.id, selected, :id => "user_group_checkbox_#{user_group.id}"
+    %td= link_to user_group.name, user_group_path(user_group)
+    %td= t("user_groups.#{user_group.membership_source.downcase}")
diff --git a/src/app/views/user_groups/add_members.html.haml b/src/app/views/user_groups/add_members.html.haml
new file mode 100644
index 0000000..4b3a30c
--- /dev/null
+++ b/src/app/views/user_groups/add_members.html.haml
@@ -0,0 +1,25 @@
+= render :partial => 'layouts/admin_nav'
+%header.page-header
+  .obj_actions
+    .return_to
+      = t'return_to'
+      = link_to @user_group.name, user_group_path(@user_group), :id => 'user_group_button'
+  %h1.user_groups= @user_group.name
+
+%section.content-section.user_groups
+  %header
+    .align-center
+      %strong= t("user_groups.choose_members")
+  .content
+    - content_for :form_footer do
+      = link_to t('cancel'), user_group_path(@user_group), :class => 'button danger'
+      = restful_submit_tag t('user_groups.show.add_members'), "add", add_members_user_group_path(@user_group), 'POST', :id => 'save_button', :class => 'button'
+    = filter_table(members_header, @users) do |user|
+      %tr{:class => cycle('nostripe','stripe')}
+        %td{:class => 'checkbox'}
+          - selected = params[:select] == 'all'
+          = check_box_tag "members_selected[]", user.id, selected, :id => "member_checkbox_#{user.id}"
+        %td
+          = link_to user.login, users_path(user)
+        %td
+          = user.name
diff --git a/src/app/views/user_groups/edit.html.haml b/src/app/views/user_groups/edit.html.haml
new file mode 100644
index 0000000..7350a43
--- /dev/null
+++ b/src/app/views/user_groups/edit.html.haml
@@ -0,0 +1,14 @@
+= render :partial => 'layouts/admin_nav'
+%header.page-header
+  .obj_actions
+    .return_to
+      =t'return_to'
+      = link_to "#{t'user_groups.groups'}", user_groups_path
+  %h1.users= @title
+
+%section.content-section.user_groups
+  .content
+    .align-center
+    = @user_group.name
+    = form_for @user_group, :url => user_group_path(@user_group), :html => {:class => 'generic horizontal'} do |f|
+      = render :partial => "form", :locals => { :form => f }
diff --git a/src/app/views/user_groups/index.html.haml b/src/app/views/user_groups/index.html.haml
new file mode 100644
index 0000000..7cc23db
--- /dev/null
+++ b/src/app/views/user_groups/index.html.haml
@@ -0,0 +1,2 @@
+= render :partial => 'layouts/admin_nav'
+= render :partial => 'layouts/admin_users_tabs'
diff --git a/src/app/views/user_groups/new.html.haml b/src/app/views/user_groups/new.html.haml
new file mode 100644
index 0000000..eaaf3a7
--- /dev/null
+++ b/src/app/views/user_groups/new.html.haml
@@ -0,0 +1,13 @@
+= render :partial => 'layouts/admin_nav'
+%header.page-header
+  .obj_actions
+    .return_to
+      =t'return_to'
+      = link_to "#{t'user_groups.groups'}", user_groups_path
+  %h1.users= @title
+
+%section.content-section.user_groups
+  .content
+    .align-center
+    = form_for @user_group, :url => user_groups_path, :html => {:class => 'generic horizontal'} do |f|
+      = render :partial => "form", :locals => { :form => f }
diff --git a/src/app/views/user_groups/show.html.haml b/src/app/views/user_groups/show.html.haml
new file mode 100644
index 0000000..426137f
--- /dev/null
+++ b/src/app/views/user_groups/show.html.haml
@@ -0,0 +1,71 @@
+= render :partial => 'layouts/admin_nav'
+%header.page-header
+  .obj_actions
+    .return_to
+      = t'return_to'
+      = link_to t("user_groups.groups"), user_groups_path
+    - if check_privilege(Privilege::CREATE, User)
+      = link_to t('user_groups.new.new_user_group'), new_user_group_url, :class => 'button primary', :id => 'new_user_group_button'
+    .button-group
+      - if check_privilege(Privilege::MODIFY, User)
+        = link_to t('edit'), edit_user_group_path(@user_group), :class => 'button pill', :id => 'edit_button'
+        = button_to t("delete"), user_group_path(@user_group), :method => 'delete', :confirm => "Are you sure you want to delete?", :class => 'button pill danger', :id => 'delete'
+  %h1.no-icon= @title
+
+%section.content-section.user
+  %header
+    %h2=t'properties'
+
+  .content
+    %table.properties_table
+      %tbody
+        %tr
+          %td= t('user_groups.show.type')
+          %td= @user_group.membership_source
+        %tr
+          %td= t('user_groups.show.name')
+          %td= @user_group.name
+        %tr
+          %td= t('user_groups.show.description')
+          %td= @user_group.description
+
+- if @user_group.membership_source == UserGroup::MEMBERSHIP_SOURCE_LOCAL
+  %section.content-section
+    %header
+      %h2=t('user_groups.show.members')
+
+    .content
+      - content_for :form_header do
+        - if check_privilege(Privilege::CREATE, Deployable)
+          %li= restful_submit_tag t("remove"), "destroy", remove_members_user_group_path(@user_group), 'DELETE', :id => 'delete_button', :class => 'button danger'
+          %li= link_to t("user_groups.show.add_members"), add_members_user_group_path(@user_group), :class => 'button', :id => 'add_members_button'
+
+      - content_for :filter_controls do
+        %li
+          = label_tag :members_preset_filter, t('filter_table.viewing')
+          = hidden_field_tag :current_path, request.fullpath
+          = select_tag(:members_preset_filter, preset_filters_options_for_select(Deployable::PRESET_FILTERS_OPTIONS, params[:members_preset_filter]), :include_blank => t("users.preset_filters.all_users"), :disabled => true)
+          = restful_submit_tag t("filter_table.apply_filters"), "filter", filter_members_user_group_path(@user_group), 'POST', :class => 'button', :id => 'apply_members_preset_filter'
+          %span.label.badge.dark= @user_group.members.count
+        %li.table-search-filter
+          = text_field_tag :members_search, params[:members_search], :placeholder => t("filter_table.search")
+          = restful_submit_tag "", "search", filter_members_user_group_path(@user_group), 'POST', :class => 'button', :id => 'apply_members_search'
+
+        :javascript
+          $(document).ready(function(){
+            $("#apply_members_preset_filter").hide();
+            $("#members_preset_filter").change(function() {
+              $("#apply_members_preset_filter").click();
+            });
+            Conductor.clickOnEnterKeypress($('#members_search'), $('#apply_members_search'));
+          });
+
+      = filter_table(members_header, @members) do |member|
+        %tr{:class => cycle('nostripe','stripe')}
+          %td{:class => 'checkbox'}
+            - selected = params[:select] == 'all'
+            = check_box_tag "members_selected[]", member.id, selected, :id => "member_checkbox_#{member.id}"
+          %td
+            = link_to member.login, users_path(member)
+          %td
+            = member.name
diff --git a/src/config/locales/en.yml b/src/config/locales/en.yml
index 8c2e010..9295373 100644
--- a/src/config/locales/en.yml
+++ b/src/config/locales/en.yml
@@ -102,6 +102,62 @@ en:
       name_starts_with_B: "Name starts with B"
     errors:
       has_running_instances: '%{login} has running instances'
+  user_groups:
+    groups: "User Groups"
+    local: Local
+    ldap: LDAP
+    return_to: "Return to:"
+    confirm_delete: "Are you sure you want to delete this user group?"
+    quick_jump: "Quick Jump:"
+    choose_members: "Choose Users to add to this Group."
+    index:
+      name: Name
+      type: Type
+    show:
+      name: Name
+      type: Type
+      description: Description
+      members: Members
+      add_members: Add Members
+      edit: Edit
+      delete: Delete
+      created: CREATED
+      last_updated: LAST UPDATED
+    form:
+      name: Name
+      type: Type
+      save_user_group: Save User Group
+    new:
+      new_user_group: New User Group
+      neccessary_information: "Enter necessary information about your new user group and click 'Save User Group' when you are finished."
+    list:
+      delete_selected: Delete Selected
+      add_user_group: Add User Group
+    edit:
+      edit_user_group: Edit User Group
+    flash:
+      notice:
+        added: "User Group added"
+        updated: "User Group updated!"
+        deleted: "User Group has been successfully deleted."
+        more_deleted:
+          one: Deleted user group
+          other: Deleted user groups
+        members_added: "These users have been added"
+        members_removed: "These users have been removed"
+      warning:
+        creation_failed: 'Could not create user group'
+        delete_failed: 'Could not delete user group'
+        not_delete: 'Cannot delete: %{reason}'
+      error:
+        members_not_added: "Could not add these users"
+        members_not_removed: "Could not remove these users"
+        not_a_local_group: 'Only locally-managed groups can be managed here.'
+        select_to_add_members: 'You must select at least one user to add.'
+    preset_filters:
+      all_user_groups: "All User Groups"
+      local: "Locally-managed User Groups"
+      ldap: "LDAP-managed User Groups"
   user_sessions:
     new:
       username: "Username:"
@@ -151,6 +207,7 @@ en:
   role: Role
   role_assignments: Role Assignments
   user: User
+  user_group: User Group
   privileges: Privileges
   general_settings: General Settings
   new_user: New User
@@ -1285,7 +1342,10 @@ en:
         user_and_role: "%{user} (%{role})"
         user_and_role_change: "%{user} (from %{old_role} to %{role})"
     preset_filters:
+      all_entities: All Entities
       all_permissions: All Permissions
+      user_permissions: User Permissions
+      group_permissions: Group Permissions
   quotas:
     quota: Quota
     edit:
@@ -1333,6 +1393,7 @@ en:
       administer: Administer
     second_level:
       users: Users
+      user_groups: User Groups
       environments: Environments
       content: Content
       cloud_providers: Cloud Providers
@@ -1353,7 +1414,7 @@ en:
       realms: Realms
       hardware: Hardware
       users: Users
-      groups: Groups
+      user_groups: User Groups
       permissions: Global Role Grants
       pool_families: Pool Families
       images: Images
diff --git a/src/config/navigation.rb b/src/config/navigation.rb
index c8a48a2..4c37e23 100644
--- a/src/config/navigation.rb
+++ b/src/config/navigation.rb
@@ -20,7 +20,7 @@ SimpleNavigation::Configuration.run do |navigation|
     first_level.dom_class = 'container'
     first_level.item :monitor, t('navigation.first_level.monitor'), pools_path, :class => 'monitor', :link => { :id => 'monitor' }, :highlights_on => /\/deployments|\/pools|\/instances|\/logs|\/\z/
     first_level.item :administer, t('navigation.first_level.administer'), users_path, :class => 'administer' do |second_level|
-      second_level.item :users, t('navigation.second_level.users'), users_path, :link => { :class => 'users' }, :highlights_on => /\/users|\/roles|\/permissions|\/account/
+      second_level.item :users, t('navigation.second_level.users'), users_path, :link => { :class => 'users' }, :highlights_on => /\/users|\/user_groups|\/roles|\/permissions|\/account/
       second_level.item :environments, t('navigation.second_level.environments'), pool_families_path, :link => { :class => 'environments' }, :highlights_on => /\/pool_families|\/images/
       second_level.item :content, t('navigation.second_level.content'), catalogs_path, :link => { :class => 'content' }, :highlights_on => /\/catalogs|\/catalog_entries|\/realms|\/hardware_profiles|\/realm_mappings|\/deployables/
       second_level.item :cloud_providers, t('navigation.second_level.cloud_providers'), providers_path, :link => { :class => 'providers' }, :highlights_on => /\/providers|\/provider_realms|\/config_servers/
diff --git a/src/config/routes.rb b/src/config/routes.rb
index da762a7..68ed728 100644
--- a/src/config/routes.rb
+++ b/src/config/routes.rb
@@ -83,6 +83,8 @@ Conductor::Application.routes.draw do
       get :list
       delete :multi_destroy
       post :multi_update
+      post :filter
+      post :filter_entities
     end
   end
 
@@ -156,6 +158,19 @@ Conductor::Application.routes.draw do
     post :filter, :on => :collection
   end
 
+  resources :user_groups do
+    collection do
+      delete 'multi_destroy'
+      post :filter
+    end
+    member do
+      get 'add_members'
+      post 'add_members'
+      delete 'remove_members'
+      post 'filter_members'
+    end
+  end
+
   resources :logs do
     collection do
       post 'filter'
diff --git a/src/features/step_definitions/user_group_steps.rb b/src/features/step_definitions/user_group_steps.rb
new file mode 100644
index 0000000..2959a21
--- /dev/null
+++ b/src/features/step_definitions/user_group_steps.rb
@@ -0,0 +1,56 @@
+#
+#   Copyright 2012 Red Hat, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+Given /^a user group "([^"]*)" exists$/ do |name|
+  unless UserGroup.find_by_name(name)
+    FactoryGirl.create :user_group, :name => name
+  end
+end
+
+Given /^there is a user group "([^"]*)"$/ do |name|
+  unless UserGroup.find_by_name(name)
+    FactoryGirl.create :user_group, :name => name
+  end
+end
+
+Then /^there should be (\d+) user groups?$/ do |number|
+  UserGroup.count.should == number.to_i
+end
+
+When /^I check "([^"]*)" user group$/ do |user_group_name|
+  user_group = UserGroup.find_by_name(user_group_name)
+  check("user_group_checkbox_#{user_group.id}")
+end
+
+When /^I check the "([^"]*)" member$/ do |login|
+  member = User.find_by_login(login)
+  check("member_checkbox_#{member.id}")
+end
+
+Then /^there should be (\d+) user belonging to "([^"]*)"$/ do |count, name|
+  user_group = UserGroup.find_by_name(name)
+  user_group.members.count == count
+end
+
+Given /^there is a user "([^"]*)" belonging to user group "([^"]*)"$/ do |login, group_name|
+  member = User.find_by_login(login)
+  user_group = UserGroup.find_by_name(group_name)
+  user_group.members << member
+end
+
+Then /^there should not exist a member belonging to "([^"]*)"$/ do |group_name|
+  user_group = UserGroup.find_by_name(group_name)
+  user_group.members.count == 0
+end
diff --git a/src/features/support/paths.rb b/src/features/support/paths.rb
index 38f926d..f809a8c 100644
--- a/src/features/support/paths.rb
+++ b/src/features/support/paths.rb
@@ -35,6 +35,9 @@ module NavigationHelpers
     when /^(.*)'s user page$/i
        user_path(User.find_by_login($1))
 
+    when /^(.*)'s user group page$/i
+       user_group_path(UserGroup.find_by_name($1))
+
     when /^(.*)'s role page$/i
        role_path(Role.find_by_name($1))
 
@@ -179,6 +182,9 @@ module NavigationHelpers
     when /^the (.*)'s edit user page$/
       edit_user_path(User.find_by_login($1))
 
+    when /^the (.*)'s edit user group page$/
+      edit_user_group_path(UserGroup.find_by_name($1))
+
     when /^the "(.*)" catalog page/
       url_for catalog_path(Catalog.find_by_name($1))
 
diff --git a/src/features/user_group.feature b/src/features/user_group.feature
new file mode 100644
index 0000000..08f4db7
--- /dev/null
+++ b/src/features/user_group.feature
@@ -0,0 +1,72 @@
+Feature: Manage User Groups
+  In order to manage user groups
+  As an admin
+  I want to add/edit/remove user groupss
+
+  Background:
+    Given I am an authorised user
+    And I am logged in
+    And a user group "testgroup" exists
+    And a user "testuser" exists
+
+  Scenario: Show user group detials
+    Given I am on the user groups page
+    And there is a user group "testgroup"
+    When I follow "testgroup"
+    Then I should be on testgroup's user group page
+
+  Scenario: Delete user groups
+    Given there is a user group "testgroup"
+    Given there is a user group "testgroup2"
+    And I am on the user groups page
+    Then there should be 2 user groups
+    And I should be on the user groups page
+    When I check "testgroup" user group
+    And I press "Delete"
+    Then I should see "Deleted user group"
+    And there should be 1 user group
+
+  Scenario: Create new user group
+    Given I am on the user groups page
+    When I follow "add_user_group_button"
+    Then I should be on the new user group page
+    And I should see "New User Group"
+    When I fill in the following:
+      | Name  | testgroup3             |
+    And I press "Save"
+    Then I should be on the user groups page
+    And I should see "User Group added"
+
+  Scenario: Edit existing user group
+    Given I am on the user groups page
+    And I follow "testgroup"
+    Then I should be on testgroup's user group page
+    And I should see "testgroup"
+    When I follow "Edit"
+    Then I should be on the testgroup's edit user group page
+    And I fill in "user_group_name" with "newname"
+    When I press "Save"
+    Then I should be on newname's user group page
+    And I should see "User Group updated"
+    And I should see "newname"
+
+  Scenario: Add member to user group
+    Given I am on the user groups page
+    And I follow "testgroup"
+    Then I should be on testgroup's user group page
+    And I should see "testgroup"
+    When I follow "Add Member"
+    And I check the "testuser" member
+    And I press "Add Member"
+    Then there should be 1 user belonging to "testgroup"
+    And I should see "testuser"
+
+  Scenario: Remove member from user group
+    And there is a user "testuser" belonging to user group "testgroup"
+    Given I am on the user groups page
+    And I follow "testgroup"
+    Then I should be on testgroup's user group page
+    Then I should see "testuser"
+    When I check the "testuser" member
+    And I press "Remove"
+    Then there should not exist a member belonging to "testgroup"
diff --git a/src/spec/controllers/user_groups_controller_spec.rb b/src/spec/controllers/user_groups_controller_spec.rb
new file mode 100644
index 0000000..d5e68bb
--- /dev/null
+++ b/src/spec/controllers/user_groups_controller_spec.rb
@@ -0,0 +1,75 @@
+#
+#   Copyright 2011 Red Hat, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+
+require 'spec_helper'
+
+describe UserGroupsController do
+
+  fixtures :all
+  before(:each) do
+    @user_group = Factory.create(:user_group)
+    @tuser = FactoryGirl.create :tuser
+    @admin_permission = FactoryGirl.create :admin_permission
+    @admin = @admin_permission.user
+  end
+
+  describe "#create" do
+    context "user enters valid input" do
+      it "creates user group" do
+        mock_warden(@admin)
+        lambda do
+          post :create, :user_group => {
+            :name => "user_group2", :membership_source => "local" }
+        end.should change(UserGroup, :count).by(1)
+
+        response.should redirect_to(user_groups_path)
+      end
+
+      it "fails to create user" do
+        mock_warden(@admin)
+        lambda do
+          post :create, :user_group => {}
+        end.should_not change(UserGroup, :count)
+
+        returned_user_group = assigns[:user_group]
+        returned_user_group.errors.empty?.should be_false
+        returned_user_group.should have(1).errors_on(:name)
+        returned_user_group.should have(2).errors_on(:membership_source)
+
+        response.should render_template('new')
+      end
+    end
+  end
+
+  it "allows an admin to create user group" do
+    mock_warden(@admin)
+    lambda do
+      post :create, :user_group => {
+           :name => "user_group3", :membership_source => "local" }
+    end.should change(UserGroup, :count)
+
+    response.should redirect_to(user_groups_url)
+  end
+
+  it "should not allow a regular user to create user group" do
+    mock_warden(@tuser)
+    lambda do
+      post :create, :user_group => {
+           :name => "user_group4", :membership_source => "local" }
+    end.should_not change(UserGroup, :count)
+  end
+
+end
diff --git a/src/spec/factories/permission.rb b/src/spec/factories/permission.rb
index e401fe8..4b2d2b4 100644
--- a/src/spec/factories/permission.rb
+++ b/src/spec/factories/permission.rb
@@ -62,4 +62,11 @@ FactoryGirl.define do
     entity { |r| FactoryGirl.create(:pool_family_user).entity }
   end
 
+  factory :group_admin_permission, :parent => :permission do
+    role { |r| Role.first(:conditions => ['name = ?', 'base.admin']) || FactoryGirl.create(:role, :name => 'base.admin') }
+    permission_object { |r| BasePermissionObject.general_permission_scope }
+    entity { |r| FactoryGirl.create(:user_group).entity }
+  end
+
+
 end
diff --git a/src/app/models/common_filter_methods.rb b/src/spec/factories/user_group.rb
similarity index 56%
copy from src/app/models/common_filter_methods.rb
copy to src/spec/factories/user_group.rb
index 7bb2be2..bf1f6fc 100644
--- a/src/app/models/common_filter_methods.rb
+++ b/src/spec/factories/user_group.rb
@@ -1,5 +1,5 @@
 #
-#   Copyright 2011 Red Hat, Inc.
+#   Copyright 2012 Red Hat, Inc.
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -13,19 +13,16 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 #
-#this module contains functions for filtering table data
-module CommonFilterMethods
-  def apply_filters(options = {})
-    apply_preset_filter(options[:preset_filter_id]).apply_search_filter(options[:search_filter])
-  end
 
-  private
+FactoryGirl.define do
+
+  factory :user_group do |u|
+    sequence(:name) { |n| "group#{n}" }
+    membership_source 'local'
+  end
 
-  def apply_preset_filter(preset_filter_id)
-    if preset_filter_id.present?
-      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] == preset_filter_id}.first[:query]
-    else
-      scoped
-    end
+  factory :ldap_group , :parent => :user_group do
+    membership_source = 'LDAP'
   end
+
 end
diff --git a/src/spec/models/permission_spec.rb b/src/spec/models/permission_spec.rb
index 7f05587..ea9a20d 100644
--- a/src/spec/models/permission_spec.rb
+++ b/src/spec/models/permission_spec.rb
@@ -84,4 +84,23 @@ describe Permission do
       should be_false
   end
 
+  it "User added to Admin group should be able to create users" do
+    newuser = FactoryGirl.create(:user)
+    group_admin_permission = FactoryGirl.create(:group_admin_permission)
+    user_group = group_admin_permission.user_group
+    SessionEntity.update_session(@session_id, newuser)
+    BasePermissionObject.general_permission_scope.has_privilege(@session_id,
+                                                                newuser,
+                                                                Privilege::CREATE,
+                                                                User).should be_false
+    user_group.members << newuser
+    newuser.reload
+    SessionEntity.update_session(@session_id, newuser)
+    BasePermissionObject.general_permission_scope.has_privilege(@session_id,
+                                                                newuser,
+                                                                Privilege::CREATE,
+                                                                User).should be_true
+
+  end
+
 end
diff --git a/src/spec/models/user_group_spec.rb b/src/spec/models/user_group_spec.rb
new file mode 100644
index 0000000..915df0a
--- /dev/null
+++ b/src/spec/models/user_group_spec.rb
@@ -0,0 +1,43 @@
+#
+#   Copyright 2012 Red Hat, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+
+require 'spec_helper'
+
+describe UserGroup do
+  before(:each) do
+  end
+
+  it "should create a new local group" do
+    user_group = Factory.create(:user_group)
+    user_group.should be_valid
+    user_group.members.size.should == 0
+  end
+
+  it "should create a new ldap group" do
+    user_group = Factory.create(:ldap_group)
+    user_group.should be_valid
+  end
+
+  it "should add members to a new local group" do
+    user_group = Factory.create(:user_group)
+    user = Factory.create(:tuser)
+    user_group.members.size.should == 0
+    user_group.members << user
+    user_group.reload
+    user_group.members.size.should == 1
+  end
+
+end
-- 
1.7.6.5




More information about the aeolus-devel mailing list