diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-08-29 16:52:35 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-08-29 16:52:35 +0000 |
commit | 603e11d7a5aa62f923e7b013cac6c66462131232 (patch) | |
tree | fbbb204d2b92b5a87b787d56fe3f9c62cc3f259b /app/models | |
parent | 8da5bad29516be6cbe1bc52e78837ac1ec292026 (diff) | |
download | redmine-603e11d7a5aa62f923e7b013cac6c66462131232.tar.gz redmine-603e11d7a5aa62f923e7b013cac6c66462131232.zip |
Merged 0.6 branch into trunk.
Permissions management was rewritten. Some permissions can now be specifically defined for non member and anonymous users.
This migration:
* is irreversible (please, don't forget to *backup* your database before upgrading)
* resets role's permissions (go to "Admin -> Roles & Permissions" to set them after upgrading)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@674 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/attachment.rb | 6 | ||||
-rw-r--r-- | app/models/changeset.rb | 6 | ||||
-rw-r--r-- | app/models/document.rb | 2 | ||||
-rw-r--r-- | app/models/issue.rb | 2 | ||||
-rw-r--r-- | app/models/mail_handler.rb | 2 | ||||
-rw-r--r-- | app/models/member.rb | 4 | ||||
-rw-r--r-- | app/models/news.rb | 4 | ||||
-rw-r--r-- | app/models/permission.rb | 68 | ||||
-rw-r--r-- | app/models/query.rb | 2 | ||||
-rw-r--r-- | app/models/role.rb | 78 | ||||
-rw-r--r-- | app/models/user.rb | 83 | ||||
-rw-r--r-- | app/models/wiki_content.rb | 7 |
12 files changed, 172 insertions, 92 deletions
diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 443a75bab..f57038b96 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -24,7 +24,11 @@ class Attachment < ActiveRecord::Base validates_presence_of :container, :filename validates_length_of :filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255 - + + acts_as_event :title => :filename, + :description => :filename, + :url => Proc.new {|o| {:controller => 'attachment', :action => 'download', :id => o.id}} + cattr_accessor :storage_path @@storage_path = "#{RAILS_ROOT}/files" diff --git a/app/models/changeset.rb b/app/models/changeset.rb index 57e2d74a4..9400df869 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -19,6 +19,12 @@ class Changeset < ActiveRecord::Base belongs_to :repository has_many :changes, :dependent => :delete_all has_and_belongs_to_many :issues + + acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))}, + :description => :comments, + :datetime => :committed_on, + :author => :committer, + :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}} validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_numericality_of :revision, :only_integer => true diff --git a/app/models/document.rb b/app/models/document.rb index 8b5d68e87..6989191ce 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -20,6 +20,8 @@ class Document < ActiveRecord::Base belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id" has_many :attachments, :as => :container, :dependent => :destroy + acts_as_event :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} + validates_presence_of :project, :title, :category validates_length_of :title, :maximum => 60 end diff --git a/app/models/issue.rb b/app/models/issue.rb index 65b34cb92..b6eda1767 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -36,6 +36,8 @@ class Issue < ActiveRecord::Base has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all acts_as_watchable + acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, + :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}} validates_presence_of :subject, :description, :priority, :tracker, :author, :status validates_length_of :subject, :maximum => 255 diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 39b088bf4..7a1d73244 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -31,7 +31,7 @@ class MailHandler < ActionMailer::Base user = User.find_active(:first, :conditions => {:mail => email.from.first}) return unless user # check permission - return unless Permission.allowed_to_role("issues/add_note", user.role_for_project(issue.project)) + return unless user.allowed_to?(:add_issue_notes, issue.project) # add the note issue.init_journal(user, email.body.chomp) diff --git a/app/models/member.rb b/app/models/member.rb index 2aa26d42f..39703147d 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -23,6 +23,10 @@ class Member < ActiveRecord::Base validates_presence_of :role, :user, :project validates_uniqueness_of :user_id, :scope => :project_id + def validate + errors.add :role_id, :activerecord_error_invalid if role && !role.member? + end + def name self.user.name end diff --git a/app/models/news.rb b/app/models/news.rb index e9a48846a..4352363d9 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -23,7 +23,9 @@ class News < ActiveRecord::Base validates_presence_of :title, :description validates_length_of :title, :maximum => 60 validates_length_of :summary, :maximum => 255 - + + acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} + # returns latest news for projects visible by user def self.latest(user=nil, count=5) find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") diff --git a/app/models/permission.rb b/app/models/permission.rb deleted file mode 100644 index bea670c4c..000000000 --- a/app/models/permission.rb +++ /dev/null @@ -1,68 +0,0 @@ -# redMine - project management software -# Copyright (C) 2006 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Permission < ActiveRecord::Base - has_and_belongs_to_many :roles - - validates_presence_of :controller, :action, :description - - GROUPS = { - 100 => :label_project, - 200 => :label_member_plural, - 300 => :label_version_plural, - 400 => :label_issue_category_plural, - 600 => :label_query_plural, - 1000 => :label_issue_plural, - 1100 => :label_news_plural, - 1200 => :label_document_plural, - 1300 => :label_attachment_plural, - 1400 => :label_repository, - 1500 => :label_time_tracking, - 1700 => :label_wiki_page_plural, - 2000 => :label_board_plural - }.freeze - - @@cached_perms_for_public = nil - @@cached_perms_for_roles = nil - - def name - self.controller + "/" + self.action - end - - def group_id - (self.sort / 100)*100 - end - - def self.allowed_to_public(action) - @@cached_perms_for_public ||= find(:all, :conditions => ["is_public=?", true]).collect {|p| "#{p.controller}/#{p.action}"} - @@cached_perms_for_public.include? action - end - - def self.allowed_to_role(action, role) - @@cached_perms_for_roles ||= - begin - perms = {} - find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } } - perms - end - allowed_to_public(action) or (role && @@cached_perms_for_roles[action] && @@cached_perms_for_roles[action].include?(role.id)) - end - - def self.allowed_to_role_expired - @@cached_perms_for_roles = nil - end -end diff --git a/app/models/query.rb b/app/models/query.rb index 28f65ddf6..ff519d71c 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -78,7 +78,7 @@ class Query < ActiveRecord::Base def editable_by?(user) return false unless user return true if !is_public && self.user_id == user.id - is_public && user.authorized_to(project, "projects/add_query") + is_public && user.allowed_to?(:manage_pulic_queries, project) end def available_filters diff --git a/app/models/role.rb b/app/models/role.rb index 98d735e8e..015146dc4 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -16,23 +16,93 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class Role < ActiveRecord::Base - before_destroy :check_integrity - has_and_belongs_to_many :permissions + # Built-in roles + BUILTIN_NON_MEMBER = 1 + BUILTIN_ANONYMOUS = 2 + + before_destroy :check_deletable has_many :workflows, :dependent => :delete_all has_many :members acts_as_list + + serialize :permissions + attr_protected :builtin validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 validates_format_of :name, :with => /^[\w\s\'\-]*$/i + def permissions + read_attribute(:permissions) || [] + end + + def permissions=(perms) + perms = perms.collect {|p| p.to_sym unless p.blank? }.compact if perms + write_attribute(:permissions, perms) + end + def <=>(role) position <=> role.position end + # Return true if the role is a builtin role + def builtin? + self.builtin != 0 + end + + # Return true if the role is a project member role + def member? + !self.builtin? + end + + # Return true if role is allowed to do the specified action + # action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + def allowed_to?(action) + if action.is_a? Hash + allowed_actions.include? "#{action[:controller]}/#{action[:action]}" + else + allowed_permissions.include? action + end + end + + # Return all the permissions that can be given to the role + def setable_permissions + setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions + setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER + setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS + setable_permissions + end + + # Find all the roles that can be given to a project member + def self.find_all_givable + find(:all, :conditions => {:builtin => 0}, :order => 'position') + end + + # Return the builtin 'non member' role + def self.non_member + find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.') + end + + # Return the builtin 'anonymous' role + def self.anonymous + find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.') + end + + private - def check_integrity - raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id]) + def allowed_permissions + @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} + end + + def allowed_actions + @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten + end + + def check_deletable + raise "Can't delete role" if members.any? + raise "Can't delete builtin role" if builtin? end end diff --git a/app/models/user.rb b/app/models/user.rb index a017b4289..4cb8da1f9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,7 +28,7 @@ class User < ActiveRecord::Base has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' - has_one :rss_key, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" + has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" belongs_to :auth_source attr_accessor :password, :password_confirmation @@ -121,24 +121,14 @@ class User < ActiveRecord::Base User.hash_password(clear_password) == self.hashed_password end - def role_for_project(project) - return nil unless project - member = memberships.detect {|m| m.project_id == project.id} - member ? member.role : nil - end - - def authorized_to(project, action) - return true if self.admin? - role = role_for_project(project) - role && Permission.allowed_to_role(action, role) - end - def pref self.preference ||= UserPreference.new(:user => self) end - def get_or_create_rss_key - self.rss_key || Token.create(:user => self, :action => 'feeds') + # Return user's RSS key (a 40 chars long string), used to access feeds + def rss_key + token = self.rss_token || Token.create(:user => self, :action => 'feeds') + token.value end def self.find_by_rss_key(key) @@ -155,9 +145,72 @@ class User < ActiveRecord::Base lastname == user.lastname ? firstname <=> user.firstname : lastname <=> user.lastname end + def to_s + name + end + + def logged? + true + end + + # Return user's role for project + def role_for_project(project) + # No role on archived projects + return nil unless project && project.active? + # Find project membership + membership = memberships.detect {|m| m.project_id == project.id} + if membership + membership.role + elsif logged? + Role.non_member + else + Role.anonymous + end + end + + # Return true if the user is a member of project + def member_of?(project) + role_for_project(project).member? + end + + # Return true if the user is allowed to do the specified action on project + # action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + def allowed_to?(action, project) + return false unless project.active? + return true if admin? + role = role_for_project(project) + return false unless role + role.allowed_to?(action) && (project.is_public? || role.member?) + end + + def self.current=(user) + @current_user = user + end + + def self.current + @current_user ||= AnonymousUser.new + end + + def self.anonymous + AnonymousUser.new + end + private # Return password digest def self.hash_password(clear_password) Digest::SHA1.hexdigest(clear_password || "") end end + +class AnonymousUser < User + def logged? + false + end + + # Anonymous user has no RSS key + def rss_key + nil + end +end diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 2d0be8225..4b60a4373 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -28,7 +28,12 @@ class WikiContent < ActiveRecord::Base belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' attr_protected :data - + + acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, + :description => :comments, + :datetime => :updated_on, + :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}} + def text=(plain) case Setting.wiki_compression when 'gzip' |