Commit cf52a9b4 authored by Douwe Maan's avatar Douwe Maan Committed by Rémy Coutable

Merge branch 'confidential-issues' into 'master'

Add confidential issues

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/3678

More information: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/227

See merge request !3282
parent 4d48c607
Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
......
......@@ -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
......@@ -158,7 +162,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:title, :assignee_id, :position, :description, :confidential,
:milestone_id, :state_event, :task_num, label_ids: []
)
end
......
......@@ -134,7 +134,7 @@ class ProjectsController < ApplicationController
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project)
autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
......
......@@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
def klass
Issue
end
private
def init_collection
Issue.visible_to_user(current_user)
end
end
......@@ -301,7 +301,7 @@ module ApplicationHelper
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.send(entity).count
project.issues.visible_to_user(current_user).send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
......
......@@ -194,7 +194,7 @@ module EventsHelper
end
def event_to_atom(xml, event)
if event.proper?
if event.proper?(current_user)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
......
......@@ -98,6 +98,10 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ')
end
def confidential_icon(issue)
icon('eye-slash') if issue.confidential?
end
def emoji_icon(name, unicode = nil, aliases = [])
unicode ||= Emoji.emoji_filename(name) rescue ""
......
......@@ -38,7 +38,7 @@ module MilestonesHelper
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
style: "width: #{milestone.percent_complete}%;"
style: "width: #{milestone.percent_complete(current_user)}%;"
}
content_tag :div, class: 'progress' do
......
......@@ -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
module Milestoneish
def closed_items_count
issues.closed.size + merge_requests.closed_and_merged.size
def closed_items_count(user = nil)
issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
end
def total_items_count
issues.size + merge_requests.size
def total_items_count(user = nil)
issues_visible_to_user(user).size + merge_requests.size
end
def complete?
total_items_count == closed_items_count
def complete?(user = nil)
total_items_count(user) == closed_items_count(user)
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
def percent_complete(user = nil)
((closed_items_count(user) * 100) / total_items_count(user)).abs
rescue ZeroDivisionError
0
end
......@@ -22,4 +22,8 @@ module Milestoneish
(due_date - Date.today).to_i
end
def issues_visible_to_user(user = nil)
issues.visible_to_user(user)
end
end
......@@ -73,15 +73,17 @@ class Event < ActiveRecord::Base
end
end
def proper?
def proper?(user = nil)
if push?
true
elsif membership_changed?
true
elsif created_project?
true
elsif issue?
Ability.abilities.allowed?(user, :read_issue, issue)
else
((issue? || merge_request? || note?) && target) || milestone?
((merge_request? || note?) && target) || milestone?
end
end
......
......@@ -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
......
......@@ -121,8 +121,8 @@ class Milestone < ActiveRecord::Base
active? && issues.opened.count.zero?
end
def is_empty?
total_items_count.zero?
def is_empty?(user = nil)
total_items_count(user).zero?
end
def author_id
......
module Projects
class AutocompleteService < BaseService
def initialize(project)
@project = project
end
def issues
@project.issues.opened.select([:iid, :title])
@project.issues.visible_to_user(current_user).opened.select([:iid, :title])
end
def merge_requests
......
......@@ -11,7 +11,7 @@ module Search
projects = ProjectsFinder.new.execute(current_user)
projects = projects.in_namespace(group.id) if group
Gitlab::SearchResults.new(projects, params[:search])
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
end
end
......@@ -7,7 +7,8 @@ module Search
end
def execute
Gitlab::ProjectSearchResults.new(project,
Gitlab::ProjectSearchResults.new(current_user,
project,
params[:search],
params[:repository_ref])
end
......
- if event.proper?
- if event.proper?(current_user)
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
......
......@@ -67,7 +67,7 @@
%span
Issues
- if @project.default_issues_tracker?
%span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
%span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
......
......@@ -5,6 +5,7 @@
.issue-title
%span.issue-title-text
= confidential_icon(issue)
= link_to_gfm issue.title, issue_path(issue), class: "title"
%ul.controls.light
- if issue.closed?
......
......@@ -22,6 +22,7 @@
= icon('angle-double-left')
.issue-meta
= confidential_icon(@issue)
%strong.identifier
Issue ##{@issue.iid}
%span.creator
......
......@@ -42,7 +42,7 @@
= preserve do
= markdown @milestone.description
- if @milestone.complete? && @milestone.active?
- if @milestone.complete?(current_user) && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
......
.search-result-row
%h4
= confidential_icon(issue)
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
.pull-right ##{issue.iid}
......
......@@ -29,6 +29,15 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
- if issuable.is_a?(Issue) && !issuable.project.private?
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :confidential do
= f.check_box :confidential
This issue is confidential and should only be visible to team members
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
%hr
.form-group
......
......@@ -10,6 +10,8 @@
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
- if issuable.is_a?(Issue)
= confidential_icon(issuable)
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
%div{class: 'issuable-detail'}
= link_to [project.namespace.becomes(Namespace), project, issuable] do
......
......@@ -6,10 +6,10 @@
.col-sm-6
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
.col-sm-6
.pull-right.light #{milestone.percent_complete}% complete
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
.col-sm-6
= link_to pluralize(milestone.issues.size, 'Issue'), issues_path
= link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
......
......@@ -3,15 +3,15 @@
.context.prepend-top-default
.milestone-summary
%h4 Progress
%strong= milestone.issues.size
%strong= milestone.issues_visible_to_user(current_user).size
issues:
%span.milestone-stat
%strong= milestone.issues.opened.size
%strong= milestone.issues_visible_to_user(current_user).opened.size
open and
%strong= milestone.issues.closed.size
%strong= milestone.issues_visible_to_user(current_user).closed.size
closed
%span.milestone-stat
%strong== #{milestone.percent_complete}%
%strong== #{milestone.percent_complete(current_user)}%
complete
%span.milestone-stat
......
......@@ -2,7 +2,7 @@
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge= milestone.issues.size
%span.badge= milestone.issues_visible_to_user(current_user).size
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
......@@ -21,7 +21,7 @@
.tab-content.milestone-content
.tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
= render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-participants
......
......@@ -28,7 +28,7 @@
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
- if milestone.complete? && milestone.active?
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
......@@ -47,7 +47,7 @@
- project_name = group ? ms.project.name : ms.project.name_with_namespace
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
%td
= ms.issues.opened.count
= ms.issues_visible_to_user(current_user).opened.count
%td
- if ms.closed?
Closed
......
class AddConfidentialToIssues < ActiveRecord::Migration
def change
add_column :issues, :confidential, :boolean, default: false
add_index :issues, :confidential
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160316123110) do
ActiveRecord::Schema.define(version: 20160316204731) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -416,10 +416,12 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
t.boolean "confidential", default: false
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
......
......@@ -35,7 +35,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see projects activity feed' do
expect(page).to have_content 'closed issue'
expect(page).to have_content 'joined project'
end
step 'I should see issues from group "Owned" assigned to me' do
......
......@@ -82,7 +82,7 @@ module API
# GET /projects/:id/issues?milestone=1.0.0&state=closed
# GET /issues?iid=42
get ":id/issues" do
issues = user_project.issues
issues = user_project.issues.visible_to_user(current_user)
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
......@@ -104,6 +104,7 @@ module API
# GET /projects/:id/issues/:issue_id
get ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
not_found! unless can?(current_user, :read_issue, @issue)
present @issue, with: Entities::Issue
end
......
......@@ -9,6 +9,11 @@ module Banzai
Issue
end
def self.user_can_see_reference?(user, node, context)
issue = Issue.find(node.attr('data-issue')) rescue nil
Ability.abilities.allowed?(user, :read_issue, issue)
end
def find_object(project, id)
project.get_issue(id)
end
......
......@@ -2,7 +2,8 @@ module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref
def initialize(project, query, repository_ref = nil)
def initialize(current_user, project, query, repository_ref = nil)
@current_user = current_user
@project = project
@repository_ref = if repository_ref.present?
repository_ref
......
module Gitlab
class SearchResults
attr_reader :query
attr_reader :current_user, :query
# Limit search results by passed projects
# It allows us to search only for projects user has access to
attr_reader :limit_projects
def initialize(limit_projects, query)
def initialize(current_user, limit_projects, query)
@current_user = current_user
@limit_projects = limit_projects || Project.all
@query = Shellwords.shellescape(query) if query.present?
end
......@@ -58,7 +59,7 @@ module Gitlab
end
def issues
issues = Issue.where(project_id: project_ids_relation)
issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation)
if query =~ /#(\d+)\z/
issues = issues.where(iid: $1)
......
require('spec_helper')
describe Projects::IssuesController do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
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
before do
sign_in(user)
project.team << [user, :developer]
end
describe "GET #index" do
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
......@@ -4,6 +4,10 @@ FactoryGirl.define do
author
project
trait :confidential do
confidential true
end
trait :closed do
state :closed
end
......
......@@ -24,7 +24,7 @@ feature 'Start new branch from an issue', feature: true do
end
let(:referenced_mr) do
create(:merge_request, :simple, source_project: project, target_project: project,
description: "Fixes ##{issue.iid}")
description: "Fixes ##{issue.iid}", author: user)
end
before do
......
......@@ -44,8 +44,78 @@ describe Banzai::Filter::RedactorFilter, lib: true do
end
end
context "for user references" do
context 'with data-issue' do
context 'for confidential issues' do
it 'removes references for non project members' do
non_member = create(:user)
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: non_member)
expect(doc.css('a').length).to eq 0
end
it 'allows references for author' do
author = create(:user)
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project, author: author)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: author)
expect(doc.css('a').length).to eq 1
end
it 'allows references for assignee' do
assignee = create(:user)
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project, assignee: assignee)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 1
end
it 'allows references for project members' do
member = create(:user)
project = create(:empty_project, :public)
project.team << [member, :developer]
issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1
end
it 'allows references for admin' do
admin = create(:admin)
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: admin)
expect(doc.css('a').length).to eq 1
end
end
it 'allows references for non confidential issues' do
user = create(:user)
project = create(:empty_project, :public)
issue = create(:issue, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
end
context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
......
......@@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
subject { described_class.new(project, project.creator) }
before do
project.team << [project.creator, :developer]
project2.team << [project.creator, :master]
end
......
require 'spec_helper'
describe Gitlab::ProjectSearchResults, lib: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') }
let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to be_nil }
......@@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) }
let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
describe 'confidential issues' do
let(:query) { 'issue' }
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
it 'should not list project confidential issues for non project members' do
results = described_class.new(non_member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 1
end
it 'should list project confidential issues for author' do
results = described_class.new(author, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 2
end
it 'should list project confidential issues for assignee' do
results = described_class.new(assignee, project.id, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 2
end
it 'should list project confidential issues for project members' do
project.team << [member, :developer]
results = described_class.new(member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 3
end
it 'should list all project issues for admin' do
results = described_class.new(admin, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(results.issues_count).to eq 3
end
end
end
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor, lib: true do
let(:project) { create(:project) }
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
it 'accesses valid user objects' do
......@@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
end
it 'accesses valid issue objects' do
project.team << [project.creator, :developer]
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
......
require 'spec_helper'
describe Gitlab::SearchResults do
let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') }
......@@ -9,7 +10,7 @@ describe Gitlab::SearchResults do
end
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
let(:results) { described_class.new(Project.all, 'foo') }
let(:results) { described_class.new(user, Project.all, 'foo') }
describe '#total_count' do
it 'returns the total amount of search hits' do
......@@ -52,4 +53,92 @@ describe Gitlab::SearchResults do
expect(results.empty?).to eq(false)
end
end
describe 'confidential issues' do
let(:project_1) { create(:empty_project) }
let(:project_2) { create(:empty_project) }
let(:project_3) { create(:empty_project) }
let(:project_4) { create(:empty_project) }
let(:query) { 'issue' }
let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') }
let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) }
let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) }
let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
it 'should not list confidential issues for non project members' do
results = described_class.new(non_member, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(issues).not_to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 1
end
it 'should list confidential issues for author' do
results = described_class.new(author, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).not_to include security_issue_2
expect(issues).to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 3
end
it 'should list confidential issues for assignee' do
results = described_class.new(assignee, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).to include security_issue_2
expect(issues).not_to include security_issue_3
expect(issues).to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 3
end
it 'should list confidential issues for project members' do
project_1.team << [member, :developer]
project_2.team << [member, :developer]
results = described_class.new(member, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(issues).to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 4
end
it 'should list all issues for admin' do
results = described_class.new(admin, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
expect(issues).to include security_issue_3
expect(issues).to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 5
end
end
end
......@@ -86,10 +86,21 @@ eos
let(:issue) { create :issue, project: project }
let(:other_project) { create :project, :public }
let(:other_issue) { create :issue, project: other_project }
let(:commiter) { create :user }
before do
project.team << [commiter, :developer]
other_project.team << [commiter, :developer]
end
it 'detects issues that this commit is marked as closing' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
allow(commit).to receive_messages(
safe_message: "Fixes ##{issue.iid} and #{ext_ref}",
committer_email: commiter.email
)
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
......
......@@ -48,7 +48,8 @@ describe Issue, "Mentionable" do
describe '#create_new_cross_references!' do
let(:project) { create(:project) }
let(:issues) { create_list(:issue, 2, project: project) }
let(:author) { create(:author) }
let(:issues) { create_list(:issue, 2, project: project, author: author) }
context 'before changes are persisted' do
it 'ignores pre-existing references' do
......@@ -91,7 +92,7 @@ describe Issue, "Mentionable" do
end
def create_issue(description:)
create(:issue, project: project, description: description)
create(:issue, project: project, description: description, author: author)
end
end
end
require 'spec_helper'
describe Milestone, 'Milestoneish' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
let!(:issue) { create(:issue, project: project, milestone: milestone) }
let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) }
let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
before do
project.team << [member, :developer]
end
describe '#closed_items_count' do
it 'should not count confidential issues for non project members' do
expect(milestone.closed_items_count(non_member)).to eq 2
end
it 'should count confidential issues for author' do
expect(milestone.closed_items_count(author)).to eq 4
end
it 'should count confidential issues for assignee' do
expect(milestone.closed_items_count(assignee)).to eq 4
end
it 'should count confidential issues for project members' do
expect(milestone.closed_items_count(member)).to eq 6
end
it 'should count all issues for admin' do
expect(milestone.closed_items_count(admin)).to eq 6
end
end
describe '#total_items_count' do
it 'should not count confidential issues for non project members' do
expect(milestone.total_items_count(non_member)).to eq 4
end
it 'should count confidential issues for author' do
expect(milestone.total_items_count(author)).to eq 7
end
it 'should count confidential issues for assignee' do
expect(milestone.total_items_count(assignee)).to eq 7
end
it 'should count confidential issues for project members' do
expect(milestone.total_items_count(member)).to eq 10
end
it 'should count all issues for admin' do
expect(milestone.total_items_count(admin)).to eq 10
end
end
describe '#complete?' do
it 'returns false when has items opened' do
expect(milestone.complete?(non_member)).to eq false
end
it 'returns true when all items are closed' do
issue.close
merge_request.close
expect(milestone.complete?(non_member)).to eq true
end
end
describe '#percent_complete' do
it 'should not count confidential issues for non project members' do
expect(milestone.percent_complete(non_member)).to eq 50
end
it 'should count confidential issues for author' do
expect(milestone.percent_complete(author)).to eq 57
end
it 'should count confidential issues for assignee' do
expect(milestone.percent_complete(assignee)).to eq 57
end
it 'should count confidential issues for project members' do
expect(milestone.percent_complete(member)).to eq 60
end
it 'should count confidential issues for admin' do
expect(milestone.percent_complete(admin)).to eq 60
end
end
end
......@@ -65,6 +65,42 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
describe '#proper?' do
context 'issue event' do
let(:project) { create(:empty_project, :public) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:author) { create(:author) }
let(:assignee) { create(:user) }
let(:admin) { create(:admin) }
let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) }
before do
project.team << [member, :developer]
end
context 'for non confidential issues' do
let(:issue) { create(:issue, project: project, author: author, assignee: assignee) }
it { expect(event.proper?(non_member)).to eq true }
it { expect(event.proper?(author)).to eq true }
it { expect(event.proper?(assignee)).to eq true }
it { expect(event.proper?(member)).to eq true }
it { expect(event.proper?(admin)).to eq true }
end
context 'for confidential issues' do
let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
it { expect(event.proper?(non_member)).to eq false }
it { expect(event.proper?(author)).to eq true }
it { expect(event.proper?(assignee)).to eq true }
it { expect(event.proper?(member)).to eq true }
it { expect(event.proper?(admin)).to eq true }
end
end
end
describe '.limit_recent' do
let!(:event1) { create(:closed_issue_event) }
let!(:event2) { create(:closed_issue_event) }
......
......@@ -150,6 +150,7 @@ describe MergeRequest, models: true do
let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
subject.project.team << [subject.author, :developer]
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
end
......
......@@ -32,6 +32,7 @@ describe Milestone, models: true do
let(:milestone) { create(:milestone) }
let(:issue) { create(:issue) }
let(:user) { create(:user) }
describe "unique milestone title per project" do
it "shouldn't accept the same title in a project twice" do
......@@ -50,18 +51,17 @@ describe Milestone, models: true do
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
expect(milestone.percent_complete).to eq(0)
expect(milestone.percent_complete(user)).to eq(0)
end
it "should count closed issues" do
issue.close
milestone.issues << issue
expect(milestone.percent_complete).to eq(100)
expect(milestone.percent_complete(user)).to eq(100)
end
it "should recover from dividing by zero" do
expect(milestone.issues).to receive(:size).and_return(0)
expect(milestone.percent_complete).to eq(0)
expect(milestone.percent_complete(user)).to eq(0)
end
end
......@@ -103,7 +103,7 @@ describe Milestone, models: true do
)
end
it { expect(milestone.percent_complete).to eq(75) }
it { expect(milestone.percent_complete(user)).to eq(75) }
end
describe :items_count do
......@@ -113,23 +113,23 @@ describe Milestone, models: true do
milestone.merge_requests << create(:merge_request)
end
it { expect(milestone.closed_items_count).to eq(1) }
it { expect(milestone.total_items_count).to eq(3) }
it { expect(milestone.is_empty?).to be_falsey }
it { expect(milestone.closed_items_count(user)).to eq(1) }
it { expect(milestone.total_items_count(user)).to eq(3) }
it { expect(milestone.is_empty?(user)).to be_falsey }
end
describe :can_be_closed? do
it { expect(milestone.can_be_closed?).to be_truthy }
end
describe :is_empty? do
describe :total_items_count do
before do
create :closed_issue, milestone: milestone
create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
expect(milestone.total_items_count).to eq 2
expect(milestone.total_items_count(user)).to eq 2
end
end
......
......@@ -3,7 +3,11 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let(:non_member) { create(:user) }
let(:author) { create(:author) }
let(:assignee) { create(:assignee) }
let(:admin) { create(:admin) }
let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
......@@ -12,6 +16,13 @@ describe API::API, api: true do
state: :closed,
milestone: milestone
end
let!(:confidential_issue) do
create :issue,
:confidential,
project: project,
author: author,
assignee: assignee
end
let!(:issue) do
create :issue,
author: user,
......@@ -123,10 +134,43 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
it "should return project issues" do
it 'should return project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
end
it 'should return project confidential issues for author' do
get api("#{base_url}/issues", author)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'should return project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'should return project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
it 'should return project confidential issues for admin' do
get api("#{base_url}/issues", admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
......@@ -206,6 +250,41 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/54321", user)
expect(response.status).to eq(404)
end
context 'confidential issues' do
it "should return 404 for non project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
expect(response.status).to eq(404)
end
it "should return confidential issue for project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "should return confidential issue for author" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "should return confidential issue for assignee" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
it "should return confidential issue for admin" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
end
end
describe "POST /projects/:id/issues" do
......@@ -294,6 +373,35 @@ describe API::API, api: true do
expect(response.status).to eq(400)
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
context 'confidential issues' do
it "should return 403 for non project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
title: 'updated title'
expect(response.status).to eq(403)
end
it "should update a confidential issue for project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
title: 'updated title'
expect(response.status).to eq(200)
expect(json_response['title']).to eq('updated title')
end
it "should update a confidential issue for author" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
title: 'updated title'
expect(response.status).to eq(200)
expect(json_response['title']).to eq('updated title')
end
it "should update a confidential issue for admin" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
title: 'updated title'
expect(response.status).to eq(200)
expect(json_response['title']).to eq('updated title')
end
end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
......
......@@ -215,12 +215,16 @@ describe GitPushService, services: true do
let(:commit) { project.commit }
before do
project.team << [commit_author, :developer]
project.team << [user, :developer]
allow(commit).to receive_messages(
safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
)
allow(project.repository).to receive(:commits_between).and_return([commit])
end
......
require 'spec_helper'
describe Projects::AutocompleteService, services: true do
describe '#issues' do
describe 'confidential issues' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
let(:member) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:empty_project, :public) }
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
it 'should not list project confidential issues for guests' do
autocomplete = described_class.new(project, nil)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).not_to include security_issue_1.iid
expect(issues).not_to include security_issue_2.iid
expect(issues.count).to eq 1
end
it 'should not list project confidential issues for non project members' do
autocomplete = described_class.new(project, non_member)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).not_to include security_issue_1.iid
expect(issues).not_to include security_issue_2.iid
expect(issues.count).to eq 1
end
it 'should list project confidential issues for author' do
autocomplete = described_class.new(project, author)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).to include security_issue_1.iid
expect(issues).not_to include security_issue_2.iid
expect(issues.count).to eq 2
end
it 'should list project confidential issues for assignee' do
autocomplete = described_class.new(project, assignee)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).not_to include security_issue_1.iid
expect(issues).to include security_issue_2.iid
expect(issues.count).to eq 2
end
it 'should list project confidential issues for project members' do
project.team << [member, :developer]
autocomplete = described_class.new(project, member)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).to include security_issue_1.iid
expect(issues).to include security_issue_2.iid
expect(issues.count).to eq 3
end
it 'should list all project issues for admin' do
autocomplete = described_class.new(project, admin)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).to include security_issue_1.iid
expect(issues).to include security_issue_2.iid
expect(issues.count).to eq 3
end
end
end
end
......@@ -52,6 +52,8 @@ shared_context 'mentionable context' do
end
set_mentionable_text.call(ref_string)
project.team << [author, :developer]
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment