From 79c812847fdf78706f078db647dc7cb81e290afc Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 30 Apr 2024 18:15:48 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../layout/leading_comment_space.yml | 6 - .rubocop_todo/layout/space_inside_parens.yml | 11 - Gemfile | 2 +- Gemfile.checksum | 2 +- Gemfile.lock | 4 +- .../components/approvals_empty_state.vue | 90 ++ .../components/show_deployment.vue | 14 +- app/assets/javascripts/deployments/show.js | 12 +- .../import_project_members_modal.vue | 30 +- .../init_import_project_members_modal.js | 9 +- .../components/inbound_token_access.vue | 8 +- app/models/users/callout.rb | 3 +- app/services/groups/base_service.rb | 19 - app/services/groups/create_service.rb | 1 - app/services/groups/update_service.rb | 2 - .../members/import_project_team_service.rb | 4 +- app/views/projects/deployments/show.html.haml | 4 +- .../projects/project_members/index.html.haml | 4 +- app/views/search/_results_list.html.haml | 8 + app/views/search/results/_user.html.haml | 30 +- ...vert_emails_disabled_to_emails_enabled.yml | 9 - ...tive_record_relation_methods_with_limit.rb | 2 +- .../packages/container_registry.md | 10 +- doc/api/graphql/reference/index.md | 1 + .../dbmigrate:multi-version-upgrade-job.md | 17 + .../site_architecture/folder_structure.md | 2 +- .../documentation/styleguide/index.md | 111 +- .../documentation/topic_types/tutorial.md | 2 +- .../testing_guide/frontend_testing.md | 4 +- doc/user/get_started/get_started_projects.md | 131 ++ .../img/get_started_projects_v16_11.png | Bin 0 -> 18963 bytes doc/user/gitlab_duo_chat.md | 12 +- doc/user/packages/container_registry/index.md | 2 - .../merge_requests/ai_in_merge_requests.md | 13 - lib/api/groups.rb | 24 +- lib/api/projects.rb | 4 +- .../security/validators/schema_validator.rb | 16 +- .../cluster-image-scanning-report-format.json | 1065 +++++++++++++ .../container-scanning-report-format.json | 998 ++++++++++++ .../coverage-fuzzing-report-format.json | 975 ++++++++++++ .../schemas/15.1.1/dast-report-format.json | 1380 +++++++++++++++++ .../dependency-scanning-report-format.json | 986 ++++++++++++ .../schemas/15.1.1/sast-report-format.json | 970 ++++++++++++ .../secret-detection-report-format.json | 994 ++++++++++++ lib/uploaded_file.rb | 4 +- locale/gitlab.pot | 47 +- .../components/approvals_empty_state_spec.js | 116 ++ .../components/show_deployment_spec.js | 28 + .../import_project_members_modal_spec.js | 44 +- .../invite_members/mock_data/api_responses.js | 5 + spec/requests/api/projects_spec.rb | 2 + spec/services/groups/create_service_spec.rb | 42 - spec/services/groups/update_service_spec.rb | 50 - .../import_project_team_service_spec.rb | 12 +- 54 files changed, 8087 insertions(+), 254 deletions(-) delete mode 100644 .rubocop_todo/layout/leading_comment_space.yml create mode 100644 app/assets/javascripts/deployments/components/approvals_empty_state.vue delete mode 100644 config/feature_flags/gitlab_com_derisk/invert_emails_disabled_to_emails_enabled.yml create mode 100644 doc/user/get_started/get_started_projects.md create mode 100644 doc/user/get_started/img/get_started_projects_v16_11.png create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/cluster-image-scanning-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/container-scanning-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/coverage-fuzzing-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dast-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dependency-scanning-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/sast-report-format.json create mode 100644 lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/secret-detection-report-format.json create mode 100644 spec/frontend/deployments/components/approvals_empty_state_spec.js diff --git a/.rubocop_todo/layout/leading_comment_space.yml b/.rubocop_todo/layout/leading_comment_space.yml deleted file mode 100644 index 9dc79492f2a..00000000000 --- a/.rubocop_todo/layout/leading_comment_space.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -# Cop supports --autocorrect. -Layout/LeadingCommentSpace: - Exclude: - - 'config/initializers/kaminari_active_record_relation_methods_with_limit.rb' - - 'lib/uploaded_file.rb' diff --git a/.rubocop_todo/layout/space_inside_parens.yml b/.rubocop_todo/layout/space_inside_parens.yml index 4c96a8803ab..513c341fd7c 100644 --- a/.rubocop_todo/layout/space_inside_parens.yml +++ b/.rubocop_todo/layout/space_inside_parens.yml @@ -2,17 +2,6 @@ # Cop supports --autocorrect. Layout/SpaceInsideParens: Exclude: - - 'ee/app/models/ee/dependency_proxy/blob.rb' - - 'ee/app/models/ee/dependency_proxy/manifest.rb' - - 'ee/app/services/gitlab_subscriptions/notify_seats_exceeded_batch_service.rb' - - 'ee/lib/ee/gitlab/auth/ldap/access.rb' - - 'ee/lib/gitlab/auth/smartcard/session.rb' - - 'ee/lib/system_check/geo/current_node_check.rb' - - 'ee/spec/controllers/projects/mirrors_controller_spec.rb' - - 'ee/spec/features/groups/saml_enforcement_spec.rb' - - 'ee/spec/finders/ee/alert_management/http_integrations_finder_spec.rb' - - 'ee/spec/finders/epics_finder_spec.rb' - - 'ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb' - 'ee/spec/finders/security/vulnerability_feedbacks_finder_spec.rb' - 'ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb' - 'ee/spec/frontend/fixtures/epic.rb' diff --git a/Gemfile b/Gemfile index a4719122227..10e75e0ff8b 100644 --- a/Gemfile +++ b/Gemfile @@ -133,7 +133,7 @@ gem 'net-ldap', '~> 0.17.1' # rubocop:todo Gemfile/MissingFeatureCategory # API gem 'grape', '~> 2.0.0', feature_category: :api -gem 'grape-entity', '~> 0.10.2', feature_category: :api +gem 'grape-entity', '~> 1.0.1', feature_category: :api gem 'grape-swagger', '~> 2.0.2', group: [:development, :test], feature_category: :api gem 'grape-swagger-entity', '~> 0.5.1', group: [:development, :test], feature_category: :api gem 'grape-path-helpers', '~> 2.0.1', feature_category: :api diff --git a/Gemfile.checksum b/Gemfile.checksum index de3d710c02b..3e1010f69b2 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -270,7 +270,7 @@ {"name":"googleauth","version":"1.8.1","platform":"ruby","checksum":"814adadaaa1221dce72a67131e3ecbd6d23491a161ec84fb15fd353b87d8c9e7"}, {"name":"gpgme","version":"2.0.23","platform":"ruby","checksum":"c87bbafdb8719da7c58ebcac08297aa1fb227022ac6cd2972829ba68adc91c04"}, {"name":"grape","version":"2.0.0","platform":"ruby","checksum":"3aeff94c17e84ccead4ff98833df691e7da0c108878cc128ca31f80c1047494a"}, -{"name":"grape-entity","version":"0.10.2","platform":"ruby","checksum":"9eb584548135419d1c8ada7d21f7c174a7644e56a8b8e5bfc65d1a7a3421b571"}, +{"name":"grape-entity","version":"1.0.1","platform":"ruby","checksum":"e00f9e94e407aff77aa2945d741f544d07e48501927942988799913151d02634"}, {"name":"grape-path-helpers","version":"2.0.1","platform":"ruby","checksum":"ad5216e52c6e796738a9118087352ab4c962900dbad1d8f8c0f96e093c6702d7"}, {"name":"grape-swagger","version":"2.0.2","platform":"ruby","checksum":"a7139a56ba36fab2e8465f10d668a8c73c30cf44ebe8af960f5a4e3beb200805"}, {"name":"grape-swagger-entity","version":"0.5.1","platform":"ruby","checksum":"f51e372d00ac96cf90d948f87b3f4eb287ab053976ca57ad503d442ad8605523"}, diff --git a/Gemfile.lock b/Gemfile.lock index d1edc0772a9..0f43d93f60e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -846,7 +846,7 @@ GEM mustermann-grape (~> 1.0.0) rack (>= 1.3.0) rack-accept - grape-entity (0.10.2) + grape-entity (1.0.1) activesupport (>= 3.0.0) multi_json (>= 1.3.2) grape-path-helpers (2.0.1) @@ -2047,7 +2047,7 @@ DEPENDENCIES googleauth (~> 1.8.1) gpgme (~> 2.0.23) grape (~> 2.0.0) - grape-entity (~> 0.10.2) + grape-entity (~> 1.0.1) grape-path-helpers (~> 2.0.1) grape-swagger (~> 2.0.2) grape-swagger-entity (~> 0.5.1) diff --git a/app/assets/javascripts/deployments/components/approvals_empty_state.vue b/app/assets/javascripts/deployments/components/approvals_empty_state.vue new file mode 100644 index 00000000000..e23956ee809 --- /dev/null +++ b/app/assets/javascripts/deployments/components/approvals_empty_state.vue @@ -0,0 +1,90 @@ + + diff --git a/app/assets/javascripts/deployments/components/show_deployment.vue b/app/assets/javascripts/deployments/components/show_deployment.vue index b5abfe241e5..729238eb52a 100644 --- a/app/assets/javascripts/deployments/components/show_deployment.vue +++ b/app/assets/javascripts/deployments/components/show_deployment.vue @@ -23,6 +23,8 @@ export default { DeploymentApprovals: () => import('ee_component/deployments/components/deployment_approvals.vue'), DeploymentTimeline: () => import('ee_component/deployments/components/deployment_timeline.vue'), + ApprovalsEmptyState: () => + import('ee_else_ce/deployments/components/approvals_empty_state.vue'), }, inject: ['projectPath', 'deploymentIid', 'environmentName', 'graphqlEtagKey'], apollo: { @@ -70,6 +72,9 @@ export default { isManual() { return this.deployment.job?.manualJob; }, + isLoading() { + return this.$apollo.queries.deployment.loading; + }, }, mounted() { toggleQueryPollingByVisibility( @@ -99,7 +104,7 @@ export default { v-else :deployment="deployment" :environment="environment" - :loading="$apollo.queries.deployment.loading" + :loading="isLoading" /> + const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), }); - const { projectPath, deploymentIid, environmentName, graphqlEtagKey } = el.dataset; + const { + projectPath, + deploymentIid, + environmentName, + graphqlEtagKey, + protectedEnvironmentsAvailable, + protectedEnvironmentsSettingsPath, + } = el.dataset; return new Vue({ el, @@ -21,6 +29,8 @@ export const initializeShowDeployment = (selector = 'js-deployment-details') => deploymentIid, environmentName, graphqlEtagKey, + protectedEnvironmentsAvailable: parseBoolean(protectedEnvironmentsAvailable), + protectedEnvironmentsSettingsPath, }, render(h) { return h(ShowDeployment); diff --git a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue index 887527bab23..623b6f50798 100644 --- a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue @@ -13,6 +13,9 @@ import { } from '../utils/trigger_successful_invite_alert'; import { + BLOCKED_SEAT_OVERAGES_ERROR_REASON, + BLOCKED_SEAT_OVERAGES_BODY, + BLOCKED_SEAT_OVERAGES_CTA, PROJECT_SELECT_LABEL_ID, IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_CATEGORY, IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_LABEL, @@ -42,6 +45,11 @@ export default { label: IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_LABEL, }), ], + inject: { + addSeatsHref: { + default: '', + }, + }, props: { projectId: { type: String, @@ -64,6 +72,7 @@ export default { }, data() { return { + errorReason: '', projectToBeImported: {}, invalidFeedbackMessage: '', totalMembersCount: 0, @@ -137,6 +146,9 @@ export default { count: this.errorsExpanded.length, }); }, + shouldShowSeatOverageNotification() { + return this.errorReason === BLOCKED_SEAT_OVERAGES_ERROR_REASON && this.addSeatsHref; + }, }, mounted() { if (this.reloadPageOnSubmit) { @@ -178,8 +190,9 @@ export default { this.onInviteSuccess(); } } catch (error) { - const message = error.response.data?.message; + const { message, reason } = error.response.data || {}; + this.errorReason = reason; this.showErrorAlert(message); } finally { this.isLoading = false; @@ -213,6 +226,7 @@ export default { this.track('click_x'); }, clearValidation() { + this.errorReason = ''; this.invalidFeedbackMessage = ''; this.invalidMembers = {}; }, @@ -240,6 +254,8 @@ export default { modalCancelButton: __('Cancel'), defaultError: s__('ImportAProjectModal|Unable to import project members'), successMessage: s__('ImportAProjectModal|Successfully imported'), + BLOCKED_SEAT_OVERAGES_BODY, + BLOCKED_SEAT_OVERAGES_CTA, }, errorsLimit: 2, projectSelectLabelId: PROJECT_SELECT_LABEL_ID, @@ -329,6 +345,18 @@ export default { > + + {{ $options.i18n.BLOCKED_SEAT_OVERAGES_BODY }} +

{{ $options.i18n.modalHelpText }}

diff --git a/app/assets/javascripts/invite_members/init_import_project_members_modal.js b/app/assets/javascripts/invite_members/init_import_project_members_modal.js index 90479038414..18902d77dc5 100644 --- a/app/assets/javascripts/invite_members/init_import_project_members_modal.js +++ b/app/assets/javascripts/invite_members/init_import_project_members_modal.js @@ -9,12 +9,19 @@ export default function initImportProjectMembersModal() { return false; } - const { projectId, projectName, reloadPageOnSubmit, usersLimitDataset } = el.dataset; + const { + projectId, + projectName, + reloadPageOnSubmit, + usersLimitDataset, + addSeatsHref, + } = el.dataset; return new Vue({ el, provide: { name: projectName, + addSeatsHref, }, render: (createElement) => createElement(ImportProjectMembersModal, { diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue index 3c44d014edc..02605dfb4fb 100644 --- a/app/assets/javascripts/token_access/components/inbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue @@ -24,7 +24,7 @@ export default { i18n: { toggleLabelTitle: s__('CICD|Limit access %{italicStart}to%{italicEnd} this project'), toggleHelpText: s__( - `CICD|Prevent access to this project from other project CI/CD job tokens, unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more.%{linkEnd}`, + `CICD|Prevent access to this project from other project CI/CD job tokens, unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more%{linkEnd}.`, ), cardHeaderTitle: s__( 'CICD|Allow CI job tokens from the following projects to access this project', @@ -218,9 +218,9 @@ export default { diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index bcf8a59a912..7a85ef35ebd 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -88,7 +88,8 @@ module Users duo_pro_trial_alert: 86, # EE-only deployment_details_feedback: 87, duo_chat_ga_alert: 88, # EE-only - board_add_new_column_trigger_popover: 89 + board_add_new_column_trigger_popover: 89, + deployment_approvals_empty_state: 90 } validates :feature_name, diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb index c59e75444a8..fbe5b3f3ddc 100644 --- a/app/services/groups/base_service.rb +++ b/app/services/groups/base_service.rb @@ -27,24 +27,5 @@ module Groups def remove_unallowed_params # overridden in EE end - - # This is a temporary shim to address an issue with - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135959 and should - # be removed when the issue is resolved. - # - # rubocop:disable Style/IfUnlessModifier -- you're entirely wrong - # rubocop:disable Style/NegatedIf -- you're entirely wrong - def invert_emails_disabled_to_emails_enabled - return unless Feature.enabled?(:invert_emails_disabled_to_emails_enabled) - return unless params.key?(:emails_disabled) - - if !params[:emails_disabled].nil? - params[:emails_enabled] = !Gitlab::Utils.to_boolean(params[:emails_disabled]) - end - - params.delete(:emails_disabled) - end - # rubocop:enable Style/IfUnlessModifier - # rubocop:enable Style/NegatedIf end end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index ad2fd77c01e..18a5299ed9d 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -48,7 +48,6 @@ module Groups def build_group remove_unallowed_params - invert_emails_disabled_to_emails_enabled set_visibility_level diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 4a79a9af9d2..5e81489b917 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -10,8 +10,6 @@ module Groups reject_parent_id! remove_unallowed_params - invert_emails_disabled_to_emails_enabled - before_assignment_hook(group, params) if renaming_group_with_container_registry_images? diff --git a/app/services/members/import_project_team_service.rb b/app/services/members/import_project_team_service.rb index 97b64d6e700..21320fc3b35 100644 --- a/app/services/members/import_project_team_service.rb +++ b/app/services/members/import_project_team_service.rb @@ -21,7 +21,7 @@ module Members result rescue ArgumentError, ImportProjectTeamForbiddenError, SeatLimitExceededError => e - ServiceResponse.error(message: e.message, reason: :unprocessable_entity) + ServiceResponse.error(message: e.message, reason: e.class.name.demodulize.underscore.to_sym) end private @@ -34,7 +34,7 @@ module Members if members.is_a?(Array) members.each { |member| check_member_validity(member) } else - @result = ServiceResponse.error(message: 'Import failed', reason: :unprocessable_entity) + @result = ServiceResponse.error(message: 'Import failed', reason: :import_failed_error) end end diff --git a/app/views/projects/deployments/show.html.haml b/app/views/projects/deployments/show.html.haml index 7bf7df0e38b..48b5c2ec83f 100644 --- a/app/views/projects/deployments/show.html.haml +++ b/app/views/projects/deployments/show.html.haml @@ -6,4 +6,6 @@ #js-deployment-details{ data: { project_path: @project.full_path, environment_name: @environment.name, deployment_iid: @deployment.iid, - graphql_etag_key: @environment.etag_cache_key } } + graphql_etag_key: @environment.etag_cache_key, + protected_environments_available: @project.licensed_feature_available?(:protected_environments).to_s, + protected_environments_settings_path: can?(current_user, :admin_pipeline, @project) && project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') } } diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 7a1c9e3c89c..c93865ea0a1 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -18,8 +18,8 @@ .js-import-project-members-trigger{ data: { classes: 'gl-md-w-auto gl-w-full' } } .js-import-project-members-modal{ data: { project_id: @project.id, project_name: @project.name, - reload_page_on_submit: true.to_s, - users_limit_dataset: common_invite_modal_dataset(@project)[:users_limit_dataset] } } + reload_page_on_submit: true.to_s } + .merge(common_invite_modal_dataset(@project).slice(:users_limit_dataset, :add_seats_href)) } - invite_group_top_margin = 'gl-md-mt-0 gl-mt-3' - if @project.allowed_to_share_with_group? .js-invite-group-trigger{ data: { classes: "gl-md-w-auto gl-w-full gl-md-ml-3 #{invite_group_top_margin}", display_text: _('Invite a group') } } diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml index dad352e376b..5d4033396b9 100644 --- a/app/views/search/_results_list.html.haml +++ b/app/views/search/_results_list.html.haml @@ -17,6 +17,14 @@ - if @scope == 'projects' .term = render 'shared/projects/list', projects: @search_objects, pipeline_status: false + - elsif @scope === 'users' + %table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' } + %thead + %tr + %th= _('User') + %th.text-right= _('Activity') + %tbody + = render_if_exists partial: "search/results/user", collection: @search_objects - else = render_if_exists partial: "search/results/#{@scope.singularize}", collection: @search_objects diff --git a/app/views/search/results/_user.html.haml b/app/views/search/results/_user.html.haml index 683674a8342..ef4cfd1c61e 100644 --- a/app/views/search/results/_user.html.haml +++ b/app/views/search/results/_user.html.haml @@ -1,10 +1,20 @@ -%ul.content-list - %li - .avatar-cell - = user_avatar(user: user, size: 40, user_name: user.name) - .user-info - = link_to user_path(user) do - .item-title - = simple_search_highlight_and_truncate(user.name, @search_term) - = user_status(user) - .cgray= simple_search_highlight_and_truncate(user.to_reference, @search_term) +%tr + %td{ data: { label: _('User') } } + %div + %div{ class: 'gl-display-inline-flex!' } + = render Pajamas::AvatarComponent.new(user, size: 32, alt: '') + .gl-ml-3{ class: 'gl-text-left!' } + = link_to user_path(user), class: 'gl-text-body' do + .gl-display-inline-block.gl-font-weight-bold= simple_search_highlight_and_truncate(user.name, @search_term) + = user_status(user) + %div{ class: 'gl-text-left!' }= simple_search_highlight_and_truncate(user.to_reference, @search_term) + %td.gl-text-right{ data: { label: _('Activity') } } + %div + %span.gl-font-weight-bold= _('User created:') + = l(user.created_at.to_date, format: :long) + %div + %span.gl-font-weight-bold= _('Last activity:') + - if user.last_activity_on + = l(user.last_activity_on.to_date, format: :long) + - else + = _('Never') diff --git a/config/feature_flags/gitlab_com_derisk/invert_emails_disabled_to_emails_enabled.yml b/config/feature_flags/gitlab_com_derisk/invert_emails_disabled_to_emails_enabled.yml deleted file mode 100644 index ce1be8ecd03..00000000000 --- a/config/feature_flags/gitlab_com_derisk/invert_emails_disabled_to_emails_enabled.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: invert_emails_disabled_to_emails_enabled -feature_issue_url: -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148577 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/454371 -milestone: '16.11' -group: group::code review -type: gitlab_com_derisk -default_enabled: false diff --git a/config/initializers/kaminari_active_record_relation_methods_with_limit.rb b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb index 982cb69e532..3b7fc46c2c0 100644 --- a/config/initializers/kaminari_active_record_relation_methods_with_limit.rb +++ b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb @@ -10,7 +10,7 @@ module Kaminari # that limit the COUNT query to a configurable value to avoid query timeouts. # The default limit value is 10,000 records # rubocop: disable Gitlab/ModuleWithInstanceVariables - def total_count_with_limit(column_name = :all, options = {}) #:nodoc: + def total_count_with_limit(column_name = :all, options = {}) # :nodoc: return @total_count if defined?(@total_count) && @total_count # There are some cases that total count can be deduced from loaded records diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 1ae20980c9c..092f8fe7677 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -957,6 +957,10 @@ response to events happening in the registry. Read more about the container registry notifications configuration options in the [Docker Registry notifications documentation](https://distribution.github.io/distribution/about/notifications/). +WARNING: +Support for the `threshold` parameter was [deprecated](https://gitlab.com/gitlab-org/container-registry/-/issues/1243) +in GitLab 17.0, and is planned for removal in 18.0. Use `maxretries` instead. + You can configure multiple endpoints for the container registry. ::Tabs @@ -973,7 +977,8 @@ To configure a notification endpoint for a Linux package installation: 'name' => 'test_endpoint', 'url' => 'https://gitlab.example.com/notify', 'timeout' => '500ms', - 'threshold' => 5, + 'threshold' => 5, # DEPRECATED: use `maxretries` instead. + 'maxretries' => 5, 'backoff' => '1s', 'headers' => { "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"] @@ -999,7 +1004,8 @@ notifications: url: https://my.listener.com/event headers: timeout: 500 - threshold: 5 + threshold: 5 # DEPRECATED: use `maxretries` instead. + maxretries: 5 backoff: 1000 ``` diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3bde7c386ee..785f4c697e9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -34414,6 +34414,7 @@ Name of the feature that the callout is for. | `CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. | | `CLOUD_LICENSING_SUBSCRIPTION_ACTIVATION_BANNER` | Callout feature name for cloud_licensing_subscription_activation_banner. | | `CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. | +| `DEPLOYMENT_APPROVALS_EMPTY_STATE` | Callout feature name for deployment_approvals_empty_state. | | `DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. | | `DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. | | `DUO_CHAT_GA_ALERT` | Callout feature name for duo_chat_ga_alert. | diff --git a/doc/development/database/dbmigrate:multi-version-upgrade-job.md b/doc/development/database/dbmigrate:multi-version-upgrade-job.md index 4d79bf5da00..4687765e29b 100644 --- a/doc/development/database/dbmigrate:multi-version-upgrade-job.md +++ b/doc/development/database/dbmigrate:multi-version-upgrade-job.md @@ -31,6 +31,23 @@ in your local GitLab Development Kit or GitLab Docker instance. For a real-life example, refer to [this failed job](https://gitlab.com/gitlab-org/gitlab/-/jobs/6418619509#L4970). +#### Broken master + +When a new required upgrade stop is added (every three or four milestones), it triggers the build of a new PostgreSQL Dump. +In some cases, this might cause the `db:migrate:multi-version-upgrade` job to fail in `master` pipeline. +For example, if new additional tables are seeded, it helps detect migration errors that might have been missed in older dumps without these seeded tables. + +Workflow for the [broken master](https://handbook.gitlab.com/handbook/engineering/workflow/#broken-master) case: + +1. Identify the root cause MR by searching for the migration that caused the error. For instance, [`db/migrate/20240416123401_add_security_policy_management_project_id_to_security_policies.rb` in failing job](https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/6671417979#L1547) + - Debug locally if needed as described in the [Database reconfigure failures](#database-reconfigure-failures) +1. Reach out to the relevant team that introduced the migration to discuss whether the MR should be reverted or if a fix will be worked on + - If the team isn't available, post about it in the `#database` Slack channel +1. While a fix or revert is being worked on, `master` pipeline can be unblocked by disabling the job temporarily by setting `DISABLE_DB_MULTI_VERSION_UPGRADE=true` in [CI/CD Settings page](https://gitlab.com/gitlab-org/gitlab/-/settings/ci_cd) + - When disabling the job, announce it in the `#master-broken` Slack channel +1. Add a note to [job stability tracking issue#458402](https://gitlab.com/gitlab-org/gitlab/-/issues/458402) +1. Reinstate the job by removing `DISABLE_DB_MULTI_VERSION_UPGRADE` from CI/CD Settings + ### Database import failures If job is failing on setup stage prior to `gitlab:db:configure` diff --git a/doc/development/documentation/site_architecture/folder_structure.md b/doc/development/documentation/site_architecture/folder_structure.md index 739c432ee27..f7ddf30f3d4 100644 --- a/doc/development/documentation/site_architecture/folder_structure.md +++ b/doc/development/documentation/site_architecture/folder_structure.md @@ -52,7 +52,7 @@ When working with directories and files: 1. When creating or renaming a file or directory and it has more than one word in its name, use underscores (`_`) instead of spaces or dashes. For example, proper naming would be `import_project/import_from_github.md`. This applies - to both [image files](../styleguide/index.md#images) and Markdown files. + to both [image files](../styleguide/index.md#illustrations) and Markdown files. 1. Do not upload video files to the product repositories. [Link or embed videos](../styleguide/index.md#videos) instead. 1. In the `doc/user/` directory: diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 08f332301d0..7b3c6f3cd15 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -96,7 +96,7 @@ Also, keep the following guidance in mind: use **custom settings for project integrations**. - Format [dates and times](https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/date-time-terms) consistently and for an international audience. -- Use [images](#images), including screenshots, sparingly. +- Use [illustrations](#illustrations), including screenshots, sparingly. - For [UI text](#ui-text), allow for up to 30% expansion and contraction in translation. To see how much a string expands or contracts in another language, paste the string into [Google Translate](https://translate.google.com/) and review the results. @@ -568,7 +568,7 @@ indentation as the list item. You can do this with: - [Code blocks](#code-blocks) - [Blockquotes](#blockquotes) - [Alert boxes](#alert-boxes) -- [Images](#images) +- [Illustrations](#illustrations) - [Tabs](#tabs) Nested items should always align with the first character of the list @@ -1121,21 +1121,71 @@ To describe multiple fields, use unordered list items: - **Branch name** must be a regular expression. - **User** must be a user with at least the **Maintainer** role. -## Images +## Illustrations -Images, including screenshots, can help a reader better understand a concept. -However, they should be used sparingly because: +Use illustrations only to supplement text, not replace it. -- They tend to become out-of-date. -- They are difficult and expensive to localize. -- They cannot be read by screen readers. - -When needed, use images to help the reader understand: +Illustrations can help the reader understand: +- A concept. - Where they are in a complicated process. - How they should interact with the application. -### Capture the image +Use illustrations sparingly because: + +- They tend to become out-of-date. +- They are difficult and expensive to localize. +- Their content cannot be read by screen readers. + +Types of illustrations used in GitLab documentation are: + +- Diagram. Use a diagram to illustrate a process or the relationship between entities, for example. +- Screenshot. Use a screenshot when you need to show a portion of the GitLab user interface. + +Use a diagram instead of a screenshot when possible because: + +- A diagram's file size is usually much smaller than that of a screenshot. +- A screenshot often needs to be compressed, which generally reduces the image's quality. +- A diagram in SVG format can be displayed at any size without affecting the image's quality. + +### Diagram + +Use a diagram to illustrate a process or the relationship between entities, for example. + +Use [Mermaid](https://mermaid.js.org/#/) to create a diagram. This method has several advantages +over a static image format (screenshot): + +- The Mermaid format is easier to maintain because: + - Their definition is stored as a code block in the documentation's Markdown source. + - The diagram is rendered dynamically at runtime. + - Text content that may change over time, such as feature names, can be found using text search + tools and edited. +- The diagram is rendered as an scalable image, better suited to various output devices and sizes. + +#### Create a diagram + +To create a diagram: + +1. Use the [Mermaid Live Editor](https://mermaid.live/) to create the diagram. +1. Copy the content of the **Code** pane into a `mermaid` code block in the Markdown file. For more + details, see [Mermaid](../../../user/markdown.md#mermaid). +1. To improve accessibility of diagrams, add a title and description. Add these lines on the next + line after declaring the type of diagram, like `flowchart` or `sequenceDiagram`: + + ```yaml + accTitle: your diagram title here + accDescr: describe what your diagram does in a single sentence, with no line breaks. + ``` + +The Mermaid diagram syntax can be difficult to learn. To make this a little easier, see the Mermaid +[Beginner's Guide](https://mermaid.js.org/intro/getting-started.html) and the examples on the +Mermaid site. + +### Screenshot + +Use a screenshot when you need to show a portion of the GitLab user interface. + +#### Capture the screenshot When you take screenshots: @@ -1155,7 +1205,7 @@ When you take screenshots: a documentation page for a consistent reading experience. Ensure your navigation theme is **Indigo** and the syntax highlighting theme is **Light**. These are the default preferences. -### Add callouts +#### Add callouts If you need to emphasize an area in a screenshot, use an arrow. @@ -1166,7 +1216,7 @@ If you need to emphasize an area in a screenshot, use an arrow. ![callout example](img/callouts.png) -### Save the image +#### Save the image - Resize any wide or tall screenshots if needed, but make sure the screenshot is still clear after being resized and compressed. @@ -1184,23 +1234,9 @@ If you need to emphasize an area in a screenshot, use an arrow. the `.md` document that you're working on is located. - Consider using PNG images instead of JPEG. - Compress GIFs with or similar tool. -- Images should be used (only when necessary) to illustrate the description - of a process, not to replace it. - See also how to link and embed [videos](#videos) to illustrate the documentation. -### Add the image link to content - -The Markdown code for including an image in a document is: -`![Image description which will be the alt tag](img/document_image_title_vX_Y.png)` - -The image description is the alt text for the rendered image on the -documentation site. For accessibility and SEO, use [descriptions](https://webaim.org/techniques/alttext/) -that: - -- Are accurate, succinct, and unique. -- Don't use **image of** or **graphic of** to describe the image. - -### Compress images +#### Compress images You should always compress any new images you add to the documentation. One known tool is [`pngquant`](https://pngquant.org/), which is cross-platform and @@ -1239,7 +1275,7 @@ copy of `https://gitlab.com/gitlab-org/gitlab`, run in a terminal: bin/pngquant compress doc/user/img ``` -### Animated images +#### Animated images Avoid using animated images (such as animated GIFs). They can be distracting and annoying for users. @@ -1250,7 +1286,18 @@ include a visual representation to help readers understand it, you can: - Use a static image (screenshot) and if necessary, add callouts to emphasize an area of the screen. - Create a short video of the interaction and link to it. -### Automatic screenshot generator +#### Add the image link to content + +The Markdown code for including an image in a document is: +`![Image description, used for alt tag](img/document_image_title_vX_Y.png)` + +The image description is the alt text for the rendered image on the +documentation site. For accessibility and SEO, use [descriptions](https://webaim.org/techniques/alttext/) +that are accurate, succinct, and unique. + +Don't use **image of** or **graphic of** to describe the image. + +#### Automatic screenshot generator You can use an automatic screenshot generator to take and compress screenshots. @@ -1262,7 +1309,7 @@ You can use an automatic screenshot generator to take and compress screenshots. 1. Identify the location of the screenshots, based on the `gitlab/doc` location defined by the `it` parameter in your script. 1. Commit the newly created screenshots. -#### Extending the tool +##### Extending the tool To add an additional screenshot generator: @@ -1289,7 +1336,7 @@ To add an additional screenshot generator: You can take a screenshot of a page with `visit `. To avoid blank screenshots, use `expect` to wait for the content to load. -##### Single-element screenshots +###### Single-element screenshots You can take a screenshot of a single element. diff --git a/doc/development/documentation/topic_types/tutorial.md b/doc/development/documentation/topic_types/tutorial.md index c5eeceac4cb..143859abaa5 100644 --- a/doc/development/documentation/topic_types/tutorial.md +++ b/doc/development/documentation/topic_types/tutorial.md @@ -94,7 +94,7 @@ do not use `Tutorial` in the title. ## Screenshots You can include screenshots in a tutorial to illustrate important steps in the process. -In the core product documentation, you should [use screenshots sparingly](../styleguide/index.md#images). +In the core product documentation, you should [use illustrations sparingly](../styleguide/index.md#illustrations). However, in tutorials, screenshots can help users understand where they are in a complex process. Try to balance the number of screenshots in the tutorial so they don't disrupt diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 9b511f1553e..4da56efc3ee 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -204,7 +204,6 @@ possible selectors include: - A semantic attribute like `name` (also verifies that `name` was setup properly) - A `data-testid` attribute ([recommended by maintainers of `@vue/test-utils`](https://github.com/vuejs/vue-test-utils/issues/1498#issuecomment-610133465)) optionally combined with [`shallowMountExtended` or `mountExtended`](#shallowmountextended-and-mountextended) -- a Vue `ref` (if using `@vue/test-utils`) ```javascript import { shallowMountExtended } from 'helpers/vue_test_utils_helper' @@ -223,9 +222,9 @@ it('exists', () => { wrapper.find('input[name=foo]'); wrapper.find('[data-testid="my-foo-id"]'); wrapper.findByTestId('my-foo-id'); // with shallowMountExtended or mountExtended – check below - wrapper.find({ ref: 'foo'}); // Bad + wrapper.find({ ref: 'foo'}); wrapper.find('.js-foo'); wrapper.find('.btn-primary'); }); @@ -234,6 +233,7 @@ it('exists', () => { You should use `kebab-case` for `data-testid` attribute. It is not recommended that you add `.js-*` classes just for testing purposes. Only do this if there are no other feasible options available. +Avoid using Vue template refs to query DOM elements in tests because they're an implementation detail of the component, not a public API. ### Querying for child components diff --git a/doc/user/get_started/get_started_projects.md b/doc/user/get_started/get_started_projects.md new file mode 100644 index 00000000000..f884a82c6e8 --- /dev/null +++ b/doc/user/get_started/get_started_projects.md @@ -0,0 +1,131 @@ +--- +stage: Data Stores +group: Tenant Scale +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Get started organizing work with projects + +In GitLab, the data related to a specific development effort goes in a project. +The project serves as a central hub for collaboration, version control, and project management. + +Projects provide the environment for managing and collaborating +on software development projects, from planning and coding to testing and deployment. + +Project creation and maintenance is part of a larger workflow: + +![Workflow](img/get_started_projects_v16_11.png) + +## Step 1: Create a project + +Start by creating a new project in GitLab to contain your codebase, +documentation, and related resources. + +A project contains a repository. A repository contains all the files, +directories, and data related to your work. + +Set the appropriate visibility level (public, internal, or private) for your project, +based on your project's security and collaboration requirements. +Configure project settings, like merge request approvals, issue tracking, +and CI/CD pipelines, to align with your development workflow. + +Use description templates to maintain consistency and provide essential information +when creating issues, merge requests, or other project entities. + +For more information, see: + +- [Create a project](../project/index.md) +- [Manage projects](../project/working_with_projects.md) +- [Project visibility](../public_access.md) +- [Project settings](../project/settings/index.md) +- [Description templates](../project/description_templates.md) + +## Step 2: Secure and control access to projects + +To grant specific access rights to automated tools or external systems, +helping ensure secure integration with your GitLab projects, generate project access tokens. + +If you want to securely deploy your project to external systems, +create deploy keys. These keys can grant read-only access to your repositories. + +And finally, to provide temporary and limited access to +your project's repository and registry, create deploy tokens, which +help enable secure deployments and automation. + +For more information, see: + +- [Project access tokens](../project/settings/project_access_tokens.md) +- [Deploy keys](../project/deploy_keys/index.md) +- [Deploy tokens](../project/deploy_tokens/index.md) + +## Step 3: Collaborate and share projects + +You can invite multiple projects to a group, sometimes called +`sharing a project with a group`. Each project has its own repository, +issues, merge requests, and other features. +When you have multiple projects in the same group, your team members can collaborate +on specific projects while still maintaining +a high-level overview of all the work being done in the group. + +To further refine who has access to which projects, you can +add subgroups to your group. + +For more information, see: + +- [Share projects](../project/members/share_project_with_groups.md) + +## Step 4: Enhance project discoverability and recognition + +To create a consistent and easily recognizable naming scheme for your projects, +use reserved project and group names. Consistent names can help make projects +more discoverable. + +Use the search functionality to quickly find specific projects, +issues, merge requests, or code snippets across your GitLab instance. + +Another way to make your projects more discoverable is to add badges +to your project's `README` file. Badges can display important information, +like build status, test coverage, or version number. They provide a +quick overview of your project's health and status. + +And finally, topics are labels that you can assign to projects +to help you organize and find them. You can assign a topic to several projects. + +For more information, see: + +- [Reserved project and group names](../reserved_names.md) +- [Search](../search/index.md) +- [Badges](../project/badges.md) +- [Project topics](../project/project_topics.md) + +## Step 5: Boost development efficiency and maintain code quality + +Use the code intelligence features, like code navigation, +hover information, and auto-completion, to enhance your productivity and +maintain a high-quality codebase. Code intelligence is a range of tools +that help you efficiently explore, analyze, and maintain your codebase. + +To quickly locate and go to specific files in your project, +use the file finder. + +For more information, see: + +- [Code intelligence](../project/code_intelligence.md) +- [File finder](../project/repository/file_finder.md) + +## Step 6: Migrate projects into GitLab + +When necessary, use file exports to migrate projects to GitLab. +You can migrate from other version control systems or GitLab instances. +When you migrate a frequently accessed repository to GitLab, you can continue to +access it by its original name by using a project alias. + +On GitLab.com, you can transfer a project from one namespace to another, +which is essentially moving it so that another group or team can have +access or ownership. + +For more information, see: + +- [Migrate projects by using file exports](../project/import/index.md) +- [Project aliases](../project/working_with_projects.md#project-aliases) +- [Transfer a project to another namespace](../project/settings/migrate_projects.md) diff --git a/doc/user/get_started/img/get_started_projects_v16_11.png b/doc/user/get_started/img/get_started_projects_v16_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d442932ce8699cbb54c29a21c6a89dbf26400c GIT binary patch literal 18963 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 K@oa^yf84)7aCL$J literal 0 HcmV?d00001 diff --git a/doc/user/gitlab_duo_chat.md b/doc/user/gitlab_duo_chat.md index 4fa016f0cd2..8fb39078497 100644 --- a/doc/user/gitlab_duo_chat.md +++ b/doc/user/gitlab_duo_chat.md @@ -26,7 +26,7 @@ It can assist various tasks of your daily work with the AI-generated content. > - GitLab self-managed users with a Premium or Ultimate subscription. > - GitLab Dedicated users. > -> Eventually a subscription add-on will be required for continued access to GitLab Duo Chat. +> Eventually a subscription add-on will be required for continued access to GitLab Duo Chat. > Learn more about [Duo Pro and Duo Enterprise pricing](https://about.gitlab.com/gitlab-duo/#pricing). Here are examples of common use cases: @@ -168,7 +168,7 @@ This feature is available in VS Code, JetBrains IDEs, and the Web IDE only. `/tests` is a special command to generate a testing suggestion for the selected code in your editor. You can also add additional instructions to be considered, for example: `/tests using the Boost.Test framework` -See [Use GitLab Duo Chat in the VS Code](#use-gitlab-duo-chat-in-vs-code) for more information. +See [Use GitLab Duo Chat in VS Code](#use-gitlab-duo-chat-in-vs-code) for more information. - Use a specific test framework, for example `/tests using the Boost.test framework` (C++) or `/tests using Jest` (JavaScript). - Focus on extreme test cases, for example `/tests focus on extreme cases, force regression testing`. @@ -431,9 +431,13 @@ To use GitLab Duo Chat in the GitLab Duo plugin for JetBrains IDEs: 1. In a JetBrains IDE, open a project. 1. Open Chat by using one of the following methods: - On the right tool window bar, select **GitLab Duo Chat**. - - In the file that you have open in the editor, select some code. + - Use a keyboard shortcut: ALT + d on Windows and Linux, or + Option + d on macOS. + - In the file that you have open in the editor: + 1. Optional. Select some code. 1. Right-click and select **GitLab Duo Chat**. - 1. Select **Explain Code** or **Generate Tests** or **Refactor Code**. + 1. Select **Open Chat Window**. + 1. Select **Explain Code**, **Generate Tests**, or **Refactor Code**. - Add keyboard or mouse shortcuts for each action under **Keymap** in the **Settings**. 1. In the message box, enter your question and press **Enter** or select **Send**. diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md index 603b9aed37a..5f7ae798ffd 100644 --- a/doc/user/packages/container_registry/index.md +++ b/doc/user/packages/container_registry/index.md @@ -10,8 +10,6 @@ DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated -> - Searching by image repository name was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31322) in GitLab 13.0. - You can use the integrated container registry to store container images for each GitLab project. To enable the container registry for your GitLab instance, see the [administrator documentation](../../../administration/packages/container_registry.md). diff --git a/doc/user/project/merge_requests/ai_in_merge_requests.md b/doc/user/project/merge_requests/ai_in_merge_requests.md index 39a44981c97..a2b7b259dec 100644 --- a/doc/user/project/merge_requests/ai_in_merge_requests.md +++ b/doc/user/project/merge_requests/ai_in_merge_requests.md @@ -99,16 +99,3 @@ Provide feedback on this experimental feature in [issue 408994](https://gitlab.c - Contents of the file - The filename - - - -## Generate suggested tests in merge requests - -> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10366) in GitLab 16.0 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141086) to GitLab Duo Chat in GitLab 16.8. - -This feature was [moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141086) -into GitLab Duo Chat in GitLab 16.8. Find more information in -[Write tests in the IDE](../../gitlab_duo_chat.md#write-tests-in-the-ide). - - diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 75eaa28945d..ef97e29a1cc 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -59,20 +59,32 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + # This is a separate method so that EE can extend its behaviour, without + # having to modify this code directly. + # def create_group - # This is a separate method so that EE can extend its behaviour, without - # having to modify this code directly. - ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute + ::Groups::CreateService + .new(current_user, translate_params_for_compatibility) + .execute end + # This is a separate method so that EE can extend its behaviour, without + # having to modify this code directly. + # def update_group(group) - # This is a separate method so that EE can extend its behaviour, without - # having to modify this code directly. ::Groups::UpdateService - .new(group, current_user, declared_params(include_missing: false)) + .new(group, current_user, translate_params_for_compatibility) .execute end + def translate_params_for_compatibility + temp_params = declared_params(include_missing: false) + + temp_params[:emails_enabled] = !temp_params.delete(:emails_disabled) if temp_params.key?(:emails_disabled) + + temp_params + end + def find_group_projects(params, finder_options) group = find_group!(params[:id]) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2c270e7c36e..61ea3a8cb2a 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -833,8 +833,8 @@ module API if result.success? { status: result.status } - elsif result.reason == :unprocessable_entity - render_api_error!(result.message, result.reason) + elsif result.reason + render_structured_api_error!({ 'message' => result.message, 'reason' => result.reason }, :unprocessable_entity) else { status: result.status, message: result.message, total_members_count: result.payload[:total_members_count] } end diff --git a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb index 196c907038d..7158489df9a 100644 --- a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb +++ b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb @@ -7,14 +7,14 @@ module Gitlab module Validators class SchemaValidator SUPPORTED_VERSIONS = { - cluster_image_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - container_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - coverage_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - dast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - api_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - dependency_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - sast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0], - secret_detection: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0] + cluster_image_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + container_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + coverage_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + dast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + api_fuzzing: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + dependency_scanning: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + sast: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1], + secret_detection: %w[15.0.0 15.0.1 15.0.2 15.0.4 15.0.5 15.0.6 15.0.7 15.1.0 15.1.1] }.freeze VERSIONS_TO_REMOVE_IN_17_0 = %w[].freeze diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/cluster-image-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/cluster-image-scanning-report-format.json new file mode 100644 index 00000000000..373f13806e3 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/cluster-image-scanning-report-format.json @@ -0,0 +1,1065 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/cluster-image-scanning-report-format.json", + "title": "Report format for GitLab Cluster Image Scanning", + "description": "This schema provides the the report format for Cluster Image Scanning (https://docs.gitlab.com/ee/user/application_security/cluster_image_scanning/).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "cluster_image_scanning" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "dependency", + "image", + "kubernetes_resource" + ], + "properties": { + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + } + } + }, + "operating_system": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The operating system that contains the vulnerable package." + }, + "image": { + "type": "string", + "minLength": 1, + "description": "The analyzed Docker image.", + "examples": [ + "index.docker.io/library/nginx:1.21" + ] + }, + "kubernetes_resource": { + "type": "object", + "description": "The specific Kubernetes resource that was scanned.", + "required": [ + "namespace", + "kind", + "name", + "container_name" + ], + "properties": { + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The Kubernetes namespace the resource that had its image scanned.", + "examples": [ + "default", + "staging", + "production" + ] + }, + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The Kubernetes kind the resource that had its image scanned.", + "examples": [ + "Deployment", + "DaemonSet" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The name of the resource that had its image scanned.", + "examples": [ + "nginx-ingress" + ] + }, + "container_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The name of the container that had its image scanned.", + "examples": [ + "nginx" + ] + }, + "agent_id": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The GitLab ID of the Kubernetes Agent which performed the scan.", + "examples": [ + "1234" + ] + }, + "cluster_id": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "The GitLab ID of the Kubernetes cluster when using cluster integration.", + "examples": [ + "1234" + ] + } + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/container-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/container-scanning-report-format.json new file mode 100644 index 00000000000..fa0af95d4cf --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/container-scanning-report-format.json @@ -0,0 +1,998 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/container-scanning-report-format.json", + "title": "Report format for GitLab Container Scanning", + "description": "This schema provides the the report format for Container Scanning (https://docs.gitlab.com/ee/user/application_security/container_scanning).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "container_scanning", + "container_scanning_for_registry" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "dependency", + "operating_system", + "image" + ], + "properties": { + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + } + } + }, + "operating_system": { + "type": "string", + "minLength": 1, + "description": "The operating system that contains the vulnerable package." + }, + "image": { + "type": "string", + "minLength": 1, + "description": "The analyzed Docker image." + }, + "default_branch_image": { + "type": "string", + "maxLength": 255, + "description": "The name of the image on the default branch." + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/coverage-fuzzing-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/coverage-fuzzing-report-format.json new file mode 100644 index 00000000000..27eeec79958 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/coverage-fuzzing-report-format.json @@ -0,0 +1,975 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/coverage-fuzzing-report-format.json", + "title": "Report format for GitLab Fuzz Testing", + "description": "This schema provides the report format for Coverage Guided Fuzz Testing (https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "coverage_fuzzing" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "description": "The location of the error", + "type": "object", + "properties": { + "crash_address": { + "type": "string", + "description": "The relative address in memory were the crash occurred.", + "examples": [ + "0xabababab" + ] + }, + "stacktrace_snippet": { + "type": "string", + "description": "The stack trace recorded during fuzzing resulting the crash.", + "examples": [ + "func_a+0xabcd\nfunc_b+0xabcc" + ] + }, + "crash_state": { + "type": "string", + "description": "Minimised and normalized crash stack-trace (called crash_state).", + "examples": [ + "func_a+0xa\nfunc_b+0xb\nfunc_c+0xc" + ] + }, + "crash_type": { + "type": "string", + "description": "Type of the crash.", + "examples": [ + "Heap-Buffer-overflow", + "Division-by-zero" + ] + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dast-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dast-report-format.json new file mode 100644 index 00000000000..700aba2810d --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dast-report-format.json @@ -0,0 +1,1380 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/dast-report-format.json", + "title": "Report format for GitLab DAST", + "description": "This schema provides the the report format for Dynamic Application Security Testing (https://docs.gitlab.com/ee/user/application_security/dast).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanned_resources", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "dast", + "api_fuzzing" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "scanned_resources": { + "type": "array", + "description": "The attack surface scanned by DAST.", + "items": { + "type": "object", + "required": [ + "method", + "url", + "type" + ], + "properties": { + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method of the scanned resource.", + "examples": [ + "GET", + "POST", + "HEAD" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the scanned resource.", + "examples": [ + "http://my.site.com/a-page" + ] + }, + "type": { + "type": "string", + "minLength": 1, + "description": "Type of the scanned resource, for DAST, this must be 'url'.", + "examples": [ + "url" + ] + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "evidence": { + "type": "object", + "properties": { + "source": { + "type": "object", + "description": "Source of evidence", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique source identifier", + "examples": [ + "assert:LogAnalysis", + "assert:StatusCode" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Source display name", + "examples": [ + "Log Analysis", + "Status Code" + ] + }, + "url": { + "type": "string", + "description": "Link to additional information", + "examples": [ + "https://docs.gitlab.com/ee/development/integrations/secure.html" + ] + } + } + }, + "summary": { + "type": "string", + "description": "Human readable string containing evidence of the vulnerability.", + "examples": [ + "Credit card 4111111111111111 found", + "Server leaked information nginx/1.17.6" + ] + }, + "request": { + "type": "object", + "description": "An HTTP request.", + "required": [ + "headers", + "method", + "url" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method used in the request.", + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the request.", + "examples": [ + "http://my.site.com/vulnerable-endpoint?show-credit-card" + ] + }, + "body": { + "type": "string", + "description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "user=jsmith&first=%27&last=smith" + ] + } + } + }, + "response": { + "type": "object", + "description": "An HTTP response.", + "required": [ + "headers", + "reason_phrase", + "status_code" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "reason_phrase": { + "type": "string", + "description": "HTTP reason phrase of the response.", + "examples": [ + "OK", + "Internal Server Error" + ] + }, + "status_code": { + "type": "integer", + "description": "HTTP status code of the response.", + "examples": [ + 200, + 500 + ] + }, + "body": { + "type": "string", + "description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "{\"user_id\": 2}" + ] + } + } + }, + "supporting_messages": { + "type": "array", + "description": "Array of supporting http messages.", + "items": { + "type": "object", + "description": "A supporting http message.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Message display name.", + "examples": [ + "Unmodified", + "Recorded" + ] + }, + "request": { + "type": "object", + "description": "An HTTP request.", + "required": [ + "headers", + "method", + "url" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "method": { + "type": "string", + "minLength": 1, + "description": "HTTP method used in the request.", + "examples": [ + "GET", + "POST" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL of the request.", + "examples": [ + "http://my.site.com/vulnerable-endpoint?show-credit-card" + ] + }, + "body": { + "type": "string", + "description": "Body of the request for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "user=jsmith&first=%27&last=smith" + ] + } + } + }, + "response": { + "type": "object", + "description": "An HTTP response.", + "required": [ + "headers", + "reason_phrase", + "status_code" + ], + "properties": { + "headers": { + "type": "array", + "description": "HTTP headers present on the request.", + "items": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the HTTP header.", + "examples": [ + "Accept", + "Content-Length", + "Content-Type" + ] + }, + "value": { + "type": "string", + "description": "Value of the HTTP header.", + "examples": [ + "*/*", + "560", + "application/json; charset=utf-8" + ] + } + } + } + }, + "reason_phrase": { + "type": "string", + "description": "HTTP reason phrase of the response.", + "examples": [ + "OK", + "Internal Server Error" + ] + }, + "status_code": { + "type": "integer", + "description": "HTTP status code of the response.", + "examples": [ + 200, + 500 + ] + }, + "body": { + "type": "string", + "description": "Body of the response for display purposes. Body must be suitable for display (not binary), and truncated to a reasonable size.", + "examples": [ + "{\"user_id\": 2}" + ] + } + } + } + } + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "properties": { + "hostname": { + "type": "string", + "description": "The protocol, domain, and port of the application where the vulnerability was found." + }, + "method": { + "type": "string", + "description": "The HTTP method that was used to request the URL where the vulnerability was found." + }, + "param": { + "type": "string", + "description": "A value provided by a vulnerability rule related to the found vulnerability. Examples include a header value, or a parameter used in a HTTP POST." + }, + "path": { + "type": "string", + "description": "The path of the URL where the vulnerability was found. Typically, this would start with a forward slash." + } + } + }, + "assets": { + "type": "array", + "description": "Array of build assets associated with vulnerability.", + "items": { + "type": "object", + "description": "Describes an asset associated with vulnerability.", + "required": [ + "type", + "name", + "url" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of asset", + "enum": [ + "http_session", + "postman" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Display name for asset", + "examples": [ + "HTTP Messages", + "Postman Collection" + ] + }, + "url": { + "type": "string", + "minLength": 1, + "description": "Link to asset in build artifacts", + "examples": [ + "https://gitlab.com/gitlab-org/security-products/dast/-/jobs/626397001/artifacts/file//output/zap_session.data" + ] + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dependency-scanning-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dependency-scanning-report-format.json new file mode 100644 index 00000000000..08a2bf2d372 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/dependency-scanning-report-format.json @@ -0,0 +1,986 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/dependency-scanning-report-format.json", + "title": "Report format for GitLab Dependency Scanning", + "description": "This schema provides the the report format for Dependency Scanning analyzers (https://docs.gitlab.com/ee/user/application_security/dependency_scanning).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "dependency_scanning" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "required": [ + "file", + "dependency" + ], + "properties": { + "file": { + "type": "string", + "minLength": 1, + "description": "Path to the manifest or lock file where the dependency is declared (such as yarn.lock)." + }, + "dependency": { + "type": "object", + "description": "Describes the dependency of a project where the vulnerability is located.", + "required": [ + "package", + "version" + ], + "properties": { + "package": { + "type": "object", + "description": "Provides information on the package where the vulnerability is located.", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the package where the vulnerability is located." + } + } + }, + "version": { + "type": "string", + "description": "Version of the vulnerable package." + }, + "direct": { + "type": "boolean", + "description": "Tells whether this is a direct, top-level dependency of the scanned project." + } + } + } + } + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/sast-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/sast-report-format.json new file mode 100644 index 00000000000..84169f11423 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/sast-report-format.json @@ -0,0 +1,970 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/sast-report-format.json", + "title": "Report format for GitLab SAST", + "description": "This schema provides the report format for Static Application Security Testing analyzers (https://docs.gitlab.com/ee/user/application_security/sast).", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "sast" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "type": "object", + "description": "Identifies the vulnerability's location.", + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the code affected by the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the code affected by the vulnerability." + }, + "class": { + "type": "string", + "description": "Provides the name of the class where the vulnerability is located." + }, + "method": { + "type": "string", + "description": "Provides the name of the method where the vulnerability is located." + } + } + }, + "raw_source_code_extract": { + "type": "string", + "description": "Provides an unsanitized excerpt of the affected source code." + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/secret-detection-report-format.json b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/secret-detection-report-format.json new file mode 100644 index 00000000000..4c00c012f73 --- /dev/null +++ b/lib/gitlab/ci/parsers/security/validators/schemas/15.1.1/secret-detection-report-format.json @@ -0,0 +1,994 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/master/dist/secret-detection-report-format.json", + "title": "Report format for GitLab Secret Detection", + "description": "This schema provides the the report format for the Secret Detection analyzer (https://docs.gitlab.com/ee/user/application_security/secret_detection)", + "definitions": { + "detail_type": { + "oneOf": [ + { + "$ref": "#/definitions/named_list" + }, + { + "$ref": "#/definitions/list" + }, + { + "$ref": "#/definitions/table" + }, + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/code" + }, + { + "$ref": "#/definitions/value" + }, + { + "$ref": "#/definitions/diff" + }, + { + "$ref": "#/definitions/markdown" + }, + { + "$ref": "#/definitions/commit" + }, + { + "$ref": "#/definitions/file_location" + }, + { + "$ref": "#/definitions/module_location" + } + ] + }, + "text_value": { + "type": "string" + }, + "named_field": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/text_value", + "type": "string", + "minLength": 1 + }, + "description": { + "$ref": "#/definitions/text_value" + } + } + }, + "named_list": { + "type": "object", + "description": "An object with named and typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "named-list" + }, + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "allOf": [ + { + "$ref": "#/definitions/named_field" + }, + { + "$ref": "#/definitions/detail_type" + } + ] + } + } + } + } + }, + "list": { + "type": "object", + "description": "A list of typed fields", + "required": [ + "type", + "items" + ], + "properties": { + "type": { + "const": "list" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + }, + "table": { + "type": "object", + "description": "A table of typed fields", + "required": [ + "type", + "rows" + ], + "properties": { + "type": { + "const": "table" + }, + "header": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + }, + "rows": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/detail_type" + } + } + } + } + }, + "text": { + "type": "object", + "description": "Raw text", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "text" + }, + "value": { + "$ref": "#/definitions/text_value" + } + } + }, + "url": { + "type": "object", + "description": "A single URL", + "required": [ + "type", + "href" + ], + "properties": { + "type": { + "const": "url" + }, + "text": { + "$ref": "#/definitions/text_value" + }, + "href": { + "type": "string", + "minLength": 1, + "examples": [ + "http://mysite.com" + ] + } + } + }, + "code": { + "type": "object", + "description": "A codeblock", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "code" + }, + "value": { + "type": "string" + }, + "lang": { + "type": "string", + "description": "A programming language" + } + } + }, + "value": { + "type": "object", + "description": "A field that can store a range of types of value", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "value" + }, + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + } + }, + "diff": { + "type": "object", + "description": "A diff", + "required": [ + "type", + "before", + "after" + ], + "properties": { + "type": { + "const": "diff" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + } + }, + "markdown": { + "type": "object", + "description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "markdown" + }, + "value": { + "$ref": "#/definitions/text_value", + "examples": [ + "Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)" + ] + } + } + }, + "commit": { + "type": "object", + "description": "A commit/tag/branch within the GitLab project", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "const": "commit" + }, + "value": { + "type": "string", + "description": "The commit SHA", + "minLength": 1 + } + } + }, + "file_location": { + "type": "object", + "description": "A location within a file in the project", + "required": [ + "type", + "file_name", + "line_start" + ], + "properties": { + "type": { + "const": "file-location" + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "line_start": { + "type": "integer" + }, + "line_end": { + "type": "integer" + } + } + }, + "module_location": { + "type": "object", + "description": "A location within a binary module of the form module+relative_offset", + "required": [ + "type", + "module_name", + "offset" + ], + "properties": { + "type": { + "const": "module-location" + }, + "module_name": { + "type": "string", + "minLength": 1, + "examples": [ + "compiled_binary" + ] + }, + "offset": { + "type": "integer", + "examples": [ + 100 + ] + } + } + } + }, + "self": { + "version": "15.1.1" + }, + "type": "object", + "required": [ + "scan", + "version", + "vulnerabilities" + ], + "additionalProperties": true, + "properties": { + "scan": { + "type": "object", + "required": [ + "analyzer", + "end_time", + "scanner", + "start_time", + "status", + "type" + ], + "properties": { + "end_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan finished.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-01-28T03:26:02" + ] + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Communication intended for the initiator of a scan.", + "required": [ + "level", + "value" + ], + "properties": { + "level": { + "type": "string", + "description": "Describes the severity of the communication. Use info to communicate normal scan behaviour; warn to communicate a potentially recoverable problem, or a partial error; fatal to communicate an issue that causes the scan to halt.", + "enum": [ + "info", + "warn", + "fatal" + ], + "examples": [ + "info" + ] + }, + "value": { + "type": "string", + "description": "The message to communicate.", + "minLength": 1, + "examples": [ + "Permission denied, scanning aborted" + ] + } + } + } + }, + "options": { + "type": "array", + "items": { + "type": "object", + "description": "A configuration option used for this scan.", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The configuration option name.", + "maxLength": 255, + "minLength": 1, + "examples": [ + "DAST_FF_ENABLE_BAS", + "DOCKER_TLS_CERTDIR", + "DS_MAX_DEPTH", + "SECURE_LOG_LEVEL" + ] + }, + "source": { + "type": "string", + "description": "The source of this option.", + "enum": [ + "argument", + "file", + "env_variable", + "other" + ] + }, + "value": { + "type": [ + "boolean", + "integer", + "null", + "string" + ], + "description": "The value used for this scan.", + "examples": [ + true, + 2, + null, + "fatal", + "" + ] + } + } + } + }, + "analyzer": { + "type": "object", + "description": "Object defining the analyzer used to perform the scan. Analyzers typically delegate to an underlying scanner to run the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the analyzer.", + "minLength": 1, + "examples": [ + "gitlab-dast" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the analyzer, not required to be unique.", + "minLength": 1, + "examples": [ + "GitLab DAST" + ] + }, + "url": { + "type": "string", + "pattern": "^https?://.+", + "description": "A link to more information about the analyzer.", + "examples": [ + "https://docs.gitlab.com/ee/user/application_security/dast" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the analyzer.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + }, + "version": { + "type": "string", + "description": "The version of the analyzer.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + } + } + }, + "scanner": { + "type": "object", + "description": "Object defining the scanner used to perform the scan.", + "required": [ + "id", + "name", + "version", + "vendor" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique id that identifies the scanner.", + "minLength": 1, + "examples": [ + "my-sast-scanner" + ] + }, + "name": { + "type": "string", + "description": "A human readable value that identifies the scanner, not required to be unique.", + "minLength": 1, + "examples": [ + "My SAST Scanner" + ] + }, + "url": { + "type": "string", + "description": "A link to more information about the scanner.", + "examples": [ + "https://scanner.url" + ] + }, + "version": { + "type": "string", + "description": "The version of the scanner.", + "minLength": 1, + "examples": [ + "1.0.2" + ] + }, + "vendor": { + "description": "The vendor/maintainer of the scanner.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the vendor.", + "minLength": 1, + "examples": [ + "GitLab" + ] + } + } + } + } + }, + "start_time": { + "type": "string", + "description": "ISO8601 UTC value with format yyyy-mm-ddThh:mm:ss, representing when the scan started.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$", + "examples": [ + "2020-02-14T16:01:59" + ] + }, + "status": { + "type": "string", + "description": "Result of the scan.", + "enum": [ + "success", + "failure" + ] + }, + "type": { + "type": "string", + "description": "Type of the scan.", + "enum": [ + "secret_detection" + ] + }, + "primary_identifiers": { + "type": "array", + "description": "An unordered array containing an exhaustive list of primary identifiers for which the analyzer may return results", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + } + } + }, + "schema": { + "type": "string", + "description": "URI pointing to the validating security report schema.", + "pattern": "^https?://.+" + }, + "version": { + "type": "string", + "description": "The version of the schema to which the JSON report conforms.", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "vulnerabilities": { + "type": "array", + "description": "Array of vulnerability objects.", + "items": { + "type": "object", + "description": "Describes the vulnerability using GitLab Flavored Markdown", + "required": [ + "id", + "identifiers", + "location" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the vulnerability. This must not include the finding's specific information." + }, + "description": { + "type": "string", + "maxLength": 1048576, + "description": "A long text section describing the vulnerability more fully." + }, + "severity": { + "type": "string", + "description": "How much the vulnerability impacts the software. Possible values are Info, Unknown, Low, Medium, High, or Critical. Note that some analyzers may not report all these possible values.", + "enum": [ + "Info", + "Unknown", + "Low", + "Medium", + "High", + "Critical" + ] + }, + "solution": { + "type": "string", + "maxLength": 7000, + "description": "Explanation of how to fix the vulnerability." + }, + "identifiers": { + "type": "array", + "minItems": 1, + "description": "An ordered array of references that identify a vulnerability on internal or external databases. The first identifier is the Primary Identifier, which has special meaning.", + "items": { + "type": "object", + "required": [ + "type", + "name", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "for example, cve, cwe, osvdb, usn, or an analyzer-dependent type such as gemnasium).", + "minLength": 1 + }, + "name": { + "type": "string", + "description": "Human-readable name of the identifier.", + "minLength": 1 + }, + "url": { + "type": "string", + "description": "URL of the identifier's documentation.", + "pattern": "^(https?|ftp)://.+" + }, + "value": { + "type": "string", + "description": "Value of the identifier, for matching purpose.", + "minLength": 1 + } + } + } + }, + "cvss_vectors": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "description": "An ordered array of CVSS vectors, each issued by a vendor to rate the vulnerability. The first item in the array is used as the primary CVSS vector, and is used to filter and sort the vulnerability.", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + }, + "required": [ + "vendor", + "vector" + ] + }, + { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "minLength": 1, + "default": "unknown" + }, + "vector": { + "type": "string", + "minLength": 32, + "maxLength": 128, + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + }, + "required": [ + "vendor", + "vector" + ] + } + ] + } + }, + "links": { + "type": "array", + "description": "An array of references to external documentation or articles that describe the vulnerability.", + "items": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the vulnerability details link." + }, + "url": { + "type": "string", + "description": "URL of the vulnerability details document.", + "pattern": "^(https?|ftp)://.+" + } + } + } + }, + "details": { + "$ref": "#/definitions/named_list/properties/items" + }, + "tracking": { + "type": "object", + "description": "Describes how this vulnerability should be tracked as the project changes.", + "oneOf": [ + { + "description": "Declares that a series of items should be tracked using source-specific tracking methods.", + "required": [ + "items" + ], + "properties": { + "type": { + "const": "source" + }, + "items": { + "type": "array", + "items": { + "description": "An item that should be tracked using source-specific tracking methods.", + "type": "object", + "required": [ + "signatures" + ], + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located." + }, + "start_line": { + "type": "number", + "description": "The first line of the file that includes the vulnerability." + }, + "end_line": { + "type": "number", + "description": "The last line of the file that includes the vulnerability." + }, + "signatures": { + "type": "array", + "description": "An array of calculated tracking signatures for this tracking item.", + "minItems": 1, + "items": { + "description": "A calculated tracking signature value and metadata.", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "description": "The algorithm used to generate the signature." + }, + "value": { + "type": "string", + "description": "The result of this signature algorithm." + } + } + } + } + } + } + } + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Each tracking type must declare its own type." + } + } + }, + "flags": { + "description": "Flags that can be attached to vulnerabilities.", + "type": "array", + "items": { + "type": "object", + "description": "Informational flags identified and assigned to a vulnerability.", + "required": [ + "type", + "origin", + "description" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1, + "description": "Result of the scan.", + "enum": [ + "flagged-as-likely-false-positive" + ] + }, + "origin": { + "minLength": 1, + "description": "Tool that issued the flag.", + "type": "string" + }, + "description": { + "minLength": 1, + "description": "What the flag is about.", + "type": "string" + } + } + } + }, + "location": { + "required": [ + "commit" + ], + "type": "object", + "properties": { + "file": { + "type": "string", + "description": "Path to the file where the vulnerability is located" + }, + "commit": { + "type": "object", + "description": "Represents the commit in which the vulnerability was detected", + "required": [ + "sha" + ], + "properties": { + "author": { + "type": "string" + }, + "date": { + "type": "string" + }, + "message": { + "type": "string" + }, + "sha": { + "type": "string", + "minLength": 1 + } + } + }, + "start_line": { + "type": "number", + "description": "The first line of the code affected by the vulnerability" + }, + "end_line": { + "type": "number", + "description": "The last line of the code affected by the vulnerability" + }, + "class": { + "type": "string", + "description": "Provides the name of the class where the vulnerability is located" + }, + "method": { + "type": "string", + "description": "Provides the name of the method where the vulnerability is located" + } + } + }, + "raw_source_code_extract": { + "type": "string", + "description": "Provides an unsanitized excerpt of the affected source code." + } + } + } + }, + "remediations": { + "type": "array", + "description": "An array of objects containing information on available remediations, along with patch diffs to apply.", + "items": { + "type": "object", + "required": [ + "fixes", + "summary", + "diff" + ], + "properties": { + "fixes": { + "type": "array", + "description": "An array of strings that represent references to vulnerabilities fixed by this remediation.", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Unique identifier of the vulnerability. This is recommended to be a UUID.", + "examples": [ + "642735a5-1425-428d-8d4e-3c854885a3c9" + ] + } + } + } + }, + "summary": { + "type": "string", + "minLength": 1, + "description": "An overview of how the vulnerabilities were fixed." + }, + "diff": { + "type": "string", + "minLength": 1, + "description": "A base64-encoded remediation code diff, compatible with git apply." + } + } + } + } + } +} diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index fb20557bb6a..af817bfc046 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -111,11 +111,11 @@ class UploadedFile alias_method :local_path, :path - def method_missing(method_name, *args, &block) #:nodoc: + def method_missing(method_name, *args, &block) # :nodoc: @tempfile.__send__(method_name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend end - def respond_to?(method_name, include_private = false) #:nodoc: + def respond_to?(method_name, include_private = false) # :nodoc: @tempfile.respond_to?(method_name, include_private) || super end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0af8f1613d8..fb04dcbcc7e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9829,7 +9829,7 @@ msgstr "" msgid "CICD|Prevent CI/CD job tokens from this project from being used to access other projects unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more%{linkEnd}." msgstr "" -msgid "CICD|Prevent access to this project from other project CI/CD job tokens, unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more.%{linkEnd}" +msgid "CICD|Prevent access to this project from other project CI/CD job tokens, unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more%{linkEnd}." msgstr "" msgid "CICD|The %{boldStart}Limit access %{boldEnd}%{italicAndBoldStart}from%{italicAndBoldEnd}%{boldStart} this project%{boldEnd} setting is deprecated and will be removed in the 18.0 milestone. Use the %{boldStart}Limit access %{boldEnd}%{italicAndBoldStart}to%{italicAndBoldEnd}%{boldStart} this project%{boldEnd} setting and allowlist instead. %{linkStart}How do I do this?%{linkEnd}" @@ -17887,6 +17887,9 @@ msgstr "" msgid "Deployments|No deployment history" msgstr "" +msgid "Deployment|A colleague" +msgstr "" + msgid "Deployment|Add approval comment" msgstr "" @@ -17920,6 +17923,9 @@ msgstr "" msgid "Deployment|Deployment ID" msgstr "" +msgid "Deployment|Deployment approvals require a Premium or Ultimate subscription" +msgstr "" + msgid "Deployment|Failed" msgstr "" @@ -17944,6 +17950,9 @@ msgstr "" msgid "Deployment|Flux sync status is unknown" msgstr "" +msgid "Deployment|Improve your continuous delivery practices with deployment approvals. Configure rules for required approvals, control which users can deploy to your environments, and collaborate throughout the delivery process." +msgstr "" + msgid "Deployment|Job" msgstr "" @@ -17962,6 +17971,9 @@ msgstr "" msgid "Deployment|Ready to be deployed." msgstr "" +msgid "Deployment|Ready to use deployment approvals?" +msgstr "" + msgid "Deployment|Reject" msgstr "" @@ -17982,6 +17994,15 @@ msgstr[1] "" msgid "Deployment|Running" msgstr "" +msgid "Deployment|Set up Deployment Approvals" +msgstr "" + +msgid "Deployment|Set up deployment approvals to get more our of your deployments" +msgstr "" + +msgid "Deployment|Set up deployment approvals to get started" +msgstr "" + msgid "Deployment|Skipped" msgstr "" @@ -18015,6 +18036,9 @@ msgstr "" msgid "Deployment|Unknown" msgstr "" +msgid "Deployment|Upgrade to get more our of your deployments" +msgstr "" + msgid "Deployment|Waiting" msgstr "" @@ -18027,6 +18051,9 @@ msgstr "" msgid "Deployment|What would you like to see here?" msgstr "" +msgid "Deployment|You" +msgstr "" + msgid "Deprecated API rate limits" msgstr "" @@ -23235,6 +23262,9 @@ msgstr "" msgid "GitLab Pages has moved" msgstr "" +msgid "GitLab Premium" +msgstr "" + msgid "GitLab Shell" msgstr "" @@ -25917,6 +25947,9 @@ msgstr "" msgid "How to track time" msgstr "" +msgid "HttpDestinationValidator validates only http external audit event destinations." +msgstr "" + msgid "I am sorry, I am unable to find what you are looking for." msgstr "" @@ -29956,6 +29989,9 @@ msgstr "" msgid "Last activity" msgstr "" +msgid "Last activity:" +msgstr "" + msgid "Last attempted number:" msgstr "" @@ -55943,6 +55979,9 @@ msgstr "" msgid "User created at" msgstr "" +msgid "User created:" +msgstr "" + msgid "User deleted own account on %{timestamp}" msgstr "" @@ -62253,6 +62292,9 @@ msgstr "" msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}" msgstr "" +msgid "should have length between 16 to 24 characters." +msgstr "" + msgid "show %{count} more" msgstr "" @@ -62409,6 +62451,9 @@ msgstr "" msgid "uploads" msgstr "" +msgid "url is already taken." +msgstr "" + msgid "user" msgid_plural "users" msgstr[0] "" diff --git a/spec/frontend/deployments/components/approvals_empty_state_spec.js b/spec/frontend/deployments/components/approvals_empty_state_spec.js new file mode 100644 index 00000000000..511b0d4dd98 --- /dev/null +++ b/spec/frontend/deployments/components/approvals_empty_state_spec.js @@ -0,0 +1,116 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlBanner, GlTableLite, GlBadge } from '@gitlab/ui'; +import ApprovalsEmptyState from '~/deployments/components/approvals_empty_state.vue'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; + +describe('~/deployments/components/approvals_empty_state.vue', () => { + let wrapper; + let userCalloutDismissSpy; + + const createComponent = ({ shouldShowCallout = true, propsData = {}, slots = {} } = {}) => { + wrapper = shallowMount(ApprovalsEmptyState, { + propsData, + slots, + stubs: { + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss: userCalloutDismissSpy, + shouldShowCallout, + }), + GlBanner, + }, + }); + }; + + const findBanner = () => wrapper.findComponent(GlBanner); + const findBadge = () => wrapper.findComponent(GlBadge); + const findTable = () => wrapper.findComponent(GlTableLite); + + describe('when the callout is not dismissed', () => { + it('shows the banner', () => { + createComponent(); + + expect(findBanner().exists()).toBe(true); + }); + + describe('with default values', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders default banner props', () => { + expect(findBanner().props()).toMatchObject({ + title: 'Upgrade to get more our of your deployments', + buttonText: 'Learn more', + buttonLink: '/help/ci/environments/deployment_approvals', + }); + }); + + it('renders table header with the correct text', () => { + expect(findBanner().text()).toContain( + 'Deployment approvals require a Premium or Ultimate subscription', + ); + }); + + it('renders table header with the premium badge', () => { + expect(findBadge().props()).toMatchObject({ icon: 'license', variant: 'tier' }); + expect(findBadge().text()).toBe('GitLab Premium'); + }); + + it('renders table with static data', () => { + expect(findTable().props('fields')).toEqual([ + { key: 'approvers', label: 'Approvers' }, + { key: 'approvals', label: 'Approvals' }, + { key: 'approvedBy', label: 'Approved By' }, + ]); + }); + + it('renders CTA text in banner actions', () => { + expect(findBanner().text()).toContain('Ready to use deployment approvals?'); + }); + }); + + describe('with custom values', () => { + it('renders default banner using values from props', () => { + const bannerTitle = 'Custom title'; + const buttonText = 'Custom button text'; + const buttonLink = '/custom/link'; + + createComponent({ + propsData: { bannerTitle, buttonText, buttonLink }, + }); + + expect(findBanner().props()).toMatchObject({ + title: bannerTitle, + buttonText, + buttonLink, + }); + }); + + it('renders custom table-header slot', () => { + createComponent({ + slots: { 'table-header': '
Custom table-header
' }, + }); + + expect(findBanner().text()).toContain('Custom table-header'); + }); + + it('renders custom banner-actions slot', () => { + createComponent({ + slots: { 'banner-actions': '
Custom banner-actions
' }, + }); + + expect(findBanner().text()).toContain('Custom banner-actions'); + }); + }); + }); + + describe('when the callout is dismissed', () => { + beforeEach(() => { + createComponent({ shouldShowCallout: false }); + }); + + it("doesn't show the banner", () => { + expect(findBanner().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/deployments/components/show_deployment_spec.js b/spec/frontend/deployments/components/show_deployment_spec.js index bd92e620ca8..4192eb5cd75 100644 --- a/spec/frontend/deployments/components/show_deployment_spec.js +++ b/spec/frontend/deployments/components/show_deployment_spec.js @@ -10,6 +10,8 @@ import ShowDeployment from '~/deployments/components/show_deployment.vue'; import DeploymentHeader from '~/deployments/components/deployment_header.vue'; import DeploymentDeployBlock from '~/deployments/components/deployment_deploy_block.vue'; import DetailsFeedback from '~/deployments/components/details_feedback.vue'; +import DeploymentAside from '~/deployments/components/deployment_aside.vue'; +import ApprovalsEmptyState from 'ee_else_ce/deployments/components/approvals_empty_state.vue'; import deploymentQuery from '~/deployments/graphql/queries/deployment.query.graphql'; import environmentQuery from '~/deployments/graphql/queries/environment.query.graphql'; import waitForPromises from 'helpers/wait_for_promises'; @@ -24,6 +26,7 @@ const PROJECT_PATH = 'group/project'; const ENVIRONMENT_NAME = mockEnvironmentFixture.data.project.environment.name; const DEPLOYMENT_IID = mockDeploymentFixture.data.project.deployment.iid; const GRAPHQL_ETAG_KEY = 'project/environments'; +const PROTECTED_ENVIRONMENTS_SETTINGS_PATH = '/settings/ci_cd#js-protected-environments-settings'; describe('~/deployments/components/show_deployment.vue', () => { let wrapper; @@ -48,6 +51,8 @@ describe('~/deployments/components/show_deployment.vue', () => { environmentName: ENVIRONMENT_NAME, deploymentIid: DEPLOYMENT_IID, graphqlEtagKey: GRAPHQL_ETAG_KEY, + protectedEnvironmentsAvailable: true, + protectedEnvironmentsSettingsPath: PROTECTED_ENVIRONMENTS_SETTINGS_PATH, }, stubs: { GlSprintf, @@ -58,6 +63,7 @@ describe('~/deployments/components/show_deployment.vue', () => { const findHeader = () => wrapper.findComponent(DeploymentHeader); const findAlert = () => wrapper.findComponent(GlAlert); + const findApprovalsEmptyState = () => wrapper.findComponent(ApprovalsEmptyState); describe('errors', () => { it('shows an error message when the deployment query fails', async () => { @@ -87,6 +93,24 @@ describe('~/deployments/components/show_deployment.vue', () => { }); }); + describe('loading', () => { + beforeEach(() => { + createComponent(); + }); + + it('shows the header component in a loading state', () => { + expect(findHeader().props('loading')).toBe(true); + }); + + it('shows the aside component in a loading state', () => { + expect(wrapper.findComponent(DeploymentAside).props('loading')).toBe(true); + }); + + it("doesn't show the approvals empty state", () => { + expect(findApprovalsEmptyState().exists()).toBe(false); + }); + }); + describe('page', () => { beforeEach(() => { deploymentQueryResponse.mockResolvedValue(mockDeploymentFixture); @@ -116,6 +140,10 @@ describe('~/deployments/components/show_deployment.vue', () => { deployment: mockDeploymentFixture.data.project.deployment, }); }); + + it('shows the approvals empty state', () => { + expect(findApprovalsEmptyState().exists()).toBe(true); + }); }); describe('etag polling', () => { diff --git a/spec/frontend/invite_members/components/import_project_members_modal_spec.js b/spec/frontend/invite_members/components/import_project_members_modal_spec.js index 79ba58bbb3d..9e312abfea1 100644 --- a/spec/frontend/invite_members/components/import_project_members_modal_spec.js +++ b/spec/frontend/invite_members/components/import_project_members_modal_spec.js @@ -12,7 +12,7 @@ import eventHub from '~/invite_members/event_hub'; import ImportProjectMembersModal from '~/invite_members/components/import_project_members_modal.vue'; import ProjectSelect from '~/invite_members/components/project_select.vue'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_CREATED } from '~/lib/utils/http_status'; +import { HTTP_STATUS_CREATED, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import { displaySuccessfulInvitationAlert, @@ -54,8 +54,11 @@ const triggerOpenModal = async () => { await nextTick(); }; -const createComponent = ({ props = {} } = {}) => { +const createComponent = ({ props = {}, provide = {} } = {}) => { wrapper = shallowMountExtended(ImportProjectMembersModal, { + provide: { + ...provide, + }, propsData: { projectId, projectName, @@ -102,6 +105,8 @@ describe('ImportProjectMembersModal', () => { const findMoreInviteErrorsButton = () => wrapper.findByTestId('accordion-button'); const findAccordion = () => wrapper.findComponent(GlCollapse); const findErrorsIcon = () => wrapper.findComponent(GlIcon); + const findSeatOveragesAlert = () => + wrapper.findByTestId('import-project-members-seat-overages-alert'); const findMemberErrorMessage = (element) => `@${Object.keys(importProjectMembersApiResponse.EXPANDED_IMPORT_ERRORS.message)[element]}: ${ Object.values(importProjectMembersApiResponse.EXPANDED_IMPORT_ERRORS.message)[element] @@ -387,5 +392,40 @@ describe('ImportProjectMembersModal', () => { expect(findMoreInviteErrorsButton().exists()).toBe(false); }); }); + + describe('when the import fails due to a seat overage', () => { + const mockInvitationsApi = (code, data) => { + mock.onPost(IMPORT_PROJECT_MEMBERS_PATH).reply(code, data); + }; + + beforeEach(() => { + createComponent({ provide: { addSeatsHref: 'add_seats_url' } }); + findProjectSelect().vm.$emit('input', projectToBeImported); + }); + + it('clears the error when the modal is closed', async () => { + mockInvitationsApi( + HTTP_STATUS_UNPROCESSABLE_ENTITY, + importProjectMembersApiResponse.SEAT_OVERAGE_IMPORT_ERRORS, + ); + + clickImportButton(); + await waitForPromises(); + + expect(formGroupInvalidFeedback()).toBe( + 'There are not enough available seats to invite this many users.', + ); + expect(formGroupErrorState()).toBe(false); + expect(findSeatOveragesAlert().exists()).toBe(true); + + closeModal(); + + await nextTick(); + + expect(formGroupInvalidFeedback()).toBe(''); + expect(formGroupErrorState()).not.toBe(false); + expect(findSeatOveragesAlert().exists()).toBe(false); + }); + }); }); }); diff --git a/spec/frontend/invite_members/mock_data/api_responses.js b/spec/frontend/invite_members/mock_data/api_responses.js index 629aaa4e0aa..e6c8481bd0e 100644 --- a/spec/frontend/invite_members/mock_data/api_responses.js +++ b/spec/frontend/invite_members/mock_data/api_responses.js @@ -110,7 +110,12 @@ const NO_COLLAPSE_IMPORT_ERRORS = { total_members_count: '2', status: 'error', }; +const SEAT_OVERAGE_IMPORT_ERRORS = { + message: 'There are not enough available seats to invite this many users.', + reason: 'seat_limit_exceeded_error', +}; export const importProjectMembersApiResponse = { EXPANDED_IMPORT_ERRORS, NO_COLLAPSE_IMPORT_ERRORS, + SEAT_OVERAGE_IMPORT_ERRORS, }; diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 40c291de1bc..ed8eda967c4 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3945,6 +3945,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(json_response['message']).to eq('Import failed') + expect(json_response['reason']).to eq('import_failed_error') end context 'when importing of members did not work for some or all members' do @@ -3960,6 +3961,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and error_message = { project_bot.username => 'User project bots cannot be added to other groups / projects' } expect(json_response['message']).to eq(error_message) expect(json_response['total_members_count']).to eq(3) + expect(json_response['status']).to eq('error') end end end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index ee952a77be5..dc6620cf5a7 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -98,48 +98,6 @@ RSpec.describe Groups::CreateService, '#execute', feature_category: :groups_and_ end end - context 'with `emails_disabled` attribute' do - context 'when emails_disabled is false' do - let(:extra_params) { { emails_disabled: false } } - - it_behaves_like 'creating a group' - - it 'sets emails_enabled to true' do - expect(created_group.emails_enabled).to eq(true) - end - end - - context 'when emails_disabled is true' do - let(:extra_params) { { emails_disabled: true } } - - it_behaves_like 'creating a group' - - it 'sets emails_enabled to false' do - expect(created_group.emails_enabled).to eq(false) - end - end - - context 'when emails_disabled is nil' do - let(:extra_params) { { emails_disabled: nil } } - - it_behaves_like 'creating a group' - - it 'sets emails_enabled to default true' do - expect(created_group.emails_enabled).to eq(true) - end - end - - context 'when emails_disabled is the string "false"' do - let(:extra_params) { { emails_disabled: "false" } } - - it_behaves_like 'creating a group' - - it 'sets emails_enabled to false' do - expect(created_group.emails_enabled).to eq(true) - end - end - end - context 'with `allow_mfa_for_subgroups` attribute' do let(:extra_params) { { allow_mfa_for_subgroups: false } } diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 3f9a0b343c1..4bc01babc30 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -390,56 +390,6 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do end end - context 'when updating #emails_disabled' do - context 'when emails_disabled is true' do - let(:service) { described_class.new(internal_group, user, emails_disabled: true) } - - it 'updates email_enabled to false' do - internal_group.add_member(user, Gitlab::Access::OWNER) - - service.execute - - expect(internal_group.emails_enabled).to be(false) - end - end - - context 'when emails_disabled is false' do - let(:service) { described_class.new(internal_group, user, emails_disabled: false) } - - it 'email_enabled is set to true' do - internal_group.add_member(user, Gitlab::Access::OWNER) - - service.execute - - expect(internal_group.emails_enabled).to be(true) - end - end - - context 'when emails_disabled is nil' do - let(:service) { described_class.new(internal_group, user, emails_disabled: nil) } - - it 'email_enabled is set to the default value of true' do - internal_group.add_member(user, Gitlab::Access::OWNER) - - service.execute - - expect(internal_group.emails_enabled).to be(true) - end - end - - context 'when emails_disabled is the string "true"' do - let(:service) { described_class.new(internal_group, user, emails_disabled: "true") } - - it 'email_enabled is set to the default value of true' do - internal_group.add_member(user, Gitlab::Access::OWNER) - - service.execute - - expect(internal_group.emails_enabled).to be(false) - end - end - end - context 'when updating #max_artifacts_size' do let(:params) { { max_artifacts_size: 10 } } diff --git a/spec/services/members/import_project_team_service_spec.rb b/spec/services/members/import_project_team_service_spec.rb index e0657cfa8cc..5b459be1b6e 100644 --- a/spec/services/members/import_project_team_service_spec.rb +++ b/spec/services/members/import_project_team_service_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Target project does not exist') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:argument_error) end end @@ -51,7 +51,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Source project does not exist') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:argument_error) end end @@ -64,7 +64,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Forbidden') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:import_project_team_forbidden_error) end end @@ -77,7 +77,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Forbidden') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:import_project_team_forbidden_error) end end @@ -90,7 +90,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Forbidden') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:import_project_team_forbidden_error) end end @@ -107,7 +107,7 @@ RSpec.describe Members::ImportProjectTeamService, feature_category: :groups_and_ expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Import failed') - expect(result.reason).to eq(:unprocessable_entity) + expect(result.reason).to eq(:import_failed_error) end end