From 888c3581eb0fbfc5ede87a24f7f03bfa4f7d810b Mon Sep 17 00:00:00 2001
From: Jean-Philippe Lang
Date: Thu, 11 Jul 2013 17:45:10 +0000
Subject: [PATCH] Role based custom queries (#1019).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@11994 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
app/controllers/queries_controller.rb | 6 +-
app/helpers/issues_helper.rb | 4 +-
app/models/issue_query.rb | 43 +++++++++++-
app/models/query.rb | 19 +++++-
app/models/user.rb | 2 +-
app/views/queries/_form.html.erb | 23 ++++++-
app/views/queries/index.api.rsb | 2 +-
config/locales/en.yml | 3 +
config/locales/fr.yml | 3 +
.../20130602092539_create_queries_roles.rb | 13 ++++
.../20130710182539_add_queries_visibility.rb | 13 ++++
public/stylesheets/application.css | 2 +
test/fixtures/queries.yml | 18 ++---
test/functional/calendars_controller_test.rb | 2 +-
test/functional/issues_controller_test.rb | 12 ++--
test/functional/queries_controller_test.rb | 34 ++++------
test/unit/query_test.rb | 66 +++++++++++++++++++
test/unit/user_test.rb | 4 +-
18 files changed, 214 insertions(+), 55 deletions(-)
create mode 100644 db/migrate/20130602092539_create_queries_roles.rb
create mode 100644 db/migrate/20130710182539_add_queries_visibility.rb
diff --git a/app/controllers/queries_controller.rb b/app/controllers/queries_controller.rb
index 639255125..cd1ec87a2 100644
--- a/app/controllers/queries_controller.rb
+++ b/app/controllers/queries_controller.rb
@@ -45,7 +45,7 @@ class QueriesController < ApplicationController
@query = IssueQuery.new
@query.user = User.current
@query.project = @project
- @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+ @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
end
@@ -53,7 +53,7 @@ class QueriesController < ApplicationController
@query = IssueQuery.new(params[:query])
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project
- @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+ @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
@@ -71,7 +71,7 @@ class QueriesController < ApplicationController
def update
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
- @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+ @query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 05090c7b3..f88a61e21 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -225,8 +225,8 @@ module IssuesHelper
def render_sidebar_queries
out = ''.html_safe
- out << query_links(l(:label_my_queries), sidebar_queries.reject(&:is_public?))
- out << query_links(l(:label_query_plural), sidebar_queries.select(&:is_public?))
+ out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
+ out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
out
end
diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb
index f9adf4697..b2e470d7a 100644
--- a/app/models/issue_query.rb
+++ b/app/models/issue_query.rb
@@ -45,9 +45,25 @@ class IssueQuery < Query
scope :visible, lambda {|*args|
user = args.shift || User.current
base = Project.allowed_to_condition(user, :view_issues, *args)
- user_id = user.logged? ? user.id : 0
+ scope = includes(:project).where("#{table_name}.project_id IS NULL OR (#{base})")
- includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
+ if user.admin?
+ scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
+ elsif user.memberships.any?
+ scope.where("#{table_name}.visibility = ?" +
+ " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
+ "SELECT DISTINCT q.id FROM #{table_name} q" +
+ " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
+ " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
+ " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
+ " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
+ " OR #{table_name}.user_id = ?",
+ VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
+ elsif user.logged?
+ scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
+ else
+ scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
+ end
}
def initialize(attributes=nil, *args)
@@ -57,7 +73,28 @@ class IssueQuery < Query
# Returns true if the query is visible to +user+ or the current user.
def visible?(user=User.current)
- (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
+ return true if user.admin?
+ return false unless project.nil? || user.allowed_to?(:view_issues, project)
+ case visibility
+ when VISIBILITY_PUBLIC
+ true
+ when VISIBILITY_ROLES
+ if project
+ (user.roles_for_project(project) & roles).any?
+ else
+ Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
+ end
+ else
+ user == self.user
+ end
+ end
+
+ def is_private?
+ visibility == VISIBILITY_PRIVATE
+ end
+
+ def is_public?
+ !is_private?
end
def initialize_available_filters
diff --git a/app/models/query.rb b/app/models/query.rb
index aa24857bd..2753c2768 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -116,8 +116,13 @@ class Query < ActiveRecord::Base
class StatementInvalid < ::ActiveRecord::StatementInvalid
end
+ VISIBILITY_PRIVATE = 0
+ VISIBILITY_ROLES = 1
+ VISIBILITY_PUBLIC = 2
+
belongs_to :project
belongs_to :user
+ has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
serialize :filters
serialize :column_names
serialize :sort_criteria, Array
@@ -126,7 +131,17 @@ class Query < ActiveRecord::Base
validates_presence_of :name
validates_length_of :name, :maximum => 255
+ validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] }
validate :validate_query_filters
+ validate do |query|
+ errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
+ end
+
+ after_save do |query|
+ if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
+ query.roles.clear
+ end
+ end
class_attribute :operators
self.operators = {
@@ -245,9 +260,9 @@ class Query < ActiveRecord::Base
def editable_by?(user)
return false unless user
# Admin can edit them all and regular users can edit their private queries
- return true if user.admin? || (!is_public && self.user_id == user.id)
+ return true if user.admin? || (is_private? && self.user_id == user.id)
# Members can not edit public queries that are for all project (only admin is allowed to)
- is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
+ is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
end
def trackers
diff --git a/app/models/user.rb b/app/models/user.rb
index 40a7120b4..441e6b5b8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -669,7 +669,7 @@ class User < Principal
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
- ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
+ ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
diff --git a/app/views/queries/_form.html.erb b/app/views/queries/_form.html.erb
index 1070b4716..116821529 100644
--- a/app/views/queries/_form.html.erb
+++ b/app/views/queries/_form.html.erb
@@ -6,15 +6,22 @@
<%= text_field 'query', 'name', :size => 80 %>
<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %>
-
-<%= check_box 'query', 'is_public',
- :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %>
+
+
+
+ <% Role.givable.sorted.each do |role| %>
+
+ <% end %>
+
+ <%= hidden_field_tag 'query[role_ids][]', '' %>
+
<% end %>
<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?,
:disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>
+