Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5ff5438a06
commit
f1503ea64b
|
@ -1467,6 +1467,12 @@
|
|||
changes: ["vendor/gems/ipynbdiff/**/*"]
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
|
||||
.vendor:rules:omniauth-azure-oauth2:
|
||||
rules:
|
||||
- <<: *if-merge-request
|
||||
changes: ["vendor/gems/omniauth-azure-oauth2/**/*"]
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
|
||||
.vendor:rules:omniauth-cas3:
|
||||
rules:
|
||||
- <<: *if-merge-request
|
||||
|
|
|
@ -14,6 +14,14 @@ vendor ipynbdiff:
|
|||
include: vendor/gems/ipynbdiff/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
|
||||
vendor omniauth-azure-oauth2:
|
||||
extends:
|
||||
- .vendor:rules:omniauth-azure-oauth2
|
||||
needs: []
|
||||
trigger:
|
||||
include: vendor/gems/omniauth-azure-oauth2/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
|
||||
vendor omniauth-cas3:
|
||||
extends:
|
||||
- .vendor:rules:omniauth-cas3
|
||||
|
|
|
@ -2,27 +2,6 @@
|
|||
# Cop supports --auto-correct.
|
||||
Layout/HashAlignment:
|
||||
Exclude:
|
||||
- 'ee/app/graphql/types/vulnerable_dependency_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_kubernetes_resource_type.rb'
|
||||
- 'ee/app/graphql/types/vulnerable_projects_by_grade_type.rb'
|
||||
- 'ee/app/graphql/types/work_items/widgets/verification_status_type.rb'
|
||||
- 'ee/app/graphql/types/work_items/widgets/weight_type.rb'
|
||||
- 'ee/app/helpers/ee/geo_helper.rb'
|
||||
- 'ee/app/serializers/ee/evidences/release_entity.rb'
|
||||
- 'ee/app/services/ci/external_pull_requests/process_github_event_service.rb'
|
||||
- 'ee/app/services/ci_cd/setup_project.rb'
|
||||
- 'ee/app/services/ee/issues/base_service.rb'
|
||||
- 'ee/app/services/elastic/cluster_reindexing_service.rb'
|
||||
- 'ee/app/services/elastic/process_bookkeeping_service.rb'
|
||||
- 'ee/app/services/epics/epic_links/list_service.rb'
|
||||
- 'ee/app/services/epics/issue_promote_service.rb'
|
||||
- 'ee/app/services/groups/memberships/export_service.rb'
|
||||
- 'ee/app/services/projects/setup_ci_cd.rb'
|
||||
- 'ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb'
|
||||
- 'ee/config/routes/project.rb'
|
||||
- 'ee/config/routes/uploads.rb'
|
||||
- 'ee/elastic/migrate/20220118150500_delete_orphaned_commits.rb'
|
||||
- 'ee/elastic/migrate/20220119120500_populate_commit_permissions_in_main_index.rb'
|
||||
- 'ee/lib/api/iterations.rb'
|
||||
- 'ee/lib/api/merge_trains.rb'
|
||||
- 'ee/lib/api/related_epic_links.rb'
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -39,7 +39,7 @@ gem 'ruby-saml', '~> 1.13.0'
|
|||
gem 'omniauth', '~> 1.8'
|
||||
gem 'omniauth-auth0', '~> 2.0.0'
|
||||
gem 'omniauth-azure-activedirectory-v2', '~> 1.0'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.9' # Deprecated v1 version
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.9' # See vendor/gems/omniauth-azure-oauth2/README.md
|
||||
gem 'omniauth-cas3', '~> 1.1.4', path: 'vendor/gems/omniauth-cas3' # See vendor/gems/omniauth-cas3/README.md
|
||||
gem 'omniauth-dingtalk-oauth2', '~> 1.0'
|
||||
gem 'omniauth-alicloud', '~> 1.0.1'
|
||||
|
|
|
@ -79,9 +79,6 @@ export default {
|
|||
size: numberToHumanSize(this.size),
|
||||
});
|
||||
},
|
||||
showJobLogSearch() {
|
||||
return this.glFeatures.jobLogSearch;
|
||||
},
|
||||
showJumpToFailures() {
|
||||
return this.glFeatures.jobLogJumpToFailures;
|
||||
},
|
||||
|
@ -198,24 +195,22 @@ export default {
|
|||
<!-- eo truncate information -->
|
||||
|
||||
<div class="controllers">
|
||||
<template v-if="showJobLogSearch">
|
||||
<gl-search-box-by-click
|
||||
v-model="searchTerm"
|
||||
class="gl-mr-3"
|
||||
:placeholder="$options.i18n.searchPlaceholder"
|
||||
data-testid="job-log-search-box"
|
||||
@clear="$emit('searchResults', [])"
|
||||
@submit="searchJobLog"
|
||||
/>
|
||||
<gl-search-box-by-click
|
||||
v-model="searchTerm"
|
||||
class="gl-mr-3"
|
||||
:placeholder="$options.i18n.searchPlaceholder"
|
||||
data-testid="job-log-search-box"
|
||||
@clear="$emit('searchResults', [])"
|
||||
@submit="searchJobLog"
|
||||
/>
|
||||
|
||||
<help-popover class="gl-mr-3">
|
||||
<template #title>{{ $options.i18n.searchPopoverTitle }}</template>
|
||||
<help-popover class="gl-mr-3">
|
||||
<template #title>{{ $options.i18n.searchPopoverTitle }}</template>
|
||||
|
||||
<p class="gl-mb-0">
|
||||
{{ $options.i18n.searchPopoverDescription }}
|
||||
</p>
|
||||
</help-popover>
|
||||
</template>
|
||||
<p class="gl-mb-0">
|
||||
{{ $options.i18n.searchPopoverDescription }}
|
||||
</p>
|
||||
</help-popover>
|
||||
|
||||
<!-- links -->
|
||||
<gl-button
|
||||
|
|
|
@ -263,10 +263,12 @@ export function insertMarkdownText({
|
|||
if (tag === LINK_TAG_PATTERN) {
|
||||
if (URL) {
|
||||
try {
|
||||
new URL(selected); // eslint-disable-line no-new
|
||||
// valid url
|
||||
tag = '[text]({text})';
|
||||
select = 'text';
|
||||
const url = new URL(selected);
|
||||
|
||||
if (url.origin !== 'null' || url.origin === null) {
|
||||
tag = '[text]({text})';
|
||||
select = 'text';
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore - no valid url
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { s__, __ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { pipelineEditorTrackingOptions } from '../../constants';
|
||||
import ValidatePipelinePopover from '../popovers/validate_pipeline_popover.vue';
|
||||
import CiLintResults from '../lint/ci_lint_results.vue';
|
||||
import getBlobContent from '../../graphql/queries/blob_content.query.graphql';
|
||||
|
@ -70,6 +72,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['ciConfigPath', 'ciLintPath', 'projectFullPath', 'validateTabIllustrationPath'],
|
||||
props: {
|
||||
ciFileContent: {
|
||||
|
@ -110,6 +113,9 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
canResimulatePipeline() {
|
||||
return this.hasSimulationResults && this.hasCiContentChanged;
|
||||
},
|
||||
isInitialCiContentLoading() {
|
||||
return this.$apollo.queries.initialBlobContent.loading;
|
||||
},
|
||||
|
@ -128,6 +134,10 @@ export default {
|
|||
variant: this.isValid ? 'success' : 'danger',
|
||||
};
|
||||
},
|
||||
trackingAction() {
|
||||
const { actions } = pipelineEditorTrackingOptions;
|
||||
return this.canResimulatePipeline ? actions.resimulatePipeline : actions.simulatePipeline;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ciFileContent(value) {
|
||||
|
@ -139,7 +149,12 @@ export default {
|
|||
cancelSimulation() {
|
||||
this.state = VALIDATE_TAB_INIT;
|
||||
},
|
||||
trackSimulation() {
|
||||
const { label } = pipelineEditorTrackingOptions;
|
||||
this.track(this.trackingAction, { label });
|
||||
},
|
||||
async validateYaml() {
|
||||
this.trackSimulation();
|
||||
this.state = VALIDATE_TAB_LOADING;
|
||||
|
||||
try {
|
||||
|
@ -198,7 +213,7 @@ export default {
|
|||
:aria-label="$options.i18n.help"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="hasSimulationResults && hasCiContentChanged">
|
||||
<div v-if="canResimulatePipeline">
|
||||
<span class="gl-text-gray-400" data-testid="content-status">
|
||||
{{ $options.i18n.contentChange }}
|
||||
</span>
|
||||
|
|
|
@ -75,6 +75,8 @@ export const pipelineEditorTrackingOptions = {
|
|||
[CI_YAML_LINK]: 'visit_help_drawer_link_yaml',
|
||||
},
|
||||
openHelpDrawer: 'open_help_drawer',
|
||||
resimulatePipeline: 'resimulate_pipeline',
|
||||
simulatePipeline: 'simulate_pipeline',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ module Projects
|
|||
prepend_before_action :repository, :project_without_auth
|
||||
|
||||
feature_category :incident_management
|
||||
# Goal is to increase the urgency to medium.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/361310.
|
||||
urgency :low, [:create]
|
||||
|
||||
def create
|
||||
|
|
|
@ -18,7 +18,6 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
before_action :verify_api_request!, only: :terminal_websocket_authorize
|
||||
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
|
||||
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
|
||||
before_action :push_job_log_search, only: [:show]
|
||||
before_action :push_job_log_jump_to_failures, only: [:show]
|
||||
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
|
||||
|
||||
|
@ -250,10 +249,6 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
::Gitlab::Workhorse.channel_websocket(service)
|
||||
end
|
||||
|
||||
def push_job_log_search
|
||||
push_frontend_feature_flag(:job_log_search, @project)
|
||||
end
|
||||
|
||||
def push_job_log_jump_to_failures
|
||||
push_frontend_feature_flag(:job_log_jump_to_failures, @project)
|
||||
end
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
.labels-container.gl-mt-5
|
||||
- if can_admin_label && search.blank?
|
||||
%p.text-muted
|
||||
= _('Labels can be applied to issues and merge requests.')
|
||||
%br
|
||||
= _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
|
||||
= _('Labels can be applied to issues and merge requests. Star a label to make it a priority label.')
|
||||
|
||||
-# Only show it in the first page
|
||||
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
|
||||
.prioritized-labels.gl-mb-7{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] }
|
||||
%h4.gl-mt-3= _('Prioritized Labels')
|
||||
%p.text-muted
|
||||
= _('Drag to reorder prioritized labels and change their relative priority.')
|
||||
.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
|
||||
#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
|
||||
= render 'shared/empty_states/priority_labels'
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues)
|
||||
- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests)
|
||||
|
||||
.label-name.gl-flex-shrink-0.gl-mt-2.gl-mr-3
|
||||
.label-name.gl-flex-shrink-0.gl-mt-2.gl-mr-5
|
||||
= render_label(label, tooltip: false)
|
||||
.label-description.gl-overflow-hidden.gl-w-full
|
||||
.gl-display-flex.gl-align-items-stretch.gl-flex-wrap.gl-mt-2
|
||||
.gl-flex-basis-half.gl-flex-grow-1.gl-overflow-hidden.gl-mr-2
|
||||
.gl-flex-basis-half.gl-flex-grow-1.gl-overflow-hidden.gl-mr-5
|
||||
- if label.description.present?
|
||||
= markdown_field(label, :description)
|
||||
- elsif show_labels_full_path?(@project, @group)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: job_log_search
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91293
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366455
|
||||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
default_enabled: false
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateVulnerabilitiesProjectIdIdActiveCisIndex < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NEW_INDEX_NAME = 'idx_vulnerabilities_on_project_id_and_id_active_cis_dft_branch'
|
||||
OLD_INDEX_NAME = 'index_vulnerabilities_on_project_id_and_id_active_cis'
|
||||
OLD_INDEX_FILTER_CONDITION = 'report_type = 7 AND state = ANY(ARRAY[1, 4])'
|
||||
NEW_INDEX_FILTER_CONDITION = 'report_type = 7 AND state = ANY(ARRAY[1, 4]) AND present_on_default_branch IS TRUE'
|
||||
|
||||
def up
|
||||
add_concurrent_index :vulnerabilities, [:project_id, :id],
|
||||
where: NEW_INDEX_FILTER_CONDITION,
|
||||
name: NEW_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name(:vulnerabilities, OLD_INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :vulnerabilities, [:project_id, :id], where: OLD_INDEX_FILTER_CONDITION, name: OLD_INDEX_NAME
|
||||
|
||||
remove_concurrent_index_by_name(:vulnerabilities, NEW_INDEX_NAME)
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
63ec85b4f8b7eb15c232c4a25c1e63027c38c23caf81a89c4d05227a6be00e4b
|
|
@ -27472,6 +27472,8 @@ CREATE UNIQUE INDEX idx_vuln_signatures_on_occurrences_id_and_signature_sha ON v
|
|||
|
||||
CREATE UNIQUE INDEX idx_vuln_signatures_uniqueness_signature_sha ON vulnerability_finding_signatures USING btree (finding_id, algorithm_type, signature_sha);
|
||||
|
||||
CREATE INDEX idx_vulnerabilities_on_project_id_and_id_active_cis_dft_branch ON vulnerabilities USING btree (project_id, id) WHERE ((report_type = 7) AND (state = ANY (ARRAY[1, 4])) AND (present_on_default_branch IS TRUE));
|
||||
|
||||
CREATE INDEX idx_vulnerabilities_partial_devops_adoption_and_default_branch ON vulnerabilities USING btree (project_id, created_at, present_on_default_branch) WHERE (state <> 1);
|
||||
|
||||
CREATE UNIQUE INDEX idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue ON vulnerability_external_issue_links USING btree (vulnerability_id, external_type, external_project_key, external_issue_key);
|
||||
|
@ -30364,8 +30366,6 @@ CREATE INDEX index_vulnerabilities_on_last_edited_by_id ON vulnerabilities USING
|
|||
|
||||
CREATE INDEX index_vulnerabilities_on_milestone_id ON vulnerabilities USING btree (milestone_id);
|
||||
|
||||
CREATE INDEX index_vulnerabilities_on_project_id_and_id_active_cis ON vulnerabilities USING btree (project_id, id) WHERE ((report_type = 7) AND (state = ANY (ARRAY[1, 4])));
|
||||
|
||||
CREATE INDEX index_vulnerabilities_on_project_id_and_state_and_severity ON vulnerabilities USING btree (project_id, state, severity);
|
||||
|
||||
CREATE INDEX index_vulnerabilities_on_resolved_by_id ON vulnerabilities USING btree (resolved_by_id);
|
||||
|
|
|
@ -152,6 +152,7 @@ From there, you can see the following actions:
|
|||
- Added, removed, or updated protected branches
|
||||
- Release was added to a project
|
||||
- Release was updated
|
||||
- Release was deleted ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94793/) in GitLab 13.5)
|
||||
- Release milestone associations changed
|
||||
- Permission to approve merge requests by committers was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
|
||||
- Permission to approve merge requests by committers was updated.
|
||||
|
|
|
@ -6,6 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Packages API **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/349418) support for [GitLab CI/CD job token](../ci/jobs/ci_job_token.md) authentication for the project-level API in GitLab 15.3.
|
||||
|
||||
This is the API documentation of [GitLab Packages](../administration/packages/index.md).
|
||||
|
||||
## List packages
|
||||
|
|
|
@ -13,6 +13,7 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints
|
|||
|
||||
- Packages:
|
||||
- [Package Registry](../../user/packages/package_registry/index.md#use-gitlab-cicd-to-build-packages).
|
||||
- [Packages API](../../api/packages.md) (project-level).
|
||||
- [Container Registry](../../user/packages/container_registry/index.md#build-and-push-by-using-gitlab-cicd)
|
||||
(the `$CI_REGISTRY_PASSWORD` is `$CI_JOB_TOKEN`).
|
||||
- [Container Registry API](../../api/container_registry.md)
|
||||
|
|
|
@ -567,6 +567,9 @@ In blocking manual jobs:
|
|||
enabled can't be merged with a blocked pipeline.
|
||||
- The pipeline shows a status of **blocked**.
|
||||
|
||||
When using manual jobs in triggered pipelines with [`strategy: depend`](../yaml/index.md#triggerstrategy),
|
||||
the type of manual job can affect the trigger job's status while the pipeline runs.
|
||||
|
||||
### Run a manual job
|
||||
|
||||
To run a manual job, you must have permission to merge to the assigned branch:
|
||||
|
|
|
@ -341,8 +341,8 @@ If you use [`only/except`](../yaml/index.md#only--except) to control job behavio
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11238) in GitLab Premium 12.3.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199224) to GitLab Free in 12.8.
|
||||
|
||||
You can mirror the pipeline status from the triggered pipeline to the source
|
||||
trigger job by using `strategy: depend`. For example:
|
||||
You can mirror the pipeline status from the triggered pipeline to the source trigger job
|
||||
by using [`strategy: depend`](../yaml/index.md#triggerstrategy). For example:
|
||||
|
||||
```yaml
|
||||
trigger_job:
|
||||
|
|
|
@ -3966,6 +3966,17 @@ trigger_job:
|
|||
In this example, jobs from subsequent stages wait for the triggered pipeline to
|
||||
successfully complete before starting.
|
||||
|
||||
**Additional details**:
|
||||
|
||||
- [Optional manual jobs](../jobs/job_control.md#types-of-manual-jobs) in the downstream pipeline
|
||||
do not affect the status of the downstream pipeline or the upstream trigger job.
|
||||
The downstream pipeline can complete successfully without running any optional manual jobs.
|
||||
- [Blocking manual jobs](../jobs/job_control.md#types-of-manual-jobs) in the downstream pipeline
|
||||
must run before the trigger job is marked as successful or failed. The trigger job
|
||||
shows **pending** (**{status_pending}**) if the downstream pipeline status is
|
||||
**waiting for manual action** (**{status_manual}**) due to manual jobs. By default,
|
||||
jobs in later stages do not start until the trigger job completes.
|
||||
|
||||
#### `trigger:forward`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213729) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `ci_trigger_forward_variables`. Disabled by default.
|
||||
|
|
|
@ -53,6 +53,7 @@ By default both administrators and anyone with the **Owner** role can delete a p
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2 for groups created after August 12, 2021.
|
||||
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) from default delayed project deletion in GitLab 15.1.
|
||||
> - [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1.
|
||||
> - [Disabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95495) in GitLab 15.3.
|
||||
|
||||
Instance-level protection against accidental deletion of groups and projects.
|
||||
|
||||
|
|
|
@ -447,9 +447,10 @@ in GitLab 12.6, and then to [immediate deletion](https://gitlab.com/gitlab-org/g
|
|||
|
||||
### Delayed project deletion **(PREMIUM)**
|
||||
|
||||
> [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1.
|
||||
> - [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1.
|
||||
> - [Disabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95495) in GitLab 15.3.
|
||||
|
||||
Projects can be deleted after a delay period. Multiple settings can affect whether
|
||||
Projects in a group (not a personal namespace) can be deleted after a delay period. Multiple settings can affect whether
|
||||
delayed project deletion is enabled for a particular project:
|
||||
|
||||
- Self-managed instance [settings](../../admin_area/settings/visibility_and_access_controls.md#delayed-project-deletion).
|
||||
|
|
|
@ -36,6 +36,7 @@ module API
|
|||
optional :status, type: String, values: Packages::Package.statuses.keys,
|
||||
desc: 'Return packages with specified status'
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get ':id/packages' do
|
||||
packages = ::Packages::PackagesFinder.new(
|
||||
user_project,
|
||||
|
@ -52,6 +53,7 @@ module API
|
|||
params do
|
||||
requires :package_id, type: Integer, desc: 'The ID of a package'
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get ':id/packages/:package_id' do
|
||||
package = ::Packages::PackageFinder
|
||||
.new(user_project, params[:package_id]).execute
|
||||
|
@ -65,6 +67,7 @@ module API
|
|||
params do
|
||||
requires :package_id, type: Integer, desc: 'The ID of a package'
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
delete ':id/packages/:package_id' do
|
||||
authorize_destroy_package!(user_project)
|
||||
|
||||
|
|
|
@ -186,6 +186,8 @@ module API
|
|||
.execute
|
||||
|
||||
if result[:status] == :success
|
||||
log_release_deleted_audit_event
|
||||
|
||||
present result[:release], with: Entities::Release, current_user: current_user
|
||||
else
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
|
@ -238,6 +240,10 @@ module API
|
|||
# extended in EE
|
||||
end
|
||||
|
||||
def log_release_deleted_audit_event
|
||||
# extended in EE
|
||||
end
|
||||
|
||||
def log_release_milestones_updated_audit_event
|
||||
# extended in EE
|
||||
end
|
||||
|
|
|
@ -120,7 +120,7 @@ module Gitlab
|
|||
stage: stage_value,
|
||||
extends: extends,
|
||||
rules: rules_value,
|
||||
job_variables: variables_value.to_h,
|
||||
job_variables: variables_entry.value_with_data,
|
||||
root_variables_inheritance: root_variables_inheritance,
|
||||
only: only_value,
|
||||
except: except_value,
|
||||
|
|
|
@ -18,7 +18,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def value
|
||||
@config.to_h { |key, value| [key.to_s, expand_value(value)[:value]] }
|
||||
@config.to_h do |key, data|
|
||||
[key.to_s, expand_data(data)[:value]]
|
||||
end
|
||||
end
|
||||
|
||||
def self.default(**)
|
||||
|
@ -26,7 +28,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def value_with_data
|
||||
@config.to_h { |key, value| [key.to_s, expand_value(value)] }
|
||||
@config.to_h do |key, data|
|
||||
[key.to_s, expand_data(data)]
|
||||
end
|
||||
end
|
||||
|
||||
def use_value_data?
|
||||
|
@ -35,11 +39,11 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def expand_value(value)
|
||||
if value.is_a?(Hash)
|
||||
{ value: value[:value].to_s, description: value[:description] }
|
||||
def expand_data(data)
|
||||
if data.is_a?(Hash)
|
||||
{ value: data[:value].to_s, description: data[:description] }.compact
|
||||
else
|
||||
{ value: value.to_s, description: nil }
|
||||
{ value: data.to_s }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,26 +6,24 @@ module Gitlab
|
|||
module Helpers
|
||||
class << self
|
||||
def merge_variables(current_vars, new_vars)
|
||||
current_vars = transform_from_yaml_variables(current_vars)
|
||||
new_vars = transform_from_yaml_variables(new_vars)
|
||||
return current_vars if new_vars.blank?
|
||||
|
||||
transform_to_yaml_variables(
|
||||
current_vars.merge(new_vars)
|
||||
)
|
||||
current_vars = transform_to_array(current_vars) if current_vars.is_a?(Hash)
|
||||
new_vars = transform_to_array(new_vars) if new_vars.is_a?(Hash)
|
||||
|
||||
(new_vars + current_vars).uniq { |var| var[:key] }
|
||||
end
|
||||
|
||||
def transform_to_yaml_variables(vars)
|
||||
vars.to_h.map do |key, value|
|
||||
{ key: key.to_s, value: value, public: true }
|
||||
def transform_to_array(vars)
|
||||
vars.to_h.map do |key, data|
|
||||
if data.is_a?(Hash)
|
||||
{ key: key.to_s, **data.except(:key) }
|
||||
else
|
||||
{ key: key.to_s, value: data }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def transform_from_yaml_variables(vars)
|
||||
return vars.stringify_keys.transform_values(&:to_s) if vars.is_a?(Hash)
|
||||
|
||||
vars.to_a.to_h { |var| [var[:key].to_s, var[:value]] }
|
||||
end
|
||||
|
||||
def inherit_yaml_variables(from:, to:, inheritance:)
|
||||
merge_variables(apply_inheritance(from, inheritance), to)
|
||||
end
|
||||
|
@ -35,7 +33,7 @@ module Gitlab
|
|||
def apply_inheritance(variables, inheritance)
|
||||
case inheritance
|
||||
when true then variables
|
||||
when false then {}
|
||||
when false then []
|
||||
when Array then variables.select { |var| inheritance.include?(var[:key]) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def root_variables
|
||||
@root_variables ||= transform_to_yaml_variables(variables)
|
||||
@root_variables ||= transform_to_array(variables)
|
||||
end
|
||||
|
||||
def jobs
|
||||
|
@ -70,7 +70,7 @@ module Gitlab
|
|||
environment: job[:environment_name],
|
||||
coverage_regex: job[:coverage],
|
||||
# yaml_variables is calculated with using job_variables in Seed::Build
|
||||
job_variables: transform_to_yaml_variables(job[:job_variables]),
|
||||
job_variables: transform_to_array(job[:job_variables]),
|
||||
root_variables_inheritance: job[:root_variables_inheritance],
|
||||
needs_attributes: job.dig(:needs, :job),
|
||||
interruptible: job[:interruptible],
|
||||
|
@ -114,7 +114,7 @@ module Gitlab
|
|||
|
||||
Gitlab::Ci::Variables::Helpers.inherit_yaml_variables(
|
||||
from: root_variables,
|
||||
to: transform_to_yaml_variables(job[:job_variables]),
|
||||
to: job[:job_variables],
|
||||
inheritance: job.fetch(:root_variables_inheritance, true)
|
||||
)
|
||||
end
|
||||
|
@ -137,8 +137,8 @@ module Gitlab
|
|||
job[:release]
|
||||
end
|
||||
|
||||
def transform_to_yaml_variables(variables)
|
||||
::Gitlab::Ci::Variables::Helpers.transform_to_yaml_variables(variables)
|
||||
def transform_to_array(variables)
|
||||
::Gitlab::Ci::Variables::Helpers.transform_to_array(variables)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13929,6 +13929,9 @@ msgstr ""
|
|||
msgid "Draft: %{filename}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Drag to reorder prioritized labels and change their relative priority."
|
||||
msgstr ""
|
||||
|
||||
msgid "Drag your designs here or %{linkStart}click to upload%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22949,7 +22952,7 @@ msgstr ""
|
|||
msgid "Labels can be applied to issues and merge requests to categorize them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Labels can be applied to issues and merge requests."
|
||||
msgid "Labels can be applied to issues and merge requests. Star a label to make it a priority label."
|
||||
msgstr ""
|
||||
|
||||
msgid "Labels with no issues in this iteration:"
|
||||
|
@ -37306,9 +37309,6 @@ msgstr ""
|
|||
msgid "Standard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
|
||||
msgstr ""
|
||||
|
||||
msgid "Star labels to start sorting by priority"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -205,7 +205,6 @@
|
|||
"@types/jest": "^27.5.1",
|
||||
"@vue/test-utils": "1.3.0",
|
||||
"@vue/vue2-jest": "^27.0.0",
|
||||
"acorn": "^6.3.0",
|
||||
"ajv": "^8.10.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios-mock-adapter": "^1.15.0",
|
||||
|
|
|
@ -1,111 +1,164 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe "User creates a merge request", :js do
|
||||
RSpec.describe 'User creates a merge request', :js do
|
||||
include ProjectForksHelper
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:title) { "Some feature" }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "creates a merge request" do
|
||||
visit(project_new_merge_request_path(project))
|
||||
|
||||
find(".js-source-branch").click
|
||||
click_link("fix")
|
||||
|
||||
find(".js-target-branch").click
|
||||
click_link("feature")
|
||||
|
||||
click_button("Compare branches")
|
||||
|
||||
page.within('.merge-request-form') do
|
||||
expect(page.find('#merge_request_description')['placeholder']).to eq 'Describe the goal of the changes and what reviewers should be aware of.'
|
||||
end
|
||||
|
||||
fill_in("Title", with: title)
|
||||
click_button("Create merge request")
|
||||
|
||||
page.within(".merge-request") do
|
||||
expect(page).to have_content(title)
|
||||
end
|
||||
end
|
||||
|
||||
context "XSS branch name exists" do
|
||||
before do
|
||||
project.repository.create_branch("<img/src='x'/onerror=alert('oops')>", "master")
|
||||
end
|
||||
|
||||
it "doesn't execute the dodgy branch name" do
|
||||
shared_examples 'creates a merge request' do
|
||||
specify do
|
||||
visit(project_new_merge_request_path(project))
|
||||
|
||||
find(".js-source-branch").click
|
||||
click_link("<img/src='x'/onerror=alert('oops')>")
|
||||
compare_source_and_target('fix', 'feature')
|
||||
|
||||
find(".js-target-branch").click
|
||||
click_link("feature")
|
||||
page.within('.merge-request-form') do
|
||||
expect(page.find('#merge_request_description')['placeholder']).to eq 'Describe the goal of the changes and what reviewers should be aware of.'
|
||||
end
|
||||
|
||||
click_button("Compare branches")
|
||||
fill_in('Title', with: title)
|
||||
click_button('Create merge request')
|
||||
|
||||
expect { page.driver.browser.switch_to.alert }.to raise_error(Selenium::WebDriver::Error::NoSuchAlertError)
|
||||
page.within('.merge-request') do
|
||||
expect(page).to have_content(title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "to a forked project" do
|
||||
let(:forked_project) { fork_project(project, user, namespace: user.namespace, repository: true) }
|
||||
shared_examples 'renders not found' do
|
||||
specify do
|
||||
visit project_new_merge_request_path(project)
|
||||
|
||||
it "creates a merge request", :sidekiq_might_not_need_inline do
|
||||
visit(project_new_merge_request_path(forked_project))
|
||||
|
||||
expect(page).to have_content("Source branch").and have_content("Target branch")
|
||||
expect(find("#merge_request_target_project_id", visible: false).value).to eq(project.id.to_s)
|
||||
|
||||
click_button("Compare branches and continue")
|
||||
|
||||
expect(page).to have_content("You must select source and target branch")
|
||||
|
||||
first(".js-source-project").click
|
||||
first(".dropdown-source-project a", text: forked_project.full_path)
|
||||
|
||||
first(".js-target-project").click
|
||||
first(".dropdown-target-project a", text: project.full_path)
|
||||
|
||||
first(".js-source-branch").click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
source_branch = "fix"
|
||||
|
||||
first(".js-source-branch-dropdown .dropdown-content a", text: source_branch).click
|
||||
|
||||
click_button("Compare branches and continue")
|
||||
|
||||
expect(page).to have_text _('New merge request')
|
||||
|
||||
page.within("form#new_merge_request") do
|
||||
fill_in("Title", with: title)
|
||||
end
|
||||
|
||||
expect(find(".js-assignee-search")["data-project-id"]).to eq(project.id.to_s)
|
||||
find('.js-assignee-search').click
|
||||
|
||||
page.within(".dropdown-menu-user") do
|
||||
expect(page).to have_content("Unassigned")
|
||||
.and have_content(user.name)
|
||||
.and have_content(project.users.first.name)
|
||||
end
|
||||
find('.js-assignee-search').click
|
||||
|
||||
click_button("Create merge request")
|
||||
|
||||
expect(page).to have_content(title).and have_content("requested to merge #{forked_project.full_path}:#{source_branch} into master")
|
||||
expect(page).to have_title('Not Found')
|
||||
expect(page).to have_content('Page Not Found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a direct project member' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:title) { 'Some feature' }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'creates a merge request'
|
||||
|
||||
context 'with XSS branch name' do
|
||||
before do
|
||||
project.repository.create_branch("<img/src='x'/onerror=alert('oops')>", 'master')
|
||||
end
|
||||
|
||||
it 'does not execute the suspicious branch name' do
|
||||
visit(project_new_merge_request_path(project))
|
||||
|
||||
compare_source_and_target("<img/src='x'/onerror=alert('oops')>", 'feature')
|
||||
|
||||
expect { page.driver.browser.switch_to.alert }.to raise_error(Selenium::WebDriver::Error::NoSuchAlertError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'to a forked project' do
|
||||
let(:forked_project) { fork_project(project, user, namespace: user.namespace, repository: true) }
|
||||
|
||||
it 'creates a merge request', :sidekiq_might_not_need_inline do
|
||||
visit(project_new_merge_request_path(forked_project))
|
||||
|
||||
expect(page).to have_content('Source branch').and have_content('Target branch')
|
||||
expect(find('#merge_request_target_project_id', visible: false).value).to eq(project.id.to_s)
|
||||
|
||||
click_button('Compare branches and continue')
|
||||
|
||||
expect(page).to have_content('You must select source and target branch')
|
||||
|
||||
first('.js-source-project').click
|
||||
first('.dropdown-source-project a', text: forked_project.full_path)
|
||||
|
||||
first('.js-target-project').click
|
||||
first('.dropdown-target-project a', text: project.full_path)
|
||||
|
||||
first('.js-source-branch').click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
source_branch = 'fix'
|
||||
|
||||
first('.js-source-branch-dropdown .dropdown-content a', text: source_branch).click
|
||||
|
||||
click_button('Compare branches and continue')
|
||||
|
||||
expect(page).to have_text _('New merge request')
|
||||
|
||||
page.within('form#new_merge_request') do
|
||||
fill_in('Title', with: title)
|
||||
end
|
||||
|
||||
expect(find('.js-assignee-search')['data-project-id']).to eq(project.id.to_s)
|
||||
find('.js-assignee-search').click
|
||||
|
||||
page.within('.dropdown-menu-user') do
|
||||
expect(page).to have_content('Unassigned')
|
||||
.and have_content(user.name)
|
||||
.and have_content(project.users.first.name)
|
||||
end
|
||||
find('.js-assignee-search').click
|
||||
|
||||
click_button('Create merge request')
|
||||
|
||||
expect(page).to have_content(title).and have_content("requested to merge #{forked_project.full_path}:#{source_branch} into master")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an inherited member from the group' do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when project is public and merge requests are private' do
|
||||
let_it_be(:project) do
|
||||
create(:project,
|
||||
:public,
|
||||
:repository,
|
||||
group: group,
|
||||
merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
context 'and user is a guest' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'renders not found'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let_it_be(:project) { create(:project, :private, :repository, group: group) }
|
||||
|
||||
context 'and user is a guest' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'renders not found'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compare_source_and_target(source_branch, target_branch)
|
||||
find('.js-source-branch').click
|
||||
click_link(source_branch)
|
||||
|
||||
find('.js-target-branch').click
|
||||
click_link(target_branch)
|
||||
|
||||
click_button('Compare branches')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Job log controllers', () => {
|
|||
jobLog: mockJobLog,
|
||||
};
|
||||
|
||||
const createWrapper = (props, { jobLogSearch = false, jobLogJumpToFailures = false } = {}) => {
|
||||
const createWrapper = (props, { jobLogJumpToFailures = false } = {}) => {
|
||||
wrapper = mount(JobLogControllers, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
|
@ -42,7 +42,6 @@ describe('Job log controllers', () => {
|
|||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
jobLogSearch,
|
||||
jobLogJumpToFailures,
|
||||
},
|
||||
},
|
||||
|
@ -290,38 +289,27 @@ describe('Job log controllers', () => {
|
|||
});
|
||||
|
||||
describe('Job log search', () => {
|
||||
describe('with feature flag off', () => {
|
||||
it('does not display job log search', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findJobLogSearch().exists()).toBe(false);
|
||||
expect(findSearchHelp().exists()).toBe(false);
|
||||
});
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
describe('with feature flag on', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({}, { jobLogSearch: true });
|
||||
});
|
||||
it('displays job log search', () => {
|
||||
expect(findJobLogSearch().exists()).toBe(true);
|
||||
expect(findSearchHelp().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays job log search', () => {
|
||||
expect(findJobLogSearch().exists()).toBe(true);
|
||||
expect(findSearchHelp().exists()).toBe(true);
|
||||
});
|
||||
it('emits search results', () => {
|
||||
const expectedSearchResults = [[[mockJobLog[6].lines[1], mockJobLog[6].lines[2]]]];
|
||||
|
||||
it('emits search results', () => {
|
||||
const expectedSearchResults = [[[mockJobLog[6].lines[1], mockJobLog[6].lines[2]]]];
|
||||
findJobLogSearch().vm.$emit('submit');
|
||||
|
||||
findJobLogSearch().vm.$emit('submit');
|
||||
expect(wrapper.emitted('searchResults')).toEqual(expectedSearchResults);
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('searchResults')).toEqual(expectedSearchResults);
|
||||
});
|
||||
it('clears search results', () => {
|
||||
findJobLogSearch().vm.$emit('clear');
|
||||
|
||||
it('clears search results', () => {
|
||||
findJobLogSearch().vm.$emit('clear');
|
||||
|
||||
expect(wrapper.emitted('searchResults')).toEqual([[[]]]);
|
||||
});
|
||||
expect(wrapper.emitted('searchResults')).toEqual([[[]]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -586,6 +586,33 @@ describe('init markdown', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('only converts valid URLs', () => {
|
||||
const notValidUrl = 'group::label';
|
||||
const expectedUrlValue = 'url';
|
||||
const expectedText = `other [${notValidUrl}](${expectedUrlValue}) text`;
|
||||
const initialValue = `other ${notValidUrl} text`;
|
||||
|
||||
textArea.value = initialValue;
|
||||
selectedIndex = initialValue.indexOf(notValidUrl);
|
||||
textArea.setSelectionRange(selectedIndex, selectedIndex + notValidUrl.length);
|
||||
|
||||
insertMarkdownText({
|
||||
textArea,
|
||||
text: textArea.value,
|
||||
tag,
|
||||
blockTag: null,
|
||||
selected: notValidUrl,
|
||||
wrap: false,
|
||||
select,
|
||||
});
|
||||
|
||||
expect(textArea.value).toEqual(expectedText);
|
||||
expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedUrlValue, 1));
|
||||
expect(textArea.selectionEnd).toEqual(
|
||||
expectedText.indexOf(expectedUrlValue, 1) + expectedUrlValue.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('adds block tags on line above and below selection', () => {
|
||||
selected = 'this text\nis multiple\nlines';
|
||||
text = `before \n${selected}\nafter `;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { GlAlert, GlDropdown, GlIcon, GlLoadingIcon, GlPopover } from '@gitlab/u
|
|||
import { nextTick } from 'vue';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
|
||||
|
@ -9,6 +10,7 @@ import CiValidate, { i18n } from '~/pipeline_editor/components/validate/ci_valid
|
|||
import ValidatePipelinePopover from '~/pipeline_editor/components/popovers/validate_pipeline_popover.vue';
|
||||
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql';
|
||||
import lintCIMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
|
||||
import { pipelineEditorTrackingOptions } from '~/pipeline_editor/constants';
|
||||
import {
|
||||
mockBlobContentQueryResponse,
|
||||
mockCiLintPath,
|
||||
|
@ -24,6 +26,7 @@ describe('Pipeline Editor Validate Tab', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
let mockBlobContentData;
|
||||
let trackingSpy;
|
||||
|
||||
const createComponent = ({
|
||||
props,
|
||||
|
@ -140,9 +143,24 @@ describe('Pipeline Editor Validate Tab', () => {
|
|||
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
|
||||
await createComponentWithApollo();
|
||||
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('tracks the simulation event', () => {
|
||||
const {
|
||||
label,
|
||||
actions: { simulatePipeline },
|
||||
} = pipelineEditorTrackingOptions;
|
||||
findCta().vm.$emit('click');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, simulatePipeline, { label });
|
||||
});
|
||||
|
||||
it('renders loading state while simulation is ongoing', async () => {
|
||||
findCta().vm.$emit('click');
|
||||
await nextTick();
|
||||
|
@ -224,10 +242,27 @@ describe('Pipeline Editor Validate Tab', () => {
|
|||
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
|
||||
await createComponentWithApollo();
|
||||
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
|
||||
await findCta().vm.$emit('click');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('tracks the second simulation event', async () => {
|
||||
const {
|
||||
label,
|
||||
actions: { resimulatePipeline },
|
||||
} = pipelineEditorTrackingOptions;
|
||||
|
||||
await wrapper.setProps({ ciFileContent: 'new yaml content' });
|
||||
findResultsCta().vm.$emit('click');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, resimulatePipeline, { label });
|
||||
});
|
||||
|
||||
it('renders content change status', async () => {
|
||||
await wrapper.setProps({ ciFileContent: 'new yaml content' });
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ RSpec.describe Mutations::MergeRequests::Create do
|
|||
|
||||
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
|
||||
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let(:context) do
|
||||
GraphQL::Query::Context.new(
|
||||
|
@ -38,62 +37,106 @@ RSpec.describe Mutations::MergeRequests::Create do
|
|||
|
||||
let(:mutated_merge_request) { subject[:merge_request] }
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
|
||||
context 'when user does not have enough permissions to create a merge request' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
shared_examples 'resource not available' do
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user can create a merge request' do
|
||||
before_all do
|
||||
project.add_developer(user)
|
||||
context 'when user is not a project member' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'resource not available'
|
||||
end
|
||||
|
||||
context 'when user is a direct project member' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
context 'and user is a guest' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'resource not available'
|
||||
end
|
||||
|
||||
it 'creates a new merge request' do
|
||||
expect { mutated_merge_request }.to change(MergeRequest, :count).by(1)
|
||||
end
|
||||
context 'and user is a developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns a new merge request' do
|
||||
expect(mutated_merge_request.title).to eq(title)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
it 'creates a new merge request' do
|
||||
expect { mutated_merge_request }.to change(MergeRequest, :count).by(1)
|
||||
end
|
||||
|
||||
context 'when optional description field is set' do
|
||||
let(:description) { 'content' }
|
||||
|
||||
it 'returns a new merge request with a description' do
|
||||
expect(mutated_merge_request.description).to eq(description)
|
||||
it 'returns a new merge request' do
|
||||
expect(mutated_merge_request.title).to eq(title)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
context 'when optional description field is set' do
|
||||
let(:description) { 'content' }
|
||||
|
||||
it 'returns a new merge request with a description' do
|
||||
expect(mutated_merge_request.description).to eq(description)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional labels field is set' do
|
||||
let(:labels) { %w[label-1 label-2] }
|
||||
|
||||
it 'returns a new merge request with labels' do
|
||||
expect(mutated_merge_request.labels.map(&:title)).to eq(labels)
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service cannot create a merge request' do
|
||||
let(:title) { nil }
|
||||
|
||||
it 'does not create a new merge request' do
|
||||
expect { mutated_merge_request }.not_to change(MergeRequest, :count)
|
||||
end
|
||||
|
||||
it 'returns errors' do
|
||||
expect(mutated_merge_request).to be_nil
|
||||
expect(subject[:errors]).to match_array(['Title can\'t be blank'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when optional labels field is set' do
|
||||
let(:labels) { %w[label-1 label-2] }
|
||||
context 'when user is an inherited member from the group' do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
|
||||
it 'returns a new merge request with labels' do
|
||||
expect(mutated_merge_request.labels.map(&:title)).to eq(labels)
|
||||
expect(subject[:errors]).to be_empty
|
||||
context 'when project is public with private merge requests' do
|
||||
let_it_be(:project) do
|
||||
create(:project,
|
||||
:public,
|
||||
:repository,
|
||||
group: group,
|
||||
merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
context 'and user is a guest' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'resource not available'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service cannot create a merge request' do
|
||||
let(:title) { nil }
|
||||
context 'when project is private' do
|
||||
let_it_be(:project) { create(:project, :private, :repository, group: group) }
|
||||
|
||||
it 'does not create a new merge request' do
|
||||
expect { mutated_merge_request }.not_to change(MergeRequest, :count)
|
||||
end
|
||||
context 'and user is a guest' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns errors' do
|
||||
expect(mutated_merge_request).to be_nil
|
||||
expect(subject[:errors]).to eq(['Title can\'t be blank'])
|
||||
it_behaves_like 'resource not available'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -155,7 +155,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
|
|||
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
|
||||
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
|
||||
only: { refs: %w(branches tags) },
|
||||
job_variables: { 'VAR' => 'job' },
|
||||
job_variables: { 'VAR' => { value: 'job' } },
|
||||
root_variables_inheritance: true,
|
||||
after_script: [],
|
||||
ignore: false,
|
||||
|
@ -215,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
|
|||
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
|
||||
stage: 'test',
|
||||
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
|
||||
job_variables: { 'VAR' => 'job' },
|
||||
job_variables: { 'VAR' => { value: 'job' } },
|
||||
root_variables_inheritance: true,
|
||||
ignore: false,
|
||||
after_script: ['make clean'],
|
||||
|
|
|
@ -97,15 +97,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
let(:attributes) do
|
||||
{ name: 'rspec',
|
||||
ref: 'master',
|
||||
job_variables: [{ key: 'VAR1', value: 'var 1', public: true },
|
||||
{ key: 'VAR2', value: 'var 2', public: true }],
|
||||
job_variables: [{ key: 'VAR1', value: 'var 1' },
|
||||
{ key: 'VAR2', value: 'var 2' }],
|
||||
rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
|
||||
{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }])
|
||||
is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
|
||||
{ key: 'VAR3', value: 'var 3' },
|
||||
{ key: 'VAR2', value: 'var 2' }])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -114,13 +114,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
{
|
||||
name: 'rspec',
|
||||
ref: 'master',
|
||||
job_variables: [{ key: 'VARIABLE', value: 'value', public: true }],
|
||||
job_variables: [{ key: 'VARIABLE', value: 'value' }],
|
||||
tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
|
||||
}
|
||||
end
|
||||
|
||||
it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
|
||||
it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value', public: true }]) }
|
||||
it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
|
||||
end
|
||||
|
||||
context 'with cache:key' do
|
||||
|
@ -257,19 +257,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
let(:attributes) do
|
||||
{ name: 'rspec',
|
||||
ref: 'master',
|
||||
yaml_variables: [{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }],
|
||||
job_variables: [{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }],
|
||||
yaml_variables: [{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' }],
|
||||
job_variables: [{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' }],
|
||||
root_variables_inheritance: root_variables_inheritance }
|
||||
end
|
||||
|
||||
context 'when the pipeline has variables' do
|
||||
let(:root_variables) do
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
|
||||
{ key: 'VAR2', value: 'var pipeline 2', public: true },
|
||||
{ key: 'VAR3', value: 'var pipeline 3', public: true },
|
||||
{ key: 'VAR4', value: 'new var pipeline 4', public: true }]
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1' },
|
||||
{ key: 'VAR2', value: 'var pipeline 2' },
|
||||
{ key: 'VAR3', value: 'var pipeline 3' },
|
||||
{ key: 'VAR4', value: 'new var pipeline 4' }]
|
||||
end
|
||||
|
||||
context 'when root_variables_inheritance is true' do
|
||||
|
@ -277,10 +277,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it 'returns calculated yaml variables' do
|
||||
expect(subject[:yaml_variables]).to match_array(
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
|
||||
{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true },
|
||||
{ key: 'VAR4', value: 'new var pipeline 4', public: true }]
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1' },
|
||||
{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' },
|
||||
{ key: 'VAR4', value: 'new var pipeline 4' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -290,8 +290,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it 'returns job variables' do
|
||||
expect(subject[:yaml_variables]).to match_array(
|
||||
[{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }]
|
||||
[{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -301,9 +301,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it 'returns calculated yaml variables' do
|
||||
expect(subject[:yaml_variables]).to match_array(
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
|
||||
{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }]
|
||||
[{ key: 'VAR1', value: 'var overridden pipeline 1' },
|
||||
{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -314,8 +314,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
|
||||
it 'returns seed yaml variables' do
|
||||
expect(subject[:yaml_variables]).to match_array(
|
||||
[{ key: 'VAR2', value: 'var 2', public: true },
|
||||
{ key: 'VAR3', value: 'var 3', public: true }])
|
||||
[{ key: 'VAR2', value: 'var 2' },
|
||||
{ key: 'VAR3', value: 'var 3' }])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -324,8 +324,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
let(:attributes) do
|
||||
{ name: 'rspec',
|
||||
ref: 'master',
|
||||
yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
|
||||
job_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
|
||||
yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
|
||||
job_variables: [{ key: 'VAR1', value: 'var 1' }],
|
||||
root_variables_inheritance: root_variables_inheritance,
|
||||
rules: rules }
|
||||
end
|
||||
|
@ -338,14 +338,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
end
|
||||
|
||||
it 'recalculates the variables' do
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
|
||||
{ key: 'VAR2', value: 'new var 2', public: true })
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
|
||||
{ key: 'VAR2', value: 'new var 2' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the rules use root variables' do
|
||||
let(:root_variables) do
|
||||
[{ key: 'VAR2', value: 'var pipeline 2', public: true }]
|
||||
[{ key: 'VAR2', value: 'var pipeline 2' }]
|
||||
end
|
||||
|
||||
let(:rules) do
|
||||
|
@ -353,15 +353,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
end
|
||||
|
||||
it 'recalculates the variables' do
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
|
||||
{ key: 'VAR2', value: 'overridden var 2', public: true })
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
|
||||
{ key: 'VAR2', value: 'overridden var 2' })
|
||||
end
|
||||
|
||||
context 'when the root_variables_inheritance is false' do
|
||||
let(:root_variables_inheritance) { false }
|
||||
|
||||
it 'does not recalculate the variables' do
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1', public: true })
|
||||
expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,21 +15,27 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
|
|||
end
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1', public: true },
|
||||
{ key: 'key2', value: 'value22', public: true },
|
||||
{ key: 'key3', value: 'value3', public: true }]
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
subject { described_class.merge_variables(current_variables, new_variables) }
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
it { is_expected.to match_array(result) }
|
||||
|
||||
context 'when new variables is a hash' do
|
||||
let(:new_variables) do
|
||||
{ 'key2' => 'value22', 'key3' => 'value3' }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
|
||||
context 'when new variables is a hash with symbol keys' do
|
||||
|
@ -37,79 +43,72 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
|
|||
{ key2: 'value22', key3: 'value3' }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
|
||||
context 'when new variables is nil' do
|
||||
let(:new_variables) {}
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1', public: true },
|
||||
{ key: 'key2', value: 'value2', public: true }]
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value2' }]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.transform_to_yaml_variables' do
|
||||
let(:variables) do
|
||||
{ 'key1' => 'value1', 'key2' => 'value2' }
|
||||
describe '.transform_to_array' do
|
||||
subject { described_class.transform_to_array(variables) }
|
||||
|
||||
context 'when values are strings' do
|
||||
let(:variables) do
|
||||
{ 'key1' => 'value1', 'key2' => 'value2' }
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value2' }]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1', public: true },
|
||||
{ key: 'key2', value: 'value2', public: true }]
|
||||
end
|
||||
|
||||
subject { described_class.transform_to_yaml_variables(variables) }
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
|
||||
context 'when variables is nil' do
|
||||
let(:variables) {}
|
||||
|
||||
it { is_expected.to eq([]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.transform_from_yaml_variables' do
|
||||
let(:variables) do
|
||||
[{ key: 'key1', value: 'value1', public: true },
|
||||
{ key: 'key2', value: 'value2', public: true }]
|
||||
it { is_expected.to match_array([]) }
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
{ 'key1' => 'value1', 'key2' => 'value2' }
|
||||
end
|
||||
|
||||
subject { described_class.transform_from_yaml_variables(variables) }
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
|
||||
context 'when variables is nil' do
|
||||
let(:variables) {}
|
||||
|
||||
it { is_expected.to eq({}) }
|
||||
end
|
||||
|
||||
context 'when variables is a hash' do
|
||||
context 'when values are hashes' do
|
||||
let(:variables) do
|
||||
{ key1: 'value1', 'key2' => 'value2' }
|
||||
{ 'key1' => { value: 'value1', description: 'var 1' }, 'key2' => { value: 'value2' } }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
|
||||
context 'when variables contain integers and symbols' do
|
||||
let(:variables) do
|
||||
{ key1: 1, key2: :value2 }
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1', description: 'var 1' },
|
||||
{ key: 'key2', value: 'value2' }]
|
||||
end
|
||||
|
||||
let(:result1) do
|
||||
{ 'key1' => '1', 'key2' => 'value2' }
|
||||
end
|
||||
it { is_expected.to match_array(result) }
|
||||
|
||||
it { is_expected.to eq(result1) }
|
||||
context 'when a value data has `key` as a key' do
|
||||
let(:variables) do
|
||||
{ 'key1' => { value: 'value1', key: 'new_key1' }, 'key2' => { value: 'value2' } }
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value2' }]
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -127,35 +126,35 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
|
|||
let(:inheritance) { true }
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key1', value: 'value1', public: true },
|
||||
{ key: 'key2', value: 'value22', public: true },
|
||||
{ key: 'key3', value: 'value3', public: true }]
|
||||
[{ key: 'key1', value: 'value1' },
|
||||
{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) }
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
it { is_expected.to match_array(result) }
|
||||
|
||||
context 'when inheritance is false' do
|
||||
let(:inheritance) { false }
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key2', value: 'value22', public: true },
|
||||
{ key: 'key3', value: 'value3', public: true }]
|
||||
[{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
|
||||
context 'when inheritance is array' do
|
||||
let(:inheritance) { ['key2'] }
|
||||
|
||||
let(:result) do
|
||||
[{ key: 'key2', value: 'value22', public: true },
|
||||
{ key: 'key3', value: 'value3', public: true }]
|
||||
[{ key: 'key2', value: 'value22' },
|
||||
{ key: 'key3', value: 'value3' }]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
it { is_expected.to match_array(result) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,8 +72,8 @@ module Gitlab
|
|||
|
||||
it 'returns calculated variables with root and job variables' do
|
||||
is_expected.to match_array([
|
||||
{ key: 'VAR1', value: 'value 11', public: true },
|
||||
{ key: 'VAR2', value: 'value 2', public: true }
|
||||
{ key: 'VAR1', value: 'value 11' },
|
||||
{ key: 'VAR2', value: 'value 2' }
|
||||
])
|
||||
end
|
||||
|
||||
|
|
|
@ -448,7 +448,7 @@ module Gitlab
|
|||
|
||||
it 'parses the root:variables as #root_variables' do
|
||||
expect(subject.root_variables)
|
||||
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
|
||||
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed' })
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -490,7 +490,7 @@ module Gitlab
|
|||
|
||||
it 'parses the root:variables as #root_variables' do
|
||||
expect(subject.root_variables)
|
||||
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
|
||||
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed' })
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1098,8 +1098,8 @@ module Gitlab
|
|||
|
||||
it 'returns job variables' do
|
||||
expect(job_variables).to contain_exactly(
|
||||
{ key: 'VAR1', value: 'value1', public: true },
|
||||
{ key: 'VAR2', value: 'value2', public: true }
|
||||
{ key: 'VAR1', value: 'value1' },
|
||||
{ key: 'VAR2', value: 'value2' }
|
||||
)
|
||||
expect(root_variables_inheritance).to eq(true)
|
||||
end
|
||||
|
@ -1203,21 +1203,21 @@ module Gitlab
|
|||
expect(config_processor.builds[0]).to include(
|
||||
name: 'test1',
|
||||
options: { script: ['test'] },
|
||||
job_variables: [{ key: 'VAR1', value: 'test1 var 1', public: true },
|
||||
{ key: 'VAR2', value: 'test2 var 2', public: true }]
|
||||
job_variables: [{ key: 'VAR1', value: 'test1 var 1' },
|
||||
{ key: 'VAR2', value: 'test2 var 2' }]
|
||||
)
|
||||
|
||||
expect(config_processor.builds[1]).to include(
|
||||
name: 'test2',
|
||||
options: { script: ['test'] },
|
||||
job_variables: [{ key: 'VAR1', value: 'base var 1', public: true },
|
||||
{ key: 'VAR2', value: 'test2 var 2', public: true }]
|
||||
job_variables: [{ key: 'VAR1', value: 'base var 1' },
|
||||
{ key: 'VAR2', value: 'test2 var 2' }]
|
||||
)
|
||||
|
||||
expect(config_processor.builds[2]).to include(
|
||||
name: 'test3',
|
||||
options: { script: ['test'] },
|
||||
job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }]
|
||||
job_variables: [{ key: 'VAR1', value: 'base var 1' }]
|
||||
)
|
||||
|
||||
expect(config_processor.builds[3]).to include(
|
||||
|
|
|
@ -7,24 +7,18 @@ RSpec.describe MergeRequestPolicy do
|
|||
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:non_team_member) { create(:user) }
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
def permissions(user, merge_request)
|
||||
described_class.new(user, merge_request)
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
project.add_guest(author)
|
||||
project.add_developer(developer)
|
||||
end
|
||||
|
||||
mr_perms = %i[create_merge_request_in
|
||||
create_merge_request_from
|
||||
read_merge_request
|
||||
update_merge_request
|
||||
create_todo
|
||||
approve_merge_request
|
||||
create_note
|
||||
|
@ -40,7 +34,28 @@ RSpec.describe MergeRequestPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'a user with access' do
|
||||
shared_examples_for 'a user with reporter access' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:policy, :is_allowed) do
|
||||
:create_merge_request_in | true
|
||||
:read_merge_request | true
|
||||
:create_todo | true
|
||||
:create_note | true
|
||||
:update_subscription | true
|
||||
:create_merge_request_from | false
|
||||
:approve_merge_request | false
|
||||
:update_merge_request | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
specify do
|
||||
is_allowed ? (is_expected.to be_allowed(policy)) : (is_expected.to be_disallowed(policy))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'a user with full access' do
|
||||
let(:perms) { permissions(subject, merge_request) }
|
||||
|
||||
mr_perms.each do |thing|
|
||||
|
@ -50,199 +65,304 @@ RSpec.describe MergeRequestPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when merge request is public' do
|
||||
context 'when user is a direct project member' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
project.add_guest(author)
|
||||
project.add_developer(developer)
|
||||
end
|
||||
|
||||
context 'when merge request is public' do
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
|
||||
let(:user) { author }
|
||||
|
||||
context 'and user is author' do
|
||||
subject { permissions(user, merge_request) }
|
||||
|
||||
context 'and the user is a guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:update_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests have been disabled' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
describe 'the author' do
|
||||
subject { author }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a guest' do
|
||||
subject { guest }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests are private' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
describe 'the author' do
|
||||
subject { author }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a user with full access'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge request is unlocked' do
|
||||
let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
|
||||
|
||||
it 'allows author to reopen merge request' do
|
||||
expect(permissions(author, merge_request)).to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'allows developer to reopen merge request' do
|
||||
expect(permissions(developer, merge_request)).to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents guest from reopening merge request' do
|
||||
expect(permissions(guest, merge_request)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge request is locked' do
|
||||
let(:merge_request_locked) { create(:merge_request, :closed, discussion_locked: true, source_project: project, target_project: project, author: author) }
|
||||
|
||||
it 'prevents author from reopening merge request' do
|
||||
expect(permissions(author, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents developer from reopening merge request' do
|
||||
expect(permissions(developer, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents guests from reopening merge request' do
|
||||
expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
context 'when the user is project member, with at least guest access' do
|
||||
let(:user) { guest }
|
||||
|
||||
it 'can create a note' do
|
||||
expect(permissions(user, merge_request_locked)).to be_allowed(:create_note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with external authorization enabled' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||
let(:policies) { described_class.new(user, merge_request) }
|
||||
|
||||
before do
|
||||
enable_external_authorization_service_check
|
||||
end
|
||||
|
||||
it 'can read the issue iid without accessing the external service' do
|
||||
expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
|
||||
|
||||
expect(policies).to be_allowed(:read_merge_request_iid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an inherited member from the parent group' do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
before_all do
|
||||
group.add_guest(guest)
|
||||
group.add_guest(author)
|
||||
group.add_reporter(reporter)
|
||||
group.add_developer(developer)
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public, group: group) }
|
||||
|
||||
describe 'the merge request author' do
|
||||
subject { permissions(author, merge_request) }
|
||||
|
||||
specify do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and merge requests are private' do
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
describe 'a guest' do
|
||||
subject { guest }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a reporter' do
|
||||
subject { permissions(reporter, merge_request) }
|
||||
|
||||
it_behaves_like 'a user with reporter access'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a user with full access'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let(:project) { create(:project, :private, group: group) }
|
||||
|
||||
describe 'a guest' do
|
||||
subject { guest }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a reporter' do
|
||||
subject { permissions(reporter, merge_request) }
|
||||
|
||||
it_behaves_like 'a user with reporter access'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a user with full access'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an inherited member from a shared group' do
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
|
||||
let(:user) { author }
|
||||
|
||||
context 'and user is anonymous' do
|
||||
before do
|
||||
project.add_guest(author)
|
||||
end
|
||||
|
||||
context 'and group is given developer access' do
|
||||
let(:user) { non_team_member }
|
||||
|
||||
subject { permissions(user, merge_request) }
|
||||
|
||||
before do
|
||||
group = create(:group)
|
||||
project.project_group_links.create!(
|
||||
group: group,
|
||||
group_access: Gitlab::Access::DEVELOPER)
|
||||
|
||||
group.add_guest(non_team_member)
|
||||
end
|
||||
|
||||
specify do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not a project member' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
context 'when merge request is public' do
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
||||
|
||||
subject { permissions(non_team_member, merge_request) }
|
||||
|
||||
specify do
|
||||
is_expected.not_to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests are disabled' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
subject { non_team_member }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
context 'when merge requests are private' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
subject { non_team_member }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
context 'when merge request is locked' do
|
||||
let(:merge_request) { create(:merge_request, :closed, discussion_locked: true, source_project: project, target_project: project) }
|
||||
|
||||
it 'cannot create a note' do
|
||||
expect(permissions(non_team_member, merge_request)).to be_disallowed(:create_note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is anonymous' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
context 'when merge request is public' do
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
||||
|
||||
subject { permissions(nil, merge_request) }
|
||||
|
||||
it do
|
||||
specify do
|
||||
is_expected.to be_disallowed(:create_todo, :update_subscription)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and user is author' do
|
||||
subject { permissions(user, merge_request) }
|
||||
|
||||
context 'and the user is a guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:update_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user is a group member' do
|
||||
let(:project) { create(:project, :public, group: group) }
|
||||
let(:group) { create(:group) }
|
||||
let(:user) { non_team_member }
|
||||
|
||||
before do
|
||||
group.add_guest(non_team_member)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user is a member of a shared group' do
|
||||
let(:user) { non_team_member }
|
||||
|
||||
before do
|
||||
group = create(:group)
|
||||
project.project_group_links.create!(
|
||||
group: group,
|
||||
group_access: Gitlab::Access::DEVELOPER)
|
||||
|
||||
group.add_guest(non_team_member)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user is not a project member' do
|
||||
let(:user) { non_team_member }
|
||||
|
||||
it do
|
||||
is_expected.not_to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests have been disabled' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
before do
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
describe 'the author' do
|
||||
subject { author }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a guest' do
|
||||
subject { guest }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'any other user' do
|
||||
subject { non_team_member }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests are private' do
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
describe 'a non-team-member' do
|
||||
subject { non_team_member }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'the author' do
|
||||
subject { author }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
it_behaves_like 'a user with access'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge request is unlocked' do
|
||||
let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
|
||||
|
||||
it 'allows author to reopen merge request' do
|
||||
expect(permissions(author, merge_request)).to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'allows developer to reopen merge request' do
|
||||
expect(permissions(developer, merge_request)).to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents guest from reopening merge request' do
|
||||
expect(permissions(guest, merge_request)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge request is locked' do
|
||||
let(:merge_request_locked) { create(:merge_request, :closed, discussion_locked: true, source_project: project, target_project: project, author: author) }
|
||||
|
||||
it 'prevents author from reopening merge request' do
|
||||
expect(permissions(author, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents developer from reopening merge request' do
|
||||
expect(permissions(developer, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it 'prevents guests from reopening merge request' do
|
||||
expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
context 'when the user is not a project member' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'cannot create a note' do
|
||||
expect(permissions(user, merge_request_locked)).to be_disallowed(:create_note)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is project member, with at least guest access' do
|
||||
let(:user) { guest }
|
||||
|
||||
it 'can create a note' do
|
||||
expect(permissions(user, merge_request_locked)).to be_allowed(:create_note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with external authorization enabled' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||
let(:policies) { described_class.new(user, merge_request) }
|
||||
|
||||
before do
|
||||
enable_external_authorization_service_check
|
||||
end
|
||||
|
||||
it 'can read the issue iid without accessing the external service' do
|
||||
expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
|
||||
|
||||
expect(policies).to be_allowed(:read_merge_request_iid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1120,6 +1120,44 @@ RSpec.describe API::MergeRequests do
|
|||
end.not_to exceed_query_limit(control)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an inherited member from the group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
shared_examples 'user cannot view merge requests' do
|
||||
it 'returns 403 forbidden' do
|
||||
get api("/projects/#{group_project.id}/merge_requests", inherited_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and user is a guest' do
|
||||
let_it_be(:inherited_user) { create(:user) }
|
||||
|
||||
before_all do
|
||||
group.add_guest(inherited_user)
|
||||
end
|
||||
|
||||
context 'when project is public with private merge requests' do
|
||||
let(:group_project) do
|
||||
create(:project,
|
||||
:public,
|
||||
:repository,
|
||||
group: group,
|
||||
merge_requests_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot view merge requests'
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let(:group_project) { create(:project, :private, :repository, group: group) }
|
||||
|
||||
it_behaves_like 'user cannot view merge requests'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /groups/:id/merge_requests" do
|
||||
|
@ -2219,6 +2257,59 @@ RSpec.describe API::MergeRequests do
|
|||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an inherited member from the group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
shared_examples 'user cannot create merge requests' do
|
||||
it 'returns 403 forbidden' do
|
||||
post api("/projects/#{group_project.id}/merge_requests", inherited_user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and user is a guest' do
|
||||
let_it_be(:inherited_user) { create(:user) }
|
||||
let_it_be(:params) do
|
||||
{
|
||||
title: 'Test merge request',
|
||||
source_branch: 'feature_conflict',
|
||||
target_branch: 'master',
|
||||
author_id: inherited_user.id
|
||||
}
|
||||
end
|
||||
|
||||
before_all do
|
||||
group.add_guest(inherited_user)
|
||||
end
|
||||
|
||||
context 'when project is public with private merge requests' do
|
||||
let(:group_project) do
|
||||
create(:project,
|
||||
:public,
|
||||
:repository,
|
||||
group: group,
|
||||
merge_requests_access_level: ProjectFeature::DISABLED,
|
||||
only_allow_merge_if_pipeline_succeeds: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot create merge requests'
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let(:group_project) do
|
||||
create(:project,
|
||||
:private,
|
||||
:repository,
|
||||
group: group,
|
||||
only_allow_merge_if_pipeline_succeeds: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot create merge requests'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /projects/:id/merge_requests/:merge_request_iid' do
|
||||
|
|
|
@ -86,6 +86,18 @@ RSpec.describe API::ProjectPackages do
|
|||
expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include(nested_project.namespace.full_path))))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with JOB-TOKEN auth' do
|
||||
let(:job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
subject { get api(url, job_token: job.token) }
|
||||
|
||||
it_behaves_like 'returns packages', :project, :maintainer
|
||||
it_behaves_like 'returns packages', :project, :developer
|
||||
it_behaves_like 'returns packages', :project, :reporter
|
||||
it_behaves_like 'returns packages', :project, :no_type
|
||||
it_behaves_like 'returns packages', :project, :guest
|
||||
end
|
||||
end
|
||||
|
||||
context 'project is private' do
|
||||
|
@ -116,6 +128,19 @@ RSpec.describe API::ProjectPackages do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with JOB-TOKEN auth' do
|
||||
let(:job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
subject { get api(url, job_token: job.token) }
|
||||
|
||||
it_behaves_like 'returns packages', :project, :maintainer
|
||||
it_behaves_like 'returns packages', :project, :developer
|
||||
it_behaves_like 'returns packages', :project, :reporter
|
||||
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
||||
# TODO uncomment when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
|
||||
# it_behaves_like 'rejects packages access', :project, :guest, :not_found
|
||||
end
|
||||
end
|
||||
|
||||
context 'with pagination params' do
|
||||
|
@ -177,6 +202,8 @@ RSpec.describe API::ProjectPackages do
|
|||
end
|
||||
|
||||
describe 'GET /projects/:id/packages/:package_id' do
|
||||
let(:single_package_schema) { 'public_api/v4/packages/package' }
|
||||
|
||||
subject { get api(package_url, user) }
|
||||
|
||||
shared_examples 'no destroy url' do
|
||||
|
@ -217,7 +244,7 @@ RSpec.describe API::ProjectPackages do
|
|||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('public_api/v4/packages/package')
|
||||
expect(response).to match_response_schema(single_package_schema)
|
||||
end
|
||||
|
||||
it 'returns 404 when the package does not exist' do
|
||||
|
@ -233,6 +260,18 @@ RSpec.describe API::ProjectPackages do
|
|||
end
|
||||
|
||||
it_behaves_like 'no destroy url'
|
||||
|
||||
context 'with JOB-TOKEN auth' do
|
||||
let(:job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
subject { get api(package_url, job_token: job.token) }
|
||||
|
||||
it_behaves_like 'returns package', :project, :maintainer
|
||||
it_behaves_like 'returns package', :project, :developer
|
||||
it_behaves_like 'returns package', :project, :reporter
|
||||
it_behaves_like 'returns package', :project, :no_type
|
||||
it_behaves_like 'returns package', :project, :guest
|
||||
end
|
||||
end
|
||||
|
||||
context 'project is private' do
|
||||
|
@ -259,7 +298,7 @@ RSpec.describe API::ProjectPackages do
|
|||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('public_api/v4/packages/package')
|
||||
expect(response).to match_response_schema(single_package_schema)
|
||||
end
|
||||
|
||||
it_behaves_like 'no destroy url'
|
||||
|
@ -273,6 +312,19 @@ RSpec.describe API::ProjectPackages do
|
|||
it_behaves_like 'destroy url'
|
||||
end
|
||||
|
||||
context 'with JOB-TOKEN auth' do
|
||||
let(:job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
subject { get api(package_url, job_token: job.token) }
|
||||
|
||||
it_behaves_like 'returns package', :project, :maintainer
|
||||
it_behaves_like 'returns package', :project, :developer
|
||||
it_behaves_like 'returns package', :project, :reporter
|
||||
# TODO uncomment when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
|
||||
# it_behaves_like 'rejects packages access', :project, :guest, :not_found
|
||||
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
||||
end
|
||||
|
||||
context 'with pipeline' do
|
||||
let!(:package1) { create(:npm_package, :with_build, project: project) }
|
||||
|
||||
|
@ -355,6 +407,26 @@ RSpec.describe API::ProjectPackages do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
|
||||
context 'with JOB-TOKEN auth' do
|
||||
let(:job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
it 'returns 403 for a user without enough permissions' do
|
||||
project.add_developer(user)
|
||||
|
||||
expect { delete api(package_url, job_token: job.token) }.not_to change { ::Packages::Package.pending_destruction.count }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'returns 204' do
|
||||
project.add_maintainer(user)
|
||||
|
||||
expect { delete api(package_url, job_token: job.token) }.to change { ::Packages::Package.pending_destruction.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a maven package' do
|
||||
|
|
|
@ -36,7 +36,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
|
|||
expect(pipeline.statuses).to match_array [test, bridge]
|
||||
expect(bridge.options).to eq(expected_bridge_options)
|
||||
expect(bridge.yaml_variables)
|
||||
.to include(key: 'CROSS', value: 'downstream', public: true)
|
||||
.to include(key: 'CROSS', value: 'downstream')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ RSpec.describe Ci::ListConfigVariablesService, :use_clean_rails_memory_store_cac
|
|||
it 'returns variable list' do
|
||||
expect(subject['KEY1']).to eq({ value: 'val 1', description: 'description 1' })
|
||||
expect(subject['KEY2']).to eq({ value: 'val 2', description: '' })
|
||||
expect(subject['KEY3']).to eq({ value: 'val 3', description: nil })
|
||||
expect(subject['KEY4']).to eq({ value: 'val 4', description: nil })
|
||||
expect(subject['KEY3']).to eq({ value: 'val 3' })
|
||||
expect(subject['KEY4']).to eq({ value: 'val 4' })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -81,6 +81,26 @@ RSpec.shared_examples 'returns packages' do |container_type, user_type|
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'returns package' do |container_type, user_type|
|
||||
context "for #{user_type}" do
|
||||
before do
|
||||
send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
|
||||
end
|
||||
|
||||
it 'returns success response' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
end
|
||||
|
||||
it 'returns a valid response schema' do
|
||||
subject
|
||||
|
||||
expect(response).to match_response_schema(single_package_schema)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
|
||||
context "with subgroups for #{user_type}" do
|
||||
before do
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
workflow:
|
||||
rules:
|
||||
- if: $CI_MERGE_REQUEST_ID
|
||||
|
||||
.rspec:
|
||||
cache:
|
||||
key: omniauth-azure-oauth2
|
||||
paths:
|
||||
- vendor/gems/omniauth-azure-oauth2/vendor/ruby
|
||||
before_script:
|
||||
- cd vendor/gems/omniauth-azure-oauth2
|
||||
- ruby -v # Print out ruby version for debugging
|
||||
- gem install bundler --no-document # Bundler is not installed with the image
|
||||
- bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
|
||||
- bundle config set with 'development'
|
||||
- bundle config set --local frozen 'true' # Disallow Gemfile.lock changes on CI
|
||||
- bundle config # Show bundler configuration
|
||||
- bundle install -j $(nproc)
|
||||
script:
|
||||
- bundle exec rspec
|
||||
|
||||
rspec-2.7:
|
||||
image: "ruby:2.7"
|
||||
extends: .rspec
|
||||
|
||||
rspec-3.0:
|
||||
image: "ruby:3.0"
|
||||
extends: .rspec
|
|
@ -0,0 +1,31 @@
|
|||
# Version 0.0.9
|
||||
* Expand JWT dep. Thanks @ronaldsalas
|
||||
|
||||
# Version 0.0.9
|
||||
* Added support for dynamic tenant urls. Thanks @marcus-fellinger-esc
|
||||
|
||||
# Version 0.0.8
|
||||
* Upgrade to omniauth-oauth2 1.4.0 and fix callback url issue
|
||||
* Allow prompt parameter, thanks @hilu
|
||||
* Add tenant id to info
|
||||
* Updated base url
|
||||
|
||||
# Version 0.0.6
|
||||
* Use 'name' from Azure for name, and 'unique_name' for nickname per Auth Hash spec. Thanks @jayme-github
|
||||
|
||||
# Version 0.0.5
|
||||
* loosen jwt requirement
|
||||
|
||||
# Version 0.0.5
|
||||
* loosen jwt requirement
|
||||
|
||||
# VERSION 0.0.4
|
||||
* fix for JWT scoping, thanks @tobsher
|
||||
|
||||
# VERSION 0.0.3
|
||||
* added common endpoint and removed mandatory requirement for tenant-id
|
||||
* upgraded jwt
|
||||
|
||||
# VERSION 0.0.1
|
||||
|
||||
* Initial build
|
|
@ -0,0 +1,8 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in omniauth-azure-oauth2.gemspec
|
||||
gemspec
|
||||
|
||||
group :example do
|
||||
gem 'sinatra'
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
omniauth-azure-oauth2 (0.0.10)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
omniauth (~> 1.0, < 3)
|
||||
omniauth-oauth2 (~> 1.4)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.5.0)
|
||||
faraday (2.5.2)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.0)
|
||||
hashie (5.0.0)
|
||||
jwt (2.4.1)
|
||||
multi_xml (0.6.0)
|
||||
mustermann (2.0.2)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
oauth2 (2.0.6)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
rash_alt (>= 0.4, < 1)
|
||||
version_gem (~> 1.1)
|
||||
omniauth (1.9.1)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-oauth2 (1.7.3)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth (>= 1.9, < 3)
|
||||
rack (2.2.4)
|
||||
rack-protection (2.2.2)
|
||||
rack
|
||||
rake (13.0.6)
|
||||
rash_alt (0.4.12)
|
||||
hashie (>= 3.4)
|
||||
rspec (3.11.0)
|
||||
rspec-core (~> 3.11.0)
|
||||
rspec-expectations (~> 3.11.0)
|
||||
rspec-mocks (~> 3.11.0)
|
||||
rspec-core (3.11.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-expectations (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-mocks (3.11.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-support (3.11.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sinatra (2.2.2)
|
||||
mustermann (~> 2.0)
|
||||
rack (~> 2.2)
|
||||
rack-protection (= 2.2.2)
|
||||
tilt (~> 2.0)
|
||||
tilt (2.0.11)
|
||||
version_gem (1.1.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
omniauth-azure-oauth2!
|
||||
rake
|
||||
rspec (>= 2.14.0)
|
||||
sinatra
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.20
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 Deltek
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,161 @@
|
|||
# OmniAuth Windows Azure Active Directory Strategy
|
||||
|
||||
This is fork of [omniauth-azure-oauth2](https://github.com/marknadig/omniauth-azure-oauth2) to support:
|
||||
|
||||
1. OmniAuth v1 and v2. OmniAuth v2 disables GET requests by default
|
||||
and defaults to POST. GitLab already has patched v1 to use POST,
|
||||
but other dependencies need to be updated:
|
||||
https://gitlab.com/gitlab-org/gitlab/-/issues/30073.
|
||||
2. We may deprecate this library entirely in the future:
|
||||
https://gitlab.com/gitlab-org/gitlab/-/issues/366212
|
||||
|
||||
[![Build Status](https://travis-ci.org/KonaTeam/omniauth-azure-oauth2.svg?branch=master)](https://travis-ci.org/KonaTeam/omniauth-azure-oauth2)
|
||||
|
||||
This gem provides a simple way to authenticate to Windows Azure Active Directory (WAAD) over OAuth2 using OmniAuth.
|
||||
|
||||
One of the unique challenges of WAAD OAuth is that WAAD is multi tenant. Any given tenant can have multiple active
|
||||
directories. The CLIENT-ID, REPLY-URL and keys will be unique to the tenant/AD/application combination. This gem simply
|
||||
provides hooks for determining those unique values for each call.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'omniauth-azure-oauth2'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, you will need to add your site as an application in WAAD.:
|
||||
[Adding, Updating, and Removing an Application](http://msdn.microsoft.com/en-us/library/azure/dn132599.aspx)
|
||||
|
||||
Summary:
|
||||
Select your Active Directory in https://manage.windowsazure.com/<tenantid> of type 'Web Application'. Name, sign-on url,
|
||||
logo are not important. You will need the CLIENT-ID from the application configuration and you will need to generate
|
||||
an expiring key (aka 'client secret'). REPLY URL is the oauth redirect uri which will be the omniauth callback path
|
||||
https://example.com/users/auth/azure_oauth2/callback. The APP ID UI just needs to be unique to that tenant and identify
|
||||
your site and isn't needed to configure the gem.
|
||||
Permissions need Delegated Permissions to at least have "Enable sign-on and read user's profiles".
|
||||
|
||||
Note: Seems like the terminology is still fluid, so follow the MS guidance (buwahaha) to set this up.
|
||||
|
||||
The TenantInfo information can be a hash or class. It must provide client_id and client_secret.
|
||||
Optionally a domain_hint and tenant_id. For a simple single-tenant app, this could be:
|
||||
|
||||
```ruby
|
||||
use OmniAuth::Builder do
|
||||
provider :azure_oauth2,
|
||||
{
|
||||
client_id: ENV['AZURE_CLIENT_ID'],
|
||||
client_secret: ENV['AZURE_CLIENT_SECRET'],
|
||||
tenant_id: ENV['AZURE_TENANT_ID']
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
Or the alternative format for use with [devise](https://github.com/plataformatec/devise):
|
||||
|
||||
```ruby
|
||||
config.omniauth :azure_oauth2, client_id: ENV['AZURE_CLIENT_ID'],
|
||||
client_secret: ENV['AZURE_CLIENT_SECRET'], tenant_id: ENV['AZURE_TENANT_ID']
|
||||
```
|
||||
|
||||
For multi-tenant apps where you don't know the tenant_id in advance, simply leave out the tenant_id to use the
|
||||
[common endpoint](http://msdn.microsoft.com/en-us/library/azure/dn645542.aspx).
|
||||
|
||||
```ruby
|
||||
use OmniAuth::Builder do
|
||||
provider :azure_oauth2,
|
||||
{
|
||||
client_id: ENV['AZURE_CLIENT_ID'],
|
||||
client_secret: ENV['AZURE_CLIENT_SECRET']
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
For dynamic tenant assignment, pass a class that supports those same attributes and accepts the strategy as a parameter
|
||||
|
||||
```ruby
|
||||
class YouTenantProvider
|
||||
def initialize(strategy)
|
||||
@strategy = strategy
|
||||
end
|
||||
|
||||
def client_id
|
||||
tenant.azure_client_id
|
||||
end
|
||||
|
||||
def client_secret
|
||||
tenant.azure_client_secret
|
||||
end
|
||||
|
||||
def tenant_id
|
||||
tenant.azure_tanant_id
|
||||
end
|
||||
|
||||
def domain_hint
|
||||
tenant.azure_domain_hint
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tenant
|
||||
# whatever strategy you want to figure out the right tenant from params/session
|
||||
@tenant ||= Customer.find(@strategy.session[:customer_id])
|
||||
end
|
||||
end
|
||||
|
||||
use OmniAuth::Builder do
|
||||
provider :azure_oauth2, YourTenantProvider
|
||||
end
|
||||
```
|
||||
|
||||
The base_azure_url can be overridden in the provider configuration for different locales; e.g. `base_azure_url: "https://login.microsoftonline.de"`
|
||||
|
||||
|
||||
## Auth Hash Schema
|
||||
|
||||
The following information is provided back to you for this provider:
|
||||
|
||||
```ruby
|
||||
{
|
||||
uid: '12345',
|
||||
info: {
|
||||
name: 'some one',
|
||||
first_name: 'some',
|
||||
last_name: 'one',
|
||||
email: 'someone@example.com'
|
||||
},
|
||||
credentials: {
|
||||
token: 'thetoken',
|
||||
refresh_token: 'refresh'
|
||||
},
|
||||
extra: { raw_info: raw_api_response }
|
||||
}
|
||||
```
|
||||
## notes
|
||||
|
||||
When you make a request to WAAD you must specify a resource. The gem currently assumes this is the AD identified as '00000002-0000-0000-c000-000000000000'.
|
||||
This can be passed in as part of the config. It currently isn't designed to be dynamic.
|
||||
|
||||
```ruby
|
||||
use OmniAuth::Builder do
|
||||
provider :azure_oauth2, TenantInfo, resource: 'myresource'
|
||||
end
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Make your changes, add tests, run tests (`rake`)
|
||||
4. Commit your changes and tests (`git commit -am 'Added some feature'`)
|
||||
5. Push to the branch (`git push origin my-new-feature`)
|
||||
6. Create new Pull Request
|
||||
|
||||
|
||||
## Misc
|
||||
Run tests `bundle exec rake`
|
||||
Push to rubygems `bundle exec rake release`.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
require File.join('bundler', 'gem_tasks')
|
||||
require File.join('rspec', 'core', 'rake_task')
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
|
@ -0,0 +1,31 @@
|
|||
$:.push File.dirname(__FILE__) + '/../lib'
|
||||
|
||||
require 'omniauth-azure-oauth2'
|
||||
require 'sinatra'
|
||||
|
||||
class MyAzureProvider
|
||||
def self.client_id
|
||||
ENV['AZURE_CLIENT_ID']
|
||||
end
|
||||
|
||||
def self.client_secret
|
||||
ENV['AZURE_CLIENT_SECRET']
|
||||
end
|
||||
|
||||
def self.tenant_id
|
||||
ENV['AZURE_TENANT_ID']
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
use Rack::Session::Cookie
|
||||
use OmniAuth::Strategies::Azure, MyAzureProvider
|
||||
|
||||
get '/' do
|
||||
"<a href='/auth/azure_oauth2'>Log in with Azure</a>"
|
||||
end
|
||||
|
||||
get '/auth/azure_oauth2/callback' do
|
||||
content_type 'text/plain'
|
||||
request.env['omniauth.auth'].inspect
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
require File.join('omniauth', 'azure_oauth2')
|
|
@ -0,0 +1 @@
|
|||
require File.join('omniauth', 'strategies', 'azure_oauth2')
|
|
@ -0,0 +1,5 @@
|
|||
module OmniAuth
|
||||
module AzureOauth2
|
||||
VERSION = "0.0.10"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
require 'omniauth/strategies/oauth2'
|
||||
require 'jwt'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class AzureOauth2 < OmniAuth::Strategies::OAuth2
|
||||
BASE_AZURE_URL = 'https://login.microsoftonline.com'
|
||||
|
||||
option :name, 'azure_oauth2'
|
||||
|
||||
option :tenant_provider, nil
|
||||
|
||||
# AD resource identifier
|
||||
option :resource, '00000002-0000-0000-c000-000000000000'
|
||||
|
||||
# tenant_provider must return client_id, client_secret and optionally tenant_id and base_azure_url
|
||||
args [:tenant_provider]
|
||||
|
||||
def client
|
||||
if options.tenant_provider
|
||||
provider = options.tenant_provider.new(self)
|
||||
else
|
||||
provider = options # if pass has to config, get mapped right on to options
|
||||
end
|
||||
|
||||
options.client_id = provider.client_id
|
||||
options.client_secret = provider.client_secret
|
||||
options.tenant_id =
|
||||
provider.respond_to?(:tenant_id) ? provider.tenant_id : 'common'
|
||||
options.base_azure_url =
|
||||
provider.respond_to?(:base_azure_url) ? provider.base_azure_url : BASE_AZURE_URL
|
||||
|
||||
options.authorize_params = provider.authorize_params if provider.respond_to?(:authorize_params)
|
||||
options.authorize_params.domain_hint = provider.domain_hint if provider.respond_to?(:domain_hint) && provider.domain_hint
|
||||
options.authorize_params.prompt = request.params['prompt'] if request.params['prompt']
|
||||
options.client_options.authorize_url = "#{options.base_azure_url}/#{options.tenant_id}/oauth2/authorize"
|
||||
options.client_options.token_url = "#{options.base_azure_url}/#{options.tenant_id}/oauth2/token"
|
||||
super
|
||||
end
|
||||
|
||||
uid {
|
||||
raw_info['sub']
|
||||
}
|
||||
|
||||
info do
|
||||
{
|
||||
name: raw_info['name'],
|
||||
nickname: raw_info['unique_name'],
|
||||
first_name: raw_info['given_name'],
|
||||
last_name: raw_info['family_name'],
|
||||
email: raw_info['email'] || raw_info['upn'],
|
||||
oid: raw_info['oid'],
|
||||
tid: raw_info['tid']
|
||||
}
|
||||
end
|
||||
|
||||
def token_params
|
||||
azure_resource = request.env['omniauth.params'] && request.env['omniauth.params']['azure_resource']
|
||||
super.merge(resource: azure_resource || options.resource)
|
||||
end
|
||||
|
||||
def callback_url
|
||||
full_host + script_name + callback_path
|
||||
end
|
||||
|
||||
def raw_info
|
||||
# it's all here in JWT http://msdn.microsoft.com/en-us/library/azure/dn195587.aspx
|
||||
@raw_info ||= ::JWT.decode(access_token.token, nil, false).first
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
require File.expand_path(File.join('..', 'lib', 'omniauth', 'azure_oauth2', 'version'), __FILE__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.authors = ["Mark Nadig"]
|
||||
gem.email = ["mark@nadigs.net"]
|
||||
gem.description = %q{An Windows Azure Active Directory OAuth2 strategy for OmniAuth}
|
||||
gem.summary = %q{An Windows Azure Active Directory OAuth2 strategy for OmniAuth}
|
||||
gem.homepage = "https://github.com/KonaTeam/omniauth-azure-oauth2"
|
||||
|
||||
gem.files = Dir.glob("lib/**/*.*")
|
||||
gem.test_files = Dir.glob("spec/**/**/*.*")
|
||||
gem.name = "omniauth-azure-oauth2"
|
||||
gem.require_paths = ["lib"]
|
||||
gem.version = OmniAuth::AzureOauth2::VERSION
|
||||
gem.license = "MIT"
|
||||
|
||||
gem.add_runtime_dependency 'omniauth', '~> 1.0', '< 3'
|
||||
gem.add_dependency 'jwt', ['>= 1.0', '< 3.0']
|
||||
|
||||
gem.add_runtime_dependency 'omniauth-oauth2', '~> 1.4'
|
||||
|
||||
gem.add_development_dependency 'rspec', '>= 2.14.0'
|
||||
gem.add_development_dependency 'rake'
|
||||
end
|
332
vendor/gems/omniauth-azure-oauth2/spec/omniauth/strategies/azure_oauth2_spec.rb
vendored
Normal file
332
vendor/gems/omniauth-azure-oauth2/spec/omniauth/strategies/azure_oauth2_spec.rb
vendored
Normal file
|
@ -0,0 +1,332 @@
|
|||
require 'spec_helper'
|
||||
require 'omniauth-azure-oauth2'
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
module JWT; end
|
||||
end
|
||||
end
|
||||
|
||||
describe OmniAuth::Strategies::AzureOauth2 do
|
||||
let(:request) { double('Request', :params => {}, :cookies => {}, :env => {}) }
|
||||
let(:app) {
|
||||
lambda do
|
||||
[200, {}, ["Hello."]]
|
||||
end
|
||||
}
|
||||
|
||||
before do
|
||||
OmniAuth.config.test_mode = true
|
||||
end
|
||||
|
||||
after do
|
||||
OmniAuth.config.test_mode = false
|
||||
end
|
||||
|
||||
describe 'static configuration' do
|
||||
let(:options) { @options || {} }
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: 'tenant'}.merge(options))
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.com/tenant/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct authorize params' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to be_nil
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.com/tenant/oauth2/token')
|
||||
end
|
||||
|
||||
describe "overrides" do
|
||||
it 'should override domain_hint' do
|
||||
@options = {domain_hint: 'hint'}
|
||||
allow(subject).to receive(:request) { request }
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to eql('hint')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'static configuration - german' do
|
||||
let(:options) { @options || {} }
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, {client_id: 'id', client_secret: 'secret', tenant_id: 'tenant', base_azure_url: 'https://login.microsoftonline.de'}.merge(options))
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.de/tenant/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct authorize params' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to be_nil
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.de/tenant/oauth2/token')
|
||||
end
|
||||
|
||||
it 'has correct token params' do
|
||||
allow(subject).to receive(:request) { request }
|
||||
subject.client
|
||||
expect(subject.token_params[:resource]).to eql('00000002-0000-0000-c000-000000000000')
|
||||
end
|
||||
|
||||
describe "overrides" do
|
||||
it 'should override domain_hint' do
|
||||
@options = {domain_hint: 'hint'}
|
||||
allow(subject).to receive(:request) { request }
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to eql('hint')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'static common configuration' do
|
||||
let(:options) { @options || {} }
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, {client_id: 'id', client_secret: 'secret'}.merge(options))
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:request) { request }
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.com/common/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.com/common/oauth2/token')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dynamic configuration' do
|
||||
let(:provider_klass) {
|
||||
Class.new {
|
||||
def initialize(strategy)
|
||||
end
|
||||
|
||||
def client_id
|
||||
'id'
|
||||
end
|
||||
|
||||
def client_secret
|
||||
'secret'
|
||||
end
|
||||
|
||||
def tenant_id
|
||||
'tenant'
|
||||
end
|
||||
|
||||
def authorize_params
|
||||
{ custom_option: 'value' }
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, provider_klass)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:request) { request }
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.com/tenant/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct authorize params' do
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to be_nil
|
||||
expect(subject.authorize_params[:custom_option]).to eql('value')
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.com/tenant/oauth2/token')
|
||||
end
|
||||
|
||||
it 'has correct token params' do
|
||||
subject.client
|
||||
expect(subject.token_params[:resource]).to eql('00000002-0000-0000-c000-000000000000')
|
||||
end
|
||||
|
||||
# todo: how to get this working?
|
||||
# describe "overrides" do
|
||||
# it 'should override domain_hint' do
|
||||
# provider_klass.domain_hint = 'hint'
|
||||
# subject.client
|
||||
# expect(subject.authorize_params[:domain_hint]).to eql('hint')
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'dynamic configuration - german' do
|
||||
let(:provider_klass) {
|
||||
Class.new {
|
||||
def initialize(strategy)
|
||||
end
|
||||
|
||||
def client_id
|
||||
'id'
|
||||
end
|
||||
|
||||
def client_secret
|
||||
'secret'
|
||||
end
|
||||
|
||||
def tenant_id
|
||||
'tenant'
|
||||
end
|
||||
|
||||
def base_azure_url
|
||||
'https://login.microsoftonline.de'
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, provider_klass)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:request) { request }
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.de/tenant/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct authorize params' do
|
||||
subject.client
|
||||
expect(subject.authorize_params[:domain_hint]).to be_nil
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.de/tenant/oauth2/token')
|
||||
end
|
||||
|
||||
it 'has correct token params' do
|
||||
subject.client
|
||||
expect(subject.token_params[:resource]).to eql('00000002-0000-0000-c000-000000000000')
|
||||
end
|
||||
|
||||
# todo: how to get this working?
|
||||
# describe "overrides" do
|
||||
# it 'should override domain_hint' do
|
||||
# provider_klass.domain_hint = 'hint'
|
||||
# subject.client
|
||||
# expect(subject.authorize_params[:domain_hint]).to eql('hint')
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'dynamic common configuration' do
|
||||
let(:provider_klass) {
|
||||
Class.new {
|
||||
def initialize(strategy)
|
||||
end
|
||||
|
||||
def client_id
|
||||
'id'
|
||||
end
|
||||
|
||||
def client_secret
|
||||
'secret'
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, provider_klass)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:request) { request }
|
||||
end
|
||||
|
||||
describe '#client' do
|
||||
it 'has correct authorize url' do
|
||||
expect(subject.client.options[:authorize_url]).to eql('https://login.microsoftonline.com/common/oauth2/authorize')
|
||||
end
|
||||
|
||||
it 'has correct token url' do
|
||||
expect(subject.client.options[:token_url]).to eql('https://login.microsoftonline.com/common/oauth2/token')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "raw_info" do
|
||||
subject do
|
||||
OmniAuth::Strategies::AzureOauth2.new(app, {client_id: 'id', client_secret: 'secret'})
|
||||
end
|
||||
|
||||
let(:token) do
|
||||
JWT.encode({"some" => "payload"}, "secret")
|
||||
end
|
||||
|
||||
let(:access_token) do
|
||||
double(:token => token)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:access_token) { access_token }
|
||||
allow(subject).to receive(:request) { request }
|
||||
end
|
||||
|
||||
it "does not clash if JWT strategy is used" do
|
||||
expect do
|
||||
subject.info
|
||||
end.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe 'token_params' do
|
||||
let(:strategy) { OmniAuth::Strategies::AzureOauth2.new(app, client_id: 'id', client_secret: 'secret') }
|
||||
let(:request) { double('Request', env: env) }
|
||||
let(:env) { {} }
|
||||
|
||||
subject { strategy.token_params }
|
||||
|
||||
before { allow(strategy).to receive(:request).and_return request }
|
||||
|
||||
it { is_expected.to be_a OmniAuth::Strategy::Options }
|
||||
it 'has default resource' do
|
||||
expect(subject.resource).to eq '00000002-0000-0000-c000-000000000000'
|
||||
end
|
||||
|
||||
context 'when custom crm url' do
|
||||
let(:crm_url) { 'https://mydomain.crm.dynamics.com/' }
|
||||
let(:env) { { 'omniauth.params' => { 'azure_resource' => crm_url } } }
|
||||
|
||||
it 'has resource from url params' do
|
||||
expect(subject.resource).to eq crm_url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require File.join('bundler', 'setup')
|
||||
require 'rspec'
|
|
@ -4,7 +4,7 @@ workflow:
|
|||
|
||||
.rspec:
|
||||
cache:
|
||||
key: omniauth-gitlab-ruby
|
||||
key: omniauth_crowd
|
||||
paths:
|
||||
- vendor/gems/omniauth_crowd/vendor/ruby
|
||||
before_script:
|
||||
|
|
|
@ -129,6 +129,7 @@ exclude (
|
|||
|
||||
// CVE-2021-42576
|
||||
github.com/microcosm-cc/bluemonday v1.0.2
|
||||
github.com/nats-io/jwt v0.3.0
|
||||
|
||||
// CVE-2020-26892
|
||||
github.com/nats-io/nats-server/v2 v2.1.2
|
||||
|
|
|
@ -873,7 +873,6 @@ github.com/montanaflynn/stats v0.0.0-20151014174947-eeaced052adb/go.mod h1:wL8QJ
|
|||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
|
||||
|
|
|
@ -2488,7 +2488,7 @@ acorn-walk@^8.0.0:
|
|||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.2.tgz#d4632bfc63fd93d0f15fd05ea0e984ffd3f5a8c3"
|
||||
integrity sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A==
|
||||
|
||||
acorn@^6.3.0, acorn@^6.4.1:
|
||||
acorn@^6.4.1:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
|
||||
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
|
||||
|
|
Loading…
Reference in New Issue