summaryrefslogtreecommitdiffstats
path: root/app/models
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-08-29 16:52:35 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-08-29 16:52:35 +0000
commit603e11d7a5aa62f923e7b013cac6c66462131232 (patch)
treefbbb204d2b92b5a87b787d56fe3f9c62cc3f259b /app/models
parent8da5bad29516be6cbe1bc52e78837ac1ec292026 (diff)
downloadredmine-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.rb6
-rw-r--r--app/models/changeset.rb6
-rw-r--r--app/models/document.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/mail_handler.rb2
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/news.rb4
-rw-r--r--app/models/permission.rb68
-rw-r--r--app/models/query.rb2
-rw-r--r--app/models/role.rb78
-rw-r--r--app/models/user.rb83
-rw-r--r--app/models/wiki_content.rb7
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'