Commit 501a90a2 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge remote-tracking branch 'dev/master'

parents e13a197c 06132caf
This diff is collapsed.
...@@ -794,7 +794,7 @@ GEM ...@@ -794,7 +794,7 @@ GEM
nenv (~> 0.1) nenv (~> 0.1)
shellany (~> 0.0) shellany (~> 0.0)
numerizer (0.2.0) numerizer (0.2.0)
oauth (0.5.4) oauth (0.5.6)
oauth2 (1.4.7) oauth2 (1.4.7)
faraday (>= 0.8, < 2.0) faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0) jwt (>= 1.0, < 3.0)
......
...@@ -66,6 +66,7 @@ export function initMermaid(mermaid) { ...@@ -66,6 +66,7 @@ export function initMermaid(mermaid) {
useMaxWidth: true, useMaxWidth: true,
htmlLabels: true, htmlLabels: true,
}, },
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
securityLevel: 'strict', securityLevel: 'strict',
}); });
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Admin::ImpersonationTokensController < Admin::ApplicationController class Admin::ImpersonationTokensController < Admin::ApplicationController
before_action :user before_action :user
before_action :verify_impersonation_enabled!
feature_category :authentication_and_authorization feature_category :authentication_and_authorization
...@@ -41,6 +42,10 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController ...@@ -41,6 +42,10 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def verify_impersonation_enabled!
access_denied! unless helpers.impersonation_enabled?
end
def finder(options = {}) def finder(options = {})
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options)) PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
end end
......
...@@ -16,6 +16,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -16,6 +16,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
@todos = @todos.with_entity_associations @todos = @todos.with_entity_associations
return if redirect_out_of_range(@todos, todos_page_count(@todos)) return if redirect_out_of_range(@todos, todos_page_count(@todos))
@allowed_todos = ::Todos::AllowedTargetFilterService.new(@todos, current_user).execute
end end
def destroy def destroy
......
...@@ -20,7 +20,7 @@ class InvitesController < ApplicationController ...@@ -20,7 +20,7 @@ class InvitesController < ApplicationController
end end
def accept def accept
if member.accept_invite!(current_user) if current_user_matches_invite? && member.accept_invite!(current_user)
redirect_to invite_details[:path], notice: helpers.invite_accepted_notice(member) redirect_to invite_details[:path], notice: helpers.invite_accepted_notice(member)
else else
redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") }) redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") })
...@@ -52,7 +52,7 @@ class InvitesController < ApplicationController ...@@ -52,7 +52,7 @@ class InvitesController < ApplicationController
end end
def current_user_matches_invite? def current_user_matches_invite?
@member.invite_email == current_user.email current_user.verified_emails.include?(@member.invite_email)
end end
def member? def member?
......
...@@ -8,8 +8,8 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -8,8 +8,8 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create, :charts, :config_variables] before_action :pipeline, except: [:index, :new, :create, :charts, :config_variables]
before_action :set_pipeline_path, only: [:show] before_action :set_pipeline_path, only: [:show]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index] before_action :authorize_read_build!, only: [:index, :show]
before_action :authorize_read_analytics!, only: [:charts] before_action :authorize_read_ci_cd_analytics!, only: [:charts]
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do before_action do
......
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
module Resolvers module Resolvers
class ProjectPipelineStatisticsResolver < BaseResolver class ProjectPipelineStatisticsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Ci::AnalyticsType, null: true type Types::Ci::AnalyticsType, null: true
authorizes_object!
authorize :read_ci_cd_analytics
def resolve def resolve
weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object) weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object)
monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object) monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object)
......
...@@ -34,7 +34,7 @@ module Resolvers ...@@ -34,7 +34,7 @@ module Resolvers
return Todo.none unless current_user.present? && target.present? return Todo.none unless current_user.present? && target.present?
return Todo.none if target.is_a?(User) && target != current_user return Todo.none if target.is_a?(User) && target != current_user
TodosFinder.new(current_user, todo_finder_params(args)).execute TodosFinder.new(current_user, todo_finder_params(args)).execute.with_entity_associations
end end
private private
......
...@@ -115,11 +115,9 @@ module Types ...@@ -115,11 +115,9 @@ module Types
null: true, null: true,
description: 'Runbook for the alert as defined in alert details.' description: 'Runbook for the alert as defined in alert details.'
field :todos, field :todos, description: 'To-do items of the current user for the alert.', resolver: Resolvers::TodoResolver do
Types::TodoType.connection_type, extension(::Gitlab::Graphql::TodosProjectPermissionPreloader::FieldExtension)
null: true, end
description: 'To-do items of the current user for the alert.',
resolver: Resolvers::TodoResolver
field :details_url, field :details_url,
GraphQL::Types::String, GraphQL::Types::String,
......
...@@ -55,9 +55,6 @@ module Types ...@@ -55,9 +55,6 @@ module Types
type: GraphQL::Types::String, type: GraphQL::Types::String,
null: false, null: false,
description: 'Web path of the user.' description: 'Web path of the user.'
field :todos,
resolver: Resolvers::TodoResolver,
description: 'To-do items of the user.'
field :group_memberships, field :group_memberships,
type: Types::GroupMemberType.connection_type, type: Types::GroupMemberType.connection_type,
null: true, null: true,
...@@ -81,6 +78,10 @@ module Types ...@@ -81,6 +78,10 @@ module Types
description: 'Projects starred by the user.', description: 'Projects starred by the user.',
resolver: Resolvers::UserStarredProjectsResolver resolver: Resolvers::UserStarredProjectsResolver
field :todos, resolver: Resolvers::TodoResolver, description: 'To-do items of the user.' do
extension(::Gitlab::Graphql::TodosProjectPermissionPreloader::FieldExtension)
end
# Merge request field: MRs can be authored, assigned, or assigned-for-review: # Merge request field: MRs can be authored, assigned, or assigned-for-review:
field :authored_merge_requests, field :authored_merge_requests,
resolver: Resolvers::AuthoredMergeRequestsResolver, resolver: Resolvers::AuthoredMergeRequestsResolver,
......
...@@ -266,6 +266,10 @@ module AlertManagement ...@@ -266,6 +266,10 @@ module AlertManagement
end end
end end
def to_ability_name
'alert_management_alert'
end
private private
def hook_data def hook_data
......
...@@ -103,4 +103,12 @@ class ApplicationRecord < ActiveRecord::Base ...@@ -103,4 +103,12 @@ class ApplicationRecord < ActiveRecord::Base
def self.cached_column_list def self.cached_column_list
self.column_names.map { |column_name| self.arel_table[column_name] } self.column_names.map { |column_name| self.arel_table[column_name] }
end end
def readable_by?(user)
Ability.allowed?(user, "read_#{to_ability_name}".to_sym, self)
end
def to_ability_name
model_name.element
end
end end
...@@ -550,6 +550,10 @@ class Commit ...@@ -550,6 +550,10 @@ class Commit
expire_note_etag_cache_for_related_mrs expire_note_etag_cache_for_related_mrs
end end
def readable_by?(user)
Ability.allowed?(user, :read_commit, self)
end
private private
def expire_note_etag_cache_for_related_mrs def expire_note_etag_cache_for_related_mrs
......
...@@ -182,10 +182,6 @@ module DesignManagement ...@@ -182,10 +182,6 @@ module DesignManagement
File.join(DesignManagement.designs_directory, "issue-#{issue.iid}", design.filename) File.join(DesignManagement.designs_directory, "issue-#{issue.iid}", design.filename)
end end
def to_ability_name
'design'
end
def description def description
'' ''
end end
......
...@@ -714,10 +714,6 @@ class Group < Namespace ...@@ -714,10 +714,6 @@ class Group < Namespace
Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists? Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists?
end end
def to_ability_name
model_name.singular
end
def activity_path def activity_path
Gitlab::Routing.url_helpers.activity_group_path(self) Gitlab::Routing.url_helpers.activity_group_path(self)
end end
......
...@@ -544,6 +544,25 @@ class Issue < ApplicationRecord ...@@ -544,6 +544,25 @@ class Issue < ApplicationRecord
self.update_column(:upvotes_count, self.upvotes) self.update_column(:upvotes_count, self.upvotes)
end end
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
# for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8
# Make sure to sync this method with issue_policy.rb
def readable_by?(user)
if user.can_read_all_resources?
true
elsif project.owner == user
true
elsif confidential? && !assignee_or_author?(user)
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
private private
def spammable_attribute_changed? def spammable_attribute_changed?
...@@ -569,25 +588,6 @@ class Issue < ApplicationRecord ...@@ -569,25 +588,6 @@ class Issue < ApplicationRecord
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author) Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author)
end end
# Returns `true` if the given User can read the current Issue.
#
# This method duplicates the same check of issue_policy.rb
# for performance reasons, check commit: 002ad215818450d2cbbc5fa065850a953dc7ada8
# Make sure to sync this method with issue_policy.rb
def readable_by?(user)
if user.can_read_all_resources?
true
elsif project.owner == user
true
elsif confidential? && !assignee_or_author?(user)
project.team.member?(user, Gitlab::Access::REPORTER)
else
project.public? ||
project.internal? && !user.external? ||
project.team.member?(user)
end
end
# Returns `true` if this Issue is visible to everybody. # Returns `true` if this Issue is visible to everybody.
def publicly_visible? def publicly_visible?
project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled? project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled?
......
...@@ -384,12 +384,6 @@ class Note < ApplicationRecord ...@@ -384,12 +384,6 @@ class Note < ApplicationRecord
super super
end end
# This method is to be used for checking read permissions on a note instead of `system_note_with_references_visible_for?`
def readable_by?(user)
# note_policy accounts for #system_note_with_references_visible_for?(user) check when granting read access
Ability.allowed?(user, :read_note, self)
end
def award_emoji? def award_emoji?
can_be_award_emoji? && contains_emoji_only? can_be_award_emoji? && contains_emoji_only?
end end
...@@ -406,10 +400,6 @@ class Note < ApplicationRecord ...@@ -406,10 +400,6 @@ class Note < ApplicationRecord
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/ note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end end
def to_ability_name
model_name.singular
end
def noteable_ability_name def noteable_ability_name
if for_snippet? if for_snippet?
'snippet' 'snippet'
......
...@@ -1495,10 +1495,6 @@ class Project < ApplicationRecord ...@@ -1495,10 +1495,6 @@ class Project < ApplicationRecord
end end
end end
def to_ability_name
model_name.singular
end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks) def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do run_after_commit_or_now do
......
...@@ -44,7 +44,18 @@ class IssuePolicy < IssuablePolicy ...@@ -44,7 +44,18 @@ class IssuePolicy < IssuablePolicy
enable :update_subscription enable :update_subscription
end end
rule { ~persisted & can?(:guest_access) }.policy do # admin can set metadata on new issues
rule { ~persisted & admin }.policy do
enable :set_issue_metadata
end
# support bot needs to be able to set metadata on new issues when service desk is enabled
rule { ~persisted & support_bot & can?(:guest_access) }.policy do
enable :set_issue_metadata
end
# guest members need to be able to set issue metadata per https://gitlab.com/gitlab-org/gitlab/-/issues/300100
rule { ~persisted & is_project_member & can?(:guest_access) }.policy do
enable :set_issue_metadata enable :set_issue_metadata
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class PersonalAccessTokenPolicy < BasePolicy class PersonalAccessTokenPolicy < BasePolicy
condition(:is_owner) { user && subject.user_id == user.id } condition(:is_owner) { user && subject.user_id == user.id && !subject.impersonation }
rule { (is_owner | admin) & ~blocked }.policy do rule { (is_owner | admin) & ~blocked }.policy do
enable :read_token enable :read_token
......
...@@ -284,6 +284,7 @@ class ProjectPolicy < BasePolicy ...@@ -284,6 +284,7 @@ class ProjectPolicy < BasePolicy
enable :read_confidential_issues enable :read_confidential_issues
enable :read_package enable :read_package
enable :read_product_analytics enable :read_product_analytics
enable :read_ci_cd_analytics
end end
# We define `:public_user_access` separately because there are cases in gitlab-ee # We define `:public_user_access` separately because there are cases in gitlab-ee
...@@ -484,6 +485,7 @@ class ProjectPolicy < BasePolicy ...@@ -484,6 +485,7 @@ class ProjectPolicy < BasePolicy
prevent(:read_insights) prevent(:read_insights)
prevent(:read_cycle_analytics) prevent(:read_cycle_analytics)
prevent(:read_repository_graphs) prevent(:read_repository_graphs)
prevent(:read_ci_cd_analytics)
end end
rule { wiki_disabled }.policy do rule { wiki_disabled }.policy do
...@@ -559,6 +561,7 @@ class ProjectPolicy < BasePolicy ...@@ -559,6 +561,7 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics enable :read_cycle_analytics
enable :read_pages_content enable :read_pages_content
enable :read_analytics enable :read_analytics
enable :read_ci_cd_analytics
enable :read_insights enable :read_insights
# NOTE: may be overridden by IssuePolicy # NOTE: may be overridden by IssuePolicy
...@@ -666,6 +669,7 @@ class ProjectPolicy < BasePolicy ...@@ -666,6 +669,7 @@ class ProjectPolicy < BasePolicy
rule { support_bot & ~service_desk_enabled }.policy do rule { support_bot & ~service_desk_enabled }.policy do
prevent :create_note prevent :create_note
prevent :read_project prevent :read_project
prevent :guest_access
end end
rule { project_bot }.enable :project_bot_access rule { project_bot }.enable :project_bot_access
......
...@@ -5,7 +5,10 @@ class TodoPolicy < BasePolicy ...@@ -5,7 +5,10 @@ class TodoPolicy < BasePolicy
condition(:own_todo) do condition(:own_todo) do
@user && @subject.user_id == @user.id @user && @subject.user_id == @user.id
end end
condition(:can_read_target) do
@user && @subject.target&.readable_by?(@user)
end
rule { own_todo }.enable :read_todo rule { own_todo & can_read_target }.enable :read_todo
rule { own_todo }.enable :update_todo rule { own_todo & can_read_target }.enable :update_todo
end end
...@@ -48,6 +48,9 @@ module Issues ...@@ -48,6 +48,9 @@ module Issues
params.delete(:created_at) unless moved_issue || current_user.can?(:set_issue_created_at, project) params.delete(:created_at) unless moved_issue || current_user.can?(:set_issue_created_at, project)
params.delete(:updated_at) unless moved_issue || current_user.can?(:set_issue_updated_at, project) params.delete(:updated_at) unless moved_issue || current_user.can?(:set_issue_updated_at, project)
# Only users with permission to handle error data can add it to issues
params.delete(:sentry_issue_attributes) unless current_user.can?(:update_sentry_issue, project)
issue.system_note_timestamp = params[:created_at] || params[:updated_at] issue.system_note_timestamp = params[:created_at] || params[:updated_at]
end end
......
# frozen_string_literal: true
module Todos
class AllowedTargetFilterService
include Gitlab::Allowable
def initialize(todos, current_user)
@todos = todos
@current_user = current_user
end
def execute
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(@todos.map(&:project).compact, @current_user).execute
@todos.select { |todo| can?(@current_user, :read_todo, todo) }
end
end
end
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
= link_to _("SSH keys"), keys_admin_user_path(@user) = link_to _("SSH keys"), keys_admin_user_path(@user)
= nav_link(controller: :identities) do = nav_link(controller: :identities) do
= link_to _("Identities"), admin_user_identities_path(@user) = link_to _("Identities"), admin_user_identities_path(@user)
= nav_link(controller: :impersonation_tokens) do - if impersonation_enabled?
= link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user) = nav_link(controller: :impersonation_tokens) do
= link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user)
.gl-mb-3 .gl-mb-3
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
= number_with_delimiter(todos_done_count) = number_with_delimiter(todos_done_count)
.nav-controls .nav-controls
- if @todos.any?(&:pending?) - if @allowed_todos.any?(&:pending?)
.gl-mr-3 .gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
Mark all as done Mark all as done
...@@ -82,11 +82,11 @@ ...@@ -82,11 +82,11 @@
= sort_title_oldest_created = sort_title_oldest_created
.row.js-todos-all .row.js-todos-all
- if @todos.any? - if @allowed_todos.any?
.col.js-todos-list-container{ data: { qa_selector: "todos_list_container" } } .col.js-todos-list-container{ data: { qa_selector: "todos_list_container" } }
.js-todos-options{ data: { per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages } } .js-todos-options{ data: { per_page: @allowed_todos.count, current_page: @todos.current_page, total_pages: @todos.total_pages } }
%ul.content-list.todos-list %ul.content-list.todos-list
= render @todos = render @allowed_todos
= paginate @todos, theme: "gitlab" = paginate @todos, theme: "gitlab"
.js-nothing-here-container.empty-state.hidden .js-nothing-here-container.empty-state.hidden
.svg-content .svg-content
......
- page_title _("Invitation") - page_title _("Invitation")
%h3.page-title= _("Invitation") %h3.page-title= _("Invitation")
%p - if current_user_matches_invite?
= _("You have been invited") - if member?
- inviter = @member.created_by %p
- if inviter = _("You are already a member of this %{member_source}.") % { member_source: @invite_details[:title] }
= _("by") .actions
= link_to inviter.name, user_url(inviter) = link_to _("Go to %{source_name}") % { source_name: @invite_details[:title] }, @invite_details[:url], class: "btn gl-button btn-confirm"
= _("to join %{source_name}") % { source_name: @invite_details[:title] }
%strong
= link_to @invite_details[:name], @invite_details[:url]
= _("as %{role}.") % { role: @member.human_access }
- if member? - else
%p %p
= _("However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation.") % { member_source: @invite_details[:title] } - inviter = @member.created_by
- link_to_inviter = link_to(inviter.name, user_url(inviter))
- link_to_source = link_to(@invite_details[:name], @invite_details[:url])
= html_escape(_("You have been invited by %{link_to_inviter} to join %{source_name} %{strong_open}%{link_to_source}%{strong_close} as %{role}")) % { link_to_inviter: link_to_inviter, source_name: @invite_details[:title], strong_open: '<strong>'.html_safe, link_to_source: link_to_source, strong_close: '</strong>'.html_safe, role: @member.human_access }
.actions
= link_to _("Accept invitation"), accept_invite_url(@token), method: :post, class: "btn gl-button btn-confirm"
= link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn gl-button btn-danger gl-ml-3"
- if !current_user_matches_invite? - else
%p %p
- mail_to_invite_email = mail_to(@member.invite_email) - mail_to_invite_email = mail_to(@member.invite_email)
- mail_to_current_user = mail_to(current_user.email) - mail_to_current_user = mail_to(current_user.email)
- link_to_current_user = link_to(current_user.to_reference, user_url(current_user)) - link_to_current_user = link_to(current_user.to_reference, user_url(current_user))
= _("Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}.").html_safe % { mail_to_invite_email: mail_to_invite_email, mail_to_current_user: mail_to_current_user, link_to_current_user: link_to_current_user } = _("This invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}.").html_safe % { mail_to_invite_email: mail_to_invite_email, mail_to_current_user: mail_to_current_user, link_to_current_user: link_to_current_user }
%p
- if !member? = _("Sign in as a user with the matching email address, add the email to this account, or sign-up for a new account using the matching email.")
.actions
= link_to _("Accept invitation"), accept_invite_url(@token), method: :post, class: "btn gl-button btn-confirm"
= link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn gl-button btn-danger gl-ml-3"
...@@ -44,26 +44,26 @@ ...@@ -44,26 +44,26 @@
:preserve :preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path} cd #{h @project.path}
git switch -c #{default_branch_name} git switch -c #{h default_branch_name}
touch README.md touch README.md
git add README.md git add README.md
git commit -m "add README" git commit -m "add README"
- if @project.can_current_user_push_to_default_branch? - if @project.can_current_user_push_to_default_branch?
%span>< %span><
git push -u origin #{ default_branch_name } git push -u origin #{h default_branch_name }
%fieldset %fieldset
%h5= _('Push an existing folder') %h5= _('Push an existing folder')
%pre.bg-light %pre.bg-light
:preserve :preserve
cd existing_folder cd existing_folder
git init --initial-branch=#{default_branch_name} git init --initial-branch=#{h default_branch_name}
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')} git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
git add . git add .
git commit -m "Initial commit" git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch? - if @project.can_current_user_push_to_default_branch?
%span>< %span><
git push -u origin #{ default_branch_name } git push -u origin #{h default_branch_name }
%fieldset %fieldset
%h5= _('Push an existing Git repository') %h5= _('Push an existing Git repository')
......
...@@ -19,6 +19,4 @@ OmniAuth.config.before_request_phase do |env| ...@@ -19,6 +19,4 @@ OmniAuth.config.before_request_phase do |env|
Gitlab::RequestForgeryProtection.call(env) Gitlab::RequestForgeryProtection.call(env)
end end
# Use json formatter OmniAuth.config.logger = Gitlab::AppLogger
OmniAuth.config.logger.formatter = Gitlab::OmniauthLogging::JSONFormatter.new
OmniAuth.config.logger.level = Logger::ERROR if Rails.env.production?
...@@ -33,54 +33,6 @@ module EE ...@@ -33,54 +33,6 @@ module EE
end end
end end
def group_has_domain_limitations?
group.licensed_feature_available?(:group_allowed_email_domains) && group_allowed_email_domains.any?
end
def group_domain_limitations
if user
return if user.project_bot?
validate_users_email
validate_email_verified
else
validate_invitation_email
end
end
def validate_email_verified
return if user.primary_email_verified?
# Do not validate if emails are verified
# for users created via SAML/SCIM.
return if group_saml_identity.present?
return if source.scim_identities.for_user(user).exists?
errors.add(:user, email_not_verified)
end
def validate_users_email
return if matches_at_least_one_group_allowed_email_domain?(user.email)
errors.add(:user, email_does_not_match_any_allowed_domains(user.email))
end
def validate_invitation_email
return if matches_at_least_one_group_allowed_email_domain?(invite_email)
errors.add(:invite_email, email_does_not_match_any_allowed_domains(invite_email))
end
def group_saml_identity
return unless source.saml_provider
if user.group_saml_identities.loaded?
user.group_saml_identities.detect { |i| i.saml_provider_id == source.saml_provider.id }
else
user.group_saml_identities.find_by(saml_provider: source.saml_provider)
end
end
def provisioned_by_this_group? def provisioned_by_this_group?
user&.user_detail&.provisioned_by_group_id == source_id user&.user_detail&.provisioned_by_group_id == source_id
end end
...@@ -95,25 +47,6 @@ module EE ...@@ -95,25 +47,6 @@ module EE
errors.add(:access_level, "is not included in the list") errors.add(:access_level, "is not included in the list")
end end
def email_does_not_match_any_allowed_domains(email)
n_("email '%{email}' does not match the allowed domain of %{email_domains}", "email '%{email}' does not match the allowed domains: %{email_domains}", group_allowed_email_domains.size) %
{ email: email, email_domains: group_allowed_email_domains.map(&:domain).join(', ') }
end
def email_not_verified
_("email '%{email}' is not a verified email." % { email: user.email })
end
def group_allowed_email_domains
group.root_ancestor_allowed_email_domains
end
def matches_at_least_one_group_allowed_email_domain?(email)
group_allowed_email_domains.any? do |allowed_email_domain|
allowed_email_domain.email_matches_domain?(email)
end
end
override :post_create_hook override :post_create_hook
def post_create_hook def post_create_hook
super super
......
...@@ -41,5 +41,80 @@ module EE ...@@ -41,5 +41,80 @@ module EE
def source_kind def source_kind
source.is_a?(Group) && source.parent.present? ? 'Sub group' : source.class.to_s source.is_a?(Group) && source.parent.present? ? 'Sub group' : source.class.to_s
end end
def group_has_domain_limitations?
return false unless group
group.licensed_feature_available?(:group_allowed_email_domains) && group_allowed_email_domains.any?
end
def group_domain_limitations
return unless group
if user
return if user.project_bot?
validate_users_email
validate_email_verified
else
validate_invitation_email
end
end
def group_saml_identity(root_ancestor: false)
saml_group = root_ancestor ? group.root_ancestor : group
return unless saml_group.saml_provider
if user.group_saml_identities.loaded?
user.group_saml_identities.detect { |i| i.saml_provider_id == saml_group.saml_provider.id }
else
user.group_saml_identities.find_by(saml_provider: saml_group.saml_provider)
end
end
private
def group_allowed_email_domains
return [] unless group
group.root_ancestor_allowed_email_domains
end
def validate_users_email
return if matches_at_least_one_group_allowed_email_domain?(user.email)
errors.add(:user, email_does_not_match_any_allowed_domains(user.email))
end
def validate_invitation_email
return if matches_at_least_one_group_allowed_email_domain?(invite_email)
errors.add(:invite_email, email_does_not_match_any_allowed_domains(invite_email))
end
def validate_email_verified
return if user.primary_email_verified?
return if group_saml_identity(root_ancestor: true).present?
return if group.root_ancestor.scim_identities.for_user(user).exists?
errors.add(:user, email_not_verified)
end
def email_does_not_match_any_allowed_domains(email)
n_("email does not match the allowed domain of %{email_domains}", "email does not match the allowed domains: %{email_domains}", group_allowed_email_domains.size) %
{ email_domains: group_allowed_email_domains.map(&:domain).join(', ') }
end
def matches_at_least_one_group_allowed_email_domain?(email)
group_allowed_email_domains.any? do |allowed_email_domain|
allowed_email_domain.email_matches_domain?(email)
end
end
def email_not_verified
_("email '%{email}' is not a verified email." % { email: user.email })
end
end end
end end
...@@ -9,8 +9,10 @@ module EE ...@@ -9,8 +9,10 @@ module EE
validate :sso_enforcement, if: :group, unless: :project_bot validate :sso_enforcement, if: :group, unless: :project_bot
validate :gma_enforcement, if: :group, unless: :project_bot validate :gma_enforcement, if: :group, unless: :project_bot
validate :group_domain_limitations, if: -> { group && group_has_domain_limitations? }, on: :create
before_destroy :delete_member_branch_protection before_destroy :delete_member_branch_protection
before_destroy :delete_protected_environment_acceses
end end
def group def group
...@@ -28,6 +30,12 @@ module EE ...@@ -28,6 +30,12 @@ module EE
end end
end end
def delete_protected_environment_acceses
return unless user.present? && project.present?
project.protected_environments.deploy_access_levels_by_user(user).delete_all
end
def gma_enforcement def gma_enforcement
unless ::Gitlab::Auth::GroupSaml::GmaMembershipEnforcer.new(project).can_add_user?(user) unless ::Gitlab::Auth::GroupSaml::GmaMembershipEnforcer.new(project).can_add_user?(user)
errors.add(:user, _('is not in the group enforcing Group Managed Account')) errors.add(:user, _('is not in the group enforcing Group Managed Account'))
...@@ -37,5 +45,11 @@ module EE ...@@ -37,5 +45,11 @@ module EE
def provisioned_by_this_group? def provisioned_by_this_group?
false false
end end
def group_saml_identity(root_ancestor: false)
return unless group
super
end
end end
end end
...@@ -71,6 +71,6 @@ class InstanceSecurityDashboard ...@@ -71,6 +71,6 @@ class InstanceSecurityDashboard
end end
def authorized_access_levels def authorized_access_levels
Gitlab::Access.vulnerability_access_levels.values Gitlab::Access.vulnerability_access_levels
end end
end end
...@@ -22,12 +22,19 @@ class ProtectedEnvironment < ApplicationRecord ...@@ -22,12 +22,19 @@ class ProtectedEnvironment < ApplicationRecord
' AND protected_environments.project_id = environments.project_id') ' AND protected_environments.project_id = environments.project_id')
end end
scope :deploy_access_levels_by_group, -> (group) do
ProtectedEnvironment::DeployAccessLevel
.joins(:protected_environment).where(group: group)
end
class << self class << self
def deploy_access_levels_by_user(user)
ProtectedEnvironment::DeployAccessLevel
.where(protected_environment_id: select(:id))
.where(user: user)
end
def deploy_access_levels_by_group(group)
ProtectedEnvironment::DeployAccessLevel
.where(protected_environment_id: select(:id))
.where(group: group)
end
def for_environment(environment) def for_environment(environment)
raise ArgumentError unless environment.is_a?(::Environment) raise ArgumentError unless environment.is_a?(::Environment)
......
...@@ -24,6 +24,11 @@ module ProtectedEnvironments ...@@ -24,6 +24,11 @@ module ProtectedEnvironments
keys = attribute.slice(:access_level, :group_id, :user_id).keys keys = attribute.slice(:access_level, :group_id, :user_id).keys
return false unless keys.count == 1 return false unless keys.count == 1
# If it's a destroy request, any group/user IDs are allowed to be passed,
# so that users who are no longer project members can be removed from the access list.
# `has_destroy_flag?` is defined in `ActiveRecord::NestedAttributes`.
return true if ProtectedEnvironment::DeployAccessLevel.new.send(:has_destroy_flag?, attribute) # rubocop:disable GitlabSecurity/PublicSend
if attribute[:group_id].present? if attribute[:group_id].present?
qualified_group_ids.include?(attribute[:group_id]) qualified_group_ids.include?(attribute[:group_id])
elsif attribute[:user_id].present? elsif attribute[:user_id].present?
......
...@@ -6,7 +6,7 @@ module Subscriptions ...@@ -6,7 +6,7 @@ module Subscriptions
attr_reader :current_user, :customer_params, :subscription_params attr_reader :current_user, :customer_params, :subscription_params
CUSTOMERS_OAUTH_APP_ID_CACHE_KEY = 'customers_oauth_app_id' CUSTOMERS_OAUTH_APP_UID_CACHE_KEY = 'customers_oauth_app_uid'
def initialize(current_user, group:, customer_params:, subscription_params:) def initialize(current_user, group:, customer_params:, subscription_params:)
@current_user = current_user @current_user = current_user
...@@ -97,9 +97,9 @@ module Subscriptions ...@@ -97,9 +97,9 @@ module Subscriptions
Gitlab::SubscriptionPortal::Client Gitlab::SubscriptionPortal::Client
end end
def customers_oauth_app_id def customers_oauth_app_uid
Rails.cache.fetch(CUSTOMERS_OAUTH_APP_ID_CACHE_KEY, expires_in: 1.hour) do Rails.cache.fetch(CUSTOMERS_OAUTH_APP_UID_CACHE_KEY, expires_in: 1.hour) do
response = client.customers_oauth_app_id response = client.customers_oauth_app_uid
response.dig(:data, 'oauth_app_id') response.dig(:data, 'oauth_app_id')
end end
...@@ -107,15 +107,15 @@ module Subscriptions ...@@ -107,15 +107,15 @@ module Subscriptions
def oauth_token def oauth_token
strong_memoize(:oauth_token) do strong_memoize(:oauth_token) do
next unless customers_oauth_app_id next unless customers_oauth_app_uid
application = Doorkeeper::Application.find_by_uid(customers_oauth_app_id) application = Doorkeeper::Application.find_by_uid(customers_oauth_app_uid)
existing_token = Doorkeeper::AccessToken.matching_token_for(application, current_user.id, application.scopes) existing_token = Doorkeeper::AccessToken.matching_token_for(application, current_user.id, application.scopes)
next existing_token if existing_token next existing_token if existing_token
Doorkeeper::AccessToken.new( Doorkeeper::AccessToken.new(
application_id: customers_oauth_app_id, application_id: application.id,
resource_owner_id: current_user.id, resource_owner_id: current_user.id,
token: Doorkeeper::OAuth::Helpers::UniqueToken.generate, token: Doorkeeper::OAuth::Helpers::UniqueToken.generate,
scopes: application.scopes.to_s scopes: application.scopes.to_s
......
...@@ -16,7 +16,7 @@ module EE ...@@ -16,7 +16,7 @@ module EE
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
def vulnerability_access_levels def vulnerability_access_levels
@vulnerability_access_levels ||= options_with_owner.except('Guest') @vulnerability_access_levels ||= sym_options_with_owner.values_at(:developer, :maintainer, :owner).freeze
end end
def options_with_minimal_access def options_with_minimal_access
......
...@@ -33,7 +33,7 @@ module Gitlab ...@@ -33,7 +33,7 @@ module Gitlab
http_get("api/payment_methods/#{id}", admin_headers) http_get("api/payment_methods/#{id}", admin_headers)
end end
def customers_oauth_app_id def customers_oauth_app_uid
http_get("api/v1/oauth_app_id", admin_headers) http_get("api/v1/oauth_app_id", admin_headers)
end end
......
...@@ -125,9 +125,9 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::Rest do ...@@ -125,9 +125,9 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::Rest do
it_behaves_like 'when http call raises an exception' it_behaves_like 'when http call raises an exception'
end end
describe '#customers_oauth_app_id' do describe '#customers_oauth_app_uid' do
subject do subject do
client.customers_oauth_app_id client.customers_oauth_app_uid
end end
let(:http_method) { :get } let(:http_method) { :get }
......
...@@ -7,132 +7,12 @@ RSpec.describe GroupMember do ...@@ -7,132 +7,12 @@ RSpec.describe GroupMember do
it_behaves_like 'member validations' it_behaves_like 'member validations'
describe 'validations' do describe 'validations' do
describe 'group domain limitations' do describe '#group_domain_validations' do
let(:group) { create(:group) } let(:member_type) { :group_member }
let(:gitlab_user) { create(:user, email: 'test@gitlab.com') } let(:source) { group }
let(:gmail_user) { create(:user, email: 'test@gmail.com') } let(:nested_source) { create(:group, parent: group) }
let(:unconfirmed_gitlab_user) { create(:user, :unconfirmed, email: 'unverified@gitlab.com') }
let(:acme_user) { create(:user, email: 'user@acme.com') }
before do
create(:allowed_email_domain, group: group, domain: 'gitlab.com')
create(:allowed_email_domain, group: group, domain: 'acme.com')
end
context 'when group has email domain feature switched on' do
before do
stub_licensed_features(group_allowed_email_domains: true)
end
it 'users email must match at least one of the allowed domain emails' do
expect(build(:group_member, group: group, user: gmail_user)).to be_invalid
expect(build(:group_member, group: group, user: gitlab_user)).to be_valid
expect(build(:group_member, group: group, user: acme_user)).to be_valid
end
it 'shows proper error message' do
group_member = build(:group_member, group: group, user: gmail_user)
expect(group_member).to be_invalid
expect(group_member.errors[:user]).to include("email 'test@gmail.com' does not match the allowed domains: gitlab.com, acme.com")
end
it 'shows proper error message for single domain limitation' do
group.allowed_email_domains.last.destroy!
group_member = build(:group_member, group: group, user: gmail_user)
expect(group_member).to be_invalid
expect(group_member.errors[:user]).to include("email 'test@gmail.com' does not match the allowed domain of gitlab.com")
end
it 'invited email must match at least one of the allowed domain emails' do
expect(build(:group_member, group: group, user: nil, invite_email: 'user@gmail.com')).to be_invalid
expect(build(:group_member, group: group, user: nil, invite_email: 'user@gitlab.com')).to be_valid
expect(build(:group_member, group: group, user: nil, invite_email: 'invite@acme.com')).to be_valid
end
it 'user emails matching allowed domain must be verified' do
group_member = build(:group_member, group: group, user: unconfirmed_gitlab_user)
expect(group_member).to be_invalid
expect(group_member.errors[:user]).to include("email 'unverified@gitlab.com' is not a verified email.")
end
context 'with project bot users' do
let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") }
it 'bot user email does not match' do
expect(group.allowed_email_domains.include?(project_bot.email)).to be_falsey
end
it 'allows the project bot user' do
expect(build(:group_member, group: group, user: project_bot)).to be_valid
end
end
context 'with group SAML users' do
let(:saml_provider) { create(:saml_provider, group: group) }
let!(:group_related_identity) do
create(:group_saml_identity, user: unconfirmed_gitlab_user, saml_provider: saml_provider)
end
it 'user emails does not have to be verified' do
expect(build(:group_member, group: group, user: unconfirmed_gitlab_user)).to be_valid
end
end
context 'with group SCIM users' do
let!(:scim_identity) do
create(:scim_identity, user: unconfirmed_gitlab_user, group: group)
end
it 'user emails does not have to be verified' do
expect(build(:group_member, group: group, user: unconfirmed_gitlab_user)).to be_valid
end
end
context 'when group is subgroup' do it_behaves_like 'member group domain validations'
let(:subgroup) { create(:group, parent: group) }
it 'users email must match at least one of the allowed domain emails' do
expect(build(:group_member, group: subgroup, user: gmail_user)).to be_invalid
expect(build(:group_member, group: subgroup, user: gitlab_user)).to be_valid
expect(build(:group_member, group: subgroup, user: acme_user)).to be_valid
end
it 'invited email must match at least one of the allowed domain emails' do
expect(build(:group_member, group: subgroup, user: nil, invite_email: 'user@gmail.com')).to be_invalid
expect(build(:group_member, group: subgroup, user: nil, invite_email: 'user@gitlab.com')).to be_valid
expect(build(:group_member, group: subgroup, user: nil, invite_email: 'invite@acme.com')).to be_valid
end
it 'user emails matching allowed domain must be verified' do
group_member = build(:group_member, group: subgroup, user: unconfirmed_gitlab_user)
expect(group_member).to be_invalid
expect(group_member.errors[:user]).to include("email 'unverified@gitlab.com' is not a verified email.")
end
end
end
context 'when group has email domain feature switched off' do
it 'users email need not match allowed domain emails' do
expect(build(:group_member, group: group, user: gmail_user)).to be_valid
expect(build(:group_member, group: group, user: gitlab_user)).to be_valid
expect(build(:group_member, group: group, user: acme_user)).to be_valid
end
it 'invited email need not match allowed domain emails' do
expect(build(:group_member, group: group, invite_email: 'user@gmail.com')).to be_valid
expect(build(:group_member, group: group, invite_email: 'user@gitlab.com')).to be_valid
expect(build(:group_member, group: group, invite_email: 'user@acme.com')).to be_valid
end
it 'user emails does not have to be verified' do
expect(build(:group_member, group: group, user: unconfirmed_gitlab_user)).to be_valid
end
end
end end
describe 'access level inclusion' do describe 'access level inclusion' do
...@@ -225,42 +105,6 @@ RSpec.describe GroupMember do ...@@ -225,42 +105,6 @@ RSpec.describe GroupMember do
end end
end end
describe '#group_saml_identity' do
subject(:group_saml_identity) { member.group_saml_identity }
let!(:member) { create :group_member }
context 'without saml_provider' do
it { is_expected.to eq nil }
end
context 'with saml_provider enabled' do
let!(:saml_provider) { create(:saml_provider, group: member.group) }
context 'when member has no connected identity' do
it { is_expected.to eq nil }
end
context 'when member has connected identity' do
let!(:group_related_identity) do
create(:group_saml_identity, user: member.user, saml_provider: saml_provider)
end
it 'returns related identity' do
expect(group_saml_identity).to eq group_related_identity
end
end
context 'when member has connected identity of different group' do
before do
create(:group_saml_identity, user: member.user)
end
it { is_expected.to eq nil }
end
end
end
context 'group member webhooks', :sidekiq_inline do context 'group member webhooks', :sidekiq_inline do
let_it_be_with_refind(:group) { create(:group_with_plan, plan: :ultimate_plan) } let_it_be_with_refind(:group) { create(:group_with_plan, plan: :ultimate_plan) }
let_it_be(:group_hook) { create(:group_hook, group: group, member_events: true) } let_it_be(:group_hook) { create(:group_hook, group: group, member_events: true) }
......
...@@ -19,7 +19,7 @@ RSpec.describe InstanceSecurityDashboard do ...@@ -19,7 +19,7 @@ RSpec.describe InstanceSecurityDashboard do
user.security_dashboard_projects << [project1, project2, project3] user.security_dashboard_projects << [project1, project2, project3]
end end
subject { described_class.new(user, project_ids: project_ids) } subject(:instance_dashboard) { described_class.new(user, project_ids: project_ids) }
describe '#project_ids_with_security_reports' do describe '#project_ids_with_security_reports' do
context 'when given project IDs' do context 'when given project IDs' do
...@@ -79,50 +79,64 @@ RSpec.describe InstanceSecurityDashboard do ...@@ -79,50 +79,64 @@ RSpec.describe InstanceSecurityDashboard do
end end
describe '#projects' do describe '#projects' do
context 'when the user cannot read all resources' do subject { instance_dashboard.projects }
context 'when the `security_and_compliance` is enabled for the project' do
before do
project1.team.truncate
end
shared_examples_for 'project permissions' do
context 'when the `security_and_compliance` is disabled for the project' do
before do before do
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED) ProjectFeature.update_all(security_and_compliance_access_level: Featurable::DISABLED)
end end
it 'returns only projects on their dashboard that they can read' do it { is_expected.to be_empty }
expect(subject.projects).to contain_exactly(project1)
end
end end
context 'when the `security_and_compliance` is disabled for the project' do context 'when the `security_and_compliance` is enabled for the project' do
before do before do
project1.project_feature.update_column(:security_and_compliance_access_level, Featurable::DISABLED) ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED)
end end
it 'returns only projects on their dashboard that they can read' do it { is_expected.to match_array(expected_projects) }
expect(subject.projects).to be_empty
end
end end
end end
context 'when the user can read all resources' do context 'when the user is auditor' do
let(:project_ids) { [project1.id, project2.id] }
let(:user) { create(:auditor) } let(:user) { create(:auditor) }
context 'when the `security_and_compliance` is enabled for the project' do it_behaves_like 'project permissions' do
before do let(:expected_projects) { [project1, project2, project3] }
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED) end
end end
context 'when the user is not an auditor' do
context 'when the user is project owner' do
let(:user) { project1.owner }
it "returns all projects on the user's dashboard" do it_behaves_like 'project permissions' do
expect(subject.projects).to contain_exactly(project1, project2, project3) let(:expected_projects) { project1 }
end end
end end
context 'when the `security_and_compliance` is disabled for the project' do context 'when the user is not project owner' do
before do shared_examples_for 'user with project role' do |as:, permitted:|
project1.project_feature.update_column(:security_and_compliance_access_level, Featurable::DISABLED) let(:expected_projects) { permitted ? project1 : [] }
end
it "returns only the feature enabled projects on the user's dashboard" do before do
expect(subject.projects).to contain_exactly(project2, project3) project1.add_role(user, as)
end
it_behaves_like 'project permissions'
end end
all_roles = Gitlab::Access.sym_options.keys
permitted_roles = %i(developer maintainer).freeze
unpermitted_roles = all_roles - permitted_roles
permitted_roles.each { |role| it_behaves_like 'user with project role', as: role, permitted: true }
unpermitted_roles.each { |role| it_behaves_like 'user with project role', as: role, permitted: false }
end end
end end
end end
......
...@@ -67,4 +67,98 @@ RSpec.describe Member, type: :model do ...@@ -67,4 +67,98 @@ RSpec.describe Member, type: :model do
it { is_expected.to eq('Project') } it { is_expected.to eq('Project') }
end end
end end
describe '#group_saml_identity' do
shared_examples_for 'member with group saml identity' do
context 'without saml_provider' do
it { is_expected.to eq nil }
end
context 'with saml_provider enabled' do
let!(:saml_provider) { create(:saml_provider, group: member.group) }
context 'when member has no connected identity' do
it { is_expected.to eq nil }
end
context 'when member has connected identity' do
let!(:group_related_identity) do
create(:group_saml_identity, user: member.user, saml_provider: saml_provider)
end
it 'returns related identity' do
expect(group_saml_identity).to eq group_related_identity
end
end
context 'when member has connected identity of different group' do
before do
create(:group_saml_identity, user: member.user)
end
it { is_expected.to eq nil }
end
end
end
shared_examples_for 'member with group saml identity on the top level' do
let!(:saml_provider) { create(:saml_provider, group: parent_group) }
let!(:group_related_identity) do
create(:group_saml_identity, user: member.user, saml_provider: saml_provider)
end
it 'returns related identity' do
expect(member.group_saml_identity(root_ancestor: true)).to eq group_related_identity
end
end
describe 'for group members' do
context 'when member is in a top-level group' do
let(:member) { create :group_member }
subject(:group_saml_identity) { member.group_saml_identity }
it_behaves_like 'member with group saml identity'
end
context 'when member is in a subgroup' do
let(:parent_group) { create(:group) }
let(:group) { create(:group, parent: parent_group) }
let(:member) { create(:group_member, source: group) }
it_behaves_like 'member with group saml identity on the top level'
end
end
describe 'for project members' do
context 'when project is nested in a group' do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group)}
let(:member) { create :project_member, source: project }
subject(:group_saml_identity) { member.group_saml_identity }
it_behaves_like 'member with group saml identity'
end
context 'when project is nested in a subgroup' do
let(:parent_group) { create(:group)}
let(:group) { create(:group, parent: parent_group) }
let(:project) { create(:project, namespace: group)}
let(:member) { create :project_member, source: project }
it_behaves_like 'member with group saml identity on the top level'
end
context 'when project is nested in a personal namespace' do
let(:project) { create(:project, namespace: create(:user).namespace )}
let(:member) { create :project_member, source: project }
it 'returns nothing' do
expect(member.group_saml_identity(root_ancestor: true)).to be_nil
end
end
end
end
end end
...@@ -89,6 +89,22 @@ RSpec.describe ProjectMember do ...@@ -89,6 +89,22 @@ RSpec.describe ProjectMember do
end end
end end
describe '#group_domain_validations' do
let(:member_type) { :project_member }
let(:source) { create(:project, namespace: group) }
let(:subgroup) { create(:group, parent: group) }
let(:nested_source) { create(:project, namespace: subgroup) }
it_behaves_like 'member group domain validations'
it 'does not validate personal projects' do
unconfirmed_gitlab_user = create(:user, :unconfirmed, email: 'unverified@gitlab.com')
member = create(:project, namespace: create(:user).namespace).add_developer(unconfirmed_gitlab_user)
expect(member).to be_valid
end
end
describe '#provisioned_by_this_group?' do describe '#provisioned_by_this_group?' do
let_it_be(:member) { build(:project_member) } let_it_be(:member) { build(:project_member) }
...@@ -96,4 +112,67 @@ RSpec.describe ProjectMember do ...@@ -96,4 +112,67 @@ RSpec.describe ProjectMember do
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end end
describe 'delete protected environment acceses cascadingly' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:protected_environment) do
create(:protected_environment, project: project, name: environment.name)
end
let!(:member) { create(:project_member, project: project, user: user) }
let!(:deploy_access) do
create(:protected_environment_deploy_access_level, protected_environment: protected_environment, user: user)
end
let!(:deploy_access_for_diffent_user) do
create(:protected_environment_deploy_access_level, protected_environment: protected_environment, user: create(:user))
end
let!(:deploy_access_for_group) do
create(:protected_environment_deploy_access_level, protected_environment: protected_environment, group: create(:group))
end
let!(:deploy_access_for_maintainer_role) do
create(:protected_environment_deploy_access_level, :maintainer_access, protected_environment: protected_environment)
end
it 'deletes associated protected environment access cascadingly' do
expect { member.destroy! }
.to change { ProtectedEnvironment::DeployAccessLevel.count }.by(-1)
expect { deploy_access.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(protected_environment.reload.deploy_access_levels)
.to include(deploy_access_for_diffent_user, deploy_access_for_group, deploy_access_for_maintainer_role)
end
context 'when the user is assiged to multiple protected environments in the same project' do
let!(:other_protected_environment) { create(:protected_environment, project: project, name: 'staging') }
let!(:other_deploy_access) { create(:protected_environment_deploy_access_level, protected_environment: other_protected_environment, user: user) }
it 'deletes all associated protected environment accesses in the project' do
expect { member.destroy! }
.to change { ProtectedEnvironment::DeployAccessLevel.count }.by(-2)
expect { deploy_access.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { other_deploy_access.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when the user is assiged to multiple protected environments across different projects' do
let!(:other_project) { create(:project) }
let!(:other_protected_environment) { create(:protected_environment, project: other_project, name: 'staging') }
let!(:other_deploy_access) { create(:protected_environment_deploy_access_level, protected_environment: other_protected_environment, user: user) }
it 'deletes all associated protected environment accesses in the project' do
expect { member.destroy! }
.to change { ProtectedEnvironment::DeployAccessLevel.count }.by(-1)
expect { deploy_access.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { other_deploy_access.reload }.not_to raise_error
end
end
end
end end
...@@ -59,7 +59,7 @@ RSpec.describe ProtectedEnvironment do ...@@ -59,7 +59,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to user' do context 'when access has been granted to user' do
before do before do
create_deploy_access_level(user: user) create_deploy_access_level(protected_environment, user: user)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -69,7 +69,7 @@ RSpec.describe ProtectedEnvironment do ...@@ -69,7 +69,7 @@ RSpec.describe ProtectedEnvironment do
let(:group) { create(:group) } let(:group) { create(:group) }
before do before do
create_deploy_access_level(group: group) create_deploy_access_level(protected_environment, group: group)
end end
it 'allows members of the group' do it 'allows members of the group' do
...@@ -85,7 +85,7 @@ RSpec.describe ProtectedEnvironment do ...@@ -85,7 +85,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to maintainers' do context 'when access has been granted to maintainers' do
before do before do
create_deploy_access_level(access_level: Gitlab::Access::MAINTAINER) create_deploy_access_level(protected_environment, access_level: Gitlab::Access::MAINTAINER)
end end
it 'allows maintainers' do it 'allows maintainers' do
...@@ -103,7 +103,7 @@ RSpec.describe ProtectedEnvironment do ...@@ -103,7 +103,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to developers' do context 'when access has been granted to developers' do
before do before do
create_deploy_access_level(access_level: Gitlab::Access::DEVELOPER) create_deploy_access_level(protected_environment, access_level: Gitlab::Access::DEVELOPER)
end end
it 'allows maintainers' do it 'allows maintainers' do
...@@ -198,18 +198,63 @@ RSpec.describe ProtectedEnvironment do ...@@ -198,18 +198,63 @@ RSpec.describe ProtectedEnvironment do
end end
end end
describe '.deploy_access_levels_by_user' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project, name: 'production') }
let(:protected_environment) { create(:protected_environment, project: project, name: 'production') }
let(:deploy_access_level_for_user) { create_deploy_access_level(protected_environment, user: user) }
before do
create_deploy_access_level(protected_environment, user: create(:user))
create_deploy_access_level(protected_environment, group: create(:group))
end
it 'returns matching deploy access levels for the given user' do
expect(described_class.deploy_access_levels_by_user(user))
.to contain_exactly(deploy_access_level_for_user)
end
context 'when user is assigned to protected environment in the other project' do
let(:other_project) { create(:project) }
let(:other_protected_environment) { create(:protected_environment, project: other_project, name: 'production') }
let(:other_deploy_access_level_for_user) { create_deploy_access_level(other_protected_environment, user: user) }
it 'returns matching deploy access levels for the given user in the specific project' do
expect(project.protected_environments.deploy_access_levels_by_user(user))
.to contain_exactly(deploy_access_level_for_user)
expect(other_project.protected_environments.deploy_access_levels_by_user(user))
.to contain_exactly(other_deploy_access_level_for_user)
end
end
end
describe '.deploy_access_levels_by_group' do describe '.deploy_access_levels_by_group' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:environment) { create(:environment, project: project, name: 'production') } let(:environment) { create(:environment, project: project, name: 'production') }
let(:protected_environment) { create(:protected_environment, project: project, name: 'production') } let(:protected_environment) { create(:protected_environment, project: project, name: 'production') }
let(:deploy_access_level_for_group) { create_deploy_access_level(protected_environment, group: group) }
it 'returns matching deploy access levels for the given group' do it 'returns matching deploy access levels for the given group' do
_deploy_access_level_for_different_group = create_deploy_access_level(group: create(:group)) _deploy_access_level_for_different_group = create_deploy_access_level(protected_environment, group: create(:group))
_deploy_access_level_for_user = create_deploy_access_level(user: create(:user)) _deploy_access_level_for_user = create_deploy_access_level(protected_environment, user: create(:user))
deploy_access_level = create_deploy_access_level(group: group)
expect(described_class.deploy_access_levels_by_group(group)).to contain_exactly(deploy_access_level) expect(described_class.deploy_access_levels_by_group(group))
.to contain_exactly(deploy_access_level_for_group)
end
context 'when user is assigned to protected environment in the other project' do
let(:other_project) { create(:project) }
let(:other_protected_environment) { create(:protected_environment, project: other_project, name: 'production') }
let(:other_deploy_access_level_for_group) { create_deploy_access_level(other_protected_environment, group: group) }
it 'returns matching deploy access levels for the given group in the specific project' do
expect(project.protected_environments.deploy_access_levels_by_group(group))
.to contain_exactly(deploy_access_level_for_group)
expect(other_project.protected_environments.deploy_access_levels_by_group(group))
.to contain_exactly(other_deploy_access_level_for_group)
end
end end
end end
...@@ -280,7 +325,7 @@ RSpec.describe ProtectedEnvironment do ...@@ -280,7 +325,7 @@ RSpec.describe ProtectedEnvironment do
end end
end end
def create_deploy_access_level(**opts) def create_deploy_access_level(protected_environment, **opts)
protected_environment.deploy_access_levels.create(**opts) protected_environment.deploy_access_levels.create(**opts)
end end
end end
...@@ -6,7 +6,9 @@ RSpec.describe 'getting project information' do ...@@ -6,7 +6,9 @@ RSpec.describe 'getting project information' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:epic_todo) { create(:todo, user: current_user, target: create(:epic)) } let_it_be(:group) { create(:group) }
let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic_todo) { create(:todo, user: current_user, target: epic) }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
...@@ -22,7 +24,13 @@ RSpec.describe 'getting project information' do ...@@ -22,7 +24,13 @@ RSpec.describe 'getting project information' do
subject { graphql_data.dig('currentUser', 'todos', 'nodes') } subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before_all do
group.add_developer(current_user)
end
before do before do
stub_licensed_features(epics: true)
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
......
...@@ -31,6 +31,8 @@ RSpec.describe API::Todos do ...@@ -31,6 +31,8 @@ RSpec.describe API::Todos do
let!(:epic_todo) { create_todo_for_new_epic } let!(:epic_todo) { create_todo_for_new_epic }
before do before do
stub_licensed_features(epics: true)
get api('/todos', personal_access_token: pat) get api('/todos', personal_access_token: pat)
end end
...@@ -45,7 +47,8 @@ RSpec.describe API::Todos do ...@@ -45,7 +47,8 @@ RSpec.describe API::Todos do
create_todo_for_new_epic create_todo_for_new_epic
expect { get api('/todos', personal_access_token: pat) }.not_to exceed_query_limit(control) # Additional query due to authorization check on new group
expect { get api('/todos', personal_access_token: pat) }.not_to exceed_query_limit(control).with_threshold(1)
end end
it 'includes the Epic Todo in the response' do it 'includes the Epic Todo in the response' do
......
...@@ -53,7 +53,7 @@ RSpec.describe Groups::GroupMembersController do ...@@ -53,7 +53,7 @@ RSpec.describe Groups::GroupMembersController do
it 'returns error message' do it 'returns error message' do
subject subject
expect(json_response).to eq({ 'message' => "User email 'test@gmail.com' does not match the allowed domain of gitlab.com" }) expect(json_response).to eq({ 'message' => "User email does not match the allowed domain of gitlab.com" })
end end
end end
end end
......
...@@ -38,6 +38,28 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do ...@@ -38,6 +38,28 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do
] ]
) )
end end
context 'with delete flag' do
let(:params) do
{
deploy_access_levels_attributes: [
{ group_id: group.id },
{ group_id: other_group.id, '_destroy' => 1 },
{ group_id: child_group.id }
]
}
end
it 'contains inappropriate group id for deleting it' do
is_expected.to eq(
deploy_access_levels_attributes: [
{ group_id: group.id },
{ group_id: other_group.id, '_destroy' => 1 },
{ group_id: child_group.id }
]
)
end
end
end end
context 'with user-based access control' do context 'with user-based access control' do
...@@ -71,6 +93,23 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do ...@@ -71,6 +93,23 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do
] ]
) )
end end
context 'with delte flag' do
let(:params) do
{
deploy_access_levels_attributes: [
{ user_id: group_maintainer.id },
{ user_id: group_developer.id, '_destroy' => 1 },
{ user_id: other_group_maintainer.id, '_destroy' => 1 },
{ user_id: child_group_maintainer.id, '_destroy' => 1 }
]
}
end
it 'contains inappropriate user ids for deleting it' do
is_expected.to eq(params)
end
end
end end
end end
end end
......
...@@ -36,7 +36,7 @@ RSpec.describe Subscriptions::CreateService do ...@@ -36,7 +36,7 @@ RSpec.describe Subscriptions::CreateService do
describe '#execute' do describe '#execute' do
before do before do
allow(client).to receive(:customers_oauth_app_id).and_return( { data: { 'oauth_app_id' => oauth_app.uid } } ) allow(client).to receive(:customers_oauth_app_uid).and_return( data: { 'oauth_app_id' => oauth_app.uid })
allow(Doorkeeper::OAuth::Helpers::UniqueToken).to receive(:generate).and_return('foo_token') allow(Doorkeeper::OAuth::Helpers::UniqueToken).to receive(:generate).and_return('foo_token')
end end
...@@ -74,6 +74,14 @@ RSpec.describe Subscriptions::CreateService do ...@@ -74,6 +74,14 @@ RSpec.describe Subscriptions::CreateService do
expect { execute }.to change { Doorkeeper::AccessToken.count }.by(1) expect { execute }.to change { Doorkeeper::AccessToken.count }.by(1)
end end
it 'creates oauth token with correct application id' do
execute
created_oauth_token = Doorkeeper::AccessToken.find_by_token('foo_token')
expect(created_oauth_token.application_id).to eq(oauth_app.id)
end
context 'when failing to create a subscription' do context 'when failing to create a subscription' do
before do before do
allow(client).to receive(:create_subscription).and_return(success: false, data: { errors: 'failed to create subscription' }) allow(client).to receive(:create_subscription).and_return(success: false, data: { errors: 'failed to create subscription' })
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Todos::AllowedTargetFilterService do
let_it_be(:authorized_group) { create(:group, :private) }
let_it_be(:authorized_project) { create(:project, group: authorized_group) }
let_it_be(:unauthorized_group) { create(:group, :private) }
let_it_be(:unauthorized_project) { create(:project, group: unauthorized_group) }
let_it_be(:user) { create(:user) }
let_it_be(:authorized_epic) { create(:epic, group: authorized_group) }
let_it_be(:authorized_epic_todo) { create(:todo, group: authorized_group, target: authorized_epic, user: user) }
let_it_be(:unauthorized_epic) { create(:epic, group: unauthorized_group) }
let_it_be(:unauthorized_epic_todo) { create(:todo, group: unauthorized_group, target: unauthorized_epic, user: user) }
before_all do
authorized_group.add_developer(user)
end
describe '#execute' do
subject(:execute_service) { described_class.new(all_todos, user).execute }
let!(:all_todos) { authorized_todos + unauthorized_todos }
let(:authorized_todos) do
[
authorized_epic_todo
]
end
let(:unauthorized_todos) do
[
unauthorized_epic_todo
]
end
before do
stub_licensed_features(epics: true)
end
it { is_expected.to match_array(authorized_todos) }
end
end
...@@ -52,3 +52,157 @@ RSpec.shared_examples 'member validations' do ...@@ -52,3 +52,157 @@ RSpec.shared_examples 'member validations' do
end end
end end
end end
RSpec.shared_examples 'member group domain validations' do
context 'validates group domain limitations' do
let(:group) { create(:group) }
let(:gitlab_user) { create(:user, email: 'test@gitlab.com') }
let(:gmail_user) { create(:user, email: 'test@gmail.com') }
let(:unconfirmed_gitlab_user) { create(:user, :unconfirmed, email: 'unverified@gitlab.com') }
let(:acme_user) { create(:user, email: 'user@acme.com') }
before do
create(:allowed_email_domain, group: group, domain: 'gitlab.com')
create(:allowed_email_domain, group: group, domain: 'acme.com')
end
context 'when project parent has email domain feature switched on' do
before do
stub_licensed_features(group_allowed_email_domains: true)
end
it 'users email must match at least one of the allowed domain emails' do
expect(build(member_type, source: source, user: gmail_user)).to be_invalid
expect(build(member_type, source: source, user: gitlab_user)).to be_valid
expect(build(member_type, source: source, user: acme_user)).to be_valid
end
it 'shows proper error message' do
member = build(member_type, source: source, user: gmail_user)
expect(member).to be_invalid
expect(member.errors[:user]).to include("email does not match the allowed domains: gitlab.com, acme.com")
end
it 'shows proper error message for single domain limitation' do
group.allowed_email_domains.last.destroy!
member = build(member_type, source: source, user: gmail_user)
expect(member).to be_invalid
expect(member.errors[:user]).to include("email does not match the allowed domain of gitlab.com")
end
it 'invited email must match at least one of the allowed domain emails' do
expect(build(member_type, source: source, user: nil, invite_email: 'user@gmail.com')).to be_invalid
expect(build(member_type, source: source, user: nil, invite_email: 'user@gitlab.com')).to be_valid
expect(build(member_type, source: source, user: nil, invite_email: 'invite@acme.com')).to be_valid
end
it 'user emails matching allowed domain must be verified' do
project_member = build(member_type, source: source, user: unconfirmed_gitlab_user)
expect(project_member).to be_invalid
expect(project_member.errors[:user]).to include("email 'unverified@gitlab.com' is not a verified email.")
end
context 'with project bot users' do
let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") }
it 'bot user email does not match' do
expect(group.allowed_email_domains.include?(project_bot.email)).to be_falsey
end
it 'allows the project bot user' do
expect(build(member_type, source: source, user: project_bot)).to be_valid
end
end
context 'with group SAML users' do
let(:saml_provider) { create(:saml_provider, group: group) }
let!(:group_related_identity) do
create(:group_saml_identity, user: unconfirmed_gitlab_user, saml_provider: saml_provider)
end
it 'user emails does not have to be verified' do
expect(build(member_type, source: source, user: unconfirmed_gitlab_user)).to be_valid
end
end
context 'with group SCIM users' do
let!(:scim_identity) do
create(:scim_identity, user: unconfirmed_gitlab_user, group: group)
end
it 'user emails does not have to be verified' do
expect(build(member_type, source: source, user: unconfirmed_gitlab_user)).to be_valid
end
end
context 'when group is subgroup' do
it 'users email must match at least one of the allowed domain emails' do
expect(build(member_type, source: nested_source, user: gmail_user)).to be_invalid
expect(build(member_type, source: nested_source, user: gitlab_user)).to be_valid
expect(build(member_type, source: nested_source, user: acme_user)).to be_valid
end
it 'invited email must match at least one of the allowed domain emails' do
expect(build(member_type, source: nested_source, user: nil, invite_email: 'user@gmail.com')).to be_invalid
expect(build(member_type, source: nested_source, user: nil, invite_email: 'user@gitlab.com')).to be_valid
expect(build(member_type, source: nested_source, user: nil, invite_email: 'invite@acme.com')).to be_valid
end
it 'user emails matching allowed domain must be verified' do
member = build(member_type, source: nested_source, user: unconfirmed_gitlab_user)
expect(member).to be_invalid
expect(member.errors[:user]).to include("email 'unverified@gitlab.com' is not a verified email.")
end
context 'with group SCIM users' do
let!(:scim_identity) do
create(:scim_identity, user: unconfirmed_gitlab_user, group: group)
end
it 'user emails does not have to be verified' do
expect(build(member_type, source: nested_source, user: unconfirmed_gitlab_user)).to be_valid
end
end
context 'with group SAML users' do
let(:saml_provider) { create(:saml_provider, group: group) }
let!(:group_related_identity) do
create(:group_saml_identity, user: unconfirmed_gitlab_user, saml_provider: saml_provider)
end
it 'user emails does not have to be verified' do
expect(build(member_type, source: nested_source, user: unconfirmed_gitlab_user)).to be_valid
end
end
end
end
context 'when project parent group has email domain feature switched off' do
before do
stub_licensed_features(group_allowed_email_domains: false)
end
it 'users email need not match allowed domain emails' do
expect(build(member_type, source: source, user: gmail_user)).to be_valid
expect(build(member_type, source: source, user: gitlab_user)).to be_valid
expect(build(member_type, source: source, user: acme_user)).to be_valid
end
it 'invited email need not match allowed domain emails' do
expect(build(member_type, source: source, invite_email: 'user@gmail.com')).to be_valid
expect(build(member_type, source: source, invite_email: 'user@gitlab.com')).to be_valid
expect(build(member_type, source: source, invite_email: 'user@acme.com')).to be_valid
end
it 'user emails does not have to be verified' do
expect(build(member_type, source: source, user: unconfirmed_gitlab_user)).to be_valid
end
end
end
end
...@@ -23,7 +23,7 @@ module API ...@@ -23,7 +23,7 @@ module API
helpers do helpers do
def finder_params(current_user) def finder_params(current_user)
current_user.admin? ? { user: user(params[:user_id]) } : { user: current_user } current_user.admin? ? { user: user(params[:user_id]) } : { user: current_user, impersonation: false }
end end
def user(user_id) def user(user_id)
......
...@@ -92,6 +92,7 @@ module API ...@@ -92,6 +92,7 @@ module API
end end
get do get do
todos = paginate(find_todos.with_entity_associations) todos = paginate(find_todos.with_entity_associations)
todos = ::Todos::AllowedTargetFilterService.new(todos, current_user).execute
options = { with: Entities::Todo, current_user: current_user } options = { with: Entities::Todo, current_user: current_user }
batch_load_issuable_metadata(todos, options) batch_load_issuable_metadata(todos, options)
......
...@@ -193,7 +193,10 @@ module Gitlab ...@@ -193,7 +193,10 @@ module Gitlab
def personal_access_token_check(password, project) def personal_access_token_check(password, project)
return unless password.present? return unless password.present?
token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password) finder_options = { state: 'active' }
finder_options[:impersonation] = false unless Gitlab.config.gitlab.impersonation_enabled
token = PersonalAccessTokensFinder.new(finder_options).find_by_token(password)
return unless token return unless token
......
...@@ -26,13 +26,13 @@ module Gitlab ...@@ -26,13 +26,13 @@ module Gitlab
def branch_exists? def branch_exists?
strong_memoize(:is_branch) do strong_memoize(:is_branch) do
project.repository.branch_exists?(ref) branch_ref? && project.repository.branch_exists?(ref)
end end
end end
def tag_exists? def tag_exists?
strong_memoize(:is_tag) do strong_memoize(:is_tag) do
project.repository.tag_exists?(ref) tag_ref? && project.repository.tag_exists?(ref)
end end
end end
...@@ -106,6 +106,32 @@ module Gitlab ...@@ -106,6 +106,32 @@ module Gitlab
metrics.pipeline_failure_reason_counter metrics.pipeline_failure_reason_counter
.increment(reason: (reason || :unknown_failure).to_s) .increment(reason: (reason || :unknown_failure).to_s)
end end
private
# Verifies that origin_ref is a fully qualified tag reference (refs/tags/<tag-name>)
#
# Fallbacks to `true` for backward compatibility reasons
# if origin_ref is a short ref
def tag_ref?
return true if full_git_ref_name_unavailable?
Gitlab::Git.tag_ref?(origin_ref).present?
end
# Verifies that origin_ref is a fully qualified branch reference (refs/heads/<branch-name>)
#
# Fallbacks to `true` for backward compatibility reasons
# if origin_ref is a short ref
def branch_ref?
return true if full_git_ref_name_unavailable?
Gitlab::Git.branch_ref?(origin_ref).present?
end
def full_git_ref_name_unavailable?
ref == origin_ref
end
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Graphql
module TodosProjectPermissionPreloader
class FieldExtension < ::GraphQL::Schema::FieldExtension
def after_resolve(value:, memo:, **rest)
todos = value.to_a
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(
todos.map(&:project).compact,
current_user(rest)
).execute
value
end
private
def current_user(options)
options.dig(:context, :current_user)
end
end
end
end
end
# frozen_string_literal: true
require 'json'
module Gitlab
module OmniauthLogging
class JSONFormatter
def call(severity, datetime, progname, msg)
{ severity: severity, timestamp: datetime.utc.iso8601(3), pid: $$, progname: progname, message: msg }.to_json << "\n"
end
end
end
end
...@@ -46,6 +46,7 @@ module Sidebars ...@@ -46,6 +46,7 @@ module Sidebars
def ci_cd_analytics_menu_item def ci_cd_analytics_menu_item
if !context.project.feature_available?(:builds, context.current_user) || if !context.project.feature_available?(:builds, context.current_user) ||
!can?(context.current_user, :read_build, context.project) || !can?(context.current_user, :read_build, context.project) ||
!can?(context.current_user, :read_ci_cd_analytics, context.project) ||
context.project.empty_repo? context.project.empty_repo?
return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics) return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics)
end end
......
...@@ -15320,6 +15320,9 @@ msgstr "" ...@@ -15320,6 +15320,9 @@ msgstr ""
msgid "Go full screen" msgid "Go full screen"
msgstr "" msgstr ""
msgid "Go to %{source_name}"
msgstr ""
msgid "Go to commits" msgid "Go to commits"
msgstr "" msgstr ""
...@@ -16452,9 +16455,6 @@ msgstr "" ...@@ -16452,9 +16455,6 @@ msgstr ""
msgid "How many users will be evaluating the trial?" msgid "How many users will be evaluating the trial?"
msgstr "" msgstr ""
msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
msgstr ""
msgid "I accept the %{terms_link}" msgid "I accept the %{terms_link}"
msgstr "" msgstr ""
...@@ -22622,9 +22622,6 @@ msgstr "" ...@@ -22622,9 +22622,6 @@ msgstr ""
msgid "Note that pushing to GitLab requires write access to this repository." msgid "Note that pushing to GitLab requires write access to this repository."
msgstr "" msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token." msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
msgstr "" msgstr ""
...@@ -30550,6 +30547,9 @@ msgstr "" ...@@ -30550,6 +30547,9 @@ msgstr ""
msgid "Sign in / Register" msgid "Sign in / Register"
msgstr "" msgstr ""
msgid "Sign in as a user with the matching email address, add the email to this account, or sign-up for a new account using the matching email."
msgstr ""
msgid "Sign in preview" msgid "Sign in preview"
msgstr "" msgstr ""
...@@ -33814,6 +33814,9 @@ msgstr "" ...@@ -33814,6 +33814,9 @@ msgstr ""
msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal." msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
msgstr "" msgstr ""
msgid "This invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed." msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
msgstr "" msgstr ""
...@@ -37582,6 +37585,9 @@ msgstr "" ...@@ -37582,6 +37585,9 @@ msgstr ""
msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete." msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
msgstr "" msgstr ""
msgid "You are already a member of this %{member_source}."
msgstr ""
msgid "You are an admin, which means granting access to %{client_name} will allow them to interact with GitLab as an admin as well. Proceed with caution." msgid "You are an admin, which means granting access to %{client_name} will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr "" msgstr ""
...@@ -37906,7 +37912,7 @@ msgstr "" ...@@ -37906,7 +37912,7 @@ msgstr ""
msgid "You have been granted %{member_human_access} access to project %{name}." msgid "You have been granted %{member_human_access} access to project %{name}."
msgstr "" msgstr ""
msgid "You have been invited" msgid "You have been invited by %{link_to_inviter} to join %{source_name} %{strong_open}%{link_to_source}%{strong_close} as %{role}"
msgstr "" msgstr ""
msgid "You have been redirected to the only result; see the %{a_start}search results%{a_end} instead." msgid "You have been redirected to the only result; see the %{a_start}search results%{a_end} instead."
...@@ -38502,9 +38508,6 @@ msgstr "" ...@@ -38502,9 +38508,6 @@ msgstr ""
msgid "archived:" msgid "archived:"
msgstr "" msgstr ""
msgid "as %{role}."
msgstr ""
msgid "assign yourself" msgid "assign yourself"
msgstr "" msgstr ""
...@@ -38976,14 +38979,14 @@ msgstr "" ...@@ -38976,14 +38979,14 @@ msgstr ""
msgid "element is not a hierarchy" msgid "element is not a hierarchy"
msgstr "" msgstr ""
msgid "email '%{email}' does not match the allowed domain of %{email_domains}"
msgid_plural "email '%{email}' does not match the allowed domains: %{email_domains}"
msgstr[0] ""
msgstr[1] ""
msgid "email '%{email}' is not a verified email." msgid "email '%{email}' is not a verified email."
msgstr "" msgstr ""
msgid "email does not match the allowed domain of %{email_domains}"
msgid_plural "email does not match the allowed domains: %{email_domains}"
msgstr[0] ""
msgstr[1] ""
msgid "enabled" msgid "enabled"
msgstr "" msgstr ""
...@@ -39983,9 +39986,6 @@ msgstr "" ...@@ -39983,9 +39986,6 @@ msgstr ""
msgid "time summary" msgid "time summary"
msgstr "" msgstr ""
msgid "to join %{source_name}"
msgstr ""
msgid "toggle collapse" msgid "toggle collapse"
msgstr "" msgstr ""
......
...@@ -25,9 +25,64 @@ RSpec.describe InvitesController do ...@@ -25,9 +25,64 @@ RSpec.describe InvitesController do
end end
end end
shared_examples 'invite email match enforcement' do |error_status:, flash_alert: nil|
it 'accepts user if invite email matches signed in user' do
expect do
request
end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include 'You have been granted'
end
it 'accepts invite if invite email matches confirmed secondary email' do
secondary_email = create(:email, :confirmed, user: user)
member.update!(invite_email: secondary_email.email)
expect do
request
end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include 'You have been granted'
end
it 'does not accept if invite email matches unconfirmed secondary email' do
secondary_email = create(:email, user: user)
member.update!(invite_email: secondary_email.email)
expect do
request
end.not_to change { project_members.include?(user) }
expect(response).to have_gitlab_http_status(error_status)
expect(flash[:alert]).to eq(flash_alert)
end
it 'does not accept if invite email does not match signed in user' do
member.update!(invite_email: 'bogus@email.com')
expect do
request
end.not_to change { project_members.include?(user) }
expect(response).to have_gitlab_http_status(error_status)
expect(flash[:alert]).to eq(flash_alert)
end
end
describe 'GET #show', :snowplow do describe 'GET #show', :snowplow do
subject(:request) { get :show, params: params } subject(:request) { get :show, params: params }
context 'when logged in' do
before do
sign_in(user)
end
it_behaves_like 'invite email match enforcement', error_status: :ok
it_behaves_like 'invalid token'
end
context 'when it is an initial invite email' do context 'when it is an initial invite email' do
let(:extra_params) { { invite_type: 'initial_email' } } let(:extra_params) { { invite_type: 'initial_email' } }
...@@ -69,34 +124,6 @@ RSpec.describe InvitesController do ...@@ -69,34 +124,6 @@ RSpec.describe InvitesController do
end end
end end
context 'when logged in' do
before do
sign_in(user)
end
it 'accepts user if invite email matches signed in user' do
expect do
request
end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include 'You have been granted'
end
it 'forces re-confirmation if email does not match signed in user' do
member.update!(invite_email: 'bogus@email.com')
expect do
request
end.not_to change { project_members.include?(user) }
expect(response).to have_gitlab_http_status(:ok)
expect(flash[:notice]).to be_nil
end
it_behaves_like 'invalid token'
end
context 'when not logged in' do context 'when not logged in' do
context 'when invite token belongs to a valid member' do context 'when invite token belongs to a valid member' do
context 'when instance allows sign up' do context 'when instance allows sign up' do
...@@ -223,6 +250,7 @@ RSpec.describe InvitesController do ...@@ -223,6 +250,7 @@ RSpec.describe InvitesController do
subject(:request) { post :accept, params: params } subject(:request) { post :accept, params: params }
it_behaves_like 'invite email match enforcement', error_status: :redirect, flash_alert: 'The invitation could not be accepted.'
it_behaves_like 'invalid token' it_behaves_like 'invalid token'
end end
......
...@@ -302,35 +302,46 @@ RSpec.describe Projects::PipelinesController do ...@@ -302,35 +302,46 @@ RSpec.describe Projects::PipelinesController do
end end
describe 'GET #show' do describe 'GET #show' do
render_views
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
subject { get_pipeline_html }
def get_pipeline_html def get_pipeline_html
get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :html get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :html
end end
def create_build_with_artifacts(stage, stage_idx, name) context 'when the project is public' do
create(:ci_build, :artifacts, :tags, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) render_views
end
before do let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
create_build_with_artifacts('build', 0, 'job1')
create_build_with_artifacts('build', 0, 'job2') def create_build_with_artifacts(stage, stage_idx, name)
create(:ci_build, :artifacts, :tags, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
end
before do
create_build_with_artifacts('build', 0, 'job1')
create_build_with_artifacts('build', 0, 'job2')
end
it 'avoids N+1 database queries', :request_store do
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_html }.count
expect(response).to have_gitlab_http_status(:ok)
create_build_with_artifacts('build', 0, 'job3')
expect { get_pipeline_html }.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
end
end end
it 'avoids N+1 database queries', :request_store do context 'when the project is private' do
get_pipeline_html let(:project) { create(:project, :private, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_html }.count it 'returns `not_found` when the user does not have access' do
expect(response).to have_gitlab_http_status(:ok) sign_in(create(:user))
create_build_with_artifacts('build', 0, 'job3') get_pipeline_html
expect { get_pipeline_html }.not_to exceed_query_limit(control_count) expect(response).to have_gitlab_http_status(:not_found)
expect(response).to have_gitlab_http_status(:ok) end
end end
end end
......
...@@ -83,4 +83,16 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do ...@@ -83,4 +83,16 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do
expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.") expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.")
end end
end end
describe "impersonation disabled state" do
before do
stub_config_setting(impersonation_enabled: false)
end
it "does not show impersonation tokens tab" do
visit admin_user_path(user)
expect(page).not_to have_content("Impersonation Tokens")
end
end
end end
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Dashboard > Todo target states' do RSpec.describe 'Dashboard > Todo target states' do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:author) { create(:user) } let_it_be(:author) { create(:user) }
let(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
before_all do
project.add_developer(user)
end
before do before do
sign_in(user) sign_in(user)
end end
it 'on a closed issue todo has closed label' do it 'on a closed issue todo has closed label' do
issue_closed = create(:issue, state: 'closed') issue_closed = create(:issue, state: 'closed', project: project)
create_todo issue_closed create_todo issue_closed
visit dashboard_todos_path visit dashboard_todos_path
...@@ -22,7 +26,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -22,7 +26,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on an open issue todo does not have an open label' do it 'on an open issue todo does not have an open label' do
issue_open = create(:issue) issue_open = create(:issue, project: project)
create_todo issue_open create_todo issue_open
visit dashboard_todos_path visit dashboard_todos_path
...@@ -32,7 +36,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -32,7 +36,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on a merged merge request todo has merged label' do it 'on a merged merge request todo has merged label' do
mr_merged = create(:merge_request, :simple, :merged, author: user) mr_merged = create(:merge_request, :simple, :merged, author: user, source_project: project)
create_todo mr_merged create_todo mr_merged
visit dashboard_todos_path visit dashboard_todos_path
...@@ -42,7 +46,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -42,7 +46,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on a closed merge request todo has closed label' do it 'on a closed merge request todo has closed label' do
mr_closed = create(:merge_request, :simple, :closed, author: user) mr_closed = create(:merge_request, :simple, :closed, author: user, source_project: project)
create_todo mr_closed create_todo mr_closed
visit dashboard_todos_path visit dashboard_todos_path
...@@ -52,7 +56,7 @@ RSpec.describe 'Dashboard > Todo target states' do ...@@ -52,7 +56,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end end
it 'on an open merge request todo does not have an open label' do it 'on an open merge request todo does not have an open label' do
mr_open = create(:merge_request, :simple, author: user) mr_open = create(:merge_request, :simple, author: user, source_project: project)
create_todo mr_open create_todo mr_open
visit dashboard_todos_path visit dashboard_todos_path
......
...@@ -128,7 +128,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do ...@@ -128,7 +128,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do
describe 'filter by action' do describe 'filter by action' do
before do before do
create(:todo, :build_failed, user: user_1, author: user_2, project: project_1) create(:todo, :build_failed, user: user_1, author: user_2, project: project_1, target: merge_request)
create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1) create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1)
create(:todo, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1) create(:todo, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1)
end end
......
...@@ -3,10 +3,16 @@ ...@@ -3,10 +3,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Dashboard Todos' do RSpec.describe 'Dashboard Todos' do
include DesignManagementTestHelpers
let_it_be(:user) { create(:user, username: 'john') } let_it_be(:user) { create(:user, username: 'john') }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, due_date: Date.today, title: "Fix bug") } let_it_be(:issue) { create(:issue, project: project, due_date: Date.today, title: "Fix bug") }
before_all do
project.add_developer(user)
end
context 'User does not have todos' do context 'User does not have todos' do
before do before do
...@@ -21,8 +27,8 @@ RSpec.describe 'Dashboard Todos' do ...@@ -21,8 +27,8 @@ RSpec.describe 'Dashboard Todos' do
context 'when the todo references a merge request' do context 'when the todo references a merge request' do
let(:referenced_mr) { create(:merge_request, source_project: project) } let(:referenced_mr) { create(:merge_request, source_project: project) }
let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}") } let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}", noteable: create(:issue, project: project)) }
let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note) } let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note, target: note.noteable) }
before do before do
sign_in(user) sign_in(user)
...@@ -39,9 +45,26 @@ RSpec.describe 'Dashboard Todos' do ...@@ -39,9 +45,26 @@ RSpec.describe 'Dashboard Todos' do
end end
end end
context 'User has a todo', :js do context 'user has an unauthorized todo' do
before do before do
sign_in(user)
end
it 'does not render the todo' do
unauthorized_issue = create(:issue)
create(:todo, :mentioned, user: user, project: unauthorized_issue.project, target: unauthorized_issue, author: author)
create(:todo, :mentioned, user: user, project: project, target: issue, author: author) create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
visit dashboard_todos_path
expect(page).to have_selector('.todos-list .todo', count: 1)
end
end
context 'User has a todo', :js do
let_it_be(:user_todo) { create(:todo, :mentioned, user: user, project: project, target: issue, author: author) }
before do
sign_in(user) sign_in(user)
visit dashboard_todos_path visit dashboard_todos_path
...@@ -183,7 +206,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -183,7 +206,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'approval todo' do context 'approval todo' do
let(:merge_request) { create(:merge_request, title: "Fixes issue") } let(:merge_request) { create(:merge_request, title: "Fixes issue", source_project: project) }
before do before do
create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user) create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
...@@ -199,7 +222,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -199,7 +222,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'review request todo' do context 'review request todo' do
let(:merge_request) { create(:merge_request, title: "Fixes issue") } let(:merge_request) { create(:merge_request, title: "Fixes issue", source_project: project) }
before do before do
create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user) create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user)
...@@ -355,7 +378,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -355,7 +378,7 @@ RSpec.describe 'Dashboard Todos' do
end end
context 'User has a Build Failed todo' do context 'User has a Build Failed todo' do
let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author, target: create(:merge_request, source_project: project)) }
before do before do
sign_in(user) sign_in(user)
...@@ -386,6 +409,7 @@ RSpec.describe 'Dashboard Todos' do ...@@ -386,6 +409,7 @@ RSpec.describe 'Dashboard Todos' do
end end
before do before do
enable_design_management
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
......
...@@ -90,48 +90,17 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do ...@@ -90,48 +90,17 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end end
context 'when signed in and an invite link is clicked' do context 'when signed in and an invite link is clicked' do
context 'when an invite email is a secondary email for the user' do
let(:invite_email) { 'user_secondary@example.com' }
before do
sign_in(user)
visit invite_path(group_invite.raw_invite_token)
end
it 'sends user to the invite url and allows them to decline' do
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
expect(page).to have_content("Note that this invitation was sent to #{invite_email}")
expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}")
click_link('Decline')
expect(page).to have_content('You have declined the invitation')
expect(current_path).to eq(dashboard_projects_path)
expect { group_invite.reload }.to raise_error ActiveRecord::RecordNotFound
end
it 'sends uer to the invite url and allows them to accept' do
expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
expect(page).to have_content("Note that this invitation was sent to #{invite_email}")
expect(page).to have_content("but you are signed in as #{user.to_reference} with email #{user.email}")
click_link('Accept invitation')
expect(page).to have_content('You have been granted')
expect(current_path).to eq(activity_group_path(group))
end
end
context 'when user is an existing member' do context 'when user is an existing member' do
before do before do
sign_in(owner) group.add_developer(user)
sign_in(user)
visit invite_path(group_invite.raw_invite_token) visit invite_path(group_invite.raw_invite_token)
end end
it 'shows message user already a member' do it 'shows message user already a member' do
expect(current_path).to eq(invite_path(group_invite.raw_invite_token)) expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
expect(page).to have_link(owner.name, href: user_url(owner)) expect(page).to have_link(user.name, href: user_path(user))
expect(page).to have_content('However, you are already a member of this group.') expect(page).to have_content('You are already a member of this group.')
end end
end end
end end
......
...@@ -260,8 +260,6 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -260,8 +260,6 @@ RSpec.describe 'Mermaid rendering', :js do
description *= 51 description *= 51
project = create(:project, :public)
wiki_page = build(:wiki_page, { container: project, content: description }) wiki_page = build(:wiki_page, { container: project, content: description })
wiki_page.create message: 'mermaid test commit' # rubocop:disable Rails/SaveBang wiki_page.create message: 'mermaid test commit' # rubocop:disable Rails/SaveBang
wiki_page = project.wiki.find_page(wiki_page.slug) wiki_page = project.wiki.find_page(wiki_page.slug)
...@@ -277,6 +275,27 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -277,6 +275,27 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).not_to have_selector('.js-lazy-render-mermaid-container') expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
end end
end end
it 'does not allow HTML injection' do
description = <<~MERMAID
```mermaid
%%{init: {"flowchart": {"htmlLabels": "false"}} }%%
flowchart
A["<iframe></iframe>"]
```
MERMAID
issue = create(:issue, project: project, description: description)
visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
page.within('.description') do
expect(page).not_to have_xpath("//iframe")
end
end
end end
def wait_for_mermaid def wait_for_mermaid
......
...@@ -365,9 +365,8 @@ RSpec.describe 'Pipeline', :js do ...@@ -365,9 +365,8 @@ RSpec.describe 'Pipeline', :js do
let(:project) { create(:project, :public, :repository, public_builds: false) } let(:project) { create(:project, :public, :repository, public_builds: false) }
let(:role) { :guest } let(:role) { :guest }
it 'does not show failed jobs tab pane' do it 'does not show the pipeline details page' do
expect(page).to have_link('Pipeline') expect(page).to have_content('Not Found')
expect(page).not_to have_content('Failed Jobs')
end end
end end
end end
......
...@@ -44,8 +44,7 @@ describe('Vuex members mutations', () => { ...@@ -44,8 +44,7 @@ describe('Vuex members mutations', () => {
describe('when error has a message', () => { describe('when error has a message', () => {
it('shows error message', () => { it('shows error message', () => {
const error = new Error('Request failed with status code 422'); const error = new Error('Request failed with status code 422');
const message = const message = 'User email does not match the allowed domain of example.com';
'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
error.response = { error.response = {
data: { message }, data: { message },
...@@ -88,8 +87,7 @@ describe('Vuex members mutations', () => { ...@@ -88,8 +87,7 @@ describe('Vuex members mutations', () => {
describe('when error has a message', () => { describe('when error has a message', () => {
it('shows error message', () => { it('shows error message', () => {
const error = new Error('Request failed with status code 422'); const error = new Error('Request failed with status code 422');
const message = const message = 'User email does not match the allowed domain of example.com';
'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
error.response = { error.response = {
data: { message }, data: { message },
......
...@@ -5,17 +5,23 @@ require 'spec_helper' ...@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::MarkDone do RSpec.describe Mutations::Todos::MarkDone do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) } specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do describe '#resolve' do
......
...@@ -5,17 +5,23 @@ require 'spec_helper' ...@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::Restore do RSpec.describe Mutations::Todos::Restore do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) } specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do describe '#resolve' do
......
...@@ -5,14 +5,24 @@ require 'spec_helper' ...@@ -5,14 +5,24 @@ require 'spec_helper'
RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project, :private) }
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let(:current_user) { reporter }
before_all do
project.add_guest(guest)
project.add_reporter(reporter)
end
specify do specify do
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType) expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end end
def resolve_statistics(project, args) def resolve_statistics(project, args)
resolve(described_class, obj: project, args: args) ctx = { current_user: current_user }
resolve(described_class, obj: project, args: args, ctx: ctx)
end end
describe '#resolve' do describe '#resolve' do
...@@ -32,5 +42,15 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do ...@@ -32,5 +42,15 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
:pipeline_times_values :pipeline_times_values
) )
end end
context 'when the user does not have access to the CI/CD analytics data' do
let(:current_user) { guest }
it 'returns nil' do
result = resolve_statistics(project, {})
expect(result).to be_nil
end
end
end end
end end
...@@ -4,19 +4,28 @@ require 'spec_helper' ...@@ -4,19 +4,28 @@ require 'spec_helper'
RSpec.describe Resolvers::TodoResolver do RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers include GraphqlHelpers
include DesignManagementTestHelpers
specify do specify do
expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type) expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type)
end end
describe '#resolve' do describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:author1) { create(:user) } let_it_be(:author1) { create(:user) }
let_it_be(:author2) { create(:user) } let_it_be(:author2) { create(:user) }
let_it_be(:merge_request_todo_pending) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) } let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2, target: issue) }
let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) } let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue) }
let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:merge_request_todo_pending) { create(:todo, user: current_user, target: merge_request, state: :pending, action: Todo::MENTIONED, author: author1) }
before_all do
project.add_developer(current_user)
end
it 'calls TodosFinder' do it 'calls TodosFinder' do
expect_next_instance_of(TodosFinder) do |finder| expect_next_instance_of(TodosFinder) do |finder|
...@@ -40,7 +49,9 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -40,7 +49,9 @@ RSpec.describe Resolvers::TodoResolver do
end end
it 'returns the todos for multiple filters' do it 'returns the todos for multiple filters' do
design_todo_pending = create(:todo, target_type: 'DesignManagement::Design', user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) enable_design_management
design = create(:design, issue: issue)
design_todo_pending = create(:todo, target: design, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design']) todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design'])
...@@ -59,11 +70,15 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -59,11 +70,15 @@ RSpec.describe Resolvers::TodoResolver do
group3 = create(:group) group3 = create(:group)
group1.add_developer(current_user) group1.add_developer(current_user)
issue1 = create(:issue, project: create(:project, group: group1))
group2.add_developer(current_user) group2.add_developer(current_user)
issue2 = create(:issue, project: create(:project, group: group2))
group3.add_developer(current_user)
issue3 = create(:issue, project: create(:project, group: group3))
todo4 = create(:todo, group: group1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) todo4 = create(:todo, group: group1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue1)
todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue2)
create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue3)
todos = resolve_todos(group_id: [group2.id, group1.id]) todos = resolve_todos(group_id: [group2.id, group1.id])
...@@ -93,9 +108,13 @@ RSpec.describe Resolvers::TodoResolver do ...@@ -93,9 +108,13 @@ RSpec.describe Resolvers::TodoResolver do
project2 = create(:project) project2 = create(:project)
project3 = create(:project) project3 = create(:project)
todo4 = create(:todo, project: project1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project1.add_developer(current_user)
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project2.add_developer(current_user)
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) project3.add_developer(current_user)
todo4 = create(:todo, project: project1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project1))
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project2))
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: create(:issue, project: project3))
todos = resolve_todos(project_id: [project2.id, project1.id]) todos = resolve_todos(project_id: [project2.id, project1.id])
......
...@@ -336,6 +336,15 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -336,6 +336,15 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
expect_results_with_abilities(impersonation_token, described_class.full_authentication_abilities) expect_results_with_abilities(impersonation_token, described_class.full_authentication_abilities)
end end
it 'fails if it is an impersonation token but impersonation is blocked' do
stub_config_setting(impersonation_enabled: false)
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
it 'limits abilities based on scope' do it 'limits abilities based on scope' do
personal_access_token = create(:personal_access_token, scopes: %w[read_user sudo]) personal_access_token = create(:personal_access_token, scopes: %w[read_user sudo])
......
...@@ -136,7 +136,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -136,7 +136,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let(:command) do let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new( Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push, source: :push,
origin_ref: 'mytag', origin_ref: origin_ref,
checkout_sha: project.commit.id, checkout_sha: project.commit.id,
after_sha: nil, after_sha: nil,
before_sha: nil, before_sha: nil,
...@@ -147,6 +147,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -147,6 +147,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
current_user: user) current_user: user)
end end
let(:origin_ref) { 'mytag' }
before do before do
allow_any_instance_of(Repository).to receive(:tag_exists?).with('mytag').and_return(true) allow_any_instance_of(Repository).to receive(:tag_exists?).with('mytag').and_return(true)
...@@ -156,6 +158,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do ...@@ -156,6 +158,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
it 'correctly indicated that this is a tagged pipeline' do it 'correctly indicated that this is a tagged pipeline' do
expect(pipeline).to be_tag expect(pipeline).to be_tag
end end
context 'when origin_ref is branch but tag ref with the same name exists' do
let(:origin_ref) { 'refs/heads/mytag' }
it 'correctly indicated that a pipeline is not tagged' do
expect(pipeline).not_to be_tag
end
end
end end
context 'when pipeline is running for a merge request' do context 'when pipeline is running for a merge request' do
......
...@@ -27,6 +27,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do ...@@ -27,6 +27,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to eq(true) } it { is_expected.to eq(true) }
end end
context 'for fully described tag ref' do
let(:origin_ref) { 'refs/tags/master' }
it { is_expected.to eq(false) }
end
context 'for fully described branch ref' do
let(:origin_ref) { 'refs/heads/master' }
it { is_expected.to eq(true) }
end
context 'for invalid branch' do context 'for invalid branch' do
let(:origin_ref) { 'something' } let(:origin_ref) { 'something' }
...@@ -43,6 +55,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do ...@@ -43,6 +55,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to eq(true) } it { is_expected.to eq(true) }
end end
context 'for fully described tag ref' do
let(:origin_ref) { 'refs/tags/v1.0.0' }
it { is_expected.to eq(true) }
end
context 'for fully described branch ref' do
let(:origin_ref) { 'refs/heads/v1.0.0' }
it { is_expected.to eq(false) }
end
context 'for invalid ref' do context 'for invalid ref' do
let(:origin_ref) { 'something' } let(:origin_ref) { 'something' }
......
...@@ -138,6 +138,25 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do ...@@ -138,6 +138,25 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect(issue.assignees).to be_empty expect(issue.assignees).to be_empty
expect(issue.milestone).to be_nil expect(issue.milestone).to be_nil
end end
context 'when issues are set to private' do
before do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end
it 'applies quick action commands present on templates' do
file_content = %(Text from service_desk2 template \n/label ~#{label.title} \n/milestone %"#{milestone.name}")
set_template_file('service_desk2', file_content)
receiver.execute
issue = Issue.last
expect(issue.description).to include('Text from service_desk2 template')
expect(issue.label_ids).to include(label.id)
expect(issue.author_id).to eq(User.support_bot.id)
expect(issue.milestone).to eq(milestone)
end
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::OmniauthLogging::JSONFormatter do
it "generates log in json format" do
Timecop.freeze(Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
expect(subject.call(:info, Time.now, 'omniauth', 'log message'))
.to eq %Q({"severity":"info","timestamp":"2019-12-04T09:10:11.123Z","pid":#{Process.pid},"progname":"omniauth","message":"log message"}\n)
end
end
end
...@@ -4,15 +4,19 @@ require 'spec_helper' ...@@ -4,15 +4,19 @@ require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:guest) do
create(:user).tap { |u| project.add_guest(u) }
end
let(:user) { project.owner } let(:owner) { project.owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) } let(:current_user) { owner }
let(:context) { Sidebars::Projects::Context.new(current_user: current_user, container: project, current_ref: project.repository.root_ref) }
subject { described_class.new(context) } subject { described_class.new(context) }
describe '#render?' do describe '#render?' do
context 'whe user cannot read analytics' do context 'whe user cannot read analytics' do
let(:user) { nil } let(:current_user) { nil }
it 'returns false' do it 'returns false' do
expect(subject.render?).to be false expect(subject.render?).to be false
...@@ -79,7 +83,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -79,7 +83,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end end
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { guest }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
...@@ -99,7 +103,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -99,7 +103,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end end
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { nil }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
...@@ -111,7 +115,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do ...@@ -111,7 +115,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
specify { is_expected.not_to be_nil } specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do describe 'when the user does not have access' do
let(:user) { nil } let(:current_user) { nil }
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
......
...@@ -552,4 +552,10 @@ RSpec.describe DiffNote do ...@@ -552,4 +552,10 @@ RSpec.describe DiffNote do
expect(subject.on_image?).to be_truthy expect(subject.on_image?).to be_truthy
end end
end end
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DiscussionNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LegacyDiffNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SyntheticNote do
describe '#to_ability_name' do
subject { described_class.new.to_ability_name }
it { is_expected.to eq('note') }
end
end
...@@ -11,13 +11,37 @@ RSpec.describe IssuePolicy do ...@@ -11,13 +11,37 @@ RSpec.describe IssuePolicy do
let(:reporter) { create(:user) } let(:reporter) { create(:user) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public) }
let(:reporter_from_group_link) { create(:user) } let(:reporter_from_group_link) { create(:user) }
let(:non_member) { create(:user) }
let(:support_bot) { User.support_bot }
def permissions(user, issue) def permissions(user, issue)
described_class.new(user, issue) described_class.new(user, issue)
end end
shared_examples 'support bot with service desk enabled' do
before do
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
project.update!(service_desk_enabled: true)
end
it 'allows support_bot to read issues, create and set metadata on new issues' do
expect(permissions(support_bot, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
end
end
shared_examples 'support bot with service desk disabled' do
it 'allows support_bot to read issues, create and set metadata on new issues' do
expect(permissions(support_bot, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
end
end
context 'a private project' do context 'a private project' do
let(:non_member) { create(:user) }
let(:project) { create(:project, :private) } let(:project) { create(:project, :private) }
let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) } let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) }
let(:issue_no_assignee) { create(:issue, project: project) } let(:issue_no_assignee) { create(:issue, project: project) }
...@@ -34,12 +58,6 @@ RSpec.describe IssuePolicy do ...@@ -34,12 +58,6 @@ RSpec.describe IssuePolicy do
create(:project_group_link, group: group, project: project) create(:project_group_link, group: group, project: project)
end end
it 'does not allow non-members to read issues' do
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
end
it 'allows guests to read issues' do it 'allows guests to read issues' do
expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid) expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata) expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
...@@ -82,6 +100,15 @@ RSpec.describe IssuePolicy do ...@@ -82,6 +100,15 @@ RSpec.describe IssuePolicy do
expect(permissions(assignee, new_issue)).to be_allowed(:create_issue, :set_issue_metadata) expect(permissions(assignee, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
end end
it 'does not allow non-members to read, update or create issues' do
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
end
it_behaves_like 'support bot with service desk disabled'
it_behaves_like 'support bot with service desk enabled'
context 'with confidential issues' do context 'with confidential issues' do
let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) } let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) }
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) } let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
...@@ -196,7 +223,8 @@ RSpec.describe IssuePolicy do ...@@ -196,7 +223,8 @@ RSpec.describe IssuePolicy do
expect(permissions(author, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue) expect(permissions(author, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
expect(permissions(author, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata) expect(permissions(author, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata)
expect(permissions(author, new_issue)).to be_allowed(:create_issue, :set_issue_metadata) expect(permissions(author, new_issue)).to be_allowed(:create_issue)
expect(permissions(author, new_issue)).to be_disallowed(:set_issue_metadata)
end end
it 'allows issue assignees to read, reopen and update their issues' do it 'allows issue assignees to read, reopen and update their issues' do
...@@ -208,14 +236,44 @@ RSpec.describe IssuePolicy do ...@@ -208,14 +236,44 @@ RSpec.describe IssuePolicy do
expect(permissions(assignee, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue) expect(permissions(assignee, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata) expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata)
end
expect(permissions(author, new_issue)).to be_allowed(:create_issue, :set_issue_metadata) it 'allows non-members to read and create issues' do
expect(permissions(non_member, issue)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(non_member, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(non_member, new_issue)).to be_allowed(:create_issue)
end
it 'allows non-members to read issues' do
expect(permissions(non_member, issue)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(non_member, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
end
it 'does not allow non-members to update, admin or set metadata' do
expect(permissions(non_member, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, new_issue)).to be_disallowed(:set_issue_metadata)
end end
it 'allows support_bot to read issues' do
# support_bot is still allowed read access in public projects through :public_access permission,
# see project_policy public_access rules policy (rule { can?(:public_access) }.policy {...})
expect(permissions(support_bot, issue)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(support_bot, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
end
it_behaves_like 'support bot with service desk enabled'
context 'when issues are private' do context 'when issues are private' do
before do before do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end end
let(:issue) { create(:issue, project: project, author: author) } let(:issue) { create(:issue, project: project, author: author) }
let(:visitor) { create(:user) } let(:visitor) { create(:user) }
let(:admin) { create(:user, :admin) } let(:admin) { create(:user, :admin) }
...@@ -258,6 +316,15 @@ RSpec.describe IssuePolicy do ...@@ -258,6 +316,15 @@ RSpec.describe IssuePolicy do
expect(permissions(admin, issue)).to be_disallowed(:create_note) expect(permissions(admin, issue)).to be_disallowed(:create_note)
end end
end end
it 'does not allow non-members to update or create issues' do
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
end
it_behaves_like 'support bot with service desk disabled'
it_behaves_like 'support bot with service desk enabled'
end end
context 'with confidential issues' do context 'with confidential issues' do
......
...@@ -41,6 +41,13 @@ RSpec.describe PersonalAccessTokenPolicy do ...@@ -41,6 +41,13 @@ RSpec.describe PersonalAccessTokenPolicy do
it { is_expected.to be_allowed(:read_token) } it { is_expected.to be_allowed(:read_token) }
it { is_expected.to be_allowed(:revoke_token) } it { is_expected.to be_allowed(:revoke_token) }
end end
context 'subject of the impersonated token' do
let_it_be(:token) { build_stubbed(:personal_access_token, user: current_user, impersonation: true) }
it { is_expected.to be_disallowed(:read_token) }
it { is_expected.to be_disallowed(:revoke_token) }
end
end end
context 'current_user is a blocked administrator', :enable_admin_mode do context 'current_user is a blocked administrator', :enable_admin_mode do
......
...@@ -480,8 +480,8 @@ RSpec.describe ProjectPolicy do ...@@ -480,8 +480,8 @@ RSpec.describe ProjectPolicy do
let(:current_user) { User.support_bot } let(:current_user) { User.support_bot }
context 'with service desk disabled' do context 'with service desk disabled' do
it { expect_allowed(:guest_access) } it { expect_allowed(:public_access) }
it { expect_disallowed(:create_note, :read_project) } it { expect_disallowed(:guest_access, :create_note, :read_project) }
end end
context 'with service desk enabled' do context 'with service desk enabled' do
...@@ -1131,12 +1131,20 @@ RSpec.describe ProjectPolicy do ...@@ -1131,12 +1131,20 @@ RSpec.describe ProjectPolicy do
let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) } let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) }
before do before do
project_with_analytics_disabled.add_guest(guest)
project_with_analytics_private.add_guest(guest)
project_with_analytics_enabled.add_guest(guest)
project_with_analytics_disabled.add_reporter(reporter)
project_with_analytics_private.add_reporter(reporter)
project_with_analytics_enabled.add_reporter(reporter)
project_with_analytics_disabled.add_developer(developer) project_with_analytics_disabled.add_developer(developer)
project_with_analytics_private.add_developer(developer) project_with_analytics_private.add_developer(developer)
project_with_analytics_enabled.add_developer(developer) project_with_analytics_enabled.add_developer(developer)
end end
context 'when analytics is enabled for the project' do context 'when analytics is disabled for the project' do
let(:project) { project_with_analytics_disabled } let(:project) { project_with_analytics_disabled }
context 'for guest user' do context 'for guest user' do
...@@ -1145,6 +1153,16 @@ RSpec.describe ProjectPolicy do ...@@ -1145,6 +1153,16 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1153,6 +1171,7 @@ RSpec.describe ProjectPolicy do ...@@ -1153,6 +1171,7 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end end
end end
...@@ -1162,9 +1181,19 @@ RSpec.describe ProjectPolicy do ...@@ -1162,9 +1181,19 @@ RSpec.describe ProjectPolicy do
context 'for guest user' do context 'for guest user' do
let(:current_user) { guest } let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1173,18 +1202,29 @@ RSpec.describe ProjectPolicy do ...@@ -1173,18 +1202,29 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_allowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) } it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
end end
context 'when analytics is enabled for the project' do context 'when analytics is enabled for the project' do
let(:project) { project_with_analytics_private } let(:project) { project_with_analytics_enabled }
context 'for guest user' do context 'for guest user' do
let(:current_user) { guest } let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) } it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
context 'for reporter user' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
context 'for developer' do context 'for developer' do
...@@ -1193,6 +1233,7 @@ RSpec.describe ProjectPolicy do ...@@ -1193,6 +1233,7 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_allowed(:read_cycle_analytics) } it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) } it { is_expected.to be_allowed(:read_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) } it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end end
end end
end end
......
...@@ -9,22 +9,28 @@ RSpec.describe TodoPolicy do ...@@ -9,22 +9,28 @@ RSpec.describe TodoPolicy do
let_it_be(:user2) { create(:user) } let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) } let_it_be(:user3) { create(:user) }
let_it_be(:todo1) { create(:todo, author: author, user: user1) } let_it_be(:project) { create(:project) }
let_it_be(:todo2) { create(:todo, author: author, user: user2) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:todo1) { create(:todo, author: author, user: user1, issue: issue) }
let_it_be(:todo2) { create(:todo, author: author, user: user2, issue: issue) }
let_it_be(:todo3) { create(:todo, author: author, user: user2) } let_it_be(:todo3) { create(:todo, author: author, user: user2) }
let_it_be(:todo4) { create(:todo, author: author, user: user3) } let_it_be(:todo4) { create(:todo, author: author, user: user3, issue: issue) }
def permissions(user, todo) def permissions(user, todo)
described_class.new(user, todo) described_class.new(user, todo)
end end
before_all do
project.add_developer(user1)
project.add_developer(user2)
end
describe 'own_todo' do describe 'own_todo' do
it 'allows owners to access their own todos' do it 'allows owners to access their own todos if they can read todo target' do
[ [
[user1, todo1], [user1, todo1],
[user2, todo2], [user2, todo2]
[user2, todo3],
[user3, todo4]
].each do |user, todo| ].each do |user, todo|
expect(permissions(user, todo)).to be_allowed(:read_todo) expect(permissions(user, todo)).to be_allowed(:read_todo)
end end
...@@ -38,7 +44,9 @@ RSpec.describe TodoPolicy do ...@@ -38,7 +44,9 @@ RSpec.describe TodoPolicy do
[user2, todo4], [user2, todo4],
[user3, todo1], [user3, todo1],
[user3, todo2], [user3, todo2],
[user3, todo3] [user3, todo3],
[user2, todo3],
[user3, todo4]
].each do |user, todo| ].each do |user, todo|
expect(permissions(user, todo)).to be_disallowed(:read_todo) expect(permissions(user, todo)).to be_disallowed(:read_todo)
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::ImpersonationTokensController, :enable_admin_mode do
let(:admin) { create(:admin) }
let!(:user) { create(:user) }
before do
sign_in(admin)
end
context "when impersonation is disabled" do
before do
stub_config_setting(impersonation_enabled: false)
end
it "shows error page for index page" do
get admin_user_impersonation_tokens_path(user_id: user.username)
expect(response).to have_gitlab_http_status(:not_found)
end
it "responds with 404 for create action" do
post admin_user_impersonation_tokens_path(user_id: user.username)
expect(response).to have_gitlab_http_status(:not_found)
end
it "responds with 404 for revoke action" do
token = create(:personal_access_token, :impersonation, user: user)
put revoke_admin_user_impersonation_token_path(user_id: user.username, id: token.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
...@@ -4,12 +4,17 @@ require 'spec_helper' ...@@ -4,12 +4,17 @@ require 'spec_helper'
RSpec.describe 'Query current user todos' do RSpec.describe 'Query current user todos' do
include GraphqlHelpers include GraphqlHelpers
include DesignManagementTestHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: create(:project, :repository)) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:issue_todo) { create(:todo, user: current_user, target: create(:issue)) } let_it_be(:unauthorize_project) { create(:project) }
let_it_be(:merge_request_todo) { create(:todo, user: current_user, target: create(:merge_request)) } let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: project) }
let_it_be(:design_todo) { create(:todo, user: current_user, target: create(:design)) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:issue_todo) { create(:todo, project: project, user: current_user, target: issue) }
let_it_be(:merge_request_todo) { create(:todo, project: project, user: current_user, target: create(:merge_request, source_project: project)) }
let_it_be(:design_todo) { create(:todo, project: project, user: current_user, target: create(:design, issue: issue)) }
let_it_be(:unauthorized_todo) { create(:todo, user: current_user, project: unauthorize_project, target: create(:issue, project: unauthorize_project)) }
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
...@@ -23,16 +28,22 @@ RSpec.describe 'Query current user todos' do ...@@ -23,16 +28,22 @@ RSpec.describe 'Query current user todos' do
graphql_query_for('currentUser', {}, query_graphql_field('todos', {}, fields)) graphql_query_for('currentUser', {}, query_graphql_field('todos', {}, fields))
end end
before_all do
project.add_developer(current_user)
end
subject { graphql_data.dig('currentUser', 'todos', 'nodes') } subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before do before do
enable_design_management
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
it 'contains the expected ids' do it 'contains the expected ids' do
is_expected.to include( is_expected.to contain_exactly(
a_hash_including('id' => commit_todo.to_global_id.to_s), a_hash_including('id' => commit_todo.to_global_id.to_s),
a_hash_including('id' => issue_todo.to_global_id.to_s), a_hash_including('id' => issue_todo.to_global_id.to_s),
a_hash_including('id' => merge_request_todo.to_global_id.to_s), a_hash_including('id' => merge_request_todo.to_global_id.to_s),
...@@ -41,11 +52,33 @@ RSpec.describe 'Query current user todos' do ...@@ -41,11 +52,33 @@ RSpec.describe 'Query current user todos' do
end end
it 'returns Todos for all target types' do it 'returns Todos for all target types' do
is_expected.to include( is_expected.to contain_exactly(
a_hash_including('targetType' => 'COMMIT'), a_hash_including('targetType' => 'COMMIT'),
a_hash_including('targetType' => 'ISSUE'), a_hash_including('targetType' => 'ISSUE'),
a_hash_including('targetType' => 'MERGEREQUEST'), a_hash_including('targetType' => 'MERGEREQUEST'),
a_hash_including('targetType' => 'DESIGN') a_hash_including('targetType' => 'DESIGN')
) )
end end
context 'when requesting a single field' do
let(:fields) do
<<~QUERY
nodes {
id
}
QUERY
end
it 'avoids N+1 queries', :request_store do
control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
project2 = create(:project)
project2.add_developer(current_user)
issue2 = create(:issue, project: project2)
create(:todo, user: current_user, target: issue2, project: project2)
# An additional query is made for each different group that owns a todo through a project
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control).with_threshold(2)
end
end
end end
...@@ -5,14 +5,16 @@ require 'spec_helper' ...@@ -5,14 +5,16 @@ require 'spec_helper'
RSpec.describe 'Marking all todos done' do RSpec.describe 'Marking all todos done' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:other_user2) { create(:user) } let_it_be(:other_user2) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo3) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
...@@ -28,6 +30,10 @@ RSpec.describe 'Marking all todos done' do ...@@ -28,6 +30,10 @@ RSpec.describe 'Marking all todos done' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todos_mark_all_done) graphql_mutation_response(:todos_mark_all_done)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Marking todos done' do RSpec.describe 'Marking todos done' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
...@@ -29,6 +31,10 @@ RSpec.describe 'Marking todos done' do ...@@ -29,6 +31,10 @@ RSpec.describe 'Marking todos done' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_mark_done) graphql_mutation_response(:todo_mark_done)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring many Todos' do RSpec.describe 'Restoring many Todos' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
...@@ -30,6 +32,10 @@ RSpec.describe 'Restoring many Todos' do ...@@ -30,6 +32,10 @@ RSpec.describe 'Restoring many Todos' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_restore_many) graphql_mutation_response(:todo_restore_many)
end end
......
...@@ -5,12 +5,14 @@ require 'spec_helper' ...@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring Todos' do RSpec.describe 'Restoring Todos' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) } let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) } let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
...@@ -29,6 +31,10 @@ RSpec.describe 'Restoring Todos' do ...@@ -29,6 +31,10 @@ RSpec.describe 'Restoring Todos' do
) )
end end
before_all do
project.add_developer(current_user)
end
def mutation_response def mutation_response
graphql_mutation_response(:todo_restore) graphql_mutation_response(:todo_restore)
end end
......
...@@ -6,6 +6,7 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -6,6 +6,7 @@ RSpec.describe API::PersonalAccessTokens do
let_it_be(:path) { '/personal_access_tokens' } let_it_be(:path) { '/personal_access_tokens' }
let_it_be(:token1) { create(:personal_access_token) } let_it_be(:token1) { create(:personal_access_token) }
let_it_be(:token2) { create(:personal_access_token) } let_it_be(:token2) { create(:personal_access_token) }
let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: token1.user) }
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
describe 'GET /personal_access_tokens' do describe 'GET /personal_access_tokens' do
...@@ -24,8 +25,9 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -24,8 +25,9 @@ RSpec.describe API::PersonalAccessTokens do
get api(path, current_user), params: { user_id: token1.user.id } get api(path, current_user), params: { user_id: token1.user.id }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq(1) expect(json_response.count).to eq(2)
expect(json_response.first['user_id']).to eq(token1.user.id) expect(json_response.first['user_id']).to eq(token1.user.id)
expect(json_response.last['id']).to eq(token_impersonated.id)
end end
end end
...@@ -34,6 +36,7 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -34,6 +36,7 @@ RSpec.describe API::PersonalAccessTokens do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: current_user)} let_it_be(:token) { create(:personal_access_token, user: current_user)}
let_it_be(:other_token) { create(:personal_access_token, user: user) } let_it_be(:other_token) { create(:personal_access_token, user: user) }
let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: current_user) }
it 'returns all PATs belonging to the signed-in user' do it 'returns all PATs belonging to the signed-in user' do
get api(path, current_user, personal_access_token: token) get api(path, current_user, personal_access_token: token)
...@@ -95,6 +98,7 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -95,6 +98,7 @@ RSpec.describe API::PersonalAccessTokens do
context 'when current_user is not an administrator' do context 'when current_user is not an administrator' do
let_it_be(:user_token) { create(:personal_access_token, user: current_user) } let_it_be(:user_token) { create(:personal_access_token, user: current_user) }
let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" } let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" }
let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: current_user) }
it 'fails revokes a different users token' do it 'fails revokes a different users token' do
delete api(path, current_user) delete api(path, current_user)
...@@ -107,6 +111,12 @@ RSpec.describe API::PersonalAccessTokens do ...@@ -107,6 +111,12 @@ RSpec.describe API::PersonalAccessTokens do
expect(response).to have_gitlab_http_status(:no_content) expect(response).to have_gitlab_http_status(:no_content)
end end
it 'cannot revoke impersonation token' do
delete api("/personal_access_tokens/#{token_impersonated.id}", current_user)
expect(response).to have_gitlab_http_status(:bad_request)
end
end end
end end
end end
...@@ -3,18 +3,22 @@ ...@@ -3,18 +3,22 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Todos do RSpec.describe API::Todos do
include DesignManagementTestHelpers
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) } let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) } let_it_be(:project_2) { create(:project) }
let_it_be(:author_1) { create(:user) } let_it_be(:author_1) { create(:user) }
let_it_be(:author_2) { create(:user) } let_it_be(:author_2) { create(:user) }
let_it_be(:john_doe) { create(:user, username: 'john_doe') } let_it_be(:john_doe) { create(:user, username: 'john_doe') }
let_it_be(:issue) { create(:issue, project: project_1) }
let_it_be(:merge_request) { create(:merge_request, source_project: project_1) } let_it_be(:merge_request) { create(:merge_request, source_project: project_1) }
let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) }
let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) }
let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe, target: issue) }
let_it_be(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) } let_it_be(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) }
let_it_be(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } let_it_be(:pending_4) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe, commit_id: 'invalid_id') }
let_it_be(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe, target: issue) }
let_it_be(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') } let_it_be(:award_emoji_1) { create(:award_emoji, awardable: merge_request, user: author_1, name: 'thumbsup') }
let_it_be(:award_emoji_2) { create(:award_emoji, awardable: pending_1.target, user: author_1, name: 'thumbsup') } let_it_be(:award_emoji_2) { create(:award_emoji, awardable: pending_1.target, user: author_1, name: 'thumbsup') }
let_it_be(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') } let_it_be(:award_emoji_3) { create(:award_emoji, awardable: pending_2.target, user: author_2, name: 'thumbsdown') }
...@@ -77,13 +81,13 @@ RSpec.describe API::Todos do ...@@ -77,13 +81,13 @@ RSpec.describe API::Todos do
expect(json_response[0]['target_type']).to eq('Commit') expect(json_response[0]['target_type']).to eq('Commit')
expect(json_response[1]['target_type']).to eq('Issue') expect(json_response[1]['target_type']).to eq('Issue')
expect(json_response[1]['target']['upvotes']).to eq(0) expect(json_response[1]['target']['upvotes']).to eq(1)
expect(json_response[1]['target']['downvotes']).to eq(1) expect(json_response[1]['target']['downvotes']).to eq(1)
expect(json_response[1]['target']['merge_requests_count']).to eq(0) expect(json_response[1]['target']['merge_requests_count']).to eq(0)
expect(json_response[2]['target_type']).to eq('Issue') expect(json_response[2]['target_type']).to eq('Issue')
expect(json_response[2]['target']['upvotes']).to eq(1) expect(json_response[2]['target']['upvotes']).to eq(1)
expect(json_response[2]['target']['downvotes']).to eq(0) expect(json_response[2]['target']['downvotes']).to eq(1)
expect(json_response[2]['target']['merge_requests_count']).to eq(0) expect(json_response[2]['target']['merge_requests_count']).to eq(0)
expect(json_response[3]['target_type']).to eq('MergeRequest') expect(json_response[3]['target_type']).to eq('MergeRequest')
...@@ -93,6 +97,19 @@ RSpec.describe API::Todos do ...@@ -93,6 +97,19 @@ RSpec.describe API::Todos do
expect(json_response[3]['target']['downvotes']).to eq(0) expect(json_response[3]['target']['downvotes']).to eq(0)
end end
context "when current user does not have access to one of the TODO's target" do
it 'filters out unauthorized todos' do
no_access_project = create(:project, :repository, group: group)
no_access_merge_request = create(:merge_request, source_project: no_access_project)
no_access_todo = create(:todo, project: no_access_project, author: author_2, user: john_doe, target: no_access_merge_request)
get api('/todos', john_doe)
expect(json_response.count).to eq(4)
expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id)
end
end
context 'and using the author filter' do context 'and using the author filter' do
it 'filters based on author_id param' do it 'filters based on author_id param' do
get api('/todos', john_doe), params: { author_id: author_2.id } get api('/todos', john_doe), params: { author_id: author_2.id }
...@@ -163,23 +180,31 @@ RSpec.describe API::Todos do ...@@ -163,23 +180,31 @@ RSpec.describe API::Todos do
end end
it 'avoids N+1 queries', :request_store do it 'avoids N+1 queries', :request_store do
create_issue_todo_for(john_doe)
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
get api('/todos', john_doe) get api('/todos', john_doe)
control = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } control1 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
create_issue_todo_for(john_doe)
create_mr_todo_for(john_doe, project_2)
create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: merge_request)
new_todo = create_mr_todo_for(john_doe)
merge_request_3 = create(:merge_request, :jira_branch, source_project: new_todo.project)
create(:on_commit_todo, project: new_todo.project, author: author_1, user: john_doe, target: merge_request_3)
create(:todo, project: new_todo.project, author: author_2, user: john_doe, target: merge_request_3)
merge_request_2 = create(:merge_request, source_project: project_2) expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(4)
create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2) control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
project_3 = create(:project, :repository) create_issue_todo_for(john_doe)
project_3.add_developer(john_doe) create_issue_todo_for(john_doe, project_1)
merge_request_3 = create(:merge_request, source_project: project_3) create_issue_todo_for(john_doe, project_1)
create(:todo, project: project_3, author: author_2, user: john_doe, target: merge_request_3)
create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) # Additional query only when target belongs to project from different group
create(:on_commit_todo, project: project_3, author: author_1, user: john_doe) expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control2).with_threshold(1)
expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
...@@ -201,6 +226,8 @@ RSpec.describe API::Todos do ...@@ -201,6 +226,8 @@ RSpec.describe API::Todos do
end end
before do before do
enable_design_management
api_request api_request
end end
...@@ -222,6 +249,20 @@ RSpec.describe API::Todos do ...@@ -222,6 +249,20 @@ RSpec.describe API::Todos do
) )
end end
end end
def create_mr_todo_for(user, project = nil)
new_project = project || create(:project, group: create(:group))
new_project.add_developer(user) if project.blank?
new_merge_request = create(:merge_request, source_project: new_project)
create(:todo, project: new_project, author: user, user: user, target: new_merge_request)
end
def create_issue_todo_for(user, project = nil)
new_project = project || create(:project, group: create(:group))
new_project.group.add_developer(user) if project.blank?
issue = create(:issue, project: new_project)
create(:todo, project: new_project, target: issue, author: user, user: user)
end
end end
describe 'POST /todos/:id/mark_as_done' do describe 'POST /todos/:id/mark_as_done' do
......
...@@ -706,6 +706,32 @@ RSpec.describe 'Git HTTP requests' do ...@@ -706,6 +706,32 @@ RSpec.describe 'Git HTTP requests' do
end end
end end
end end
context 'when token is impersonated' do
context 'when impersonation is off' do
before do
stub_config_setting(impersonation_enabled: false)
end
it 'responds to uploads with status 401 unauthorized' do
write_access_token = create(:personal_access_token, :impersonation, user: user, scopes: [:write_repository])
upload(path, user: user.username, password: write_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
context 'when impersonation is on' do
it 'responds to uploads with status 200' do
write_access_token = create(:personal_access_token, :impersonation, user: user, scopes: [:write_repository])
upload(path, user: user.username, password: write_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
end end
end end
......
...@@ -1209,6 +1209,73 @@ RSpec.describe Ci::CreatePipelineService do ...@@ -1209,6 +1209,73 @@ RSpec.describe Ci::CreatePipelineService do
end end
end end
context 'when pipeline is running for a nonexistant-branch' do
let(:gitlab_ci_yaml) { YAML.dump(test: { script: 'test' }) }
let(:ref_name) { 'refs/heads/nonexistant-branch' }
let(:pipeline) { execute_service.payload }
it 'does not create the pipeline' do
expect(pipeline).not_to be_created_successfully
expect(pipeline.errors[:base]).to eq(['Reference not found'])
end
context 'when there is a tag with that nonexistant-branch' do
# v1.0.0 is on the test repo as a tag
let(:ref_name) { 'refs/heads/v1.0.0' }
it 'does not create the pipeline' do
expect(pipeline).not_to be_created_successfully
expect(pipeline.errors[:base]).to eq(['Reference not found'])
end
end
end
context 'when pipeline is running for a branch with the name of both a branch and a tag' do
let(:gitlab_ci_yaml) { YAML.dump(test: { script: 'test' }) }
# v1.1.0 is on the test repo as branch and tag
let(:ref_name) { 'refs/heads/v1.1.0' }
let(:pipeline) { execute_service.payload }
it 'creates the pipeline for the branch' do
expect(pipeline).to be_created_successfully
expect(pipeline.branch?).to be true
expect(pipeline.tag?).to be false
end
end
context 'when pipeline is running for a tag with the name of both a branch and a tag' do
let(:gitlab_ci_yaml) { YAML.dump(test: { script: 'test' }) }
# v1.1.0 is on the test repo as branch and tag
let(:ref_name) { 'refs/tags/v1.1.0' }
let(:pipeline) { execute_service.payload }
it 'creates the pipeline for the tag' do
expect(pipeline).to be_created_successfully
expect(pipeline.branch?).to be false
expect(pipeline.tag?).to be true
end
end
context 'when pipeline is running for an ambiguous ref' do
let(:gitlab_ci_yaml) { YAML.dump(test: { script: 'test' }) }
# v1.1.0 is on the test repo as branch and tag
let(:ref_name) { 'v1.1.0' }
let(:pipeline) { execute_service.payload }
it 'does not create the pipeline' do
expect(pipeline).not_to be_created_successfully
expect(pipeline.errors[:base]).to eq(['Ref is ambiguous'])
end
end
context 'when pipeline variables are specified' do context 'when pipeline variables are specified' do
let(:variables_attributes) do let(:variables_attributes) do
[{ key: 'first', secret_value: 'world' }, [{ key: 'first', secret_value: 'world' },
......
...@@ -109,9 +109,13 @@ RSpec.describe Git::ProcessRefChangesService do ...@@ -109,9 +109,13 @@ RSpec.describe Git::ProcessRefChangesService do
.to receive(:commit) .to receive(:commit)
.and_return(project.commit) .and_return(project.commit)
allow_any_instance_of(Repository) if changes_method == :branch_changes
.to receive(:branch_exists?) allow_any_instance_of(Repository).to receive(:branch_exists?) { true }
.and_return(true) end
if changes_method == :tag_changes
allow_any_instance_of(Repository).to receive(:tag_exists?) { true }
end
end end
context 'when git_push_create_all_pipelines is disabled' do context 'when git_push_create_all_pipelines is disabled' do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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