Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
888564d614
commit
cb37aee989
47 changed files with 628 additions and 277 deletions
|
@ -4,9 +4,10 @@ import { __, s__ } from '~/locale';
|
|||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
||||
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
|
||||
import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
|
||||
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
|
||||
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
|
||||
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
|
||||
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, LICENSE_ULTIMATE } from './constants';
|
||||
import FeatureCard from './feature_card.vue';
|
||||
import TrainingProviderList from './training_provider_list.vue';
|
||||
import UpgradeBanner from './upgrade_banner.vue';
|
||||
|
@ -50,6 +51,14 @@ export default {
|
|||
TrainingProviderList,
|
||||
},
|
||||
inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
|
||||
apollo: {
|
||||
currentLicensePlan: {
|
||||
query: currentLicenseQuery,
|
||||
update({ currentLicense }) {
|
||||
return currentLicense?.plan;
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
augmentedSecurityFeatures: {
|
||||
type: Array,
|
||||
|
@ -89,6 +98,7 @@ export default {
|
|||
return {
|
||||
autoDevopsEnabledAlertDismissedProjects: [],
|
||||
errorMessage: '',
|
||||
currentLicensePlan: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -109,6 +119,9 @@ export default {
|
|||
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath)
|
||||
);
|
||||
},
|
||||
shouldShowVulnerabilityManagementTab() {
|
||||
return this.currentLicensePlan === LICENSE_ULTIMATE;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dismissAutoDevopsEnabledAlert() {
|
||||
|
@ -250,6 +263,7 @@ export default {
|
|||
</section-layout>
|
||||
</gl-tab>
|
||||
<gl-tab
|
||||
v-if="shouldShowVulnerabilityManagementTab"
|
||||
data-testid="vulnerability-management-tab"
|
||||
:title="$options.i18n.vulnerabilityManagement"
|
||||
query-param-value="vulnerability-management"
|
||||
|
|
|
@ -310,3 +310,7 @@ export const TEMP_PROVIDER_URLS = {
|
|||
Kontra: 'https://application.security/',
|
||||
[__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
|
||||
};
|
||||
|
||||
export const LICENSE_ULTIMATE = 'ultimate';
|
||||
export const LICENSE_FREE = 'free';
|
||||
export const LICENSE_PREMIUM = 'premium';
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
query getCurrentLicensePlan {
|
||||
currentLicense {
|
||||
id
|
||||
plan
|
||||
}
|
||||
}
|
|
@ -64,6 +64,10 @@
|
|||
border-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.canary-badge {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.project-item-select {
|
||||
right: auto;
|
||||
left: 0;
|
||||
|
|
|
@ -807,6 +807,9 @@ input {
|
|||
margin: 4px 2px 4px -12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navbar-gitlab .header-content .title .canary-badge {
|
||||
margin-left: -8px;
|
||||
}
|
||||
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
|
|
@ -792,6 +792,9 @@ input {
|
|||
margin: 4px 2px 4px -12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navbar-gitlab .header-content .title .canary-badge {
|
||||
margin-left: -8px;
|
||||
}
|
||||
.navbar-gitlab .header-content .navbar-collapse > ul.nav > li:not(.d-none) {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ class Projects::ServicePingController < Projects::ApplicationController
|
|||
return render_404 unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
|
||||
|
||||
Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_success_count
|
||||
Gitlab::UsageDataCounters::EditorUniqueCounter.track_live_preview_edit_action(author: current_user)
|
||||
|
||||
head(200)
|
||||
end
|
||||
|
|
30
app/models/concerns/integrations/loggable.rb
Normal file
30
app/models/concerns/integrations/loggable.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
module Loggable
|
||||
def log_info(message, params = {})
|
||||
message = build_message(message, params)
|
||||
|
||||
logger.info(message)
|
||||
end
|
||||
|
||||
def log_error(message, params = {})
|
||||
message = build_message(message, params)
|
||||
|
||||
logger.error(message)
|
||||
end
|
||||
|
||||
def build_message(message, params = {})
|
||||
{
|
||||
integration_class: self.class.name,
|
||||
project_id: project&.id,
|
||||
project_path: project&.full_path,
|
||||
message: message
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
def logger
|
||||
Gitlab::IntegrationsLogger
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProjectServicesLoggable
|
||||
def log_info(message, params = {})
|
||||
message = build_message(message, params)
|
||||
|
||||
logger.info(message)
|
||||
end
|
||||
|
||||
def log_error(message, params = {})
|
||||
message = build_message(message, params)
|
||||
|
||||
logger.error(message)
|
||||
end
|
||||
|
||||
def build_message(message, params = {})
|
||||
{
|
||||
service_class: self.class.name,
|
||||
project_id: project&.id,
|
||||
project_path: project&.full_path,
|
||||
message: message
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
def logger
|
||||
Gitlab::ProjectServiceLogger
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@
|
|||
class Integration < ApplicationRecord
|
||||
include Sortable
|
||||
include Importable
|
||||
include ProjectServicesLoggable
|
||||
include Integrations::Loggable
|
||||
include Integrations::HasDataFields
|
||||
include Integrations::ResetSecretFields
|
||||
include FromUnion
|
||||
|
|
|
@ -12,8 +12,11 @@ class IssuePolicy < IssuablePolicy
|
|||
@user && IssueCollection.new([@subject]).visible_to(@user).any?
|
||||
end
|
||||
|
||||
desc "User can read contacts belonging to the issue group"
|
||||
condition(:can_read_crm_contacts, scope: :subject) { @user.can?(:read_crm_contact, @subject.project.root_ancestor) }
|
||||
desc "Project belongs to a group, crm is enabled and user can read contacts in the root group"
|
||||
condition(:can_read_crm_contacts, scope: :subject) do
|
||||
subject.project.group&.crm_enabled? &&
|
||||
@user.can?(:read_crm_contact, @subject.project.root_ancestor)
|
||||
end
|
||||
|
||||
desc "Issue is confidential"
|
||||
condition(:confidential, scope: :subject) { @subject.confidential? }
|
||||
|
@ -81,6 +84,10 @@ class IssuePolicy < IssuablePolicy
|
|||
enable :set_confidentiality
|
||||
end
|
||||
|
||||
rule { can_read_crm_contacts }.policy do
|
||||
enable :read_crm_contacts
|
||||
end
|
||||
|
||||
rule { can?(:set_issue_metadata) & can_read_crm_contacts }.policy do
|
||||
enable :set_issue_crm_contacts
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
|
|||
end
|
||||
|
||||
expose :show_crm_contacts do |issuable|
|
||||
current_user&.can?(:read_crm_contact, issuable.project.root_ancestor) &&
|
||||
current_user&.can?(:read_crm_contacts, issuable) &&
|
||||
CustomerRelations::Contact.exists_for_group?(issuable.project.root_ancestor)
|
||||
end
|
||||
end
|
||||
|
|
38
app/services/concerns/group_linkable.rb
Normal file
38
app/services/concerns/group_linkable.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module GroupLinkable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def execute
|
||||
return error('Not Found', 404) unless valid_to_create?
|
||||
|
||||
build_link
|
||||
|
||||
if link.save
|
||||
after_successful_save
|
||||
success(link: link)
|
||||
else
|
||||
error(link.errors.full_messages.to_sentence, 409)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :shared_with_group, :link
|
||||
|
||||
def sharing_allowed?
|
||||
sharing_outside_hierarchy_allowed? || within_hierarchy?
|
||||
end
|
||||
|
||||
def sharing_outside_hierarchy_allowed?
|
||||
!root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
|
||||
end
|
||||
|
||||
def within_hierarchy?
|
||||
root_ancestor.self_and_descendants_ids.include?(shared_with_group.id)
|
||||
end
|
||||
|
||||
def after_successful_save
|
||||
setup_authorizations
|
||||
end
|
||||
end
|
|
@ -172,7 +172,7 @@ module Git
|
|||
else
|
||||
# This service runs in Sidekiq, so this shouldn't ever be
|
||||
# called, but this is included just in case.
|
||||
Gitlab::ProjectServiceLogger
|
||||
Gitlab::IntegrationsLogger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,50 +3,35 @@
|
|||
module Groups
|
||||
module GroupLinks
|
||||
class CreateService < Groups::BaseService
|
||||
def initialize(shared_group, shared_with_group, user, params)
|
||||
@shared_group = shared_group
|
||||
super(shared_with_group, user, params)
|
||||
end
|
||||
include GroupLinkable
|
||||
|
||||
def execute
|
||||
unless shared_with_group && shared_group &&
|
||||
can?(current_user, :admin_group_member, shared_group) &&
|
||||
can?(current_user, :read_group, shared_with_group) &&
|
||||
sharing_allowed?
|
||||
return error('Not Found', 404)
|
||||
end
|
||||
def initialize(group, shared_with_group, user, params)
|
||||
@shared_with_group = shared_with_group
|
||||
|
||||
link = GroupGroupLink.new(
|
||||
shared_group: shared_group,
|
||||
shared_with_group: shared_with_group,
|
||||
group_access: params[:shared_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
|
||||
if link.save
|
||||
shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
|
||||
success(link: link)
|
||||
else
|
||||
error(link.errors.full_messages.to_sentence, 409)
|
||||
end
|
||||
super(group, user, params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :shared_group
|
||||
delegate :root_ancestor, to: :group
|
||||
|
||||
alias_method :shared_with_group, :group
|
||||
|
||||
def sharing_allowed?
|
||||
sharing_outside_hierarchy_allowed? || within_hierarchy?
|
||||
def valid_to_create?
|
||||
can?(current_user, :admin_group_member, group) &&
|
||||
can?(current_user, :read_group, shared_with_group) &&
|
||||
sharing_allowed?
|
||||
end
|
||||
|
||||
def sharing_outside_hierarchy_allowed?
|
||||
!shared_group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
|
||||
def build_link
|
||||
@link = GroupGroupLink.new(
|
||||
shared_group: group,
|
||||
shared_with_group: shared_with_group,
|
||||
group_access: params[:shared_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
end
|
||||
|
||||
def within_hierarchy?
|
||||
shared_group.root_ancestor.self_and_descendants_ids.include?(shared_with_group.id)
|
||||
def setup_authorizations
|
||||
shared_with_group.refresh_members_authorized_projects(blocking: false, direct_members_only: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Jira
|
||||
module Requests
|
||||
class Base
|
||||
include ProjectServicesLoggable
|
||||
include Integrations::Loggable
|
||||
|
||||
JIRA_API_VERSION = 2
|
||||
# Limit the size of the JSON error message we will attempt to parse, as the JSON is external input.
|
||||
|
|
|
@ -39,7 +39,7 @@ module JiraConnect
|
|||
end
|
||||
|
||||
def logger
|
||||
Gitlab::ProjectServiceLogger
|
||||
Gitlab::IntegrationsLogger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,26 +3,31 @@
|
|||
module Projects
|
||||
module GroupLinks
|
||||
class CreateService < BaseService
|
||||
def execute(group)
|
||||
return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group)
|
||||
include GroupLinkable
|
||||
|
||||
link = project.project_group_links.new(
|
||||
group: group,
|
||||
group_access: params[:link_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
def initialize(project, shared_with_group, user, params)
|
||||
@shared_with_group = shared_with_group
|
||||
|
||||
if link.save
|
||||
setup_authorizations(group)
|
||||
success(link: link)
|
||||
else
|
||||
error(link.errors.full_messages.to_sentence, 409)
|
||||
end
|
||||
super(project, user, params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_authorizations(group)
|
||||
delegate :root_ancestor, to: :project
|
||||
|
||||
def valid_to_create?
|
||||
can?(current_user, :read_namespace, shared_with_group) && sharing_allowed?
|
||||
end
|
||||
|
||||
def build_link
|
||||
@link = project.project_group_links.new(
|
||||
group: shared_with_group,
|
||||
group_access: params[:link_group_access],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
end
|
||||
|
||||
def setup_authorizations
|
||||
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project.id)
|
||||
|
||||
# AuthorizedProjectsWorker uses an exclusive lease per user but
|
||||
|
@ -30,7 +35,7 @@ module Projects
|
|||
# compare the inconsistency rates of both approaches, we still run
|
||||
# AuthorizedProjectsWorker but with some delay and lower urgency as a
|
||||
# safety net.
|
||||
group.refresh_members_authorized_projects(
|
||||
shared_with_group.refresh_members_authorized_projects(
|
||||
blocking: false,
|
||||
priority: UserProjectAccessChangedService::LOW_PRIORITY
|
||||
)
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358831
|
|||
milestone: '14.10'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
26
config/metrics/counts_28d/20220428154012_live_preview.yml
Normal file
26
config/metrics/counts_28d/20220428154012_live_preview.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: usage_activity_by_stage_monthly.create.action_monthly_active_users_live_preview_edit
|
||||
description: Count of monthly unique users that successfully connect to Live Preview
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::editor
|
||||
product_category: web_ide
|
||||
value_type: number
|
||||
status: active
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85420
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- g_edit_by_live_preview
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
performance_indicator_type: []
|
||||
milestone: "15.0"
|
14
data/removals/15_0/15-0-removal-testcoveragesetting.yml
Normal file
14
data/removals/15_0/15-0-removal-testcoveragesetting.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
- name: "Test coverage project CI/CD setting" # The headline announcing the removal. i.e. "`CI_PROJECT_CONFIG_PATH` removed in Gitlab 14.0"
|
||||
announcement_milestone: "14.8" # The milestone when this feature was deprecated.
|
||||
announcement_date: "2022-03-22" # The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post.
|
||||
removal_milestone: "15.0" # The milestone when this feature is being removed.
|
||||
removal_date: "2022-05-22" # This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed.
|
||||
breaking_change: true # Change to true if this removal is a breaking change.
|
||||
reporter: exampleuser # GitLab username of the person reporting the removal
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
To specify a test coverage pattern, beginning in GitLab 15.0 the
|
||||
[project setting for test coverage parsing](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request-deprecated)
|
||||
has been removed.
|
||||
|
||||
To set test coverage parsing, use the project’s `.gitlab-ci.yml` file by providing a regular expression with the
|
||||
[`coverage` keyword](https://docs.gitlab.com/ee/ci/yaml/index.html#coverage).
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddGroupInheritanceTypeToPeAuthorizable < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
add_column :protected_environment_deploy_access_levels,
|
||||
:group_inheritance_type,
|
||||
:smallint,
|
||||
default: 0, limit: 2, null: false
|
||||
add_column :protected_environment_approval_rules,
|
||||
:group_inheritance_type,
|
||||
:smallint,
|
||||
default: 0, limit: 2, null: false
|
||||
end
|
||||
end
|
1
db/schema_migrations/20220420173247
Normal file
1
db/schema_migrations/20220420173247
Normal file
|
@ -0,0 +1 @@
|
|||
a4113363674f268a3beaef22e29b2aba4e5ba7566bc47dc5676ddc8f8733d331
|
|
@ -19786,6 +19786,7 @@ CREATE TABLE protected_environment_approval_rules (
|
|||
updated_at timestamp with time zone NOT NULL,
|
||||
access_level smallint,
|
||||
required_approvals smallint NOT NULL,
|
||||
group_inheritance_type smallint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT chk_rails_bed75249bc CHECK ((((access_level IS NOT NULL) AND (group_id IS NULL) AND (user_id IS NULL)) OR ((user_id IS NOT NULL) AND (access_level IS NULL) AND (group_id IS NULL)) OR ((group_id IS NOT NULL) AND (user_id IS NULL) AND (access_level IS NULL)))),
|
||||
CONSTRAINT chk_rails_cfa90ae3b5 CHECK ((required_approvals > 0))
|
||||
);
|
||||
|
@ -19806,7 +19807,8 @@ CREATE TABLE protected_environment_deploy_access_levels (
|
|||
access_level integer DEFAULT 40,
|
||||
protected_environment_id integer NOT NULL,
|
||||
user_id integer,
|
||||
group_id integer
|
||||
group_id integer,
|
||||
group_inheritance_type smallint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE protected_environment_deploy_access_levels_id_seq
|
||||
|
|
|
@ -104,6 +104,21 @@ A feature flag was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11
|
|||
|
||||
In GitLab 15.0, we will remove the feature flag, and you must always authenticate when you use the Dependency Proxy.
|
||||
|
||||
### Test coverage project CI/CD setting
|
||||
|
||||
WARNING:
|
||||
This feature was changed or removed in 15.0
|
||||
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||
changes to your code, settings, or workflow.
|
||||
|
||||
To specify a test coverage pattern, beginning in GitLab 15.0 the
|
||||
[project setting for test coverage parsing](https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-to-a-merge-request-deprecated)
|
||||
has been removed.
|
||||
|
||||
To set test coverage parsing, use the project’s `.gitlab-ci.yml` file by providing a regular expression with the
|
||||
[`coverage` keyword](https://docs.gitlab.com/ee/ci/yaml/index.html#coverage).
|
||||
|
||||
### Update to the Container Registry group-level API
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -199,8 +199,8 @@ and the CI YAML file:
|
|||
dependencies:
|
||||
- plan
|
||||
when: manual
|
||||
only:
|
||||
- master
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
1. Push your project to GitLab, which triggers a CI job pipeline. This pipeline
|
||||
|
|
|
@ -418,7 +418,6 @@ module API
|
|||
optional :expires_at, type: Date, desc: 'Share expiration date'
|
||||
end
|
||||
post ":id/share", feature_category: :subgroups do
|
||||
shared_group = find_group!(params[:id])
|
||||
shared_with_group = find_group!(params[:group_id])
|
||||
|
||||
group_link_create_params = {
|
||||
|
@ -426,11 +425,11 @@ module API
|
|||
expires_at: params[:expires_at]
|
||||
}
|
||||
|
||||
result = ::Groups::GroupLinks::CreateService.new(shared_group, shared_with_group, current_user, group_link_create_params).execute
|
||||
shared_group.preload_shared_group_links
|
||||
result = ::Groups::GroupLinks::CreateService.new(user_group, shared_with_group, current_user, group_link_create_params).execute
|
||||
user_group.preload_shared_group_links
|
||||
|
||||
if result[:status] == :success
|
||||
present shared_group, with: Entities::GroupDetail, current_user: current_user
|
||||
present user_group, with: Entities::GroupDetail, current_user: current_user
|
||||
else
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
end
|
||||
|
|
|
@ -575,14 +575,14 @@ module API
|
|||
end
|
||||
post ":id/share", feature_category: :authentication_and_authorization do
|
||||
authorize! :admin_project, user_project
|
||||
group = Group.find_by_id(params[:group_id])
|
||||
shared_with_group = Group.find_by_id(params[:group_id])
|
||||
|
||||
unless user_project.allowed_to_share_with_group?
|
||||
break render_api_error!("The project sharing with group is disabled", 400)
|
||||
end
|
||||
|
||||
result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false))
|
||||
.execute(group)
|
||||
result = ::Projects::GroupLinks::CreateService
|
||||
.new(user_project, shared_with_group, current_user, declared_params(include_missing: false)).execute
|
||||
|
||||
if result[:status] == :success
|
||||
present result[:link], with: Entities::ProjectGroupLink
|
||||
|
|
|
@ -63,10 +63,6 @@ module BulkImports
|
|||
pipeline: BulkImports::Projects::Pipelines::ProtectedBranchesPipeline,
|
||||
stage: 4
|
||||
},
|
||||
ci_pipelines: {
|
||||
pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
|
||||
stage: 4
|
||||
},
|
||||
project_feature: {
|
||||
pipeline: BulkImports::Projects::Pipelines::ProjectFeaturePipeline,
|
||||
stage: 4
|
||||
|
@ -83,6 +79,10 @@ module BulkImports
|
|||
pipeline: BulkImports::Projects::Pipelines::ReleasesPipeline,
|
||||
stage: 4
|
||||
},
|
||||
ci_pipelines: {
|
||||
pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
|
||||
stage: 5
|
||||
},
|
||||
wiki: {
|
||||
pipeline: BulkImports::Common::Pipelines::WikiPipeline,
|
||||
stage: 5
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class ProjectServiceLogger < Gitlab::JsonLogger
|
||||
class IntegrationsLogger < Gitlab::JsonLogger
|
||||
def self.file_name_noext
|
||||
'integrations_json'
|
||||
end
|
|
@ -8,6 +8,7 @@ module Gitlab
|
|||
EDIT_BY_WEB_IDE = 'g_edit_by_web_ide'
|
||||
EDIT_BY_SSE = 'g_edit_by_sse'
|
||||
EDIT_CATEGORY = 'ide_edit'
|
||||
EDIT_BY_LIVE_PREVIEW = 'g_edit_by_live_preview'
|
||||
|
||||
class << self
|
||||
def track_web_ide_edit_action(author:, time: Time.zone.now)
|
||||
|
@ -47,6 +48,10 @@ module Gitlab
|
|||
count_unique(EDIT_BY_SSE, date_from, date_to)
|
||||
end
|
||||
|
||||
def track_live_preview_edit_action(author:, time: Time.zone.now)
|
||||
track_unique_action(EDIT_BY_LIVE_PREVIEW, author, time)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_action(action, author, time)
|
||||
|
|
|
@ -40,6 +40,11 @@
|
|||
redis_slot: edit
|
||||
expiry: 29
|
||||
aggregation: daily
|
||||
- name: g_edit_by_live_preview
|
||||
category: ide_edit
|
||||
redis_slot: edit
|
||||
expiry: 29
|
||||
aggregation: daily
|
||||
- name: i_search_total
|
||||
category: search
|
||||
redis_slot: search
|
||||
|
|
|
@ -11725,25 +11725,25 @@ msgstr ""
|
|||
msgid "DastSiteValidation|Revoke validation"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 1 - Choose site validation method"
|
||||
msgid "DastSiteValidation|Step 1 - Choose site validation method."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 2 - Add following HTTP header to your site"
|
||||
msgid "DastSiteValidation|Step 2 - Add the following HTTP header to your site."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 2 - Add following meta tag to your site"
|
||||
msgid "DastSiteValidation|Step 2 - Add the following meta tag to your site."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 2 - Add following text to the target site"
|
||||
msgid "DastSiteValidation|Step 2 - Download the following text file, then upload it to the target site."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 3 - Confirm header location and validate"
|
||||
msgid "DastSiteValidation|Step 3 - Confirm header location."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 3 - Confirm meta tag location and validate"
|
||||
msgid "DastSiteValidation|Step 3 - Confirm meta tag location."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Step 3 - Confirm text file location and validate"
|
||||
msgid "DastSiteValidation|Step 3 - Confirm text file location."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Text file validation"
|
||||
|
@ -11760,13 +11760,13 @@ msgid_plural "DastSiteValidation|This will affect %d other profiles targeting th
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "DastSiteValidation|To run an active scan, validate your target site. All site profiles that share the same base URL share the same validation status."
|
||||
msgid "DastSiteValidation|To run an active scan, validate your site. Site profile validation reduces the risk of running an active scan against the wrong website. All site profiles that share the same base URL share the same validation status."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Validate"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Validate target site"
|
||||
msgid "DastSiteValidation|Validate site"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastSiteValidation|Validated"
|
||||
|
|
|
@ -12,9 +12,10 @@ RSpec.describe Groups::SharedProjectsController do
|
|||
|
||||
Projects::GroupLinks::CreateService.new(
|
||||
project,
|
||||
group,
|
||||
user,
|
||||
link_group_access: Gitlab::Access::DEVELOPER
|
||||
).execute(group)
|
||||
).execute
|
||||
end
|
||||
|
||||
let!(:group) { create(:group) }
|
||||
|
|
|
@ -79,6 +79,18 @@ RSpec.describe Projects::ServicePingController do
|
|||
|
||||
it_behaves_like 'counter is not increased'
|
||||
it_behaves_like 'counter is increased', 'WEB_IDE_PREVIEWS_SUCCESS_COUNT'
|
||||
|
||||
context 'when the user has access to the project' do
|
||||
let(:user) { project.owner }
|
||||
|
||||
it 'increases the live preview view counter' do
|
||||
expect(Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_live_preview_edit_action).with(author: user)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when web ide clientside preview is not enabled' do
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlTab, GlTabs, GlLink } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
|
||||
import stubChildren from 'helpers/stub_children';
|
||||
|
@ -18,15 +20,22 @@ import {
|
|||
LICENSE_COMPLIANCE_DESCRIPTION,
|
||||
LICENSE_COMPLIANCE_HELP_PATH,
|
||||
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
|
||||
LICENSE_ULTIMATE,
|
||||
LICENSE_PREMIUM,
|
||||
LICENSE_FREE,
|
||||
} from '~/security_configuration/components/constants';
|
||||
import FeatureCard from '~/security_configuration/components/feature_card.vue';
|
||||
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
|
||||
import {
|
||||
REPORT_TYPE_LICENSE_COMPLIANCE,
|
||||
REPORT_TYPE_SAST,
|
||||
} from '~/vue_shared/security_reports/constants';
|
||||
import { getCurrentLicensePlanResponse } from '../mock_data';
|
||||
|
||||
const upgradePath = '/upgrade';
|
||||
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
|
||||
|
@ -36,14 +45,24 @@ const projectFullPath = 'namespace/project';
|
|||
const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
|
||||
|
||||
useLocalStorageSpy();
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('App component', () => {
|
||||
let wrapper;
|
||||
let userCalloutDismissSpy;
|
||||
let mockApollo;
|
||||
|
||||
const createComponent = ({ shouldShowCallout = true, ...propsData }) => {
|
||||
const createComponent = ({
|
||||
shouldShowCallout = true,
|
||||
license = LICENSE_ULTIMATE,
|
||||
...propsData
|
||||
}) => {
|
||||
userCalloutDismissSpy = jest.fn();
|
||||
|
||||
mockApollo = createMockApollo([
|
||||
[currentLicenseQuery, jest.fn().mockResolvedValue(getCurrentLicensePlanResponse(license))],
|
||||
]);
|
||||
|
||||
wrapper = extendedWrapper(
|
||||
mount(SecurityConfigurationApp, {
|
||||
propsData,
|
||||
|
@ -54,6 +73,7 @@ describe('App component', () => {
|
|||
projectFullPath,
|
||||
vulnerabilityTrainingDocsPath,
|
||||
},
|
||||
apolloProvider: mockApollo,
|
||||
stubs: {
|
||||
...stubChildren(SecurityConfigurationApp),
|
||||
GlLink: false,
|
||||
|
@ -128,14 +148,16 @@ describe('App component', () => {
|
|||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
mockApollo = null;
|
||||
});
|
||||
|
||||
describe('basic structure', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
augmentedSecurityFeatures: securityFeaturesMock,
|
||||
augmentedComplianceFeatures: complianceFeaturesMock,
|
||||
});
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders main-heading with correct text', () => {
|
||||
|
@ -438,11 +460,12 @@ describe('App component', () => {
|
|||
});
|
||||
|
||||
describe('Vulnerability management', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
createComponent({
|
||||
augmentedSecurityFeatures: securityFeaturesMock,
|
||||
augmentedComplianceFeatures: complianceFeaturesMock,
|
||||
});
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders TrainingProviderList component', () => {
|
||||
|
@ -459,5 +482,21 @@ describe('App component', () => {
|
|||
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
|
||||
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
|
||||
});
|
||||
|
||||
it.each`
|
||||
license | display
|
||||
${LICENSE_ULTIMATE} | ${true}
|
||||
${LICENSE_PREMIUM} | ${false}
|
||||
${LICENSE_FREE} | ${false}
|
||||
${null} | ${false}
|
||||
`('displays $display for license $license', async ({ license, display }) => {
|
||||
createComponent({
|
||||
license,
|
||||
augmentedSecurityFeatures: securityFeaturesMock,
|
||||
augmentedComplianceFeatures: complianceFeaturesMock,
|
||||
});
|
||||
await waitForPromises();
|
||||
expect(findVulnerabilityManagementTab().exists()).toBe(display);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -111,3 +111,12 @@ export const tempProviderLogos = {
|
|||
svg: `<svg>${[testProviderName[1]]}</svg>`,
|
||||
},
|
||||
};
|
||||
|
||||
export const getCurrentLicensePlanResponse = (plan) => ({
|
||||
data: {
|
||||
currentLicense: {
|
||||
id: 'gid://gitlab/License/1',
|
||||
plan,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,11 +20,11 @@ RSpec.describe BulkImports::Projects::Stage do
|
|||
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ProtectedBranchesPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::CiPipelinesPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ProjectFeaturePipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::ReleasesPipeline],
|
||||
[5, BulkImports::Projects::Pipelines::CiPipelinesPipeline],
|
||||
[5, BulkImports::Common::Pipelines::WikiPipeline],
|
||||
[5, BulkImports::Common::Pipelines::UploadsPipeline],
|
||||
[5, BulkImports::Common::Pipelines::LfsObjectsPipeline],
|
||||
|
|
|
@ -80,10 +80,13 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
|||
|
||||
it 'can return the count of actions per user deduplicated' do
|
||||
described_class.track_web_ide_edit_action(author: user1)
|
||||
described_class.track_live_preview_edit_action(author: user1)
|
||||
described_class.track_snippet_editor_edit_action(author: user1)
|
||||
described_class.track_sfe_edit_action(author: user1)
|
||||
described_class.track_web_ide_edit_action(author: user2, time: time - 2.days)
|
||||
described_class.track_web_ide_edit_action(author: user3, time: time - 3.days)
|
||||
described_class.track_live_preview_edit_action(author: user2, time: time - 2.days)
|
||||
described_class.track_live_preview_edit_action(author: user3, time: time - 3.days)
|
||||
described_class.track_snippet_editor_edit_action(author: user3, time: time - 3.days)
|
||||
described_class.track_sfe_edit_action(author: user3, time: time - 3.days)
|
||||
|
||||
|
|
|
@ -848,7 +848,7 @@ RSpec.describe Integration do
|
|||
let(:test_message) { "test message" }
|
||||
let(:arguments) do
|
||||
{
|
||||
service_class: integration.class.name,
|
||||
integration_class: integration.class.name,
|
||||
project_path: project.full_path,
|
||||
project_id: project.id,
|
||||
message: test_message,
|
||||
|
@ -857,13 +857,13 @@ RSpec.describe Integration do
|
|||
end
|
||||
|
||||
it 'logs info messages using json logger' do
|
||||
expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
|
||||
expect(Gitlab::IntegrationsLogger).to receive(:info).with(arguments)
|
||||
|
||||
integration.log_info(test_message, additional_argument: 'some argument')
|
||||
end
|
||||
|
||||
it 'logs error messages using json logger' do
|
||||
expect(Gitlab::JsonLogger).to receive(:error).with(arguments)
|
||||
expect(Gitlab::IntegrationsLogger).to receive(:error).with(arguments)
|
||||
|
||||
integration.log_error(test_message, additional_argument: 'some argument')
|
||||
end
|
||||
|
@ -872,7 +872,7 @@ RSpec.describe Integration do
|
|||
let(:project) { nil }
|
||||
let(:arguments) do
|
||||
{
|
||||
service_class: integration.class.name,
|
||||
integration_class: integration.class.name,
|
||||
project_path: nil,
|
||||
project_id: nil,
|
||||
message: test_message,
|
||||
|
@ -881,7 +881,7 @@ RSpec.describe Integration do
|
|||
end
|
||||
|
||||
it 'logs info messages using json logger' do
|
||||
expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
|
||||
expect(Gitlab::IntegrationsLogger).to receive(:info).with(arguments)
|
||||
|
||||
integration.log_info(test_message, additional_argument: 'some argument')
|
||||
end
|
||||
|
|
|
@ -397,7 +397,7 @@ RSpec.describe IssuePolicy do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'set_issue_crm_contacts' do
|
||||
describe 'crm permissions' do
|
||||
let(:user) { create(:user) }
|
||||
let(:subgroup) { create(:group, :crm_enabled, parent: create(:group, :crm_enabled)) }
|
||||
let(:project) { create(:project, group: subgroup) }
|
||||
|
@ -408,6 +408,7 @@ RSpec.describe IssuePolicy do
|
|||
it 'is disallowed' do
|
||||
project.add_reporter(user)
|
||||
|
||||
expect(policies).to be_disallowed(:read_crm_contacts)
|
||||
expect(policies).to be_disallowed(:set_issue_crm_contacts)
|
||||
end
|
||||
end
|
||||
|
@ -416,6 +417,7 @@ RSpec.describe IssuePolicy do
|
|||
it 'is allowed' do
|
||||
subgroup.add_reporter(user)
|
||||
|
||||
expect(policies).to be_disallowed(:read_crm_contacts)
|
||||
expect(policies).to be_disallowed(:set_issue_crm_contacts)
|
||||
end
|
||||
end
|
||||
|
@ -424,8 +426,31 @@ RSpec.describe IssuePolicy do
|
|||
it 'is allowed' do
|
||||
subgroup.parent.add_reporter(user)
|
||||
|
||||
expect(policies).to be_allowed(:read_crm_contacts)
|
||||
expect(policies).to be_allowed(:set_issue_crm_contacts)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when crm disabled on subgroup' do
|
||||
let(:subgroup) { create(:group, parent: create(:group, :crm_enabled)) }
|
||||
|
||||
it 'is disallowed' do
|
||||
subgroup.parent.add_reporter(user)
|
||||
|
||||
expect(policies).to be_disallowed(:read_crm_contacts)
|
||||
expect(policies).to be_disallowed(:set_issue_crm_contacts)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when peronsal namespace' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'is disallowed' do
|
||||
project.add_reporter(user)
|
||||
|
||||
expect(policies).to be_disallowed(:read_crm_contacts)
|
||||
expect(policies).to be_disallowed(:set_issue_crm_contacts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -94,5 +94,37 @@ RSpec.describe IssueSidebarBasicEntity do
|
|||
expect(entity[:show_crm_contacts]).to be(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'in subgroup' do
|
||||
let(:subgroup_project) { create(:project, :repository, group: subgroup) }
|
||||
let(:subgroup_issue) { create(:issue, project: subgroup_project) }
|
||||
let(:serializer) { IssueSerializer.new(current_user: user, project: subgroup_project) }
|
||||
|
||||
subject(:entity) { serializer.represent(subgroup_issue, serializer: 'sidebar') }
|
||||
|
||||
before do
|
||||
subgroup_project.root_ancestor.add_reporter(user)
|
||||
end
|
||||
|
||||
context 'with crm enabled' do
|
||||
let(:subgroup) { create(:group, :crm_enabled, parent: group) }
|
||||
|
||||
it 'is true' do
|
||||
allow(CustomerRelations::Contact).to receive(:exists_for_group?).with(group).and_return(true)
|
||||
|
||||
expect(entity[:show_crm_contacts]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with crm disabled' do
|
||||
let(:subgroup) { create(:group, parent: group) }
|
||||
|
||||
it 'is false' do
|
||||
allow(CustomerRelations::Contact).to receive(:exists_for_group?).with(group).and_return(true)
|
||||
|
||||
expect(entity[:show_crm_contacts]).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,23 +3,13 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
|
||||
let(:parent_group_user) { create(:user) }
|
||||
let(:group_user) { create(:user) }
|
||||
let(:child_group_user) { create(:user) }
|
||||
let(:prevent_sharing) { false }
|
||||
let_it_be(:shared_with_group_parent) { create(:group, :private) }
|
||||
let_it_be(:shared_with_group) { create(:group, :private, parent: shared_with_group_parent) }
|
||||
let_it_be(:shared_with_group_child) { create(:group, :private, parent: shared_with_group) }
|
||||
|
||||
let_it_be(:group_parent) { create(:group, :private) }
|
||||
let_it_be(:group) { create(:group, :private, parent: group_parent) }
|
||||
let_it_be(:group_child) { create(:group, :private, parent: group) }
|
||||
|
||||
let(:ns_for_parent) { create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: prevent_sharing) }
|
||||
let(:shared_group_parent) { create(:group, :private, namespace_settings: ns_for_parent) }
|
||||
let(:shared_group) { create(:group, :private, parent: shared_group_parent) }
|
||||
let(:shared_group_child) { create(:group, :private, parent: shared_group) }
|
||||
|
||||
let(:project_parent) { create(:project, group: shared_group_parent) }
|
||||
let(:project) { create(:project, group: shared_group) }
|
||||
let(:project_child) { create(:project, group: shared_group_child) }
|
||||
let(:group) { create(:group, :private, parent: group_parent) }
|
||||
|
||||
let(:opts) do
|
||||
{
|
||||
|
@ -28,127 +18,161 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
|
|||
}
|
||||
end
|
||||
|
||||
let(:user) { group_user }
|
||||
subject { described_class.new(group, shared_with_group, user, opts) }
|
||||
|
||||
subject { described_class.new(shared_group, group, user, opts) }
|
||||
shared_examples_for 'not shareable' do
|
||||
it 'does not share and returns an error' do
|
||||
expect do
|
||||
result = subject.execute
|
||||
|
||||
before do
|
||||
group.add_guest(group_user)
|
||||
shared_group.add_owner(group_user)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
end.not_to change { group.shared_with_group_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds group to another group' do
|
||||
expect { subject.execute }.to change { group.shared_group_links.count }.from(0).to(1)
|
||||
shared_examples_for 'shareable' do
|
||||
it 'adds group to another group' do
|
||||
expect do
|
||||
result = subject.execute
|
||||
|
||||
expect(result[:status]).to eq(:success)
|
||||
end.to change { group.shared_with_group_links.count }.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false if shared group is blank' do
|
||||
expect { described_class.new(nil, group, user, opts) }.not_to change { group.shared_group_links.count }
|
||||
context 'when user has proper membership to share a group' do
|
||||
let_it_be(:group_user) { create(:user) }
|
||||
|
||||
let(:user) { group_user }
|
||||
|
||||
before do
|
||||
shared_with_group.add_guest(group_user)
|
||||
group.add_owner(group_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'shareable'
|
||||
|
||||
context 'when sharing outside the hierarchy is disabled' do
|
||||
let_it_be(:group_parent) do
|
||||
create(:group,
|
||||
namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true))
|
||||
end
|
||||
|
||||
it_behaves_like 'not shareable'
|
||||
|
||||
context 'when group is inside hierarchy' do
|
||||
let(:shared_with_group) { create(:group, :private, parent: group_parent) }
|
||||
|
||||
it_behaves_like 'shareable'
|
||||
end
|
||||
end
|
||||
|
||||
context 'project authorizations based on group hierarchies' do
|
||||
let_it_be(:child_group_user) { create(:user) }
|
||||
let_it_be(:parent_group_user) { create(:user) }
|
||||
|
||||
before do
|
||||
shared_with_group_parent.add_owner(parent_group_user)
|
||||
shared_with_group.add_owner(group_user)
|
||||
shared_with_group_child.add_owner(child_group_user)
|
||||
end
|
||||
|
||||
context 'project authorizations refresh' do
|
||||
it 'is executed only for the direct members of the group' do
|
||||
expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id))
|
||||
.and_call_original
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'project authorizations' do
|
||||
let(:group_child) { create(:group, :private, parent: group) }
|
||||
let(:project_parent) { create(:project, group: group_parent) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
let(:project_child) { create(:project, group: group_child) }
|
||||
|
||||
context 'group user' do
|
||||
let(:user) { group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_truthy
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'parent group user' do
|
||||
let(:user) { parent_group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'child group user' do
|
||||
let(:user) { child_group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'user does not have access to group' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
shared_group.add_owner(user)
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
result = subject.execute
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
end
|
||||
it_behaves_like 'not shareable'
|
||||
end
|
||||
|
||||
context 'user does not have admin access to shared group' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_guest(user)
|
||||
shared_group.add_developer(user)
|
||||
shared_with_group.add_guest(user)
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
result = subject.execute
|
||||
it_behaves_like 'not shareable'
|
||||
end
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
context 'when group is blank' do
|
||||
let(:group_user) { create(:user) }
|
||||
let(:user) { group_user }
|
||||
let(:group) { nil }
|
||||
|
||||
it 'does not share and returns an error' do
|
||||
expect do
|
||||
result = subject.execute
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
end.not_to change { shared_with_group.shared_group_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'project authorizations based on group hierarchies' do
|
||||
before do
|
||||
group_parent.add_owner(parent_group_user)
|
||||
group.add_owner(group_user)
|
||||
group_child.add_owner(child_group_user)
|
||||
end
|
||||
context 'when shared_with_group is blank' do
|
||||
let(:group_user) { create(:user) }
|
||||
let(:user) { group_user }
|
||||
let(:shared_with_group) { nil }
|
||||
|
||||
context 'project authorizations refresh' do
|
||||
it 'is executed only for the direct members of the group' do
|
||||
expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id)).and_call_original
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'project authorizations' do
|
||||
context 'group user' do
|
||||
let(:user) { group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_truthy
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'parent group user' do
|
||||
let(:user) { parent_group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'child group user' do
|
||||
let(:user) { child_group_user }
|
||||
|
||||
it 'create proper authorizations' do
|
||||
subject.execute
|
||||
|
||||
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
|
||||
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'sharing outside the hierarchy is disabled' do
|
||||
let(:prevent_sharing) { true }
|
||||
|
||||
it 'prevents sharing with a group outside the hierarchy' do
|
||||
result = subject.execute
|
||||
|
||||
expect(group.reload.shared_group_links.count).to eq(0)
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
end
|
||||
|
||||
it 'allows sharing with a group within the hierarchy' do
|
||||
sibling_group = create(:group, :private, parent: shared_group_parent)
|
||||
sibling_group.add_guest(group_user)
|
||||
|
||||
result = described_class.new(shared_group, sibling_group, user, opts).execute
|
||||
|
||||
expect(sibling_group.reload.shared_group_links.count).to eq(1)
|
||||
expect(result[:status]).to eq(:success)
|
||||
end
|
||||
it_behaves_like 'not shareable'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe Issues::SetCrmContactsService do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :crm_enabled) }
|
||||
let_it_be(:project) { create(:project, group: create(:group, parent: group)) }
|
||||
let_it_be(:project) { create(:project, group: create(:group, :crm_enabled, parent: group)) }
|
||||
let_it_be(:contacts) { create_list(:contact, 4, group: group) }
|
||||
let_it_be(:issue, reload: true) { create(:issue, project: project) }
|
||||
let_it_be(:issue_contact_1) do
|
||||
|
@ -58,6 +58,20 @@ RSpec.describe Issues::SetCrmContactsService do
|
|||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
context 'but the crm setting is disabled' do
|
||||
let(:params) { { replace_ids: [contacts[1].id, contacts[2].id] } }
|
||||
let(:subgroup_with_crm_disabled) { create(:group, parent: group) }
|
||||
let(:project_with_crm_disabled) { create(:project, group: subgroup_with_crm_disabled) }
|
||||
let(:issue_with_crm_disabled) { create(:issue, project: project_with_crm_disabled) }
|
||||
|
||||
it 'returns expected error response' do
|
||||
response = described_class.new(project: project_with_crm_disabled, current_user: user, params: params).execute(issue_with_crm_disabled)
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.message).to eq('You have insufficient permissions to set customer relations contacts for this issue')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the contact does not exist' do
|
||||
let(:params) { { replace_ids: [non_existing_record_id] } }
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe JiraConnect::SyncService do
|
|||
end
|
||||
|
||||
def expect_log(type, message)
|
||||
expect(Gitlab::ProjectServiceLogger)
|
||||
expect(Gitlab::IntegrationsLogger)
|
||||
.to receive(type).with(
|
||||
message: 'response from jira dev_info api',
|
||||
integration: 'JiraConnect',
|
||||
|
|
|
@ -5,65 +5,104 @@ require 'spec_helper'
|
|||
RSpec.describe Projects::GroupLinks::CreateService, '#execute' do
|
||||
let_it_be(:user) { create :user }
|
||||
let_it_be(:group) { create :group }
|
||||
let_it_be(:project) { create :project }
|
||||
let_it_be(:project) { create(:project, namespace: create(:namespace, :with_namespace_settings)) }
|
||||
|
||||
let(:group_access) { Gitlab::Access::DEVELOPER }
|
||||
let(:opts) do
|
||||
{
|
||||
link_group_access: group_access,
|
||||
link_group_access: Gitlab::Access::DEVELOPER,
|
||||
expires_at: nil
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.new(project, user, opts) }
|
||||
subject { described_class.new(project, group, user, opts) }
|
||||
|
||||
before do
|
||||
group.add_developer(user)
|
||||
shared_examples_for 'not shareable' do
|
||||
it 'does not share and returns an error' do
|
||||
expect do
|
||||
result = subject.execute
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:http_status]).to eq(404)
|
||||
end.not_to change { project.project_group_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds group to project' do
|
||||
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
|
||||
shared_examples_for 'shareable' do
|
||||
it 'adds group to project' do
|
||||
expect do
|
||||
result = subject.execute
|
||||
|
||||
expect(result[:status]).to eq(:success)
|
||||
end.to change { project.project_group_links.count }.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates authorization', :sidekiq_inline do
|
||||
expect { subject.execute(group) }.to(
|
||||
change { Ability.allowed?(user, :read_project, project) }
|
||||
.from(false).to(true))
|
||||
end
|
||||
|
||||
it 'returns false if group is blank' do
|
||||
expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
|
||||
end
|
||||
|
||||
it 'returns error if user is not allowed to share with a group' do
|
||||
expect { subject.execute(create(:group)) }.not_to change { project.project_group_links.count }
|
||||
end
|
||||
|
||||
context 'with specialized project_authorization workers' do
|
||||
let_it_be(:other_user) { create(:user) }
|
||||
|
||||
context 'when user has proper membership to share a group' do
|
||||
before do
|
||||
group.add_developer(other_user)
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it 'schedules authorization update for users with access to group' do
|
||||
expect(AuthorizedProjectsWorker).not_to(
|
||||
receive(:bulk_perform_async)
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to(
|
||||
receive(:perform_async)
|
||||
.with(project.id)
|
||||
.and_call_original
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
|
||||
receive(:bulk_perform_in)
|
||||
.with(1.hour,
|
||||
array_including([user.id], [other_user.id]),
|
||||
batch_delay: 30.seconds, batch_size: 100)
|
||||
.and_call_original
|
||||
)
|
||||
it_behaves_like 'shareable'
|
||||
|
||||
subject.execute(group)
|
||||
it 'updates authorization', :sidekiq_inline do
|
||||
expect { subject.execute }.to(
|
||||
change { Ability.allowed?(user, :read_project, project) }
|
||||
.from(false).to(true))
|
||||
end
|
||||
|
||||
context 'with specialized project_authorization workers' do
|
||||
let_it_be(:other_user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_developer(other_user)
|
||||
end
|
||||
|
||||
it 'schedules authorization update for users with access to group' do
|
||||
expect(AuthorizedProjectsWorker).not_to(
|
||||
receive(:bulk_perform_async)
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to(
|
||||
receive(:perform_async)
|
||||
.with(project.id)
|
||||
.and_call_original
|
||||
)
|
||||
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
|
||||
receive(:bulk_perform_in)
|
||||
.with(1.hour,
|
||||
array_including([user.id], [other_user.id]),
|
||||
batch_delay: 30.seconds, batch_size: 100)
|
||||
.and_call_original
|
||||
)
|
||||
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sharing outside the hierarchy is disabled' do
|
||||
let_it_be(:shared_group_parent) do
|
||||
create(:group,
|
||||
namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true))
|
||||
end
|
||||
|
||||
let_it_be(:project, reload: true) { create(:project, group: shared_group_parent) }
|
||||
|
||||
it_behaves_like 'not shareable'
|
||||
|
||||
context 'when group is inside hierarchy' do
|
||||
let(:group) { create(:group, :private, parent: shared_group_parent) }
|
||||
|
||||
it_behaves_like 'shareable'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permissions for the group' do
|
||||
it_behaves_like 'not shareable'
|
||||
end
|
||||
|
||||
context 'when group is blank' do
|
||||
let(:group) { nil }
|
||||
|
||||
it_behaves_like 'not shareable'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -66,11 +66,11 @@ RSpec.shared_examples 'a service that handles Jira API errors' do
|
|||
it 'logs the error' do
|
||||
stub_client_and_raise(Timeout::Error, 'foo')
|
||||
|
||||
expect(Gitlab::ProjectServiceLogger).to receive(:error).with(
|
||||
expect(Gitlab::IntegrationsLogger).to receive(:error).with(
|
||||
hash_including(
|
||||
client_url: be_present,
|
||||
message: 'Error sending message',
|
||||
service_class: described_class.name,
|
||||
integration_class: described_class.name,
|
||||
error: hash_including(
|
||||
exception_class: Timeout::Error.name,
|
||||
exception_message: 'foo',
|
||||
|
|
Loading…
Reference in a new issue