Commit dc737fd3 authored by jplang's avatar jplang

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: https://svn.redmine.org/redmine/trunk@674 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent f19fda16
......@@ -22,7 +22,6 @@ class AccountController < ApplicationController
# prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register]
before_filter :require_login, :only => :logout
# Show user's account
def show
......@@ -31,7 +30,7 @@ class AccountController < ApplicationController
# show only public projects and private projects that the logged in user is also a member of
@memberships = @user.memberships.select do |membership|
membership.project.is_public? || (logged_in_user && logged_in_user.role_for_project(membership.project))
membership.project.is_public? || (User.current.role_for_project(membership.project))
end
rescue ActiveRecord::RecordNotFound
render_404
......@@ -41,12 +40,12 @@ class AccountController < ApplicationController
def login
if request.get?
# Logout user
self.logged_in_user = nil
self.logged_user = nil
else
# Authenticate user
user = User.try_to_login(params[:login], params[:password])
if user
self.logged_in_user = user
self.logged_user = user
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
token = Token.create(:user => user, :action => 'autologin')
......@@ -62,8 +61,8 @@ class AccountController < ApplicationController
# Log out current user and redirect to welcome page
def logout
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", logged_in_user.id, "autologin"]) if logged_in_user
self.logged_in_user = nil
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
self.logged_user = nil
redirect_to :controller => 'welcome'
end
......@@ -140,4 +139,15 @@ class AccountController < ApplicationController
end
end
end
private
def logged_user=(user)
if user && user.is_a?(User)
User.current = user
session[:user_id] = user.id
else
User.current = User.anonymous
session[:user_id] = nil
end
end
end
......@@ -46,14 +46,6 @@ class AdminController < ApplicationController
end
def mail_options
@actions = Permission.find(:all, :conditions => ["mail_option=?", true]) || []
if request.post?
@actions.each { |a|
a.mail_enabled = (params[:action_ids] || []).include? a.id.to_s
a.save
}
flash.now[:notice] = l(:notice_successful_update)
end
end
def test_email
......@@ -61,8 +53,8 @@ class AdminController < ApplicationController
# Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true
begin
@test = Mailer.deliver_test(logged_in_user)
flash[:notice] = l(:notice_email_sent, logged_in_user.mail)
@test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e
flash[:error] = l(:notice_email_error, e.message)
end
......
......@@ -16,48 +16,47 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ApplicationController < ActionController::Base
before_filter :check_if_login_required, :set_localization
before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
REDMINE_SUPPORTED_SCM.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def logged_in_user=(user)
@logged_in_user = user
session[:user_id] = (user ? user.id : nil)
def logged_in_user
User.current.logged? ? User.current : nil
end
def logged_in_user
def current_role
@current_role ||= User.current.role_for_project(@project)
end
def user_setup
if session[:user_id]
@logged_in_user ||= User.find(session[:user_id])
# existing session
User.current = User.find(session[:user_id])
elsif cookies[:autologin] && Setting.autologin?
# auto-login feature
User.current = User.find_by_autologin_key(autologin_key)
elsif params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication
User.current = User.find_by_rss_key(params[:key])
else
nil
User.current = User.anonymous
end
end
# Returns the role that the logged in user has on the current project
# or nil if current user is not a member of the project
def logged_in_user_membership
@user_membership ||= logged_in_user.role_for_project(@project)
end
# check if login is globally required to access the application
def check_if_login_required
# no check needed if user is already logged in
return true if logged_in_user
# auto-login feature
autologin_key = cookies[:autologin]
if autologin_key && Setting.autologin?
self.logged_in_user = User.find_by_autologin_key(autologin_key)
end
return true if User.current.logged?
require_login if Setting.login_required?
end
def set_localization
lang = begin
if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
self.logged_in_user.language
if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym
User.current.language
elsif request.env['HTTP_ACCEPT_LANGUAGE']
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
......@@ -71,7 +70,7 @@ class ApplicationController < ActionController::Base
end
def require_login
unless self.logged_in_user
if !User.current.logged?
store_location
redirect_to :controller => "account", :action => "login"
return false
......@@ -81,34 +80,17 @@ class ApplicationController < ActionController::Base
def require_admin
return unless require_login
unless self.logged_in_user.admin?
if !User.current.admin?
render_403
return false
end
true
end
# authorizes the user for the requested action.
# Authorize the user for the requested action
def authorize(ctrl = params[:controller], action = params[:action])
unless @project.active?
@project = nil
render_404
return false
end
# check if action is allowed on public projects
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ]
return true
end
# if action is not public, force login
return unless require_login
# admin is always authorized
return true if self.logged_in_user.admin?
# if not admin, check membership permission
if logged_in_user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], logged_in_user_membership )
return true
end
render_403
false
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
allowed ? true : (User.current.logged? ? render_403 : require_login)
end
# make sure that the user is a member of the project (or admin) if project is private
......@@ -119,11 +101,8 @@ class ApplicationController < ActionController::Base
render_404
return false
end
return true if @project.is_public?
return false unless logged_in_user
return true if logged_in_user.admin? || logged_in_user_membership
render_403
false
return true if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
User.current.logged? ? render_403 : require_login
end
# store current uri in session.
......@@ -154,6 +133,21 @@ class ApplicationController < ActionController::Base
render :template => "common/404", :layout => true, :status => 404
return false
end
def render_feed(items, options={})
@items = items.sort {|x,y| y.event_datetime <=> x.event_datetime }
@title = options[:title] || Setting.app_title
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
end
def self.accept_key_auth(*actions)
actions = actions.flatten.map(&:to_s)
write_inheritable_attribute('accept_key_auth_actions', actions)
end
def accept_key_auth_actions
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
end
# qvalues http header parser
# code taken from webrick
......@@ -173,4 +167,4 @@ class ApplicationController < ActionController::Base
end
return tmp
end
end
\ No newline at end of file
end
......@@ -17,9 +17,7 @@
class BoardsController < ApplicationController
layout 'base'
before_filter :find_project
before_filter :authorize, :except => [:index, :show]
before_filter :check_project_privacy, :only => [:index, :show]
before_filter :find_project, :authorize
helper :messages
include MessagesHelper
......
......@@ -52,7 +52,7 @@ class DocumentsController < ApplicationController
a = Attachment.create(:container => @document, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? #and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @document
end
......
......@@ -72,8 +72,15 @@ class IssuesController < ApplicationController
unless params[:notes].empty?
journal = @issue.init_journal(self.logged_in_user, params[:notes])
if @issue.save
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
return
end
......@@ -100,14 +107,14 @@ class IssuesController < ApplicationController
} if params[:attachments] and params[:attachments].is_a? Array
# Log time
if logged_in_user.authorized_to(@project, "timelog/edit")
if current_role.allowed_to?(:log_time)
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
@time_entry.save
end
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
end
rescue ActiveRecord::StaleObjectError
......@@ -124,23 +131,6 @@ class IssuesController < ApplicationController
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
end
def add_attachment
# Save the attachments
@attachments = []
journal = @issue.init_journal(self.logged_in_user)
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
journal.save if journal.details.any?
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
end
def destroy_attachment
a = @issue.attachments.find(params[:attachment_id])
a.destroy
......
......@@ -17,8 +17,7 @@
class MessagesController < ApplicationController
layout 'base'
before_filter :find_project, :check_project_privacy
before_filter :require_login, :only => [:new, :reply]
before_filter :find_project, :authorize
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
......
This diff is collapsed.
......@@ -16,9 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class QueriesController < ApplicationController
layout 'base'
before_filter :require_login, :except => :index
before_filter :find_project, :check_project_privacy
layout 'base'
before_filter :find_project, :authorize
def index
@queries = @project.queries.find(:all,
......@@ -31,7 +30,7 @@ class QueriesController < ApplicationController
@query.project = @project
@query.user = logged_in_user
@query.executed_by = logged_in_user
@query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
@query.is_public = false unless current_role.allowed_to?(:manage_pulic_queries)
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
......@@ -52,7 +51,7 @@ class QueriesController < ApplicationController
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
@query.attributes = params[:query]
@query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
@query.is_public = false unless current_role.allowed_to?(:manage_pulic_queries)
if @query.save
flash[:notice] = l(:notice_successful_update)
......
......@@ -22,8 +22,8 @@ require 'digest/sha1'
class RepositoriesController < ApplicationController
layout 'base'
before_filter :find_project, :except => [:update_form]
before_filter :authorize, :except => [:update_form, :stats, :graph]
before_filter :check_project_privacy, :only => [:stats, :graph]
before_filter :authorize, :except => [:update_form]
accept_key_auth :revisions
def show
# check if new revisions have been committed in the repository
......@@ -57,7 +57,10 @@ class RepositoriesController < ApplicationController
:limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset)
render :action => "revisions", :layout => false if request.xhr?
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
end
end
def entry
......
......@@ -28,40 +28,35 @@ class RolesController < ApplicationController
end
def list
@role_pages, @roles = paginate :roles, :per_page => 25, :order => "position"
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "list", :layout => false if request.xhr?
end
def new
@role = Role.new(params[:role])
if request.post?
@role.permissions = Permission.find(params[:permission_ids]) if params[:permission_ids]
if @role.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
end
if request.post? && @role.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
end
@permissions = Permission.find(:all, :conditions => ["is_public=?", false], :order => 'sort ASC')
@permissions = @role.setable_permissions
end
def edit
@role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
@role.permissions = Permission.find(params[:permission_ids] || [])
Permission.allowed_to_role_expired
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
end
@permissions = Permission.find(:all, :conditions => ["is_public=?", false], :order => 'sort ASC')
@permissions = @role.setable_permissions
end
def destroy
@role = Role.find(params[:id])
unless @role.members.empty?
flash[:error] = 'Some members have this role. Can\'t delete it.'
else
#unless @role.members.empty?
# flash[:error] = 'Some members have this role. Can\'t delete it.'
#else
@role.destroy
end
#end
redirect_to :action => 'list'
end
......@@ -95,19 +90,19 @@ class RolesController < ApplicationController
flash[:notice] = l(:notice_successful_update)
end
end
@roles = Role.find(:all, :order => 'position')
@roles = Role.find(:all, :order => 'builtin, position')
@trackers = Tracker.find(:all, :order => 'position')
@statuses = IssueStatus.find(:all, :include => :workflows, :order => 'position')
end
def report
@roles = Role.find(:all, :order => 'position')
@permissions = Permission.find :all, :conditions => ["is_public=?", false], :order => 'sort'
@roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post?
@roles.each do |role|
role.permissions = Permission.find(params[:permission_ids] ? (params[:permission_ids][role.id.to_s] || []) : [] )
role.permissions = params[:permissions][role.id.to_s]
role.save
end
Permission.allowed_to_role_expired
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
end
......
......@@ -16,11 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class TimelogController < ApplicationController
layout 'base'
before_filter :find_project
before_filter :authorize, :only => :edit
before_filter :check_project_privacy, :except => :edit
layout 'base'
before_filter :find_project, :authorize
helper :sort
include SortHelper
......
......@@ -87,7 +87,7 @@ class UsersController < ApplicationController
end
end
@auth_sources = AuthSource.find(:all)
@roles = Role.find(:all, :order => 'position')
@roles = Role.find_all_givable
@projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
@membership ||= Member.new
end
......
......@@ -20,7 +20,7 @@ class WatchersController < ApplicationController
before_filter :require_login, :find_project, :check_project_privacy
def add
user = logged_in_user
user = User.current
@watched.add_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher added.', :layout => true }
......@@ -29,7 +29,7 @@ class WatchersController < ApplicationController
end
def remove
user = logged_in_user
user = User.current
@watched.remove_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher removed.', :layout => true }
......
......@@ -21,7 +21,5 @@ class WelcomeController < ApplicationController
def index
@news = News.latest logged_in_user
@projects = Project.latest logged_in_user
@key = logged_in_user.get_or_create_rss_key.value if logged_in_user
end
end
......@@ -19,8 +19,7 @@ require 'diff'
class WikiController < ApplicationController
layout 'base'
before_filter :find_wiki, :check_project_privacy
before_filter :authorize, :only => [:destroy, :add_attachment, :destroy_attachment]
before_filter :find_wiki, :authorize
verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
......
......@@ -25,32 +25,18 @@ end
module ApplicationHelper
# Return current logged in user or nil
def loggedin?
@logged_in_user
def current_role
@current_role ||= User.current.role_for_project(@project)
end
# Return true if user is logged in and is admin, otherwise false
def admin_loggedin?
@logged_in_user and @logged_in_user.admin?
end
# Return true if user is authorized for controller/action, otherwise false
def authorize_for(controller, action)
# check if action is allowed on public projects
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
return true
end
# check if user is authorized
if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project) ) )
return true
end
return false
def authorize_for(controller, action)
User.current.allowed_to?({:controller => controller, :action => action}, @project)
end
# Display a link if user is authorized
def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
end
# Display a link to user's account page
......
......@@ -175,4 +175,12 @@ module ProjectsHelper
gc.draw(imgl)
imgl
end if Object.const_defined?(:Magick)
def new_issue_selector
trackers = Tracker.find(:all, :order => 'position')
form_tag({:controller => 'projects', :action => 'add_issue', :id => @project}, :method => :get) +
select_tag('tracker_id', '<option></option' + options_from_collection_for_select(trackers, 'id', 'name'),
:onchange => "if (this.value != '') {this.form.submit()}") +
end_form_tag
end
end
......@@ -21,7 +21,7 @@ module WatchersHelper
end
def watcher_link(object, user)
return '' unless user && object.respond_to?('watched_by?')
return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'remove' : 'add'),
......
......@@ -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"
......
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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")
......
......@@ -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")