Commit e5c1f23e authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch 'ps-fix-fake-date' into 'master'

Fix fake date when called as function

See merge request gitlab-org/gitlab!46921
parents df7b6de8 89ae6833
...@@ -81,7 +81,7 @@ export default { ...@@ -81,7 +81,7 @@ export default {
}) })
.then(({ data }) => { .then(({ data }) => {
if (data.updateIssue.errors.length) { if (data.updateIssue.errors.length) {
createFlash(data.updateIssue.errors.join('. ')); createFlash({ message: data.updateIssue.errors.join('. ') });
return; return;
} }
...@@ -95,7 +95,7 @@ export default { ...@@ -95,7 +95,7 @@ export default {
// Dispatch event which updates open/close state, shared among the issue show page // Dispatch event which updates open/close state, shared among the issue show page
document.dispatchEvent(new CustomEvent('issuable_vue_app:change', payload)); document.dispatchEvent(new CustomEvent('issuable_vue_app:change', payload));
}) })
.catch(() => createFlash(__('Update failed. Please try again.'))) .catch(() => createFlash({ message: __('Update failed. Please try again.') }))
.finally(() => { .finally(() => {
this.isUpdatingState = false; this.isUpdatingState = false;
}); });
......
...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (gon?.features?.ciLintVue) { if (gon?.features?.ciLintVue) {
import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index') import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index')
.then(module => module.default()) .then(module => module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} else { } else {
import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor') import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor')
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
.then(module => new module.default()) .then(module => new module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} }
}); });
...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,11 +7,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (gon?.features?.ciLintVue) { if (gon?.features?.ciLintVue) {
import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index') import(/* webpackChunkName: 'ciLintIndex' */ '~/ci_lint/index')
.then(module => module.default()) .then(module => module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} else { } else {
import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor') import(/* webpackChunkName: 'ciLintEditor' */ '../ci_lint_editor')
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
.then(module => new module.default()) .then(module => new module.default())
.catch(() => createFlash(ERROR)); .catch(() => createFlash({ message: ERROR }));
} }
}); });
...@@ -40,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -40,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Diff(); new Diff();
}) })
.catch(() => { .catch(() => {
flash(__('An error occurred while retrieving diff files')); flash({ message: __('An error occurred while retrieving diff files') });
}); });
} else { } else {
new Diff(); new Diff();
......
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
.then(({ data }) => { .then(({ data }) => {
this.milestones = data; this.milestones = data;
}) })
.catch(() => createFlash(__('There was a problem fetching milestones.'))) .catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
......
...@@ -52,7 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController
end end
def toggle_shared_runners def toggle_shared_runners
if Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true) && !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable' if !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable'
return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it") return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it")
end end
......
# frozen_string_literal: true
module Types
class GroupInvitationType < BaseObject
expose_permissions Types::PermissionTypes::Group
authorize :read_group
implements InvitationInterface
graphql_name 'GroupInvitation'
description 'Represents a Group Invitation'
field :group, Types::GroupType, null: true,
description: 'Group that a User is invited to',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
end
end
# frozen_string_literal: true
module Types
module InvitationInterface
include BaseInterface
field :email, GraphQL::STRING_TYPE, null: false,
description: 'Email of the member to invite'
field :access_level, Types::AccessLevelType, null: true,
description: 'GitLab::Access level'
field :created_by, Types::UserType, null: true,
description: 'User that authorized membership'
field :created_at, Types::TimeType, null: true,
description: 'Date and time the membership was created'
field :updated_at, Types::TimeType, null: true,
description: 'Date and time the membership was last updated'
field :expires_at, Types::TimeType, null: true,
description: 'Date and time the membership expires'
field :user, Types::UserType, null: true,
description: 'User that is associated with the member object'
definition_methods do
def resolve_type(object, context)
case object
when GroupMember
Types::GroupInvitationType
when ProjectMember
Types::ProjectInvitationType
else
raise ::Gitlab::Graphql::Errors::BaseError, "Unknown member type #{object.class.name}"
end
end
end
end
end
# frozen_string_literal: true
module Types
class ProjectInvitationType < BaseObject
graphql_name 'ProjectInvitation'
description 'Represents a Project Membership Invitation'
expose_permissions Types::PermissionTypes::Project
implements InvitationInterface
authorize :read_project
field :project, Types::ProjectType, null: true,
description: 'Project ID for the project of the invitation'
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_id).find
end
end
end
...@@ -160,7 +160,7 @@ module IssuesHelper ...@@ -160,7 +160,7 @@ module IssuesHelper
can_report_spam: issue.submittable_as_spam_by?(current_user).to_s, can_report_spam: issue.submittable_as_spam_by?(current_user).to_s,
can_update_issue: can?(current_user, :update_issue, issue).to_s, can_update_issue: can?(current_user, :update_issue, issue).to_s,
iid: issue.iid, iid: issue.iid,
is_issue_author: issue.author == current_user, is_issue_author: (issue.author == current_user).to_s,
new_issue_path: new_project_issue_path(project), new_issue_path: new_project_issue_path(project),
project_path: project.full_path, project_path: project.full_path,
report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)), report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)),
......
...@@ -37,27 +37,6 @@ module FromUnion ...@@ -37,27 +37,6 @@ module FromUnion
# rubocop: disable Gitlab/Union # rubocop: disable Gitlab/Union
extend FromSetOperator extend FromSetOperator
define_set_operator Gitlab::SQL::Union define_set_operator Gitlab::SQL::Union
alias_method :from_union_set_operator, :from_union
def from_union(members, remove_duplicates: true, alias_as: table_name)
if Feature.enabled?(:sql_set_operators)
from_union_set_operator(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
else
# The original from_union method.
standard_from_union(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
end
end
private
def standard_from_union(members, remove_duplicates: true, alias_as: table_name)
union = Gitlab::SQL::Union
.new(members, remove_duplicates: remove_duplicates)
.to_sql
from(Arel.sql("(#{union}) #{alias_as}"))
end
# rubocop: enable Gitlab/Union # rubocop: enable Gitlab/Union
end end
end end
...@@ -79,8 +79,6 @@ class Deployment < ApplicationRecord ...@@ -79,8 +79,6 @@ class Deployment < ApplicationRecord
after_transition any => :running do |deployment| after_transition any => :running do |deployment|
deployment.run_after_commit do deployment.run_after_commit do
next unless Feature.enabled?(:ci_send_deployment_hook_when_start, deployment.project)
Deployments::ExecuteHooksWorker.perform_async(id) Deployments::ExecuteHooksWorker.perform_async(id)
end end
end end
......
...@@ -16,6 +16,7 @@ class Discussion ...@@ -16,6 +16,7 @@ class Discussion
:commit_id, :commit_id,
:confidential?, :confidential?,
:for_commit?, :for_commit?,
:for_design?,
:for_merge_request?, :for_merge_request?,
:noteable_ability_name, :noteable_ability_name,
:to_ability_name, :to_ability_name,
......
...@@ -96,6 +96,8 @@ class Member < ApplicationRecord ...@@ -96,6 +96,8 @@ class Member < ApplicationRecord
scope :owners, -> { active.where(access_level: OWNER) } scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) } scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
scope :with_user, -> (user) { where(user: user) } scope :with_user, -> (user) { where(user: user) }
scope :with_user_by_email, -> (email) { left_join_users.where(users: { email: email } ) }
scope :preload_user_and_notification_settings, -> { preload(user: :notification_settings) } scope :preload_user_and_notification_settings, -> { preload(user: :notification_settings) }
scope :with_source_id, ->(source_id) { where(source_id: source_id) } scope :with_source_id, ->(source_id) { where(source_id: source_id) }
......
...@@ -393,7 +393,6 @@ class Namespace < ApplicationRecord ...@@ -393,7 +393,6 @@ class Namespace < ApplicationRecord
end end
def changing_shared_runners_enabled_is_allowed def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled) return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable' if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
...@@ -402,7 +401,6 @@ class Namespace < ApplicationRecord ...@@ -402,7 +401,6 @@ class Namespace < ApplicationRecord
end end
def changing_allow_descendants_override_disabled_shared_runners_is_allowed def changing_allow_descendants_override_disabled_shared_runners_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners) return unless new_record? || changes.has_key?(:allow_descendants_override_disabled_shared_runners)
if shared_runners_enabled && !new_record? if shared_runners_enabled && !new_record?
......
...@@ -1195,7 +1195,6 @@ class Project < ApplicationRecord ...@@ -1195,7 +1195,6 @@ class Project < ApplicationRecord
end end
def changing_shared_runners_enabled_is_allowed def changing_shared_runners_enabled_is_allowed
return unless Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true)
return unless new_record? || changes.has_key?(:shared_runners_enabled) return unless new_record? || changes.has_key?(:shared_runners_enabled)
if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable' if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable'
......
...@@ -7,13 +7,15 @@ class NotePolicy < BasePolicy ...@@ -7,13 +7,15 @@ class NotePolicy < BasePolicy
delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) } delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
condition(:is_author) { @user && @subject.author == @user } condition(:is_author) { @user && @subject.author == @user }
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id } condition(:is_noteable_author) { @user && @subject.noteable.try(:author_id) == @user.id }
condition(:editable, scope: :subject) { @subject.editable? } condition(:editable, scope: :subject) { @subject.editable? }
condition(:can_read_noteable) { can?(:"read_#{@subject.noteable_ability_name}") } condition(:can_read_noteable) { can?(:"read_#{@subject.noteable_ability_name}") }
condition(:commit_is_deleted) { @subject.for_commit? && @subject.noteable.blank? } condition(:commit_is_deleted) { @subject.for_commit? && @subject.noteable.blank? }
condition(:for_design) { @subject.for_design? }
condition(:is_visible) { @subject.system_note_with_references_visible_for?(@user) } condition(:is_visible) { @subject.system_note_with_references_visible_for?(@user) }
condition(:confidential, scope: :subject) { @subject.confidential? } condition(:confidential, scope: :subject) { @subject.confidential? }
...@@ -28,6 +30,7 @@ class NotePolicy < BasePolicy ...@@ -28,6 +30,7 @@ class NotePolicy < BasePolicy
rule { ~can_read_noteable }.policy do rule { ~can_read_noteable }.policy do
prevent :admin_note prevent :admin_note
prevent :resolve_note prevent :resolve_note
prevent :reposition_note
prevent :award_emoji prevent :award_emoji
end end
...@@ -46,6 +49,7 @@ class NotePolicy < BasePolicy ...@@ -46,6 +49,7 @@ class NotePolicy < BasePolicy
prevent :read_note prevent :read_note
prevent :admin_note prevent :admin_note
prevent :resolve_note prevent :resolve_note
prevent :reposition_note
prevent :award_emoji prevent :award_emoji
end end
...@@ -57,9 +61,14 @@ class NotePolicy < BasePolicy ...@@ -57,9 +61,14 @@ class NotePolicy < BasePolicy
prevent :read_note prevent :read_note
prevent :admin_note prevent :admin_note
prevent :resolve_note prevent :resolve_note
prevent :reposition_note
prevent :award_emoji prevent :award_emoji
end end
rule { can?(:admin_note) | (for_design & can?(:create_note)) }.policy do
enable :reposition_note
end
def parent_namespace def parent_namespace
strong_memoize(:parent_namespace) do strong_memoize(:parent_namespace) do
next if @subject.is_a?(PersonalSnippet) next if @subject.is_a?(PersonalSnippet)
......
# frozen_string_literal: true
class InvitationPresenter < Gitlab::View::Presenter::Delegated
presents :invitation
end
# frozen_string_literal: true
module Members
class InviteService < Members::BaseService
DEFAULT_LIMIT = 100
attr_reader :errors
def initialize(current_user, params)
@current_user, @params = current_user, params.dup
@errors = {}
end
def execute(source)
return error(s_('Email cannot be blank')) if params[:email].blank?
emails = params[:email].split(',').uniq.flatten
return error(s_("Too many users specified (limit is %{user_limit})") % { user_limit: user_limit }) if
user_limit && emails.size > user_limit
emails.each do |email|
next if existing_member?(source, email)
next if existing_invite?(source, email)
if existing_user?(email)
add_existing_user_as_member(current_user, source, params, email)
next
end
invite_new_member_and_user(current_user, source, params, email)
end
return success unless errors.any?
error(errors)
end
private
def invite_new_member_and_user(current_user, source, params, email)
new_member = (source.class.name + 'Member').constantize.create(source_id: source.id,
user_id: nil,
access_level: params[:access_level],
invite_email: email,
created_by_id: current_user.id,
expires_at: params[:expires_at],
requested_at: Time.current.utc)
unless new_member.valid? && new_member.persisted?
errors[params[:email]] = new_member.errors.full_messages.to_sentence
end
end
def add_existing_user_as_member(current_user, source, params, email)
new_member = create_member(current_user, existing_user(email), source, params.merge({ invite_email: email }))
unless new_member.valid? && new_member.persisted?
errors[email] = new_member.errors.full_messages.to_sentence
end
end
def create_member(current_user, user, source, params)
source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
end
def user_limit
limit = params.fetch(:limit, DEFAULT_LIMIT)
limit && limit < 0 ? nil : limit
end
def existing_member?(source, email)
existing_member = source.members.with_user_by_email(email).exists?
if existing_member
errors[email] = "Already a member of #{source.name}"
return true
end
false
end
def existing_invite?(source, email)
existing_invite = source.members.search_invite_email(email).exists?
if existing_invite
errors[email] = "Member already invited to #{source.name}"
return true
end
false
end
def existing_user(email)
User.find_by_email(email)
end
def existing_user?(email)
existing_user(email).present?
end
end
end
...@@ -9,7 +9,7 @@ module Packages ...@@ -9,7 +9,7 @@ module Packages
def execute def execute
if @tag_name.present? if @tag_name.present?
@tag_name.match(Gitlab::Regex.composer_package_version_regex).captures[0] @tag_name.delete_prefix('v')
elsif @branch_name.present? elsif @branch_name.present?
branch_sufix_or_prefix(@branch_name.match(Gitlab::Regex.composer_package_version_regex)) branch_sufix_or_prefix(@branch_name.match(Gitlab::Regex.composer_package_version_regex))
end end
......
...@@ -16,6 +16,7 @@ module Search ...@@ -16,6 +16,7 @@ module Search
Gitlab::SearchResults.new(current_user, Gitlab::SearchResults.new(current_user,
params[:search], params[:search],
projects, projects,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { state: params[:state], confidential: params[:confidential] }) filters: { state: params[:state], confidential: params[:confidential] })
end end
......
...@@ -16,6 +16,7 @@ module Search ...@@ -16,6 +16,7 @@ module Search
params[:search], params[:search],
projects, projects,
group: group, group: group,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { state: params[:state], confidential: params[:confidential] } filters: { state: params[:state], confidential: params[:confidential] }
) )
......
...@@ -17,6 +17,7 @@ module Search ...@@ -17,6 +17,7 @@ module Search
params[:search], params[:search],
project: project, project: project,
repository_ref: params[:repository_ref], repository_ref: params[:repository_ref],
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { confidential: params[:confidential], state: params[:state] } filters: { confidential: params[:confidential], state: params[:state] }
) )
......
...@@ -19,7 +19,7 @@ module ApplicationWorker ...@@ -19,7 +19,7 @@ module ApplicationWorker
def structured_payload(payload = {}) def structured_payload(payload = {})
context = Labkit::Context.current.to_h.merge( context = Labkit::Context.current.to_h.merge(
'class' => self.class, 'class' => self.class.name,
'job_status' => 'running', 'job_status' => 'running',
'queue' => self.class.queue, 'queue' => self.class.queue,
'jid' => jid 'jid' => jid
......
---
title: Allow semver versions in composer packages
merge_request: 46301
author:
type: fixed
---
title: Add ability to sort to search API
merge_request: 46646
author:
type: added
---
title: Add metric count for projects with alerts created
merge_request: 46636
author:
type: added
---
title: Enable refactored union set operator
merge_request: 46295
author:
type: added
---
title: Add API post /invitations by email
merge_request: 45950
author:
type: added
---
title: Forbid top level route sitemap
merge_request: 46677
author:
type: changed
---
name: ci_send_deployment_hook_when_start
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41214
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247137
group: group::progressive delivery
type: development
default_enabled: false
---
name: disable_shared_runners_on_group
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36080
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258991
type: development
group: group::runner
default_enabled: true
---
name: sql_set_operators
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
rollout_issue_url:
group: group::access
type: development
default_enabled: false
...@@ -8,3 +8,4 @@ Grape::Validations.register_validator(:integer_none_any, ::API::Validations::Val ...@@ -8,3 +8,4 @@ Grape::Validations.register_validator(:integer_none_any, ::API::Validations::Val
Grape::Validations.register_validator(:array_none_any, ::API::Validations::Validators::ArrayNoneAny) Grape::Validations.register_validator(:array_none_any, ::API::Validations::Validators::ArrayNoneAny)
Grape::Validations.register_validator(:check_assignees_count, ::API::Validations::Validators::CheckAssigneesCount) Grape::Validations.register_validator(:check_assignees_count, ::API::Validations::Validators::CheckAssigneesCount)
Grape::Validations.register_validator(:untrusted_regexp, ::API::Validations::Validators::UntrustedRegexp) Grape::Validations.register_validator(:untrusted_regexp, ::API::Validations::Validators::UntrustedRegexp)
Grape::Validations.register_validator(:email_or_email_list, ::API::Validations::Validators::EmailOrEmailList)
# frozen_string_literal: true
class CreateVulnerabilityFindingLinks < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :vulnerability_finding_links, if_not_exists: true do |t|
t.timestamps_with_timezone null: false
t.references :vulnerability_occurrence, index: { name: 'finding_links_on_vulnerability_occurrence_id' }, null: false, foreign_key: { on_delete: :cascade }
t.text :name, limit: 255
t.text :url, limit: 2048, null: false
end
add_text_limit :vulnerability_finding_links, :name, 255
add_text_limit :vulnerability_finding_links, :url, 2048
end
def down
drop_table :vulnerability_finding_links
end
end
# frozen_string_literal: true
class RenameSitemapNamespace < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::RenameReservedPathsMigration::V1
DOWNTIME = false
disable_ddl_transaction!
# We're taking over the /sitemap namespace
# since it's necessary for the default behavior of Sitemaps
def up
disable_statement_timeout do
rename_root_paths(['sitemap'])
end
end
def down
disable_statement_timeout do
revert_renames
end
end
end
50e4e42c804d3abdcfe9ab2bbb890262d4b2ddd93bff1b2af1da1e55a0300cf5
\ No newline at end of file
a861c91ebc7f7892020ba10a151df761b38bf69d5e02bcdf72a965eb266e6aff
\ No newline at end of file
...@@ -17104,6 +17104,26 @@ CREATE SEQUENCE vulnerability_feedback_id_seq ...@@ -17104,6 +17104,26 @@ CREATE SEQUENCE vulnerability_feedback_id_seq
ALTER SEQUENCE vulnerability_feedback_id_seq OWNED BY vulnerability_feedback.id; ALTER SEQUENCE vulnerability_feedback_id_seq OWNED BY vulnerability_feedback.id;
CREATE TABLE vulnerability_finding_links (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
vulnerability_occurrence_id bigint NOT NULL,
name text,
url text NOT NULL,
CONSTRAINT check_55f0a95439 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_b7fe886df6 CHECK ((char_length(url) <= 2048))
);
CREATE SEQUENCE vulnerability_finding_links_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE vulnerability_finding_links_id_seq OWNED BY vulnerability_finding_links.id;
CREATE TABLE vulnerability_historical_statistics ( CREATE TABLE vulnerability_historical_statistics (
id bigint NOT NULL, id bigint NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
...@@ -18203,6 +18223,8 @@ ALTER TABLE ONLY vulnerability_exports ALTER COLUMN id SET DEFAULT nextval('vuln ...@@ -18203,6 +18223,8 @@ ALTER TABLE ONLY vulnerability_exports ALTER COLUMN id SET DEFAULT nextval('vuln
ALTER TABLE ONLY vulnerability_feedback ALTER COLUMN id SET DEFAULT nextval('vulnerability_feedback_id_seq'::regclass); ALTER TABLE ONLY vulnerability_feedback ALTER COLUMN id SET DEFAULT nextval('vulnerability_feedback_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_finding_links ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_links_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_historical_statistics ALTER COLUMN id SET DEFAULT nextval('vulnerability_historical_statistics_id_seq'::regclass); ALTER TABLE ONLY vulnerability_historical_statistics ALTER COLUMN id SET DEFAULT nextval('vulnerability_historical_statistics_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_identifiers ALTER COLUMN id SET DEFAULT nextval('vulnerability_identifiers_id_seq'::regclass); ALTER TABLE ONLY vulnerability_identifiers ALTER COLUMN id SET DEFAULT nextval('vulnerability_identifiers_id_seq'::regclass);
...@@ -19646,6 +19668,9 @@ ALTER TABLE ONLY vulnerability_exports ...@@ -19646,6 +19668,9 @@ ALTER TABLE ONLY vulnerability_exports
ALTER TABLE ONLY vulnerability_feedback ALTER TABLE ONLY vulnerability_feedback
ADD CONSTRAINT vulnerability_feedback_pkey PRIMARY KEY (id); ADD CONSTRAINT vulnerability_feedback_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_finding_links
ADD CONSTRAINT vulnerability_finding_links_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_historical_statistics ALTER TABLE ONLY vulnerability_historical_statistics
ADD CONSTRAINT vulnerability_historical_statistics_pkey PRIMARY KEY (id); ADD CONSTRAINT vulnerability_historical_statistics_pkey PRIMARY KEY (id);
...@@ -19870,6 +19895,8 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user ...@@ -19870,6 +19895,8 @@ CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_and_note_id_index ON epic_user
CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL); CREATE UNIQUE INDEX epic_user_mentions_on_epic_id_index ON epic_user_mentions USING btree (epic_id) WHERE (note_id IS NULL);
CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id);
CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at); CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1); CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
...@@ -24195,6 +24222,9 @@ ALTER TABLE ONLY gpg_signatures ...@@ -24195,6 +24222,9 @@ ALTER TABLE ONLY gpg_signatures
ALTER TABLE ONLY board_group_recent_visits ALTER TABLE ONLY board_group_recent_visits
ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_finding_links
ADD CONSTRAINT fk_rails_cbdfde27ce FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
ALTER TABLE ONLY issues_self_managed_prometheus_alert_events ALTER TABLE ONLY issues_self_managed_prometheus_alert_events
ADD CONSTRAINT fk_rails_cc5d88bbb0 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_cc5d88bbb0 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
......
This diff is collapsed.
...@@ -40,6 +40,7 @@ The following API resources are available in the project context: ...@@ -40,6 +40,7 @@ The following API resources are available in the project context:
| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | | [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
| [Feature Flags](feature_flags.md) | `/projects/:id/feature_flags` | | [Feature Flags](feature_flags.md) | `/projects/:id/feature_flags` |
| [Feature Flag User Lists](feature_flag_user_lists.md) | `/projects/:id/feature_flags_user_lists` | | [Feature Flag User Lists](feature_flag_user_lists.md) | `/projects/:id/feature_flags_user_lists` |
| [Invitations](invitations.md) | `/projects/:id/invitations` (also available for groups) |
| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | | [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | | [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) |
| [Issue boards](boards.md) | `/projects/:id/boards` | | [Issue boards](boards.md) | `/projects/:id/boards` |
...@@ -108,6 +109,7 @@ The following API resources are available in the group context: ...@@ -108,6 +109,7 @@ The following API resources are available in the group context:
| [Group labels](group_labels.md) | `/groups/:id/labels` | | [Group labels](group_labels.md) | `/groups/:id/labels` |
| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | | [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | | [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
| [Invitations](invitations.md) | `/groups/:id/invitations` (also available for projects) |
| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | | [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | | [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) |
| [Members](members.md) | `/groups/:id/members` (also available for projects) | | [Members](members.md) | `/groups/:id/members` (also available for projects) |
......
This diff is collapsed.
---
stage: Growth
group: Expansion
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Invitations API
Use the Invitations API to send email to users you want to join a group or project.
## Valid access levels
To send an invitation, you must have access to the project or group you are sending email for. Valid access
levels are defined in the `Gitlab::Access` module. Currently, these levels are valid:
- No access (`0`)
- Guest (`10`)
- Reporter (`20`)
- Developer (`30`)
- Maintainer (`40`)
- Owner (`50`) - Only valid to set for groups
CAUTION: **Caution:**
Due to [an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/219299),
projects in personal namespaces will not show owner (`50`) permission.
## Invite by email to group or project
Invites a new user by email to join a group or project.
```plaintext
POST /groups/:id/invitations
POST /projects/:id/invitations
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `email` | integer/string | yes | The email of the new member or multiple emails separated by commas |
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "email=test@example.com&access_level=30" "https://gitlab.example.com/api/v4/groups/:id/invitations"
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "email=test@example.com&access_level=30" "https://gitlab.example.com/api/v4/projects/:id/invitations"
```
Example responses:
When all emails were successfully sent:
```json
{ "status": "success" }
```
When there was any error sending the email:
```json
{
"status": "error",
"message": {
"test@example.com": "Already invited",
"test2@example.com": "Member already exsists"
}
}
```
...@@ -216,7 +216,7 @@ Parameters: ...@@ -216,7 +216,7 @@ Parameters:
Example request: Example request:
```shell ```shell
curl --request POST --header "PRIVATE_TOKEN: <your_access_token>" --header "Content-Type: application/json" \ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
--data '{"destination_storage_name":"storage2"}' "https://gitlab.example.com/api/v4/projects/1/repository_storage_moves" --data '{"destination_storage_name":"storage2"}' "https://gitlab.example.com/api/v4/projects/1/repository_storage_moves"
``` ```
......
...@@ -26,6 +26,8 @@ GET /search ...@@ -26,6 +26,8 @@ GET /search
| `search` | string | yes | The search query | | `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. | | `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, users. Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, users.
...@@ -436,6 +438,8 @@ GET /groups/:id/search ...@@ -436,6 +438,8 @@ GET /groups/:id/search
| `search` | string | yes | The search query | | `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. | | `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users. Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
...@@ -816,6 +820,8 @@ GET /projects/:id/search ...@@ -816,6 +820,8 @@ GET /projects/:id/search
| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. | | `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. | | `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results will either be sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users. Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
......
...@@ -207,6 +207,12 @@ guide on how you can add a new custom validator. ...@@ -207,6 +207,12 @@ guide on how you can add a new custom validator.
checks if the value of the given parameter is either an `Array`, `None`, or `Any`. checks if the value of the given parameter is either an `Array`, `None`, or `Any`.
It allows only either of these mentioned values to move forward in the request. It allows only either of these mentioned values to move forward in the request.
- `EmailOrEmailList`:
The [`EmailOrEmailList` validator](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/email_or_email_list.rb)
checks if the value of a string or a list of strings contains only valid
email addresses. It allows only lists with all valid email addresses to move forward in the request.
### Adding a new custom validator ### Adding a new custom validator
Custom validators are a great way to validate parameters before sending Custom validators are a great way to validate parameters before sending
......
...@@ -258,6 +258,7 @@ Table description links: ...@@ -258,6 +258,7 @@ Table description links:
| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | ✅ | ✅ | ⚙ | ✅ | ⤓ | ❌ | CE & EE | | [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | ✅ | ✅ | ⚙ | ✅ | ⤓ | ❌ | CE & EE |
| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | ✅ | N/A | N/A | ✅ | ❌ | ❌ | CE & EE | | [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | ✅ | N/A | N/A | ✅ | ❌ | ❌ | CE & EE |
| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE | | [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
| [Patroni](#patroni) | Manage PostgreSQL HA cluster leader selection and replication | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | CE & EE | | [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | CE & EE |
| [PgBouncer](#pgbouncer) | Database connection pooling, failover | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only | | [PgBouncer](#pgbouncer) | Database connection pooling, failover | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
| [PostgreSQL Exporter](#postgresql-exporter) | Prometheus endpoint with PostgreSQL metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE | | [PostgreSQL Exporter](#postgresql-exporter) | Prometheus endpoint with PostgreSQL metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
...@@ -545,6 +546,15 @@ NGINX has an Ingress port for all HTTP requests and routes them to the appropria ...@@ -545,6 +546,15 @@ NGINX has an Ingress port for all HTTP requests and routes them to the appropria
[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine (think CPU/Disk/Load). It's just a packaged version of the common open source offering from the Prometheus project. [Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine (think CPU/Disk/Load). It's just a packaged version of the common open source offering from the Prometheus project.
#### Patroni
- [Project Page](https://github.com/zalando/patroni)
- Configuration:
- [Omnibus](../administration/postgresql/replication_and_failover.md#patroni)
- Layer: Core Service (Data)
- Process: `patroni`
- GitLab.com: [Database Architecture](https://about.gitlab.com/handbook/engineering/infrastructure/production/architecture/#database-architecture)
#### PgBouncer #### PgBouncer
- [Project page](https://github.com/pgbouncer/pgbouncer/blob/master/README.md) - [Project page](https://github.com/pgbouncer/pgbouncer/blob/master/README.md)
......
...@@ -17,9 +17,8 @@ we suggest investigating to see if a plugin exists. For instance here is the ...@@ -17,9 +17,8 @@ we suggest investigating to see if a plugin exists. For instance here is the
## Pre-push static analysis ## Pre-push static analysis
We strongly recommend installing We strongly recommend installing [Lefthook](https://github.com/Arkweid/lefthook) to automatically check
[Lefthook](https://github.com/Arkweid/lefthook) to automatically check for for static analysis offenses before pushing your changes.
static analysis offenses before pushing your changes.
To install `lefthook`, run the following in your GitLab source directory: To install `lefthook`, run the following in your GitLab source directory:
...@@ -33,10 +32,9 @@ overcommit --uninstall ...@@ -33,10 +32,9 @@ overcommit --uninstall
gem install lefthook && lefthook install -f gem install lefthook && lefthook install -f
``` ```
Before you push your changes, Lefthook will then automatically run Danger checks, as well Before you push your changes, Lefthook then automatically run Danger checks, and other checks
as RuboCop, ES Lint, HAML Lint, and SCSS Lint for the changed files. for changed files. This saves you time as you don't have to wait for the same errors to be detected
by CI/CD.
This saves you time as you don't have to wait for the same errors to be detected by CI/CD.
Lefthook relies on a pre-push hook to prevent commits that violate its ruleset. Lefthook relies on a pre-push hook to prevent commits that violate its ruleset.
If you wish to override this behavior, pass the environment variable `LEFTHOOK=0`. If you wish to override this behavior, pass the environment variable `LEFTHOOK=0`.
...@@ -45,7 +43,8 @@ That is, `LEFTHOOK=0 git push`. ...@@ -45,7 +43,8 @@ That is, `LEFTHOOK=0 git push`.
You can also: You can also:
- Define [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config). - Define [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config).
- Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly). - Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly), e.g. `LEFTHOOK_EXCLUDE=frontend git push origin`.
- Run [hooks manually](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly), e.g. `lefthook run pre-push`.
## Ruby, Rails, RSpec ## Ruby, Rails, RSpec
......
...@@ -708,15 +708,14 @@ Git [pre-commit hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) ...@@ -708,15 +708,14 @@ Git [pre-commit hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
run tests or other processes before committing to a branch, with the ability to not commit to the branch if run tests or other processes before committing to a branch, with the ability to not commit to the branch if
failures occur with these tests. failures occur with these tests.
[`overcommit`](https://github.com/sds/overcommit) is a Git hooks manager, making configuring, [`lefthook`](https://github.com/Arkweid/lefthook) is a Git hooks manager, making configuring,
installing, and removing Git hooks easy. installing, and removing Git hooks easy.
Sample configuration for `overcommit` is available in the Configuration for `left` is available in the [`lefthook.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml)
[`.overcommit.yml.example`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.overcommit.yml.example)
file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project. file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project.
To set up `overcommit` for documentation linting, see To set up `lefthook` for documentation linting, see
[Pre-commit static analysis](../contributing/style_guides.md#pre-push-static-analysis). [Pre-push static analysis](../contributing/style_guides.md#pre-push-static-analysis).
#### Disable Vale tests #### Disable Vale tests
......
...@@ -161,6 +161,12 @@ headers whose values you want masked. For details on how to mask headers, see ...@@ -161,6 +161,12 @@ headers whose values you want masked. For details on how to mask headers, see
It's also possible to authenticate the user before performing the DAST checks. It's also possible to authenticate the user before performing the DAST checks.
**Important:** It is highly recommended that you configure the scanner to authenticate to the application,
or it will not be able to check most of the application for security risks, as most
of your application is likely not accessible without authentication. It is also recommended
that you periodically confirm the scanner's authentication is still working as this tends to break over
time due to authentication changes to the application.
Create masked variables to pass the credentials that DAST uses. Create masked variables to pass the credentials that DAST uses.
To create masked variables for the username and password, see [Create a custom variable in the UI](../../../ci/variables/README.md#create-a-custom-variable-in-the-ui). To create masked variables for the username and password, see [Create a custom variable in the UI](../../../ci/variables/README.md#create-a-custom-variable-in-the-ui).
Note that the key of the username variable must be `DAST_USERNAME` Note that the key of the username variable must be `DAST_USERNAME`
......
...@@ -82,6 +82,7 @@ Currently the following names are reserved as top level groups: ...@@ -82,6 +82,7 @@ Currently the following names are reserved as top level groups:
- `s` - `s`
- `search` - `search`
- `sent_notifications` - `sent_notifications`
- `sitemap`
- `sitemap.xml` - `sitemap.xml`
- `sitemap.xml.gz` - `sitemap.xml.gz`
- `slash-command-logo.png` - `slash-command-logo.png`
......
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
i18n: { i18n: {
editPermissions: s__('Members|Edit permissions'), editPermissions: s__('Members|Edit permissions'),
modalBody: s__( modalBody: s__(
'Members|%{userName} is currently a LDAP user. Editing their permissions will override the settings from the LDAP group sync.', 'Members|%{userName} is currently an LDAP user. Editing their permissions will override the settings from the LDAP group sync.',
), ),
toastMessage: s__('Members|LDAP override enabled.'), toastMessage: s__('Members|LDAP override enabled.'),
}, },
......
...@@ -75,7 +75,7 @@ module Security ...@@ -75,7 +75,7 @@ module Security
def normalize_report_findings(report_findings, vulnerabilities) def normalize_report_findings(report_findings, vulnerabilities)
report_findings.map do |report_finding| report_findings.map do |report_finding|
finding_hash = report_finding.to_hash finding_hash = report_finding.to_hash
.except(:compare_key, :identifiers, :location, :scanner) .except(:compare_key, :identifiers, :location, :scanner, :links)
finding = Vulnerabilities::Finding.new(finding_hash) finding = Vulnerabilities::Finding.new(finding_hash)
# assigning Vulnerabilities to Findings to enable the computed state # assigning Vulnerabilities to Findings to enable the computed state
...@@ -84,6 +84,9 @@ module Security ...@@ -84,6 +84,9 @@ module Security
finding.project = pipeline.project finding.project = pipeline.project
finding.sha = pipeline.sha finding.sha = pipeline.sha
finding.build_scanner(report_finding.scanner&.to_hash) finding.build_scanner(report_finding.scanner&.to_hash)
finding.finding_links = report_finding.links.map do |link|
Vulnerabilities::FindingLink.new(link.to_hash)
end
finding.identifiers = report_finding.identifiers.map do |identifier| finding.identifiers = report_finding.identifiers.map do |identifier|
Vulnerabilities::Identifier.new(identifier.to_hash) Vulnerabilities::Identifier.new(identifier.to_hash)
end end
......
...@@ -26,6 +26,8 @@ module Vulnerabilities ...@@ -26,6 +26,8 @@ module Vulnerabilities
has_many :finding_identifiers, class_name: 'Vulnerabilities::FindingIdentifier', inverse_of: :finding, foreign_key: 'occurrence_id' has_many :finding_identifiers, class_name: 'Vulnerabilities::FindingIdentifier', inverse_of: :finding, foreign_key: 'occurrence_id'
has_many :identifiers, through: :finding_identifiers, class_name: 'Vulnerabilities::Identifier' has_many :identifiers, through: :finding_identifiers, class_name: 'Vulnerabilities::Identifier'
has_many :finding_links, class_name: 'Vulnerabilities::FindingLink', inverse_of: :finding, foreign_key: 'vulnerability_occurrence_id'
has_many :finding_pipelines, class_name: 'Vulnerabilities::FindingPipeline', inverse_of: :finding, foreign_key: 'occurrence_id' has_many :finding_pipelines, class_name: 'Vulnerabilities::FindingPipeline', inverse_of: :finding, foreign_key: 'occurrence_id'
has_many :pipelines, through: :finding_pipelines, class_name: 'Ci::Pipeline' has_many :pipelines, through: :finding_pipelines, class_name: 'Ci::Pipeline'
...@@ -256,7 +258,9 @@ module Vulnerabilities ...@@ -256,7 +258,9 @@ module Vulnerabilities
end end
def links def links
metadata.fetch('links', []) return metadata.fetch('links', []) if finding_links.load.empty?
finding_links.as_json(only: [:name, :url])
end end
def remediations def remediations
......
# frozen_string_literal: true
module Vulnerabilities
class FindingLink < ApplicationRecord
self.table_name = 'vulnerability_finding_links'
belongs_to :finding, class_name: 'Vulnerabilities::Finding', inverse_of: :finding_identifiers, foreign_key: 'vulnerability_occurrence_id'
validates :finding, presence: true
validates :url, presence: true, length: { maximum: 255 }
validates :name, length: { maximum: 2048 }
end
end
...@@ -16,6 +16,7 @@ module EE ...@@ -16,6 +16,7 @@ module EE
params[:search], params[:search],
elastic_projects, elastic_projects,
public_and_internal_projects: elastic_global, public_and_internal_projects: elastic_global,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { confidential: params[:confidential], state: params[:state] } filters: { confidential: params[:confidential], state: params[:state] }
) )
......
...@@ -30,6 +30,7 @@ module EE ...@@ -30,6 +30,7 @@ module EE
elastic_projects, elastic_projects,
group: group, group: group,
public_and_internal_projects: elastic_global, public_and_internal_projects: elastic_global,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { confidential: params[:confidential], state: params[:state] } filters: { confidential: params[:confidential], state: params[:state] }
) )
......
...@@ -15,6 +15,7 @@ module EE ...@@ -15,6 +15,7 @@ module EE
params[:search], params[:search],
project: project, project: project,
repository_ref: repository_ref, repository_ref: repository_ref,
order_by: params[:order_by],
sort: params[:sort], sort: params[:sort],
filters: { confidential: params[:confidential], state: params[:state] } filters: { confidential: params[:confidential], state: params[:state] }
) )
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Issues module Issues
class CreateFromVulnerabilityDataService < ::BaseService class CreateFromVulnerabilityDataService < ::BaseService
def execute def execute
return error("Can't create issue") unless can?(@current_user, :create_issue, @project) return error("User is not permitted to create issue") unless can?(@current_user, :create_issue, @project)
begin begin
vulnerability = Gitlab::Vulnerabilities::Parser.fabricate(@params) vulnerability = Gitlab::Vulnerabilities::Parser.fabricate(@params)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Issues module Issues
class CreateFromVulnerabilityService < ::BaseContainerService class CreateFromVulnerabilityService < ::BaseContainerService
def execute def execute
return error("Can't create issue") unless can?(@current_user, :create_issue, @container) return error("User is not permitted to create issue") unless can?(@current_user, :create_issue, @container)
vulnerability = params[:vulnerability].present vulnerability = params[:vulnerability].present
link_type = params[:link_type] link_type = params[:link_type]
......
...@@ -16,8 +16,8 @@ module MergeRequests ...@@ -16,8 +16,8 @@ module MergeRequests
] ]
target_branch = vulnerability.target_branch || @project.default_branch target_branch = vulnerability.target_branch || @project.default_branch
return error("Can't create merge_request") unless can_create_merge_request?(source_branch) return error("User is not permitted to create merge request") unless can_create_merge_request?(source_branch)
return error("Can't create merge request") if vulnerability.remediations.empty? return error("No remediations available for merge request") if vulnerability.remediations.empty?
patch_result = create_patch(vulnerability, source_branch, target_branch) patch_result = create_patch(vulnerability, source_branch, target_branch)
......
...@@ -47,7 +47,8 @@ module Security ...@@ -47,7 +47,8 @@ module Security
return return
end end
vulnerability_params = finding.to_hash.except(:compare_key, :identifiers, :location, :scanner, :scan) vulnerability_params = finding.to_hash.except(:compare_key, :identifiers, :location, :scanner, :scan, :links)
vulnerability_params[:uuid] = calculate_uuid_v5(finding)
vulnerability_finding = create_or_find_vulnerability_finding(finding, vulnerability_params) vulnerability_finding = create_or_find_vulnerability_finding(finding, vulnerability_params)
update_vulnerability_scanner(finding) update_vulnerability_scanner(finding)
...@@ -60,6 +61,8 @@ module Security ...@@ -60,6 +61,8 @@ module Security
create_or_update_vulnerability_identifier_object(vulnerability_finding, identifier) create_or_update_vulnerability_identifier_object(vulnerability_finding, identifier)
end end
create_or_update_vulnerability_links(finding, vulnerability_finding)
create_vulnerability_pipeline_object(vulnerability_finding, pipeline) create_vulnerability_pipeline_object(vulnerability_finding, pipeline)
create_vulnerability(vulnerability_finding, pipeline) create_vulnerability(vulnerability_finding, pipeline)
...@@ -79,8 +82,6 @@ module Security ...@@ -79,8 +82,6 @@ module Security
.create_with(create_params) .create_with(create_params)
.find_or_initialize_by(find_params) .find_or_initialize_by(find_params)
vulnerability_finding.uuid = calculcate_uuid_v5(vulnerability_finding, find_params)
vulnerability_finding.save! vulnerability_finding.save!
vulnerability_finding vulnerability_finding
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
...@@ -90,11 +91,11 @@ module Security ...@@ -90,11 +91,11 @@ module Security
end end
end end
def calculcate_uuid_v5(vulnerability_finding, finding_params) def calculate_uuid_v5(vulnerability_finding)
uuid_v5_name_components = { uuid_v5_name_components = {
report_type: vulnerability_finding.report_type, report_type: vulnerability_finding.report_type,
primary_identifier_fingerprint: vulnerability_finding.primary_identifier&.fingerprint || finding_params.dig(:primary_identifier, :fingerprint), primary_identifier_fingerprint: vulnerability_finding.primary_fingerprint,
location_fingerprint: vulnerability_finding.location_fingerprint, location_fingerprint: vulnerability_finding.location.fingerprint,
project_id: project.id project_id: project.id
} }
...@@ -104,8 +105,6 @@ module Security ...@@ -104,8 +105,6 @@ module Security
name = uuid_v5_name_components.values.join('-') name = uuid_v5_name_components.values.join('-')
Gitlab::AppLogger.debug(message: "Generating UUIDv5 with name: #{name}") if Gitlab.dev_env_or_com?
Gitlab::Vulnerabilities::CalculateFindingUUID.call(name) Gitlab::Vulnerabilities::CalculateFindingUUID.call(name)
end end
...@@ -125,6 +124,15 @@ module Security ...@@ -125,6 +124,15 @@ module Security
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
end end
def create_or_update_vulnerability_links(finding, vulnerability_finding)
return if finding.links.blank?
finding.links.each do |link|
vulnerability_finding.finding_links.safe_find_or_create_by!(link.to_hash)
end
rescue ActiveRecord::RecordNotUnique
end
def create_vulnerability_pipeline_object(vulnerability_finding, pipeline) def create_vulnerability_pipeline_object(vulnerability_finding, pipeline)
vulnerability_finding.finding_pipelines.find_or_create_by!(pipeline: pipeline) vulnerability_finding.finding_pipelines.find_or_create_by!(pipeline: pipeline)
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
.card-footer.p-3 .card-footer.p-3
.float-right{ class: ("invisible" unless purchase_link.action == 'upgrade' || is_current_plan) } .float-right{ class: ("invisible" unless purchase_link.action == 'upgrade' || is_current_plan) }
- if show_contact_sales_button?(purchase_link.action) - if show_contact_sales_button?(purchase_link.action)
= link_to s_('BillingPlan|Contact sales'), "#{contact_sales_url}?test=inappcontactsales#{plan.code}", class: "btn btn-success btn-inverted", data: { **experiment_tracking_data_for_button_click('contact_sales') } = link_to s_('BillingPlan|Contact sales'), "#{contact_sales_url}?test=inappcontactsales#{plan.code}", class: "btn btn-success-secondary gl-button", data: { **experiment_tracking_data_for_button_click('contact_sales') }
- upgrade_button_class = "disabled" if is_current_plan && !namespace.trial_active? - upgrade_button_class = "disabled" if is_current_plan && !namespace.trial_active?
- cta_class = '-new' if use_new_purchase_flow?(namespace) - cta_class = '-new' if use_new_purchase_flow?(namespace)
= link_to s_('BillingPlan|Upgrade'), plan_purchase_or_upgrade_url(namespace, plan, current_plan), class: "btn btn-success #{upgrade_button_class} billing-cta-purchase#{cta_class}", data: { **experiment_tracking_data_for_button_click('upgrade') } = link_to s_('BillingPlan|Upgrade'), plan_purchase_or_upgrade_url(namespace, plan, current_plan), class: "btn btn-success gl-button #{upgrade_button_class} billing-cta-purchase#{cta_class}", data: { **experiment_tracking_data_for_button_click('upgrade') }
...@@ -24,10 +24,10 @@ ...@@ -24,10 +24,10 @@
%p= s_("BillingPlans|This group uses the plan associated with its parent group.") %p= s_("BillingPlans|This group uses the plan associated with its parent group.")
- parent_billing_page_link = link_to parent_group.full_name, group_billings_path(parent_group) - parent_billing_page_link = link_to parent_group.full_name, group_billings_path(parent_group)
%p= s_("BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}.").html_safe % { parent_billing_page_link: parent_billing_page_link } %p= s_("BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}.").html_safe % { parent_billing_page_link: parent_billing_page_link }
= link_to s_("BillingPlans|Manage plan"), group_billings_path(parent_group), class: 'btn btn-success' = link_to s_("BillingPlans|Manage plan"), group_billings_path(parent_group), class: 'btn btn-success gl-button'
- else - else
= render 'shared/billings/trial_status', namespace: namespace = render 'shared/billings/trial_status', namespace: namespace
- if namespace.eligible_for_trial? - if namespace.eligible_for_trial?
- glm_content = namespace_for_user ? 'user-billing' : 'group-billing' - glm_content = namespace_for_user ? 'user-billing' : 'group-billing'
%p= link_to 'Start your free trial', new_trial_registration_path(glm_source: 'gitlab.com', glm_content: glm_content), class: 'btn btn-primary' %p= link_to 'Start your free trial', new_trial_registration_path(glm_source: 'gitlab.com', glm_content: glm_content), class: 'btn btn-primary gl-button'
---
title: Add Vulnerabilities::FindingLink model
merge_request: 46555
author:
type: added
---
title: Improve error messages for Vulnerability Issue/MR creation
merge_request: 46589
author:
type: changed
...@@ -137,14 +137,16 @@ module Elastic ...@@ -137,14 +137,16 @@ module Elastic
end end
def apply_sort(query_hash, options) def apply_sort(query_hash, options)
case options[:sort] # Due to different uses of sort param we prefer order_by when
when 'created_asc' # present
case ::Gitlab::Search::SortOptions.sort_and_direction(options[:order_by], options[:sort])
when :created_at_asc
query_hash.merge(sort: { query_hash.merge(sort: {
created_at: { created_at: {
order: 'asc' order: 'asc'
} }
}) })
when 'created_desc' when :created_at_desc
query_hash.merge(sort: { query_hash.merge(sort: {
created_at: { created_at: {
order: 'desc' order: 'desc'
......
...@@ -55,6 +55,7 @@ module Gitlab ...@@ -55,6 +55,7 @@ module Gitlab
def create_vulnerability(report, data, version) def create_vulnerability(report, data, version)
identifiers = create_identifiers(report, data['identifiers']) identifiers = create_identifiers(report, data['identifiers'])
links = create_links(report, data['links'])
report.add_finding( report.add_finding(
::Gitlab::Ci::Reports::Security::Finding.new( ::Gitlab::Ci::Reports::Security::Finding.new(
uuid: SecureRandom.uuid, uuid: SecureRandom.uuid,
...@@ -67,6 +68,7 @@ module Gitlab ...@@ -67,6 +68,7 @@ module Gitlab
scanner: create_scanner(report, data['scanner']), scanner: create_scanner(report, data['scanner']),
scan: report&.scan, scan: report&.scan,
identifiers: identifiers, identifiers: identifiers,
links: links,
raw_metadata: data.to_json, raw_metadata: data.to_json,
metadata_version: version)) metadata_version: version))
end end
...@@ -106,6 +108,22 @@ module Gitlab ...@@ -106,6 +108,22 @@ module Gitlab
url: identifier['url'])) url: identifier['url']))
end end
def create_links(report, links)
return [] unless links.is_a?(Array)
links
.map { |link| create_link(report, link) }
.compact
end
def create_link(report, link)
return unless link.is_a?(Hash)
::Gitlab::Ci::Reports::Security::Link.new(
name: link['name'],
url: link['url'])
end
def parse_severity_level(input) def parse_severity_level(input)
return input if ::Vulnerabilities::Finding::SEVERITY_LEVELS.key?(input) return input if ::Vulnerabilities::Finding::SEVERITY_LEVELS.key?(input)
......
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
attr_reader :compare_key attr_reader :compare_key
attr_reader :confidence attr_reader :confidence
attr_reader :identifiers attr_reader :identifiers
attr_reader :links
attr_reader :location attr_reader :location
attr_reader :metadata_version attr_reader :metadata_version
attr_reader :name attr_reader :name
...@@ -24,10 +25,11 @@ module Gitlab ...@@ -24,10 +25,11 @@ module Gitlab
delegate :file_path, :start_line, :end_line, to: :location delegate :file_path, :start_line, :end_line, to: :location
def initialize(compare_key:, identifiers:, location:, metadata_version:, name:, raw_metadata:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil) # rubocop:disable Metrics/ParameterLists def initialize(compare_key:, identifiers:, links: [], location:, metadata_version:, name:, raw_metadata:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil) # rubocop:disable Metrics/ParameterLists
@compare_key = compare_key @compare_key = compare_key
@confidence = confidence @confidence = confidence
@identifiers = identifiers @identifiers = identifiers
@links = links
@location = location @location = location
@metadata_version = metadata_version @metadata_version = metadata_version
@name = name @name = name
...@@ -46,6 +48,7 @@ module Gitlab ...@@ -46,6 +48,7 @@ module Gitlab
compare_key compare_key
confidence confidence
identifiers identifiers
links
location location
metadata_version metadata_version
name name
...@@ -94,8 +97,6 @@ module Gitlab ...@@ -94,8 +97,6 @@ module Gitlab
end end
end end
protected
def primary_fingerprint def primary_fingerprint
primary_identifier&.fingerprint primary_identifier&.fingerprint
end end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
class Link
attr_accessor :name, :url
def initialize(name: nil, url: nil)
@name = name
@url = url
end
def to_hash
{
name: name,
url: url
}.compact
end
end
end
end
end
end
...@@ -8,13 +8,15 @@ module Gitlab ...@@ -8,13 +8,15 @@ module Gitlab
class GroupSearchResults < Gitlab::Elastic::SearchResults class GroupSearchResults < Gitlab::Elastic::SearchResults
attr_reader :group, :default_project_filter, :filters attr_reader :group, :default_project_filter, :filters
def initialize(current_user, query, limit_project_ids = nil, group:, public_and_internal_projects: false, default_project_filter: false, sort: nil, filters: {}) # rubocop:disable Metrics/ParameterLists
def initialize(current_user, query, limit_project_ids = nil, group:, public_and_internal_projects: false, default_project_filter: false, order_by: nil, sort: nil, filters: {})
@group = group @group = group
@default_project_filter = default_project_filter @default_project_filter = default_project_filter
@filters = filters @filters = filters
super(current_user, query, limit_project_ids, public_and_internal_projects: public_and_internal_projects, sort: sort, filters: filters) super(current_user, query, limit_project_ids, public_and_internal_projects: public_and_internal_projects, order_by: order_by, sort: sort, filters: filters)
end end
# rubocop:enable Metrics/ParameterLists
end end
end end
end end
...@@ -8,11 +8,11 @@ module Gitlab ...@@ -8,11 +8,11 @@ module Gitlab
class ProjectSearchResults < Gitlab::Elastic::SearchResults class ProjectSearchResults < Gitlab::Elastic::SearchResults
attr_reader :project, :repository_ref, :filters attr_reader :project, :repository_ref, :filters
def initialize(current_user, query, project:, repository_ref: nil, sort: nil, filters: {}) def initialize(current_user, query, project:, repository_ref: nil, order_by: nil, sort: nil, filters: {})
@project = project @project = project
@repository_ref = repository_ref.presence || project.default_branch @repository_ref = repository_ref.presence || project.default_branch
super(current_user, query, [project.id], public_and_internal_projects: false, sort: sort, filters: filters) super(current_user, query, [project.id], public_and_internal_projects: false, order_by: order_by, sort: sort, filters: filters)
end end
private private
......
...@@ -7,17 +7,18 @@ module Gitlab ...@@ -7,17 +7,18 @@ module Gitlab
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
attr_reader :current_user, :query, :public_and_internal_projects, :sort, :filters attr_reader :current_user, :query, :public_and_internal_projects, :order_by, :sort, :filters
# Limit search results by passed projects # Limit search results by passed projects
# It allows us to search only for projects user has access to # It allows us to search only for projects user has access to
attr_reader :limit_project_ids attr_reader :limit_project_ids
def initialize(current_user, query, limit_project_ids = nil, public_and_internal_projects: true, sort: nil, filters: {}) def initialize(current_user, query, limit_project_ids = nil, public_and_internal_projects: true, order_by: nil, sort: nil, filters: {})
@current_user = current_user @current_user = current_user
@query = query @query = query
@limit_project_ids = limit_project_ids @limit_project_ids = limit_project_ids
@public_and_internal_projects = public_and_internal_projects @public_and_internal_projects = public_and_internal_projects
@order_by = order_by
@sort = sort @sort = sort
@filters = filters @filters = filters
end end
...@@ -202,6 +203,7 @@ module Gitlab ...@@ -202,6 +203,7 @@ module Gitlab
current_user: current_user, current_user: current_user,
project_ids: limit_project_ids, project_ids: limit_project_ids,
public_and_internal_projects: public_and_internal_projects, public_and_internal_projects: public_and_internal_projects,
order_by: order_by,
sort: sort sort: sort
} }
end end
...@@ -214,7 +216,7 @@ module Gitlab ...@@ -214,7 +216,7 @@ module Gitlab
def issues def issues
strong_memoize(:issues) do strong_memoize(:issues) do
options = base_options.merge(filters.slice(:sort, :confidential, :state)) options = base_options.merge(filters.slice(:order_by, :sort, :confidential, :state))
Issue.elastic_search(query, options: options) Issue.elastic_search(query, options: options)
end end
...@@ -235,7 +237,7 @@ module Gitlab ...@@ -235,7 +237,7 @@ module Gitlab
def merge_requests def merge_requests
strong_memoize(:merge_requests) do strong_memoize(:merge_requests) do
options = base_options.merge(filters.slice(:sort, :state)) options = base_options.merge(filters.slice(:order_by, :sort, :state))
MergeRequest.elastic_search(query, options: options) MergeRequest.elastic_search(query, options: options)
end end
......
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_link, class: '::Gitlab::Ci::Reports::Security::Link' do
name { 'CVE-2020-0202' }
url { 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0202' }
skip_create
initialize_with do
::Gitlab::Ci::Reports::Security::Link.new(**attributes)
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :finding_link, class: 'Vulnerabilities::FindingLink' do
finding factory: :vulnerabilities_finding
name { 'CVE-2018-1234' }
url { 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=2018-1234' }
end
end
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Groups > Audit Events', :js do RSpec.describe 'Groups > Audit Events', :js do
include Spec::Support::Helpers::Features::MembersHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:alex) { create(:user, name: 'Alex') } let(:alex) { create(:user, name: 'Alex') }
let(:group) { create(:group) } let(:group) { create(:group) }
before do before do
stub_feature_flags(vue_group_members_list: false)
group.add_owner(user) group.add_owner(user)
group.add_developer(alex) group.add_developer(alex)
sign_in(user) sign_in(user)
...@@ -47,11 +47,9 @@ RSpec.describe 'Groups > Audit Events', :js do ...@@ -47,11 +47,9 @@ RSpec.describe 'Groups > Audit Events', :js do
wait_for_requests wait_for_requests
group_member = group.members.find_by(user_id: alex) page.within first_row do
page.within "#group_member_#{group_member.id}" do
click_button 'Developer' click_button 'Developer'
click_link 'Maintainer' click_button 'Maintainer'
end end
find(:link, text: 'Settings').click find(:link, text: 'Settings').click
......
...@@ -2,14 +2,12 @@ ...@@ -2,14 +2,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Groups > Members > List members' do RSpec.describe 'Groups > Members > List members' do
include Spec::Support::Helpers::Features::MembersHelpers
let(:user1) { create(:user, name: 'John Doe') } let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') } let(:user2) { create(:user, name: 'Mary Jane') }
let(:group) { create(:group) } let(:group) { create(:group) }
before do
stub_feature_flags(vue_group_members_list: false)
end
context 'with Group SAML identity linked for a user' do context 'with Group SAML identity linked for a user' do
let(:saml_provider) { create(:saml_provider) } let(:saml_provider) { create(:saml_provider) }
let(:group) { saml_provider.group } let(:group) { saml_provider.group }
...@@ -23,12 +21,10 @@ RSpec.describe 'Groups > Members > List members' do ...@@ -23,12 +21,10 @@ RSpec.describe 'Groups > Members > List members' do
extern_uid: 'user2@example.com') extern_uid: 'user2@example.com')
end end
it 'shows user with SSO status badge' do it 'shows user with SSO status badge', :js do
visit group_group_members_path(group) visit group_group_members_path(group)
member = GroupMember.find_by(user: user2, group: group) expect(second_row).to have_content('SAML')
expect(find("#group_member_#{member.id}").find('.badge-info')).to have_content('SAML')
end end
end end
...@@ -40,12 +36,10 @@ RSpec.describe 'Groups > Members > List members' do ...@@ -40,12 +36,10 @@ RSpec.describe 'Groups > Members > List members' do
managed_group.add_guest(managed_user) managed_group.add_guest(managed_user)
end end
it 'shows user with "Managed Account" badge' do it 'shows user with "Managed Account" badge', :js do
visit group_group_members_path(managed_group) visit group_group_members_path(managed_group)
member = GroupMember.find_by(user: managed_user, group: managed_group) expect(first_row).to have_content('Managed Account')
expect(page).to have_selector("#group_member_#{member.id} .badge-info", text: 'Managed Account')
end end
end end
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access levels' do RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access levels' do
include WaitForRequests include WaitForRequests
include Spec::Support::Helpers::Features::MembersHelpers
let(:johndoe) { create(:user, name: 'John Doe') } let(:johndoe) { create(:user, name: 'John Doe') }
let(:maryjane) { create(:user, name: 'Mary Jane') } let(:maryjane) { create(:user, name: 'Mary Jane') }
...@@ -16,8 +17,6 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -16,8 +17,6 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
let!(:regular_member) { create(:group_member, :guest, group: group, user: maryjane, ldap: false) } let!(:regular_member) { create(:group_member, :guest, group: group, user: maryjane, ldap: false) }
before do before do
stub_feature_flags(vue_group_members_list: false)
# We need to actually activate the LDAP config otherwise `Group#ldap_synced?` will always be false! # We need to actually activate the LDAP config otherwise `Group#ldap_synced?` will always be false!
allow(Gitlab.config.ldap).to receive_messages(enabled: true) allow(Gitlab.config.ldap).to receive_messages(enabled: true)
...@@ -35,7 +34,7 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -35,7 +34,7 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
visit group_group_members_path(group) visit group_group_members_path(group)
within "#group_member_#{ldap_member.id}" do within first_row do
expect(page).not_to have_content 'LDAP' expect(page).not_to have_content 'LDAP'
expect(page).not_to have_button 'Guest' expect(page).not_to have_button 'Guest'
expect(page).not_to have_button 'Edit permissions' expect(page).not_to have_button 'Edit permissions'
...@@ -47,7 +46,7 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -47,7 +46,7 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
visit group_group_members_path(group) visit group_group_members_path(group)
within "#group_member_#{ldap_member.id}" do within first_row do
expect(page).to have_content 'LDAP' expect(page).to have_content 'LDAP'
expect(page).to have_button 'Guest', disabled: true expect(page).to have_button 'Guest', disabled: true
expect(page).to have_button 'Edit permissions' expect(page).to have_button 'Edit permissions'
...@@ -55,29 +54,26 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -55,29 +54,26 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
click_button 'Edit permissions' click_button 'Edit permissions'
end end
expect(page).to have_content ldap_override_message page.within('[role="dialog"]') do
expect(page).to have_content ldap_override_message
click_button 'Change permissions' click_button 'Edit permissions'
end
expect(page).not_to have_content ldap_override_message expect(page).not_to have_content ldap_override_message
expect(page).not_to have_button 'Change permissions'
within "#group_member_#{ldap_member.id}" do within first_row do
expect(page).not_to have_button 'Edit permissions' expect(page).not_to have_button 'Edit permissions'
expect(page).to have_button 'Guest', disabled: false expect(page).to have_button 'Guest', disabled: false
end end
refresh # controls should still be enabled after a refresh refresh # controls should still be enabled after a refresh
within "#group_member_#{ldap_member.id}" do within first_row do
expect(page).not_to have_button 'Edit permissions' expect(page).not_to have_button 'Edit permissions'
expect(page).to have_button 'Guest', disabled: false expect(page).to have_button 'Guest', disabled: false
click_button 'Guest' click_button 'Guest'
click_button 'Revert to LDAP group sync settings'
within '.dropdown-menu' do
click_link 'Revert to LDAP group sync settings'
end
wait_for_requests wait_for_requests
...@@ -85,16 +81,14 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -85,16 +81,14 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
expect(page).to have_button 'Edit permissions' expect(page).to have_button 'Edit permissions'
end end
within "#group_member_#{regular_member.id}" do within third_row do
expect(page).not_to have_content 'LDAP' expect(page).not_to have_content 'LDAP'
expect(page).not_to have_button 'Edit permissions' expect(page).not_to have_button 'Edit permissions'
expect(page).to have_button 'Guest', disabled: false expect(page).to have_button 'Guest', disabled: false
click_button 'Guest' click_button 'Guest'
within '.dropdown-menu' do expect(page).not_to have_content 'Revert to LDAP group sync settings'
expect(page).not_to have_content 'Revert to LDAP group sync settings'
end
end end
end end
end end
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"identifiers": [], "identifiers": [],
"links": [ "links": [
{ {
"url": "" "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
} }
] ]
}, },
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
"identifiers": [], "identifiers": [],
"links": [ "links": [
{ {
"url": "" "name": "CVE-1030",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030"
} }
] ]
}, },
...@@ -56,12 +57,6 @@ ...@@ -56,12 +57,6 @@
"location": {}, "location": {},
"identifiers": [], "identifiers": [],
"links": [ "links": [
{
"url": ""
},
{
"url": ""
}
] ]
} }
], ],
...@@ -122,4 +117,4 @@ ...@@ -122,4 +117,4 @@
"end_time": "placeholder-value", "end_time": "placeholder-value",
"status": "success" "status": "success"
} }
} }
\ No newline at end of file
...@@ -81,7 +81,7 @@ describe('LdapOverrideConfirmationModal', () => { ...@@ -81,7 +81,7 @@ describe('LdapOverrideConfirmationModal', () => {
it('displays modal body', () => { it('displays modal body', () => {
expect( expect(
getByText( getByText(
`${member.user.name} is currently a LDAP user. Editing their permissions will override the settings from the LDAP group sync.`, `${member.user.name} is currently an LDAP user. Editing their permissions will override the settings from the LDAP group sync.`,
).exists(), ).exists(),
).toBe(true); ).toBe(true);
}); });
......
...@@ -16,6 +16,8 @@ RSpec.describe Mutations::DastOnDemandScans::Create do ...@@ -16,6 +16,8 @@ RSpec.describe Mutations::DastOnDemandScans::Create do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -33,52 +35,6 @@ RSpec.describe Mutations::DastOnDemandScans::Create do ...@@ -33,52 +35,6 @@ RSpec.describe Mutations::DastOnDemandScans::Create do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a maintainer' do
it 'has no errors' do
project.add_maintainer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a developer' do
it 'has no errors' do
project.add_developer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a reporter' do
it 'raises an exception' do
project.add_reporter(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is a guest' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -152,14 +108,6 @@ RSpec.describe Mutations::DastOnDemandScans::Create do ...@@ -152,14 +108,6 @@ RSpec.describe Mutations::DastOnDemandScans::Create do
end end
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -16,6 +16,8 @@ RSpec.describe Mutations::DastScannerProfiles::Create do ...@@ -16,6 +16,8 @@ RSpec.describe Mutations::DastScannerProfiles::Create do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -35,12 +37,6 @@ RSpec.describe Mutations::DastScannerProfiles::Create do ...@@ -35,12 +37,6 @@ RSpec.describe Mutations::DastScannerProfiles::Create do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
group.add_owner(user) group.add_owner(user)
...@@ -83,14 +79,6 @@ RSpec.describe Mutations::DastScannerProfiles::Create do ...@@ -83,14 +79,6 @@ RSpec.describe Mutations::DastScannerProfiles::Create do
expect(response[:errors]).to include('Name has already been taken') expect(response[:errors]).to include('Name has already been taken')
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
...@@ -15,6 +15,8 @@ RSpec.describe Mutations::DastScannerProfiles::Delete do ...@@ -15,6 +15,8 @@ RSpec.describe Mutations::DastScannerProfiles::Delete do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -54,14 +56,6 @@ RSpec.describe Mutations::DastScannerProfiles::Delete do ...@@ -54,14 +56,6 @@ RSpec.describe Mutations::DastScannerProfiles::Delete do
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when deletion fails' do context 'when deletion fails' do
it 'returns an error' do it 'returns an error' do
allow_next_instance_of(::DastScannerProfiles::DestroyService) do |service| allow_next_instance_of(::DastScannerProfiles::DestroyService) do |service|
......
...@@ -22,6 +22,8 @@ RSpec.describe Mutations::DastScannerProfiles::Update do ...@@ -22,6 +22,8 @@ RSpec.describe Mutations::DastScannerProfiles::Update do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -47,20 +49,6 @@ RSpec.describe Mutations::DastScannerProfiles::Update do ...@@ -47,20 +49,6 @@ RSpec.describe Mutations::DastScannerProfiles::Update do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user can not run a DAST scan' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a DAST scan' do context 'when the user can run a DAST scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -108,14 +96,6 @@ RSpec.describe Mutations::DastScannerProfiles::Update do ...@@ -108,14 +96,6 @@ RSpec.describe Mutations::DastScannerProfiles::Update do
expect(subject[:errors]).to include('Scanner profile not found for given parameters') expect(subject[:errors]).to include('Scanner profile not found for given parameters')
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -17,6 +17,8 @@ RSpec.describe Mutations::DastSiteProfiles::Create do ...@@ -17,6 +17,8 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -35,28 +37,6 @@ RSpec.describe Mutations::DastSiteProfiles::Create do ...@@ -35,28 +37,6 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'returns the dast_site_profile id' do
group.add_owner(user)
expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end
end
context 'when the user is a maintainer' do
it 'returns the dast_site_profile id' do
project.add_maintainer(user)
expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -89,14 +69,6 @@ RSpec.describe Mutations::DastSiteProfiles::Create do ...@@ -89,14 +69,6 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
expect(response[:errors]).to include('Name has already been taken') expect(response[:errors]).to include('Name has already been taken')
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -15,6 +15,8 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do ...@@ -15,6 +15,8 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -32,52 +34,6 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do ...@@ -32,52 +34,6 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a maintainer' do
it 'has no errors' do
project.add_maintainer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a developer' do
it 'has no errors' do
project.add_developer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a reporter' do
it 'raises an exception' do
project.add_reporter(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is a guest' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -96,14 +52,6 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do ...@@ -96,14 +52,6 @@ RSpec.describe Mutations::DastSiteProfiles::Delete do
expect(subject[:errors]).to include('Name is weird') expect(subject[:errors]).to include('Name is weird')
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -18,6 +18,8 @@ RSpec.describe Mutations::DastSiteProfiles::Update do ...@@ -18,6 +18,8 @@ RSpec.describe Mutations::DastSiteProfiles::Update do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -37,52 +39,6 @@ RSpec.describe Mutations::DastSiteProfiles::Update do ...@@ -37,52 +39,6 @@ RSpec.describe Mutations::DastSiteProfiles::Update do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'has no errors' do
group.add_owner(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a maintainer' do
it 'has no errors' do
project.add_maintainer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a developer' do
it 'has no errors' do
project.add_developer(user)
expect(subject[:errors]).to be_empty
end
end
context 'when the user is a reporter' do
it 'raises an exception' do
project.add_reporter(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is a guest' do
it 'raises an exception' do
project.add_guest(user)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -96,14 +52,6 @@ RSpec.describe Mutations::DastSiteProfiles::Update do ...@@ -96,14 +52,6 @@ RSpec.describe Mutations::DastSiteProfiles::Update do
expect(dast_site_profile.dast_site.url).to eq(new_target_url) expect(dast_site_profile.dast_site.url).to eq(new_target_url)
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -18,6 +18,8 @@ RSpec.describe Mutations::DastSiteTokens::Create do ...@@ -18,6 +18,8 @@ RSpec.describe Mutations::DastSiteTokens::Create do
allow(SecureRandom).to receive(:uuid).and_return(uuid) allow(SecureRandom).to receive(:uuid).and_return(uuid)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -35,28 +37,6 @@ RSpec.describe Mutations::DastSiteTokens::Create do ...@@ -35,28 +37,6 @@ RSpec.describe Mutations::DastSiteTokens::Create do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'returns the dast_site_token id' do
group.add_owner(user)
expect(subject[:id]).to eq(dast_site_token.to_global_id)
end
end
context 'when the user is a maintainer' do
it 'returns the dast_site_token id' do
project.add_maintainer(user)
expect(subject[:id]).to eq(dast_site_token.to_global_id)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -94,14 +74,6 @@ RSpec.describe Mutations::DastSiteTokens::Create do ...@@ -94,14 +74,6 @@ RSpec.describe Mutations::DastSiteTokens::Create do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -17,6 +17,8 @@ RSpec.describe Mutations::DastSiteValidations::Create do ...@@ -17,6 +17,8 @@ RSpec.describe Mutations::DastSiteValidations::Create do
stub_licensed_features(security_on_demand_scans: true) stub_licensed_features(security_on_demand_scans: true)
end end
specify { expect(described_class).to require_graphql_authorizations(:create_on_demand_dast_scan) }
describe '#resolve' do describe '#resolve' do
subject do subject do
mutation.resolve( mutation.resolve(
...@@ -36,28 +38,6 @@ RSpec.describe Mutations::DastSiteValidations::Create do ...@@ -36,28 +38,6 @@ RSpec.describe Mutations::DastSiteValidations::Create do
end end
end end
context 'when the user is not associated with the project' do
it 'raises an exception' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user is an owner' do
it 'returns the dast_site_validation id' do
group.add_owner(user)
expect(subject[:id]).to eq(dast_site_validation.to_global_id)
end
end
context 'when the user is a maintainer' do
it 'returns the dast_site_validation id' do
project.add_maintainer(user)
expect(subject[:id]).to eq(dast_site_validation.to_global_id)
end
end
context 'when the user can run a dast scan' do context 'when the user can run a dast scan' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -78,14 +58,6 @@ RSpec.describe Mutations::DastSiteValidations::Create do ...@@ -78,14 +58,6 @@ RSpec.describe Mutations::DastSiteValidations::Create do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
context 'when on demand scan licensed feature is not available' do
it 'raises an exception' do
stub_licensed_features(security_on_demand_scans: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end end
end end
end end
......
...@@ -78,5 +78,16 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -78,5 +78,16 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
expect(empty_report.scan).to be(nil) expect(empty_report.scan).to be(nil)
end end
end end
context 'parsing links' do
it 'returns links object for each finding', :aggregate_failures do
links = report.findings.flat_map(&:links)
expect(links.map(&:url)).to match_array(['https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030'])
expect(links.map(&:name)).to match_array([nil, 'CVE-1030'])
expect(links.size).to eq(2)
expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link)
end
end
end end
end end
...@@ -10,6 +10,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do ...@@ -10,6 +10,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
let(:primary_identifier) { create(:ci_reports_security_identifier) } let(:primary_identifier) { create(:ci_reports_security_identifier) }
let(:other_identifier) { create(:ci_reports_security_identifier) } let(:other_identifier) { create(:ci_reports_security_identifier) }
let(:link) { create(:ci_reports_security_link) }
let(:scanner) { create(:ci_reports_security_scanner) } let(:scanner) { create(:ci_reports_security_scanner) }
let(:location) { create(:ci_reports_security_locations_sast) } let(:location) { create(:ci_reports_security_locations_sast) }
...@@ -18,6 +19,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do ...@@ -18,6 +19,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
compare_key: 'this_is_supposed_to_be_a_unique_value', compare_key: 'this_is_supposed_to_be_a_unique_value',
confidence: :medium, confidence: :medium,
identifiers: [primary_identifier, other_identifier], identifiers: [primary_identifier, other_identifier],
links: [link],
location: location, location: location,
metadata_version: 'sast:1.0', metadata_version: 'sast:1.0',
name: 'Cipher with no integrity', name: 'Cipher with no integrity',
...@@ -39,6 +41,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do ...@@ -39,6 +41,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
confidence: :medium, confidence: :medium,
project_fingerprint: '9a73f32d58d87d94e3dc61c4c1a94803f6014258', project_fingerprint: '9a73f32d58d87d94e3dc61c4c1a94803f6014258',
identifiers: [primary_identifier, other_identifier], identifiers: [primary_identifier, other_identifier],
links: [link],
location: location, location: location,
metadata_version: 'sast:1.0', metadata_version: 'sast:1.0',
name: 'Cipher with no integrity', name: 'Cipher with no integrity',
...@@ -84,6 +87,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do ...@@ -84,6 +87,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
compare_key: occurrence.compare_key, compare_key: occurrence.compare_key,
confidence: occurrence.confidence, confidence: occurrence.confidence,
identifiers: occurrence.identifiers, identifiers: occurrence.identifiers,
links: occurrence.links,
location: occurrence.location, location: occurrence.location,
metadata_version: occurrence.metadata_version, metadata_version: occurrence.metadata_version,
name: occurrence.name, name: occurrence.name,
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Link do
subject(:security_link) { described_class.new(name: 'CVE-2020-0202', url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0202') }
describe '#initialize' do
context 'when all params are given' do
it 'initializes an instance' do
expect { subject }.not_to raise_error
expect(subject).to have_attributes(
name: 'CVE-2020-0202',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0202'
)
end
end
describe '#to_hash' do
it 'returns expected hash' do
expect(security_link.to_hash).to eq(
{
name: 'CVE-2020-0202',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-0202'
}
)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Vulnerabilities::FindingLink do
describe 'associations' do
it { is_expected.to belong_to(:finding).class_name('Vulnerabilities::Finding') }
end
describe 'validations' do
let_it_be(:link) { create(:finding_link) }
it { is_expected.to validate_presence_of(:url) }
it { is_expected.to validate_length_of(:url).is_at_most(255) }
it { is_expected.to validate_length_of(:name).is_at_most(2048) }
it { is_expected.to validate_presence_of(:finding) }
end
end
...@@ -16,6 +16,7 @@ RSpec.describe Vulnerabilities::Finding do ...@@ -16,6 +16,7 @@ RSpec.describe Vulnerabilities::Finding do
it { is_expected.to have_many(:finding_pipelines).class_name('Vulnerabilities::FindingPipeline').with_foreign_key('occurrence_id') } it { is_expected.to have_many(:finding_pipelines).class_name('Vulnerabilities::FindingPipeline').with_foreign_key('occurrence_id') }
it { is_expected.to have_many(:identifiers).class_name('Vulnerabilities::Identifier') } it { is_expected.to have_many(:identifiers).class_name('Vulnerabilities::Identifier') }
it { is_expected.to have_many(:finding_identifiers).class_name('Vulnerabilities::FindingIdentifier').with_foreign_key('occurrence_id') } it { is_expected.to have_many(:finding_identifiers).class_name('Vulnerabilities::FindingIdentifier').with_foreign_key('occurrence_id') }
it { is_expected.to have_many(:finding_links).class_name('Vulnerabilities::FindingLink').with_foreign_key('vulnerability_occurrence_id') }
end end
describe 'validations' do describe 'validations' do
...@@ -405,6 +406,33 @@ RSpec.describe Vulnerabilities::Finding do ...@@ -405,6 +406,33 @@ RSpec.describe Vulnerabilities::Finding do
end end
end end
describe '#links' do
let_it_be(:finding, reload: true) do
create(
:vulnerabilities_finding,
raw_metadata: {
links: [{ url: 'https://raw.gitlab.com', name: 'raw_metadata_link' }]
}.to_json
)
end
subject(:links) { finding.links }
context 'when there are no finding links' do
it 'returns links from raw_metadata' do
expect(links).to eq([{ 'url' => 'https://raw.gitlab.com', 'name' => 'raw_metadata_link' }])
end
end
context 'when there are finding links assigned to given finding' do
let_it_be(:finding_link) { create(:finding_link, name: 'finding_link', url: 'https://link.gitlab.com', finding: finding) }
it 'returns links from finding link' do
expect(links).to eq([{ 'url' => 'https://link.gitlab.com', 'name' => 'finding_link' }])
end
end
end
describe 'feedback' do describe 'feedback' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let(:finding) do let(:finding) do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DastScannerProfilePolicy do
it_behaves_like 'a dast on-demand scan policy' do
let_it_be(:record) { create(:dast_scanner_profile, project: project) }
end
end
...@@ -3,43 +3,7 @@ ...@@ -3,43 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe DastSiteProfilePolicy do RSpec.describe DastSiteProfilePolicy do
describe 'create_on_demand_dast_scan' do it_behaves_like 'a dast on-demand scan policy' do
let(:dast_site_profile) { create(:dast_site_profile) } let_it_be(:record) { create(:dast_site_profile, project: project) }
let(:project) { dast_site_profile.project }
let(:user) { create(:user) }
subject { described_class.new(user, dast_site_profile) }
before do
stub_licensed_features(security_on_demand_scans: true)
end
context 'when a user does not have access to the project' do
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user does not have access to dast_site_profiles' do
before do
project.add_guest(user)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user has access dast_site_profiles' do
before do
project.add_developer(user)
end
it { is_expected.to be_allowed(:create_on_demand_dast_scan) }
context 'when on demand scan licensed feature is not available' do
before do
stub_licensed_features(security_on_demand_scans: false)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
end
end end
end end
...@@ -3,43 +3,7 @@ ...@@ -3,43 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe DastSiteValidationPolicy do RSpec.describe DastSiteValidationPolicy do
describe 'create_on_demand_dast_scan' do it_behaves_like 'a dast on-demand scan policy' do
let_it_be(:dast_site_validation, reload: true) { create(:dast_site_validation) } let_it_be(:record) { create(:dast_site_validation, dast_site_token: create(:dast_site_token, project: project)) }
let_it_be(:project) { dast_site_validation.dast_site_token.project }
let_it_be(:user) { create(:user) }
subject { described_class.new(user, dast_site_validation) }
before do
stub_licensed_features(security_on_demand_scans: true)
end
context 'when a user does not have access to the project' do
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user does not have access to dast_site_validations' do
before do
project.add_guest(user)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
context 'when a user has access dast_site_validations' do
before do
project.add_developer(user)
end
it { is_expected.to be_allowed(:create_on_demand_dast_scan) }
context 'when on demand scan licensed feature is not available' do
before do
stub_licensed_features(security_on_demand_scans: false)
end
it { is_expected.to be_disallowed(:create_on_demand_dast_scan) }
end
end
end end
end end
...@@ -22,6 +22,7 @@ RSpec.describe API::Search, factory_default: :keep do ...@@ -22,6 +22,7 @@ RSpec.describe API::Search, factory_default: :keep do
it 'returns a different result for each page' do it 'returns a different result for each page' do
get api(endpoint, user), params: { scope: scope, search: search, page: 1, per_page: 1 } get api(endpoint, user), params: { scope: scope, search: search, page: 1, per_page: 1 }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.count).to eq(1) expect(json_response.count).to eq(1)
first = json_response.first first = json_response.first
...@@ -37,6 +38,30 @@ RSpec.describe API::Search, factory_default: :keep do ...@@ -37,6 +38,30 @@ RSpec.describe API::Search, factory_default: :keep do
end end
end end
shared_examples 'orderable by created_at' do |scope:|
it 'allows ordering results by created_at asc' do
get api(endpoint, user), params: { scope: scope, search: '*', order_by: 'created_at', sort: 'asc' }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.count).to be > 1
created_ats = json_response.map { |r| Time.parse(r['created_at']) }
expect(created_ats).to eq(created_ats.sort)
end
it 'allows ordering results by created_at desc' do
get api(endpoint, user), params: { scope: scope, search: '*', order_by: 'created_at', sort: 'desc' }
expect(response).to have_gitlab_http_status(:success)
expect(json_response.count).to be > 1
created_ats = json_response.map { |r| Time.parse(r['created_at']) }
expect(created_ats).to eq(created_ats.sort.reverse)
end
end
shared_examples 'elasticsearch disabled' do shared_examples 'elasticsearch disabled' do
it 'returns 400 error for wiki_blobs, blobs and commits scope' do it 'returns 400 error for wiki_blobs, blobs and commits scope' do
get api(endpoint, user), params: { scope: 'wiki_blobs', search: 'awesome' } get api(endpoint, user), params: { scope: 'wiki_blobs', search: 'awesome' }
...@@ -61,6 +86,7 @@ RSpec.describe API::Search, factory_default: :keep do ...@@ -61,6 +86,7 @@ RSpec.describe API::Search, factory_default: :keep do
end end
it_behaves_like 'pagination', scope: 'merge_requests' it_behaves_like 'pagination', scope: 'merge_requests'
it_behaves_like 'orderable by created_at', scope: 'merge_requests'
it 'avoids N+1 queries' do it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new { get api(endpoint, user), params: { scope: 'merge_requests', search: '*' } } control = ActiveRecord::QueryRecorder.new { get api(endpoint, user), params: { scope: 'merge_requests', search: '*' } }
...@@ -213,6 +239,7 @@ RSpec.describe API::Search, factory_default: :keep do ...@@ -213,6 +239,7 @@ RSpec.describe API::Search, factory_default: :keep do
end end
it_behaves_like 'pagination', scope: 'issues' it_behaves_like 'pagination', scope: 'issues'
it_behaves_like 'orderable by created_at', scope: 'issues'
end end
unless level == :project unless level == :project
......
...@@ -37,7 +37,7 @@ RSpec.describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -37,7 +37,7 @@ RSpec.describe Issues::CreateFromVulnerabilityDataService, '#execute' do
it 'returns expected error' do it 'returns expected error' do
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create issue") expect(result[:message]).to eq("User is not permitted to create issue")
end end
end end
...@@ -47,7 +47,7 @@ RSpec.describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -47,7 +47,7 @@ RSpec.describe Issues::CreateFromVulnerabilityDataService, '#execute' do
it 'returns expected error' do it 'returns expected error' do
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create issue") expect(result[:message]).to eq("User is not permitted to create issue")
end end
end end
......
...@@ -80,7 +80,7 @@ RSpec.describe Issues::CreateFromVulnerabilityService, '#execute' do ...@@ -80,7 +80,7 @@ RSpec.describe Issues::CreateFromVulnerabilityService, '#execute' do
it 'returns expected error' do it 'returns expected error' do
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create issue") expect(result[:message]).to eq("User is not permitted to create issue")
end end
end end
...@@ -89,7 +89,7 @@ RSpec.describe Issues::CreateFromVulnerabilityService, '#execute' do ...@@ -89,7 +89,7 @@ RSpec.describe Issues::CreateFromVulnerabilityService, '#execute' do
it 'returns expected error' do it 'returns expected error' do
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create issue") expect(result[:message]).to eq("User is not permitted to create issue")
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment