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