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

Merge remote-tracking branch 'dev/master'

parents e13a197c 06132caf
......@@ -2,6 +2,30 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 14.1.2 (2021-08-03)
### Security (19 changes)
- [Add project member validation for domain limitation](gitlab-org/security/gitlab@d17016dde463811c81a22c07aeab817ff7b5757c) ([merge request](gitlab-org/security/gitlab!1564))
- [Hide project-level CI/CD Analytics for Guests](gitlab-org/security/gitlab@ce3b41daadd795e906b5bbbec424a494c491a1d4) ([merge request](gitlab-org/security/gitlab!1600))
- [Only allow invite to be accepted by user with matching email](gitlab-org/security/gitlab@9d9e439c6a923fa4791a056e599c7b7e76de59a1) ([merge request](gitlab-org/security/gitlab!1632))
- [Add html escaping for default branch name](gitlab-org/security/gitlab@549101007452bd43d866d314b1c787120cfcb36a) ([merge request](gitlab-org/security/gitlab!1630))
- [Configure OmniAuth to use GitLab AppLogger](gitlab-org/security/gitlab@0b234f0058bbaa0415ab43182761757c332764d1) ([merge request](gitlab-org/security/gitlab!1615))
- [Add permissions check to pipelines#show action](gitlab-org/security/gitlab@6901d52d5265d126419e78848344ae9a886ee1a7) ([merge request](gitlab-org/security/gitlab!1612))
- [Prevent impersonation in gitlab-shell SSH certs](gitlab-org/security/gitlab@82a878ba276c6500af5aa3d951819240535127de) ([merge request](gitlab-org/security/gitlab!1609))
- [Fix Protected Environment Accesses Cleanup](gitlab-org/security/gitlab@0c954547dbdee6a47fc755eebef0882852080579) ([merge request](gitlab-org/security/gitlab!1606)) **GitLab Enterprise Edition**
- [Use oauth_app id instead of uid](gitlab-org/security/gitlab@9c49cbbbc730eb16ef109c1f1fc1b167768d5dd3) ([merge request](gitlab-org/security/gitlab!1603)) **GitLab Enterprise Edition**
- [Block impersonation token use if it is not permitted](gitlab-org/security/gitlab@1a73b228549dfe1fe98f44a8cee8e3ebcc36d841) ([merge request](gitlab-org/security/gitlab!1583))
- [Fix XSS in Mermaid Markdown rendering](gitlab-org/security/gitlab@6bff57b10739c42d177371dbf44143d92de1e595) ([merge request](gitlab-org/security/gitlab!1488))
- [Do not show email address in error message](gitlab-org/security/gitlab@fdee78b193d9744253c7b7d671247cc50175c643) ([merge request](gitlab-org/security/gitlab!1596)) **GitLab Enterprise Edition**
- [Updates oauth to 0.5.6](gitlab-org/security/gitlab@bfa3de880659b0156cf8c2a7085b1705596380a4) ([merge request](gitlab-org/security/gitlab!1592))
- [Fix tag ref detection for pipelines](gitlab-org/security/gitlab@87a03ffd263ad153a911e14512cb7776b98a435d) ([merge request](gitlab-org/security/gitlab!1591))
- [Disallow non-members to set issue metadata on issue create](gitlab-org/security/gitlab@abe9d660ce3314c1540ec20b3a0640e623c56ecc) ([merge request](gitlab-org/security/gitlab!1586))
- [Prevent guests from linking issues with errors](gitlab-org/security/gitlab@4a74667407b725176c4722e86bba3f942ffc9487) ([merge request](gitlab-org/security/gitlab!1587))
- [Filter todos whose target users no longer have access to](gitlab-org/security/gitlab@a05dd90c43ae84bb37956217d9cf4effd1edae50) ([merge request](gitlab-org/security/gitlab!1556))
- [Remove impersonation token from api response for non-admin user](gitlab-org/security/gitlab@928eaf1b82d45fbfa0d82b6515d192453b944ab9) ([merge request](gitlab-org/security/gitlab!1565))
- [Restrict access to instance-level security features for reporters](gitlab-org/security/gitlab@d4097341cede050e0066fa2a5445cbf51a1cc1bd) ([merge request](gitlab-org/security/gitlab!1561)) **GitLab Enterprise Edition**
## 14.1.1 (2021-07-28)
### Added (1 change)
......@@ -585,6 +609,29 @@ entry.
- [Remove diffs gradual load feature flag](gitlab-org/gitlab@027d7c4327b5b6205a84281239027273517bf81b) ([merge request](gitlab-org/gitlab!55478))
- [Remove partial index for Hashed Storage migration](gitlab-org/gitlab@3ed017a1023d7b0941a7606b69e6caee8d22f15c) ([merge request](gitlab-org/gitlab!62920))
## 14.0.7 (2021-08-03)
### Security (18 changes)
- [Add project member validation for domain limitation](gitlab-org/security/gitlab@f9a0e78111cbbfe93b6f8ca27bd9f064e146d005) ([merge request](gitlab-org/security/gitlab!1563))
- [Hide project-level CI/CD Analytics for Guests](gitlab-org/security/gitlab@56a17ae80c1f179bcdf939d6b8e71737f9501949) ([merge request](gitlab-org/security/gitlab!1574))
- [Only allow invite to be accepted by user with matching email](gitlab-org/security/gitlab@a79d0e6dbbc32247c10c4928a04f0149071eb5fe) ([merge request](gitlab-org/security/gitlab!1633))
- [Add html escaping for default branch name](gitlab-org/security/gitlab@d26f0c4d5ef386100d40e92f815b7e754fccacc3) ([merge request](gitlab-org/security/gitlab!1631))
- [Configure OmniAuth to use GitLab AppLogger](gitlab-org/security/gitlab@dfcff90cb86fac0dff05d8bd5f25f46da2cc8ce0) ([merge request](gitlab-org/security/gitlab!1616))
- [Add permissions check to pipelines#show action](gitlab-org/security/gitlab@c611a8154dc5776a0767b4153ff8963d46e7f39a) ([merge request](gitlab-org/security/gitlab!1613))
- [Prevent impersonation in gitlab-shell SSH certs](gitlab-org/security/gitlab@320457b16cbfd5dec4e05937c4d61b96aba4c290) ([merge request](gitlab-org/security/gitlab!1610))
- [Fix Protected Environment Accesses Cleanup](gitlab-org/security/gitlab@99846cdeda6acf6223fb0ee5364e375765d3cbb1) ([merge request](gitlab-org/security/gitlab!1607)) **GitLab Enterprise Edition**
- [Do not show email address in error message](gitlab-org/security/gitlab@5c4adf419e38f0fd9d540d2f7cd9d14888bc6b96) ([merge request](gitlab-org/security/gitlab!1597)) **GitLab Enterprise Edition**
- [Disallow non-members to set issue metadata on issue create](gitlab-org/security/gitlab@0bb4499e5f4514beb647d0e6ac3f9b15720c42ce) ([merge request](gitlab-org/security/gitlab!1581))
- [Prevent guests from linking issues with errors](gitlab-org/security/gitlab@94462a56e9490ddd85ec7d1d869b6fda2042fb99) ([merge request](gitlab-org/security/gitlab!1588))
- [Block impersonation token use if it is not permitted](gitlab-org/security/gitlab@31b8bc506dd89a576a2cda094c711c22be764398) ([merge request](gitlab-org/security/gitlab!1584))
- [Updates oauth to 0.5.6](gitlab-org/security/gitlab@c839b6107c41bcd02e048d0ae0499c140bfbec1c) ([merge request](gitlab-org/security/gitlab!1568))
- [Remove impersonation token from api response for non-admin user](gitlab-org/security/gitlab@845dc284cc8ee8736e4f65740d61ffeb197f7b7c) ([merge request](gitlab-org/security/gitlab!1566))
- [Filter todos whose target users no longer have access to](gitlab-org/security/gitlab@a6c81e5cadb277f80d1b9565700f8b1f201cfb05) ([merge request](gitlab-org/security/gitlab!1554))
- [Fix tag ref detection for pipelines](gitlab-org/security/gitlab@cd5f61dc50c44d69896b38f3bd44129a8f1f01d8) ([merge request](gitlab-org/security/gitlab!1548))
- [Restrict access to instance-level security features for reporters](gitlab-org/security/gitlab@c8a75e8032c68065524a85f7030960b614a915bd) ([merge request](gitlab-org/security/gitlab!1539)) **GitLab Enterprise Edition**
- [Fix XSS in Mermaid Markdown rendering](gitlab-org/security/gitlab@86139e79c13cf87183cdec9f84ec114cdfc6d215) ([merge request](gitlab-org/security/gitlab!1489))
## 14.0.6 (2021-07-20)
### Fixed (4 changes)
......@@ -1295,6 +1342,26 @@ entry.
- [Add missing metrics information](gitlab-org/gitlab@89cd7fe3b95323e635b2d73e08549b2e6153dc4d) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61772/edit))
- [Track usage of the resolve UI](gitlab-org/gitlab@35c8e30fce288cecefcf2f7c0077d4608e696519) ([merge request](gitlab-org/gitlab!61654))
## 13.12.9 (2021-08-03)
### Security (15 changes)
- [Add project member validation for domain limitation](gitlab-org/security/gitlab@8aff1815f897c2c454c87b1ccdd98c7a2c9eedb3) ([merge request](gitlab-org/security/gitlab!1562))
- [Block impersonation token use if it is not permitted](gitlab-org/security/gitlab@99ab170ae5a2d991600dec9e7dfd8b5ca502c437) ([merge request](gitlab-org/security/gitlab!1585))
- [Hide project-level CI/CD Analytics for Guests](gitlab-org/security/gitlab@740395d9663be41d52d831b8f90e271c08137220) ([merge request](gitlab-org/security/gitlab!1575))
- [Only allow invite to be accepted by user with matching email](gitlab-org/security/gitlab@ae7ade09920486f6124496d800bf5f63f5a909eb) ([merge request](gitlab-org/security/gitlab!1634))
- [Configure OmniAuth to use GitLab AppLogger](gitlab-org/security/gitlab@ed5e7742173878e59d760744e3f4f6686268584b) ([merge request](gitlab-org/security/gitlab!1617))
- [Fix Protected Environment Accesses Cleanup](gitlab-org/security/gitlab@79eb0cb13a35864267c30663fd6033e8c6224cac) ([merge request](gitlab-org/security/gitlab!1608)) **GitLab Enterprise Edition**
- [Add permissions check to pipelines#show action](gitlab-org/security/gitlab@1a293b409226ce743527f1ac5ac5d216998339e1) ([merge request](gitlab-org/security/gitlab!1618))
- [Prevent impersonation in gitlab-shell SSH certs](gitlab-org/security/gitlab@42521d9e7e72047bac09bd42779203ae6e508227) ([merge request](gitlab-org/security/gitlab!1611))
- [Prevent guests from linking issues with errors](gitlab-org/security/gitlab@da799b0c7bcade058d4b57e065b1a1bebf903fa3) ([merge request](gitlab-org/security/gitlab!1599))
- [Do not show email address in error message](gitlab-org/security/gitlab@2c3318edaa39ed0837b8fb30acae9f2cdc3d158f) ([merge request](gitlab-org/security/gitlab!1598)) **GitLab Enterprise Edition**
- [Updates oauth to 0.5.6](gitlab-org/security/gitlab@33df3791b646026016303a9d64661fbee7563630) ([merge request](gitlab-org/security/gitlab!1569))
- [Remove impersonation token from api response for non-admin user](gitlab-org/security/gitlab@b56ae1953b2cd6b9d12c584e0f2c298a931f6f08) ([merge request](gitlab-org/security/gitlab!1567))
- [Filter todos whose target users no longer have access to](gitlab-org/security/gitlab@ba613574b12e40fb61e5fbae8b1159f9ad037e84) ([merge request](gitlab-org/security/gitlab!1555))
- [Fix tag ref detection for pipelines](gitlab-org/security/gitlab@4c36e98bcecd6e42e23ec5e20443f41de7f5bf18) ([merge request](gitlab-org/security/gitlab!1549))
- [Fix XSS in Mermaid Markdown rendering](gitlab-org/security/gitlab@b27425816723b53db2f65b39f4702711b858cdfc) ([merge request](gitlab-org/security/gitlab!1487))
## 13.12.8 (2021-07-07)
### Security (1 change)
......
......@@ -794,7 +794,7 @@ GEM
nenv (~> 0.1)
shellany (~> 0.0)
numerizer (0.2.0)
oauth (0.5.4)
oauth (0.5.6)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
......
......@@ -66,6 +66,7 @@ export function initMermaid(mermaid) {
useMaxWidth: true,
htmlLabels: true,
},
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
securityLevel: 'strict',
});
......
......@@ -2,6 +2,7 @@
class Admin::ImpersonationTokensController < Admin::ApplicationController
before_action :user
before_action :verify_impersonation_enabled!
feature_category :authentication_and_authorization
......@@ -41,6 +42,10 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
def verify_impersonation_enabled!
access_denied! unless helpers.impersonation_enabled?
end
def finder(options = {})
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
end
......
......@@ -16,6 +16,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
@todos = @todos.with_entity_associations
return if redirect_out_of_range(@todos, todos_page_count(@todos))
@allowed_todos = ::Todos::AllowedTargetFilterService.new(@todos, current_user).execute
end
def destroy
......
......@@ -20,7 +20,7 @@ class InvitesController < ApplicationController
end
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)
else
redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") })
......@@ -52,7 +52,7 @@ class InvitesController < ApplicationController
end
def current_user_matches_invite?
@member.invite_email == current_user.email
current_user.verified_emails.include?(@member.invite_email)
end
def member?
......
......@@ -8,8 +8,8 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :pipeline, except: [:index, :new, :create, :charts, :config_variables]
before_action :set_pipeline_path, only: [:show]
before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index]
before_action :authorize_read_analytics!, only: [:charts]
before_action :authorize_read_build!, only: [:index, :show]
before_action :authorize_read_ci_cd_analytics!, only: [:charts]
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do
......
......@@ -2,8 +2,12 @@
module Resolvers
class ProjectPipelineStatisticsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Ci::AnalyticsType, null: true
authorizes_object!
authorize :read_ci_cd_analytics
def resolve
weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object)
monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object)
......
......@@ -34,7 +34,7 @@ module Resolvers
return Todo.none unless current_user.present? && target.present?
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
private
......
......@@ -115,11 +115,9 @@ module Types
null: true,
description: 'Runbook for the alert as defined in alert details.'
field :todos,
Types::TodoType.connection_type,
null: true,
description: 'To-do items of the current user for the alert.',
resolver: Resolvers::TodoResolver
field :todos, description: 'To-do items of the current user for the alert.', resolver: Resolvers::TodoResolver do
extension(::Gitlab::Graphql::TodosProjectPermissionPreloader::FieldExtension)
end
field :details_url,
GraphQL::Types::String,
......
......@@ -55,9 +55,6 @@ module Types
type: GraphQL::Types::String,
null: false,
description: 'Web path of the user.'
field :todos,
resolver: Resolvers::TodoResolver,
description: 'To-do items of the user.'
field :group_memberships,
type: Types::GroupMemberType.connection_type,
null: true,
......@@ -81,6 +78,10 @@ module Types
description: 'Projects starred by the user.',
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:
field :authored_merge_requests,
resolver: Resolvers::AuthoredMergeRequestsResolver,
......
......@@ -266,6 +266,10 @@ module AlertManagement
end
end
def to_ability_name
'alert_management_alert'
end
private
def hook_data
......
......@@ -103,4 +103,12 @@ class ApplicationRecord < ActiveRecord::Base
def self.cached_column_list
self.column_names.map { |column_name| self.arel_table[column_name] }
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
......@@ -550,6 +550,10 @@ class Commit
expire_note_etag_cache_for_related_mrs
end
def readable_by?(user)
Ability.allowed?(user, :read_commit, self)
end
private
def expire_note_etag_cache_for_related_mrs
......
......@@ -182,10 +182,6 @@ module DesignManagement
File.join(DesignManagement.designs_directory, "issue-#{issue.iid}", design.filename)
end
def to_ability_name
'design'
end
def description
''
end
......
......@@ -714,10 +714,6 @@ class Group < Namespace
Gitlab::ServiceDesk.supported? && all_projects.service_desk_enabled.exists?
end
def to_ability_name
model_name.singular
end
def activity_path
Gitlab::Routing.url_helpers.activity_group_path(self)
end
......
......@@ -544,6 +544,25 @@ class Issue < ApplicationRecord
self.update_column(:upvotes_count, self.upvotes)
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
def spammable_attribute_changed?
......@@ -569,25 +588,6 @@ class Issue < ApplicationRecord
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_created_action(author: author)
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.
def publicly_visible?
project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled?
......
......@@ -384,12 +384,6 @@ class Note < ApplicationRecord
super
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?
can_be_award_emoji? && contains_emoji_only?
end
......@@ -406,10 +400,6 @@ class Note < ApplicationRecord
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end
def to_ability_name
model_name.singular
end
def noteable_ability_name
if for_snippet?
'snippet'
......
......@@ -1495,10 +1495,6 @@ class Project < ApplicationRecord
end
end
def to_ability_name
model_name.singular
end
# rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
......
......@@ -44,7 +44,18 @@ class IssuePolicy < IssuablePolicy
enable :update_subscription
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
end
......
# frozen_string_literal: true
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
enable :read_token
......
......@@ -284,6 +284,7 @@ class ProjectPolicy < BasePolicy
enable :read_confidential_issues
enable :read_package
enable :read_product_analytics
enable :read_ci_cd_analytics
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
......@@ -484,6 +485,7 @@ class ProjectPolicy < BasePolicy
prevent(:read_insights)
prevent(:read_cycle_analytics)
prevent(:read_repository_graphs)
prevent(:read_ci_cd_analytics)
end
rule { wiki_disabled }.policy do
......@@ -559,6 +561,7 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics
enable :read_pages_content
enable :read_analytics
enable :read_ci_cd_analytics
enable :read_insights
# NOTE: may be overridden by IssuePolicy
......@@ -666,6 +669,7 @@ class ProjectPolicy < BasePolicy
rule { support_bot & ~service_desk_enabled }.policy do
prevent :create_note
prevent :read_project
prevent :guest_access
end
rule { project_bot }.enable :project_bot_access
......
......@@ -5,7 +5,10 @@ class TodoPolicy < BasePolicy
condition(:own_todo) do
@user && @subject.user_id == @user.id
end
condition(:can_read_target) do
@user && @subject.target&.readable_by?(@user)
end
rule { own_todo }.enable :read_todo
rule { own_todo }.enable :update_todo
rule { own_todo & can_read_target }.enable :read_todo
rule { own_todo & can_read_target }.enable :update_todo
end
......@@ -48,6 +48,9 @@ module Issues
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)
# 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]
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 @@
= link_to _("SSH keys"), keys_admin_user_path(@user)
= nav_link(controller: :identities) do
= link_to _("Identities"), admin_user_identities_path(@user)
= nav_link(controller: :impersonation_tokens) do
= link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user)
- if impersonation_enabled?
= nav_link(controller: :impersonation_tokens) do
= link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user)
.gl-mb-3
......@@ -25,7 +25,7 @@
= number_with_delimiter(todos_done_count)
.nav-controls
- if @todos.any?(&:pending?)
- if @allowed_todos.any?(&:pending?)
.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
Mark all as done
......@@ -82,11 +82,11 @@
= sort_title_oldest_created
.row.js-todos-all
- if @todos.any?
- if @allowed_todos.any?
.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
= render @todos
= render @allowed_todos
= paginate @todos, theme: "gitlab"
.js-nothing-here-container.empty-state.hidden
.svg-content
......
- page_title _("Invitation")
%h3.page-title= _("Invitation")
%p
= _("You have been invited")
- inviter = @member.created_by
- if inviter
= _("by")
= link_to inviter.name, user_url(inviter)
= _("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 current_user_matches_invite?
- if member?
%p
= _("You are already a member of this %{member_source}.") % { member_source: @invite_details[:title] }
.actions
= link_to _("Go to %{source_name}") % { source_name: @invite_details[:title] }, @invite_details[:url], class: "btn gl-button btn-confirm"
- if member?
%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] }
- else
%p
- 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
- mail_to_invite_email = mail_to(@member.invite_email)
- mail_to_current_user = mail_to(current_user.email)
- 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 }
- if !member?
.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"
= _("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
= _("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.")
......@@ -44,26 +44,26 @@
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path}
git switch -c #{default_branch_name}
git switch -c #{h default_branch_name}
touch README.md
git add README.md
git commit -m "add README"
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin #{ default_branch_name }
git push -u origin #{h default_branch_name }
%fieldset
%h5= _('Push an existing folder')
%pre.bg-light
:preserve
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 add .
git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin #{ default_branch_name }
git push -u origin #{h default_branch_name }
%fieldset
%h5= _('Push an existing Git repository')
......
......@@ -19,6 +19,4 @@ OmniAuth.config.before_request_phase do |env|
Gitlab::RequestForgeryProtection.call(env)
end
# Use json formatter
OmniAuth.config.logger.formatter = Gitlab::OmniauthLogging::JSONFormatter.new
OmniAuth.config.logger.level = Logger::ERROR if Rails.env.production?
OmniAuth.config.logger = Gitlab::AppLogger
......@@ -33,54 +33,6 @@ module EE
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?
user&.user_detail&.provisioned_by_group_id == source_id
end
......@@ -95,25 +47,6 @@ module EE
errors.add(:access_level, "is not included in the list")
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
def post_create_hook
super
......
......@@ -41,5 +41,80 @@ module EE
def source_kind
source.is_a?(Group) && source.parent.present? ? 'Sub group' : source.class.to_s
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
......@@ -9,8 +9,10 @@ module EE
validate :sso_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_protected_environment_acceses
end
def group
......@@ -28,6 +30,12 @@ module EE
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
unless ::Gitlab::Auth::GroupSaml::GmaMembershipEnforcer.new(project).can_add_user?(user)
errors.add(:user, _('is not in the group enforcing Group Managed Account'))
......@@ -37,5 +45,11 @@ module EE
def provisioned_by_this_group?
false
end
def group_saml_identity(root_ancestor: false)
return unless group
super
end
end
end
......@@ -71,6 +71,6 @@ class InstanceSecurityDashboard
end
def authorized_access_levels
Gitlab::Access.vulnerability_access_levels.values
Gitlab::Access.vulnerability_access_levels
end
end
......@@ -22,12 +22,19 @@ class ProtectedEnvironment < ApplicationRecord
' AND protected_environments.project_id = environments.project_id')
end
scope :deploy_access_levels_by_group, -> (group) do
ProtectedEnvironment::DeployAccessLevel
.joins(:protected_environment).where(group: group)
end
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)
raise ArgumentError unless environment.is_a?(::Environment)
......
......@@ -24,6 +24,11 @@ module ProtectedEnvironments
keys = attribute.slice(:access_level, :group_id, :user_id).keys
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?
qualified_group_ids.include?(attribute[:group_id])
elsif attribute[:user_id].present?
......
......@@ -6,7 +6,7 @@ module Subscriptions
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:)
@current_user = current_user
......@@ -97,9 +97,9 @@ module Subscriptions
Gitlab::SubscriptionPortal::Client
end
def customers_oauth_app_id
Rails.cache.fetch(CUSTOMERS_OAUTH_APP_ID_CACHE_KEY, expires_in: 1.hour) do
response = client.customers_oauth_app_id
def customers_oauth_app_uid
Rails.cache.fetch(CUSTOMERS_OAUTH_APP_UID_CACHE_KEY, expires_in: 1.hour) do
response = client.customers_oauth_app_uid
response.dig(:data, 'oauth_app_id')
end
......@@ -107,15 +107,15 @@ module Subscriptions
def oauth_token
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)
next existing_token if existing_token
Doorkeeper::AccessToken.new(
application_id: customers_oauth_app_id,
application_id: application.id,
resource_owner_id: current_user.id,
token: Doorkeeper::OAuth::Helpers::UniqueToken.generate,
scopes: application.scopes.to_s
......
......@@ -16,7 +16,7 @@ module EE
extend ::Gitlab::Utils::Override
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
def options_with_minimal_access
......
......@@ -33,7 +33,7 @@ module Gitlab
http_get("api/payment_methods/#{id}", admin_headers)
end
def customers_oauth_app_id
def customers_oauth_app_uid
http_get("api/v1/oauth_app_id", admin_headers)
end
......
......@@ -125,9 +125,9 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::Rest do
it_behaves_like 'when http call raises an exception'
end
describe '#customers_oauth_app_id' do
describe '#customers_oauth_app_uid' do
subject do
client.customers_oauth_app_id
client.customers_oauth_app_uid
end
let(:http_method) { :get }
......
......@@ -7,132 +7,12 @@ RSpec.describe GroupMember do
it_behaves_like 'member validations'
describe 'validations' do
describe '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 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
describe '#group_domain_validations' do
let(:member_type) { :group_member }
let(:source) { group }
let(:nested_source) { create(:group, parent: group) }
context 'when group is subgroup' do
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
it_behaves_like 'member group domain validations'
end
describe 'access level inclusion' do
......@@ -225,42 +105,6 @@ RSpec.describe GroupMember do
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
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) }
......
......@@ -19,7 +19,7 @@ RSpec.describe InstanceSecurityDashboard do
user.security_dashboard_projects << [project1, project2, project3]
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
context 'when given project IDs' do
......@@ -79,50 +79,64 @@ RSpec.describe InstanceSecurityDashboard do
end
describe '#projects' do
context 'when the user cannot read all resources' do
context 'when the `security_and_compliance` is enabled for the project' do
subject { instance_dashboard.projects }
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
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED)
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::DISABLED)
end
it 'returns only projects on their dashboard that they can read' do
expect(subject.projects).to contain_exactly(project1)
end
it { is_expected.to be_empty }
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
project1.project_feature.update_column(:security_and_compliance_access_level, Featurable::DISABLED)
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED)
end
it 'returns only projects on their dashboard that they can read' do
expect(subject.projects).to be_empty
end
it { is_expected.to match_array(expected_projects) }
end
end
context 'when the user can read all resources' do
let(:project_ids) { [project1.id, project2.id] }
context 'when the user is auditor' do
let(:user) { create(:auditor) }
context 'when the `security_and_compliance` is enabled for the project' do
before do
ProjectFeature.update_all(security_and_compliance_access_level: Featurable::ENABLED)
end
it_behaves_like 'project permissions' do
let(:expected_projects) { [project1, project2, project3] }
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
expect(subject.projects).to contain_exactly(project1, project2, project3)
it_behaves_like 'project permissions' do
let(:expected_projects) { project1 }
end
end
context 'when the `security_and_compliance` is disabled for the project' do
before do
project1.project_feature.update_column(:security_and_compliance_access_level, Featurable::DISABLED)
end
context 'when the user is not project owner' do
shared_examples_for 'user with project role' do |as:, permitted:|
let(:expected_projects) { permitted ? project1 : [] }
it "returns only the feature enabled projects on the user's dashboard" do
expect(subject.projects).to contain_exactly(project2, project3)
before do
project1.add_role(user, as)
end
it_behaves_like 'project permissions'
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
......
......@@ -67,4 +67,98 @@ RSpec.describe Member, type: :model do
it { is_expected.to eq('Project') }
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
......@@ -89,6 +89,22 @@ RSpec.describe ProjectMember do
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
let_it_be(:member) { build(:project_member) }
......@@ -96,4 +112,67 @@ RSpec.describe ProjectMember do
it { is_expected.to eq(false) }
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
......@@ -59,7 +59,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to user' do
before do
create_deploy_access_level(user: user)
create_deploy_access_level(protected_environment, user: user)
end
it { is_expected.to be_truthy }
......@@ -69,7 +69,7 @@ RSpec.describe ProtectedEnvironment do
let(:group) { create(:group) }
before do
create_deploy_access_level(group: group)
create_deploy_access_level(protected_environment, group: group)
end
it 'allows members of the group' do
......@@ -85,7 +85,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to maintainers' do
before do
create_deploy_access_level(access_level: Gitlab::Access::MAINTAINER)
create_deploy_access_level(protected_environment, access_level: Gitlab::Access::MAINTAINER)
end
it 'allows maintainers' do
......@@ -103,7 +103,7 @@ RSpec.describe ProtectedEnvironment do
context 'when access has been granted to developers' do
before do
create_deploy_access_level(access_level: Gitlab::Access::DEVELOPER)
create_deploy_access_level(protected_environment, access_level: Gitlab::Access::DEVELOPER)
end
it 'allows maintainers' do
......@@ -198,18 +198,63 @@ RSpec.describe ProtectedEnvironment do
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
let(:group) { create(:group) }
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_group) { create_deploy_access_level(protected_environment, group: group) }
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_user = create_deploy_access_level(user: create(:user))
deploy_access_level = create_deploy_access_level(group: 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(protected_environment, user: create(:user))
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
......@@ -280,7 +325,7 @@ RSpec.describe ProtectedEnvironment do
end
end
def create_deploy_access_level(**opts)
def create_deploy_access_level(protected_environment, **opts)
protected_environment.deploy_access_levels.create(**opts)
end
end
......@@ -6,7 +6,9 @@ RSpec.describe 'getting project information' do
include GraphqlHelpers
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
<<~QUERY
......@@ -22,7 +24,13 @@ RSpec.describe 'getting project information' do
subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before_all do
group.add_developer(current_user)
end
before do
stub_licensed_features(epics: true)
post_graphql(query, current_user: current_user)
end
......
......@@ -31,6 +31,8 @@ RSpec.describe API::Todos do
let!(:epic_todo) { create_todo_for_new_epic }
before do
stub_licensed_features(epics: true)
get api('/todos', personal_access_token: pat)
end
......@@ -45,7 +47,8 @@ RSpec.describe API::Todos do
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
it 'includes the Epic Todo in the response' do
......
......@@ -53,7 +53,7 @@ RSpec.describe Groups::GroupMembersController do
it 'returns error message' do
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
......
......@@ -38,6 +38,28 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do
]
)
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
context 'with user-based access control' do
......@@ -71,6 +93,23 @@ RSpec.describe ProtectedEnvironments::BaseService, '#execute' do
]
)
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
......
......@@ -36,7 +36,7 @@ RSpec.describe Subscriptions::CreateService do
describe '#execute' 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')
end
......@@ -74,6 +74,14 @@ RSpec.describe Subscriptions::CreateService do
expect { execute }.to change { Doorkeeper::AccessToken.count }.by(1)
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
before do
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
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
helpers do
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
def user(user_id)
......
......@@ -92,6 +92,7 @@ module API
end
get do
todos = paginate(find_todos.with_entity_associations)
todos = ::Todos::AllowedTargetFilterService.new(todos, current_user).execute
options = { with: Entities::Todo, current_user: current_user }
batch_load_issuable_metadata(todos, options)
......
......@@ -193,7 +193,10 @@ module Gitlab
def personal_access_token_check(password, project)
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
......
......@@ -26,13 +26,13 @@ module Gitlab
def branch_exists?
strong_memoize(:is_branch) do
project.repository.branch_exists?(ref)
branch_ref? && project.repository.branch_exists?(ref)
end
end
def tag_exists?
strong_memoize(:is_tag) do
project.repository.tag_exists?(ref)
tag_ref? && project.repository.tag_exists?(ref)
end
end
......@@ -106,6 +106,32 @@ module Gitlab
metrics.pipeline_failure_reason_counter
.increment(reason: (reason || :unknown_failure).to_s)
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
......
# 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
def ci_cd_analytics_menu_item
if !context.project.feature_available?(:builds, context.current_user) ||
!can?(context.current_user, :read_build, context.project) ||
!can?(context.current_user, :read_ci_cd_analytics, context.project) ||
context.project.empty_repo?
return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics)
end
......
......@@ -15320,6 +15320,9 @@ msgstr ""
msgid "Go full screen"
msgstr ""
msgid "Go to %{source_name}"
msgstr ""
msgid "Go to commits"
msgstr ""
......@@ -16452,9 +16455,6 @@ msgstr ""
msgid "How many users will be evaluating the trial?"
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}"
msgstr ""
......@@ -22622,9 +22622,6 @@ msgstr ""
msgid "Note that pushing to GitLab requires write access to this repository."
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."
msgstr ""
......@@ -30550,6 +30547,9 @@ msgstr ""
msgid "Sign in / Register"
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"
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."
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."
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."
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."
msgstr ""
......@@ -37906,7 +37912,7 @@ msgstr ""
msgid "You have been granted %{member_human_access} access to project %{name}."
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 ""
msgid "You have been redirected to the only result; see the %{a_start}search results%{a_end} instead."
......@@ -38502,9 +38508,6 @@ msgstr ""
msgid "archived:"
msgstr ""
msgid "as %{role}."
msgstr ""
msgid "assign yourself"
msgstr ""
......@@ -38976,14 +38979,14 @@ msgstr ""
msgid "element is not a hierarchy"
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."
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"
msgstr ""
......@@ -39983,9 +39986,6 @@ msgstr ""
msgid "time summary"
msgstr ""
msgid "to join %{source_name}"
msgstr ""
msgid "toggle collapse"
msgstr ""
......
......@@ -25,9 +25,64 @@ RSpec.describe InvitesController do
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
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
let(:extra_params) { { invite_type: 'initial_email' } }
......@@ -69,34 +124,6 @@ RSpec.describe InvitesController do
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 invite token belongs to a valid member' do
context 'when instance allows sign up' do
......@@ -223,6 +250,7 @@ RSpec.describe InvitesController do
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'
end
......
......@@ -302,35 +302,46 @@ RSpec.describe Projects::PipelinesController do
end
describe 'GET #show' do
render_views
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
subject { get_pipeline_html }
def get_pipeline_html
get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :html
end
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
context 'when the project is public' do
render_views
before do
create_build_with_artifacts('build', 0, 'job1')
create_build_with_artifacts('build', 0, 'job2')
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
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
it 'avoids N+1 database queries', :request_store do
get_pipeline_html
context 'when the project is private' do
let(:project) { create(:project, :private, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_html }.count
expect(response).to have_gitlab_http_status(:ok)
it 'returns `not_found` when the user does not have access' do
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(:ok)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
......
......@@ -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.")
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
......@@ -3,16 +3,20 @@
require 'spec_helper'
RSpec.describe 'Dashboard > Todo target states' do
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :public) }
before_all do
project.add_developer(user)
end
before do
sign_in(user)
end
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
visit dashboard_todos_path
......@@ -22,7 +26,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end
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
visit dashboard_todos_path
......@@ -32,7 +36,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end
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
visit dashboard_todos_path
......@@ -42,7 +46,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end
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
visit dashboard_todos_path
......@@ -52,7 +56,7 @@ RSpec.describe 'Dashboard > Todo target states' do
end
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
visit dashboard_todos_path
......
......@@ -128,7 +128,7 @@ RSpec.describe 'Dashboard > User filters todos', :js do
describe 'filter by action' 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, :review_requested, user: user_1, author: user_2, project: project_1, target: issue1)
end
......
......@@ -3,10 +3,16 @@
require 'spec_helper'
RSpec.describe 'Dashboard Todos' do
include DesignManagementTestHelpers
let_it_be(:user) { create(:user, username: 'john') }
let_it_be(:author) { create(:user) }
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
before do
......@@ -21,8 +27,8 @@ RSpec.describe 'Dashboard Todos' do
context 'when the todo references a merge request' do
let(:referenced_mr) { create(:merge_request, source_project: project) }
let(:note) { create(:note, project: project, note: "Check out #{referenced_mr.to_reference}") }
let!(:todo) { create(:todo, :mentioned, user: user, project: project, author: author, note: note) }
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, target: note.noteable) }
before do
sign_in(user)
......@@ -39,9 +45,26 @@ RSpec.describe 'Dashboard Todos' do
end
end
context 'User has a todo', :js do
context 'user has an unauthorized todo' 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)
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)
visit dashboard_todos_path
......@@ -183,7 +206,7 @@ RSpec.describe 'Dashboard Todos' do
end
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
create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
......@@ -199,7 +222,7 @@ RSpec.describe 'Dashboard Todos' do
end
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
create(:todo, :review_requested, user: user, project: project, target: merge_request, author: user)
......@@ -355,7 +378,7 @@ RSpec.describe 'Dashboard Todos' do
end
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
sign_in(user)
......@@ -386,6 +409,7 @@ RSpec.describe 'Dashboard Todos' do
end
before do
enable_design_management
project.add_developer(user)
sign_in(user)
......
......@@ -90,48 +90,17 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
end
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
before do
sign_in(owner)
group.add_developer(user)
sign_in(user)
visit invite_path(group_invite.raw_invite_token)
end
it 'shows message user already a member' do
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_content('However, you are already a member of this group.')
expect(page).to have_link(user.name, href: user_path(user))
expect(page).to have_content('You are already a member of this group.')
end
end
end
......
......@@ -260,8 +260,6 @@ RSpec.describe 'Mermaid rendering', :js do
description *= 51
project = create(:project, :public)
wiki_page = build(:wiki_page, { container: project, content: description })
wiki_page.create message: 'mermaid test commit' # rubocop:disable Rails/SaveBang
wiki_page = project.wiki.find_page(wiki_page.slug)
......@@ -277,6 +275,27 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
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
def wait_for_mermaid
......
......@@ -365,9 +365,8 @@ RSpec.describe 'Pipeline', :js do
let(:project) { create(:project, :public, :repository, public_builds: false) }
let(:role) { :guest }
it 'does not show failed jobs tab pane' do
expect(page).to have_link('Pipeline')
expect(page).not_to have_content('Failed Jobs')
it 'does not show the pipeline details page' do
expect(page).to have_content('Not Found')
end
end
end
......
......@@ -44,8 +44,7 @@ describe('Vuex members mutations', () => {
describe('when error has a message', () => {
it('shows error message', () => {
const error = new Error('Request failed with status code 422');
const message =
'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
const message = 'User email does not match the allowed domain of example.com';
error.response = {
data: { message },
......@@ -88,8 +87,7 @@ describe('Vuex members mutations', () => {
describe('when error has a message', () => {
it('shows error message', () => {
const error = new Error('Request failed with status code 422');
const message =
'User email "john.smith@gmail.com" does not match the allowed domain of example.com';
const message = 'User email does not match the allowed domain of example.com';
error.response = {
data: { message },
......
......@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::MarkDone do
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(:author) { 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(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
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, target: issue) }
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) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do
......
......@@ -5,17 +5,23 @@ require 'spec_helper'
RSpec.describe Mutations::Todos::Restore do
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(:author) { 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(:todo2) { create(:todo, user: current_user, author: author, state: :pending) }
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, target: issue) }
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) }
before_all do
project.add_developer(current_user)
end
specify { expect(described_class).to require_graphql_authorizations(:update_todo) }
describe '#resolve' do
......
......@@ -5,14 +5,24 @@ require 'spec_helper'
RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
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
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end
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
describe '#resolve' do
......@@ -32,5 +42,15 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
:pipeline_times_values
)
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
......@@ -4,19 +4,28 @@ require 'spec_helper'
RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
specify do
expect(described_class).to have_nullable_graphql_type(Types::TodoType.connection_type)
end
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:author1) { 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) }
let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, 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_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1, target: issue) }
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
expect_next_instance_of(TodosFinder) do |finder|
......@@ -40,7 +49,9 @@ RSpec.describe Resolvers::TodoResolver do
end
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'])
......@@ -59,11 +70,15 @@ RSpec.describe Resolvers::TodoResolver do
group3 = create(:group)
group1.add_developer(current_user)
issue1 = create(:issue, project: create(:project, group: group1))
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)
todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, group: group3, 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, target: issue2)
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])
......@@ -93,9 +108,13 @@ RSpec.describe Resolvers::TodoResolver do
project2 = create(:project)
project3 = create(:project)
todo4 = create(:todo, project: project1, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
project1.add_developer(current_user)
project2.add_developer(current_user)
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])
......
......@@ -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)
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
personal_access_token = create(:personal_access_token, scopes: %w[read_user sudo])
......
......@@ -136,7 +136,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push,
origin_ref: 'mytag',
origin_ref: origin_ref,
checkout_sha: project.commit.id,
after_sha: nil,
before_sha: nil,
......@@ -147,6 +147,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
current_user: user)
end
let(:origin_ref) { 'mytag' }
before do
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
it 'correctly indicated that this is a tagged pipeline' do
expect(pipeline).to be_tag
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
context 'when pipeline is running for a merge request' do
......
......@@ -27,6 +27,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to eq(true) }
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
let(:origin_ref) { 'something' }
......@@ -43,6 +55,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to eq(true) }
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
let(:origin_ref) { 'something' }
......
......@@ -138,6 +138,25 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect(issue.assignees).to be_empty
expect(issue.milestone).to be_nil
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
......
# 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'
RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
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(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) }
let(:owner) { project.owner }
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) }
describe '#render?' do
context 'whe user cannot read analytics' do
let(:user) { nil }
let(:current_user) { nil }
it 'returns false' do
expect(subject.render?).to be false
......@@ -79,7 +83,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end
describe 'when the user does not have access' do
let(:user) { nil }
let(:current_user) { guest }
specify { is_expected.to be_nil }
end
......@@ -99,7 +103,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
end
describe 'when the user does not have access' do
let(:user) { nil }
let(:current_user) { nil }
specify { is_expected.to be_nil }
end
......@@ -111,7 +115,7 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
let(:current_user) { nil }
specify { is_expected.to be_nil }
end
......
......@@ -552,4 +552,10 @@ RSpec.describe DiffNote do
expect(subject.on_image?).to be_truthy
end
end
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 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
let(:reporter) { create(:user) }
let(:group) { create(:group, :public) }
let(:reporter_from_group_link) { create(:user) }
let(:non_member) { create(:user) }
let(:support_bot) { User.support_bot }
def permissions(user, issue)
described_class.new(user, issue)
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
let(:non_member) { create(:user) }
let(:project) { create(:project, :private) }
let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) }
let(:issue_no_assignee) { create(:issue, project: project) }
......@@ -34,12 +58,6 @@ RSpec.describe IssuePolicy do
create(:project_group_link, group: group, project: project)
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
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)
......@@ -82,6 +100,15 @@ RSpec.describe IssuePolicy do
expect(permissions(assignee, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
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
let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) }
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
......@@ -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_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
it 'allows issue assignees to read, reopen and update their issues' 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_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
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
before do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end
let(:issue) { create(:issue, project: project, author: author) }
let(:visitor) { create(:user) }
let(:admin) { create(:user, :admin) }
......@@ -258,6 +316,15 @@ RSpec.describe IssuePolicy do
expect(permissions(admin, issue)).to be_disallowed(:create_note)
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
context 'with confidential issues' do
......
......@@ -41,6 +41,13 @@ RSpec.describe PersonalAccessTokenPolicy do
it { is_expected.to be_allowed(:read_token) }
it { is_expected.to be_allowed(:revoke_token) }
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
context 'current_user is a blocked administrator', :enable_admin_mode do
......
......@@ -480,8 +480,8 @@ RSpec.describe ProjectPolicy do
let(:current_user) { User.support_bot }
context 'with service desk disabled' do
it { expect_allowed(:guest_access) }
it { expect_disallowed(:create_note, :read_project) }
it { expect_allowed(:public_access) }
it { expect_disallowed(:guest_access, :create_note, :read_project) }
end
context 'with service desk enabled' do
......@@ -1131,12 +1131,20 @@ RSpec.describe ProjectPolicy do
let_it_be(:project_with_analytics_enabled) { create(:project, :analytics_enabled) }
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_private.add_developer(developer)
project_with_analytics_enabled.add_developer(developer)
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 }
context 'for guest user' 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_insights) }
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
context 'for developer' 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_insights) }
it { is_expected.to be_disallowed(:read_repository_graphs) }
it { is_expected.to be_disallowed(:read_ci_cd_analytics) }
end
end
......@@ -1162,9 +1181,19 @@ RSpec.describe ProjectPolicy do
context 'for guest user' do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
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
context 'for developer' 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_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end
end
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
let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_cycle_analytics) }
it { is_expected.to be_disallowed(:read_insights) }
it { is_expected.to be_allowed(:read_cycle_analytics) }
it { is_expected.to be_allowed(:read_insights) }
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
context 'for developer' 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_insights) }
it { is_expected.to be_allowed(:read_repository_graphs) }
it { is_expected.to be_allowed(:read_ci_cd_analytics) }
end
end
end
......
......@@ -9,22 +9,28 @@ RSpec.describe TodoPolicy do
let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) }
let_it_be(:todo1) { create(:todo, author: author, user: user1) }
let_it_be(:todo2) { create(:todo, author: author, user: user2) }
let_it_be(:project) { create(:project) }
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(:todo4) { create(:todo, author: author, user: user3) }
let_it_be(:todo4) { create(:todo, author: author, user: user3, issue: issue) }
def permissions(user, todo)
described_class.new(user, todo)
end
before_all do
project.add_developer(user1)
project.add_developer(user2)
end
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],
[user2, todo2],
[user2, todo3],
[user3, todo4]
[user2, todo2]
].each do |user, todo|
expect(permissions(user, todo)).to be_allowed(:read_todo)
end
......@@ -38,7 +44,9 @@ RSpec.describe TodoPolicy do
[user2, todo4],
[user3, todo1],
[user3, todo2],
[user3, todo3]
[user3, todo3],
[user2, todo3],
[user3, todo4]
].each do |user, todo|
expect(permissions(user, todo)).to be_disallowed(:read_todo)
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'
RSpec.describe 'Query current user todos' do
include GraphqlHelpers
include DesignManagementTestHelpers
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(:issue_todo) { create(:todo, user: current_user, target: create(:issue)) }
let_it_be(:merge_request_todo) { create(:todo, user: current_user, target: create(:merge_request)) }
let_it_be(:design_todo) { create(:todo, user: current_user, target: create(:design)) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:unauthorize_project) { create(:project) }
let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: project) }
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
<<~QUERY
......@@ -23,16 +28,22 @@ RSpec.describe 'Query current user todos' do
graphql_query_for('currentUser', {}, query_graphql_field('todos', {}, fields))
end
before_all do
project.add_developer(current_user)
end
subject { graphql_data.dig('currentUser', 'todos', 'nodes') }
before do
enable_design_management
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
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' => issue_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
end
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' => 'ISSUE'),
a_hash_including('targetType' => 'MERGEREQUEST'),
a_hash_including('targetType' => 'DESIGN')
)
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
......@@ -5,14 +5,16 @@ require 'spec_helper'
RSpec.describe 'Marking all todos done' do
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(:author) { create(:user) }
let_it_be(:other_user) { 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(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo3) { 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, target: issue) }
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) }
......@@ -28,6 +30,10 @@ RSpec.describe 'Marking all todos done' do
)
end
before_all do
project.add_developer(current_user)
end
def mutation_response
graphql_mutation_response(:todos_mark_all_done)
end
......
......@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Marking todos done' do
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(:author) { 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(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
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, target: issue) }
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
)
end
before_all do
project.add_developer(current_user)
end
def mutation_response
graphql_mutation_response(:todo_mark_done)
end
......
......@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring many Todos' do
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(:author) { 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(:todo2) { 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, target: issue) }
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
)
end
before_all do
project.add_developer(current_user)
end
def mutation_response
graphql_mutation_response(:todo_restore_many)
end
......
......@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Restoring Todos' do
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(:author) { 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(:todo2) { create(:todo, user: current_user, author: author, state: :pending) }
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, target: issue) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
......@@ -29,6 +31,10 @@ RSpec.describe 'Restoring Todos' do
)
end
before_all do
project.add_developer(current_user)
end
def mutation_response
graphql_mutation_response(:todo_restore)
end
......
......@@ -6,6 +6,7 @@ RSpec.describe API::PersonalAccessTokens do
let_it_be(:path) { '/personal_access_tokens' }
let_it_be(:token1) { 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) }
describe 'GET /personal_access_tokens' do
......@@ -24,8 +25,9 @@ RSpec.describe API::PersonalAccessTokens do
get api(path, current_user), params: { user_id: token1.user.id }
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.last['id']).to eq(token_impersonated.id)
end
end
......@@ -34,6 +36,7 @@ RSpec.describe API::PersonalAccessTokens do
let_it_be(:user) { create(: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(:token_impersonated) { create(:personal_access_token, impersonation: true, user: current_user) }
it 'returns all PATs belonging to the signed-in user' do
get api(path, current_user, personal_access_token: token)
......@@ -95,6 +98,7 @@ RSpec.describe API::PersonalAccessTokens 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_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
delete api(path, current_user)
......@@ -107,6 +111,12 @@ RSpec.describe API::PersonalAccessTokens do
expect(response).to have_gitlab_http_status(:no_content)
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
......@@ -3,18 +3,22 @@
require 'spec_helper'
RSpec.describe API::Todos do
include DesignManagementTestHelpers
let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) }
let_it_be(:author_1) { create(:user) }
let_it_be(:author_2) { create(:user) }
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_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_2) { create(:todo, project: project_2, author: author_2, 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, target: issue) }
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_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') }
......@@ -77,13 +81,13 @@ RSpec.describe API::Todos do
expect(json_response[0]['target_type']).to eq('Commit')
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']['merge_requests_count']).to eq(0)
expect(json_response[2]['target_type']).to eq('Issue')
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[3]['target_type']).to eq('MergeRequest')
......@@ -93,6 +97,19 @@ RSpec.describe API::Todos do
expect(json_response[3]['target']['downvotes']).to eq(0)
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
it 'filters based on author_id param' do
get api('/todos', john_doe), params: { author_id: author_2.id }
......@@ -163,23 +180,31 @@ RSpec.describe API::Todos do
end
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)
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)
create(:todo, project: project_2, author: author_2, user: john_doe, target: merge_request_2)
expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(4)
control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) }
project_3 = create(:project, :repository)
project_3.add_developer(john_doe)
merge_request_3 = create(:merge_request, source_project: project_3)
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)
create(:on_commit_todo, project: project_3, author: author_1, user: john_doe)
create_issue_todo_for(john_doe)
create_issue_todo_for(john_doe, project_1)
create_issue_todo_for(john_doe, project_1)
# Additional query only when target belongs to project from different group
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)
end
......@@ -201,6 +226,8 @@ RSpec.describe API::Todos do
end
before do
enable_design_management
api_request
end
......@@ -222,6 +249,20 @@ RSpec.describe API::Todos do
)
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
describe 'POST /todos/:id/mark_as_done' do
......
......@@ -706,6 +706,32 @@ RSpec.describe 'Git HTTP requests' do
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
......
......@@ -1209,6 +1209,73 @@ RSpec.describe Ci::CreatePipelineService do
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
let(:variables_attributes) do
[{ key: 'first', secret_value: 'world' },
......
......@@ -109,9 +109,13 @@ RSpec.describe Git::ProcessRefChangesService do
.to receive(:commit)
.and_return(project.commit)
allow_any_instance_of(Repository)
.to receive(:branch_exists?)
.and_return(true)
if changes_method == :branch_changes
allow_any_instance_of(Repository).to receive(:branch_exists?) { true }
end
if changes_method == :tag_changes
allow_any_instance_of(Repository).to receive(:tag_exists?) { true }
end
end
context 'when git_push_create_all_pipelines is disabled' do
......
......@@ -226,6 +226,27 @@ RSpec.describe Issues::CreateService do
end
end
context 'when sentry identifier is given' do
before do
sentry_attributes = { sentry_issue_attributes: { sentry_issue_identifier: 42 } }
opts.merge!(sentry_attributes)
end
it 'does not assign the sentry error' do
expect(issue.sentry_issue).to eq(nil)
end
context 'user is reporter or above' do
before do
project.add_reporter(user)
end
it 'assigns the sentry error' do
expect(issue.sentry_issue).to be_kind_of(SentryIssue)
end
end
end
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
......
......@@ -82,6 +82,31 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.milestone).to eq milestone
end
context 'when sentry identifier is given' do
before do
sentry_attributes = { sentry_issue_attributes: { sentry_issue_identifier: 42 } }
opts.merge!(sentry_attributes)
end
it 'assigns the sentry error' do
update_issue(opts)
expect(issue.sentry_issue).to be_kind_of(SentryIssue)
end
context 'user is a guest' do
before do
project.add_guest(user)
end
it 'does not assign the sentry error' do
update_issue(opts)
expect(issue.sentry_issue).to eq(nil)
end
end
end
context 'when issue type is not incident' do
before do
update_issue(opts)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Todos::AllowedTargetFilterService do
include DesignManagementTestHelpers
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_issue) { create(:issue, project: authorized_project) }
let_it_be(:authorized_issue_todo) { create(:todo, project: authorized_project, target: authorized_issue, user: user) }
let_it_be(:unauthorized_issue) { create(:issue, project: unauthorized_project) }
let_it_be(:unauthorized_issue_todo) { create(:todo, project: unauthorized_project, target: unauthorized_issue, user: user) }
let_it_be(:authorized_design) { create(:design, issue: authorized_issue) }
let_it_be(:authorized_design_todo) { create(:todo, project: authorized_project, target: authorized_design, user: user) }
let_it_be(:unauthorized_design) { create(:design, issue: unauthorized_issue) }
let_it_be(:unauthorized_design_todo) { create(:todo, project: unauthorized_project, target: unauthorized_design, user: user) }
# Cannot use let_it_be with MRs
let(:authorized_mr) { create(:merge_request, source_project: authorized_project) }
let(:authorized_mr_todo) { create(:todo, project: authorized_project, user: user, target: authorized_mr) }
let(:unauthorized_mr) { create(:merge_request, source_project: unauthorized_project) }
let(:unauthorized_mr_todo) { create(:todo, project: unauthorized_project, user: user, target: unauthorized_mr) }
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_mr_todo,
authorized_issue_todo,
authorized_design_todo
]
end
let(:unauthorized_todos) do
[
unauthorized_mr_todo,
unauthorized_issue_todo,
unauthorized_design_todo
]
end
before do
enable_design_management
end
it { is_expected.to match_array(authorized_todos) }
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment