Restrict access to confidential issues

This commit is contained in:
Douglas Barbosa Alexandre 2016-03-17 16:38:51 -03:00
parent 6b86d3fb80
commit 34ee75379c
6 changed files with 193 additions and 11 deletions

View file

@ -5,7 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :issue, only: [:edit, :update, :show]
# Allow read any issue
before_action :authorize_read_issue!
before_action :authorize_read_issue!, only: [:show]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@ -128,6 +128,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
alias_method :subscribable_resource, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
end
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
end

View file

@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
def klass
Issue
end
private
def init_collection
Issue.visible_to_user(current_user)
end
end

View file

@ -49,7 +49,6 @@ class Ability
rules = [
:read_project,
:read_wiki,
:read_issue,
:read_label,
:read_milestone,
:read_project_snippet,
@ -63,6 +62,9 @@ class Ability
# Allow to read builds by anonymous user if guests are allowed
rules << :read_build if project.public_builds?
# Allow to read issues by anonymous user if issue is not confidential
rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
rules - project_disabled_features_rules(project)
else
[]
@ -321,6 +323,7 @@ class Ability
end
rules += project_abilities(user, subject.project)
rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
rules
end
end
@ -439,5 +442,17 @@ class Ability
:"admin_#{name}"
]
end
def filter_confidential_issues_abilities(user, issue, rules)
return rules if user.admin? || !issue.confidential?
unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id)
rules.delete(:admin_issue)
rules.delete(:read_issue)
rules.delete(:update_issue)
end
rules
end
end
end

View file

@ -58,6 +58,13 @@ class Issue < ActiveRecord::Base
attributes
end
def self.visible_to_user(user)
return where(confidential: false) if user.blank?
return all if user.admin?
where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id))
end
def self.reference_prefix
'#'
end

View file

@ -1,16 +1,16 @@
require('spec_helper')
describe Projects::IssuesController do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
before do
sign_in(user)
project.team << [user, :developer]
end
describe "GET #index" do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
before do
sign_in(user)
project.team << [user, :developer]
end
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
@ -38,6 +38,152 @@ describe Projects::IssuesController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
end
describe 'Confidential Issues' do
let(:project) { create(:empty_project, :public) }
let(:assignee) { create(:assignee) }
let(:author) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let!(:issue) { create(:issue, project: project) }
let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
describe 'GET #index' do
it 'should not list confidential issues for guests' do
sign_out(:user)
get_issues
expect(assigns(:issues)).to eq [issue]
end
it 'should not list confidential issues for non project members' do
sign_in(non_member)
get_issues
expect(assigns(:issues)).to eq [issue]
end
it 'should list confidential issues for author' do
sign_in(author)
get_issues
expect(assigns(:issues)).to include unescaped_parameter_value
expect(assigns(:issues)).not_to include request_forgery_timing_attack
end
it 'should list confidential issues for assignee' do
sign_in(assignee)
get_issues
expect(assigns(:issues)).not_to include unescaped_parameter_value
expect(assigns(:issues)).to include request_forgery_timing_attack
end
it 'should list confidential issues for project members' do
sign_in(member)
project.team << [member, :developer]
get_issues
expect(assigns(:issues)).to include unescaped_parameter_value
expect(assigns(:issues)).to include request_forgery_timing_attack
end
it 'should list confidential issues for admin' do
sign_in(admin)
get_issues
expect(assigns(:issues)).to include unescaped_parameter_value
expect(assigns(:issues)).to include request_forgery_timing_attack
end
def get_issues
get :index,
namespace_id: project.namespace.to_param,
project_id: project.to_param
end
end
shared_examples_for 'restricted action' do |http_status|
it 'returns 404 for guests' do
sign_out :user
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status :not_found
end
it 'returns 404 for non project members' do
sign_in(non_member)
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status :not_found
end
it "returns #{http_status[:success]} for author" do
sign_in(author)
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status http_status[:success]
end
it "returns #{http_status[:success]} for assignee" do
sign_in(assignee)
go(id: request_forgery_timing_attack.to_param)
expect(response).to have_http_status http_status[:success]
end
it "returns #{http_status[:success]} for project members" do
sign_in(member)
project.team << [member, :developer]
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status http_status[:success]
end
it "returns #{http_status[:success]} for admin" do
sign_in(admin)
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status http_status[:success]
end
end
describe 'GET #show' do
it_behaves_like 'restricted action', success: 200
def go(id:)
get :show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: id
end
end
describe 'GET #edit' do
it_behaves_like 'restricted action', success: 200
def go(id:)
get :edit,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: id
end
end
describe 'PUT #update' do
it_behaves_like 'restricted action', success: 302
def go(id:)
put :update,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: id,
issue: { title: 'New title' }
end
end
end
end

View file

@ -4,6 +4,10 @@ FactoryGirl.define do
author
project
trait :confidential do
confidential true
end
trait :closed do
state :closed
end