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 | 12 +-
src/app/controllers/user_groups_controller.rb | 202
++++++++++++++++++++ .../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 | 62 ++++++-
src/config/routes.rb | 15 ++
.../step_definitions/user_group_steps.rb} | 26 ++-
src/features/support/paths.rb | 6 +
src/features/user_group.feature | 54 ++++++
.../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 ++++
29 files changed, 851 insertions(+), 44 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
copy src/{app/models/entity.rb =>
features/step_definitions/user_group_steps.rb} (52%) 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 d2c4efd..1ed8159 100644
--- a/aeolus-conductor.spec.in
+++ b/aeolus-conductor.spec.in
@@ -199,6 +199,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 eb5931b..b30326b
100644
--- a/src/app/controllers/application_controller.rb
+++ b/src/app/controllers/application_controller.rb
@@ -281,7 +281,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}
@@ -350,7 +350,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 acdbb2c..6d8ae54
100644
--- a/src/app/controllers/permissions_controller.rb
+++ b/src/app/controllers/permissions_controller.rb
@@ -165,11 +165,21 @@ 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_order),
params[:page]) + @entities =
paginate_collection(Entity.order(sort_order).
+ apply_filters(:preset_filter_id => params[:entities_preset_filter],
+ :search_filter => params[:entities_search]),
params[:page]) end
def load_headers
diff --git a/src/app/controllers/user_groups_controller.rb
b/src/app/controllers/user_groups_controller.rb new file mode 100644
index 0000000..189ec09
--- /dev/null
+++ b/src/app/controllers/user_groups_controller.rb
@@ -0,0 +1,202 @@
+#
+# 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((a)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 !(a)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 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_typ
:e]
+ # :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.#{(a)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..19b825a 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:
@@ -1353,7 +1413,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/routes.rb b/src/config/routes.rb
index da762a7..38fbb53 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'
+ post 'remove_members'
+ post 'filter_members'
+ end
+ end
+
resources :logs do
collection do
post 'filter'
diff --git a/src/app/models/entity.rb
b/src/features/step_definitions/user_group_steps.rb similarity index 52%
copy from src/app/models/entity.rb
copy to src/features/step_definitions/user_group_steps.rb
index bbc56fe..b5426f2 100644
--- a/src/app/models/entity.rb
+++ b/src/features/step_definitions/user_group_steps.rb
@@ -13,17 +13,23 @@
# 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
-class Entity < ActiveRecord::Base
- belongs_to :entity_target, :polymorphic => true
- validates_presence_of :entity_target_id
- has_many :session_entities, :dependent => :destroy
- has_many :permissions, :dependent => :destroy
- has_many :derived_permissions, :dependent => :destroy
+Given /^there is a user group "([^"]*)"$/ do |name|
+ unless UserGroup.find_by_name(name)
+ FactoryGirl.create :user_group, :name => name
+ end
+end
- # type-specific associations
- belongs_to :user, :class_name => "User", :foreign_key =>
"entity_target_id" - belongs_to :user_group, :class_name =>
"UserGroup",
- :foreign_key => "entity_target_id"
+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
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..d858035
--- /dev/null
+++ b/src/features/user_group.feature
@@ -0,0 +1,54 @@
+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
+
+ 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
+ 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"
+
+
+
+
remove this ^, because git said "src/features/user_group.feature:51: new blank
line at EOF" when I applied this patch
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
NACK with one inline nit.
I'd like to advise you for next time to split patch to smaller parts(patches),
then the reviewing will be more comfortable. Also whole new funcionality could
be more readable.
It's good approach to have only 1-3 changed files per patch, in this patch is
29 files changed, 851 insertions(+), 44 deletions(-)
Issues to fix:
- navigation.rb - need to be added user_groups to "second_level.item :users
... , :highlights_on => .."
- method remove_members is missing in user_groups_controller.rb (what caused
that member cannot be removed from group)
- missing test for removing members