diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 51827a1dd66..2226bc37182 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -108,6 +108,9 @@ .if-merge-request-labels-run-observability-e2e-tests-current-branch: &if-merge-request-labels-run-observability-e2e-tests-current-branch if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-observability-e2e-tests-current-branch/' +.if-merge-request-labels-skip-observability-e2e-tests: &if-merge-request-labels-skip-observability-e2e-tests + if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-observability-e2e-tests/' + .if-merge-request-labels-run-single-db: &if-merge-request-labels-run-single-db if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-single-db/' @@ -3397,6 +3400,8 @@ rules: - <<: *if-observability-skip-e2e-jobs when: never + - <<: *if-merge-request-labels-skip-observability-e2e-tests + when: never - <<: *if-merge-request-labels-run-observability-e2e-tests-current-branch when: never - <<: *if-merge-request-labels-run-observability-e2e-tests-main-branch @@ -3407,6 +3412,8 @@ rules: - <<: *if-observability-skip-e2e-jobs when: never + - <<: *if-merge-request-labels-skip-observability-e2e-tests + when: never - <<: *if-merge-request-labels-run-observability-e2e-tests-main-branch when: never - <<: *if-merge-request-labels-run-observability-e2e-tests-current-branch diff --git a/Gemfile b/Gemfile index 97e2c6f6021..647e5564533 100644 --- a/Gemfile +++ b/Gemfile @@ -256,7 +256,7 @@ gem 'creole', '~> 0.5.0', feature_category: :markdown gem 'wikicloth', '0.8.1', feature_category: :markdown gem 'asciidoctor', '~> 2.0.18', feature_category: :markdown gem 'asciidoctor-include-ext', '~> 0.4.0', require: false, feature_category: :markdown -gem 'asciidoctor-plantuml', '~> 0.0.16', feature_category: :markdown +gem 'asciidoctor-plantuml', '~> 0.1.1', feature_category: :markdown gem 'asciidoctor-kroki', '~> 0.10.0', require: false, feature_category: :markdown gem 'rouge', '~> 4.4.0', feature_category: :shared gem 'truncato', '~> 0.7.12', feature_category: :team_planning diff --git a/Gemfile.checksum b/Gemfile.checksum index a2bf565f303..13d5bc39de4 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -26,7 +26,7 @@ {"name":"asciidoctor","version":"2.0.23","platform":"ruby","checksum":"52208807f237dfa0ca29882f8b13d60b820496116ad191cf197ca56f2b7fddf3"}, {"name":"asciidoctor-include-ext","version":"0.4.0","platform":"ruby","checksum":"406adb9d2fbfc25536609ca13b787ed704dc06a4e49d6709b83f3bad578f7878"}, {"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"}, -{"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"}, +{"name":"asciidoctor-plantuml","version":"0.1.1","platform":"ruby","checksum":"2bfa1a79349aa3fff611cdc54ade674e91423e38c20a21b24d8ca59006a0b0ae"}, {"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"}, {"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"}, {"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"}, diff --git a/Gemfile.lock b/Gemfile.lock index 991258ab0bb..e59452b0317 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -331,7 +331,7 @@ GEM asciidoctor (>= 1.5.6, < 3.0.0) asciidoctor-kroki (0.10.0) asciidoctor (~> 2.0) - asciidoctor-plantuml (0.0.16) + asciidoctor-plantuml (0.1.1) asciidoctor (>= 2.0.17, < 3.0.0) ast (2.4.2) async (2.12.1) @@ -1985,7 +1985,7 @@ DEPENDENCIES asciidoctor (~> 2.0.18) asciidoctor-include-ext (~> 0.4.0) asciidoctor-kroki (~> 0.10.0) - asciidoctor-plantuml (~> 0.0.16) + asciidoctor-plantuml (~> 0.1.1) async (~> 2.12.1) atlassian-jwt (~> 0.2.1) attr_encrypted (~> 3.2.4)! diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index dee86ba1398..341da957291 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -26,7 +26,7 @@ {"name":"asciidoctor","version":"2.0.23","platform":"ruby","checksum":"52208807f237dfa0ca29882f8b13d60b820496116ad191cf197ca56f2b7fddf3"}, {"name":"asciidoctor-include-ext","version":"0.4.0","platform":"ruby","checksum":"406adb9d2fbfc25536609ca13b787ed704dc06a4e49d6709b83f3bad578f7878"}, {"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"}, -{"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"}, +{"name":"asciidoctor-plantuml","version":"0.1.1","platform":"ruby","checksum":"2bfa1a79349aa3fff611cdc54ade674e91423e38c20a21b24d8ca59006a0b0ae"}, {"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"}, {"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"}, {"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index dd918ce8896..37d3d7d21dc 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -340,7 +340,7 @@ GEM asciidoctor (>= 1.5.6, < 3.0.0) asciidoctor-kroki (0.10.0) asciidoctor (~> 2.0) - asciidoctor-plantuml (0.0.16) + asciidoctor-plantuml (0.1.1) asciidoctor (>= 2.0.17, < 3.0.0) ast (2.4.2) async (2.12.1) @@ -2012,7 +2012,7 @@ DEPENDENCIES asciidoctor (~> 2.0.18) asciidoctor-include-ext (~> 0.4.0) asciidoctor-kroki (~> 0.10.0) - asciidoctor-plantuml (~> 0.0.16) + asciidoctor-plantuml (~> 0.1.1) async (~> 2.12.1) atlassian-jwt (~> 0.2.1) attr_encrypted (~> 3.2.4)! diff --git a/app/models/ability.rb b/app/models/ability.rb index df4156dcd0b..8320e21a092 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -80,14 +80,23 @@ class Ability before_check(policy, ability.to_sym, user, subject, opts) - case opts[:scope] - when :user - DeclarativePolicy.user_scope { policy.allowed?(ability) } - when :subject - DeclarativePolicy.subject_scope { policy.allowed?(ability) } + result = case opts[:scope] + when :user + DeclarativePolicy.user_scope { policy.allowed?(ability) } + when :subject + DeclarativePolicy.subject_scope { policy.allowed?(ability) } + else + policy.allowed?(ability) + end + + identity = ::Gitlab::Auth::Identity.fabricate(user) + + if identity.present? && identity.composite? + result && allowed?(identity.scoped_user, ability, subject, **opts) else - policy.allowed?(ability) + result end + ensure # TODO: replace with runner invalidation: # See: https://gitlab.com/gitlab-org/declarative-policy/-/merge_requests/24 diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 5a13c6120e9..5335ff48583 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -886,10 +886,6 @@ class ApplicationSetting < ApplicationRecord validate_url(parsed_kroki_url, :kroki_url, KROKI_URL_ERROR_MESSAGE) end - def kroki_url_absolute? - parsed_kroki_url&.absolute? - end - def sourcegraph_url_is_com? !!(sourcegraph_url =~ %r{\Ahttps://(www\.)?sourcegraph\.com}) end diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb index 1a682760f59..936196fdbf4 100644 --- a/app/models/notification_recipient.rb +++ b/app/models/notification_recipient.rb @@ -30,6 +30,7 @@ class NotificationRecipient end def notifiable? + return false if has_composite_identity? return false unless has_access? return false if emails_disabled? return false if own_activity? @@ -121,6 +122,10 @@ class NotificationRecipient end end + def has_composite_identity? + user.has_composite_identity? + end + def excluded_watcher_action? return false unless notification_level == :watch return false unless @custom_action diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index bbbd547c2a6..54f65f5c87a 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class OauthAccessToken < Doorkeeper::AccessToken + include Gitlab::Utils::StrongMemoize + belongs_to :resource_owner, class_name: 'User' belongs_to :application, class_name: 'Doorkeeper::Application' belongs_to :organization, class_name: 'Organizations::Organization' @@ -12,6 +14,9 @@ class OauthAccessToken < Doorkeeper::AccessToken scope :latest_per_application, -> { select('distinct on(application_id) *').order(application_id: :desc, created_at: :desc) } scope :preload_application, -> { preload(:application) } + # user scope format is: `user:$USER_ID` + SCOPED_USER_REGEX = /user:(\d+)(?:\s|$)/ + def scopes=(value) if value.is_a?(Array) super(Doorkeeper::OAuth::Scopes.from_array(value).to_s) @@ -36,4 +41,20 @@ class OauthAccessToken < Doorkeeper::AccessToken def self.matching_token_for(application, resource_owner, scopes) # no-op end + + def scope_user + user_id = extract_user_id_from_scopes + return unless user_id + + ::User.find_by_id(user_id) + end + strong_memoize_attr :scope_user + + private + + def extract_user_id_from_scopes + return false unless scopes.present? + + scopes.to_s[SCOPED_USER_REGEX, 1]&.to_i + end end diff --git a/app/models/user.rb b/app/models/user.rb index 96090ec8e45..14a264b492c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2522,6 +2522,10 @@ class User < ApplicationRecord true end + def has_composite_identity? + false + end + protected # override, from Devise::Validatable diff --git a/app/services/webauthn/authenticate_service.rb b/app/services/webauthn/authenticate_service.rb index 7855b509595..c4a6460f615 100644 --- a/app/services/webauthn/authenticate_service.rb +++ b/app/services/webauthn/authenticate_service.rb @@ -49,7 +49,7 @@ module Webauthn # def verify_webauthn_credential(webauthn_credential, stored_credential, challenge, encoder) # We need to adjust the relaying party id (RP id) we verify against if the registration in question - # is a migrated U2F registration. This is beacuse the appid of U2F and the rp id of WebAuthn differ. + # is a migrated U2F registration. This is because the appid of U2F and the rp id of WebAuthn differ. rp_id = webauthn_credential.client_extension_outputs['appid'] ? WebAuthn.configuration.origin : URI(WebAuthn.configuration.origin).host webauthn_credential.response.verify( encoder.decode(challenge), diff --git a/config/feature_flags/wip/composite_identity.yml b/config/feature_flags/wip/composite_identity.yml new file mode 100644 index 00000000000..a78fc842867 --- /dev/null +++ b/config/feature_flags/wip/composite_identity.yml @@ -0,0 +1,9 @@ +--- +name: composite_identity +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468370 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173006 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/506473 +milestone: "17.7" +group: group::authorization +type: wip +default_enabled: false diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index 090bf08c6a4..030443c0a3c 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -9,18 +9,23 @@ module Banzai # class PlantumlFilter < HTML::Pipeline::Filter prepend Concerns::PipelineTimingCheck + include ActionView::Helpers::TagHelper + include Gitlab::Utils::StrongMemoize def call return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag) + return doc unless plantuml_url_valid? Gitlab::Plantuml.configure doc.xpath(lang_tag).each do |node| - img_tag = Nokogiri::HTML::DocumentFragment.parse( - Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {})).css('img').first + next if node.content.blank? - next if img_tag.nil? + image_src = create_image_src('png', node.content) + img_tag = Nokogiri::HTML::DocumentFragment.parse(content_tag(:img, nil, src: image_src)) + img_tag = img_tag.children.first + img_tag.add_class('plantuml') img_tag.set_attribute('data-diagram', 'plantuml') img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(node.content)}") @@ -40,6 +45,15 @@ module Banzai def settings Gitlab::CurrentSettings.current_application_settings end + strong_memoize_attr :settings + + def create_image_src(format, text) + Asciidoctor::PlantUml::Processor.gen_url(text, format) + end + + def plantuml_url_valid? + ::Gitlab::UrlSanitizer.valid_web?(settings.plantuml_url) + end end end end diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index 83e87ce2041..52d23dbdb39 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -71,8 +71,7 @@ module Gitlab end def find_user_from_bearer_token - find_user_from_job_bearer_token || - find_user_from_access_token + find_user_from_job_bearer_token || find_user_from_access_token end def find_user_from_job_token @@ -299,6 +298,11 @@ module Gitlab raise UnauthorizedError unless oauth_token oauth_token.revoke_previous_refresh_token! + + ::Gitlab::Auth::Identity.link_from_oauth_token(oauth_token).tap do |identity| + raise UnauthorizedError if identity && !identity.valid? + end + oauth_token end diff --git a/lib/gitlab/auth/identity.rb b/lib/gitlab/auth/identity.rb new file mode 100644 index 00000000000..06768df717d --- /dev/null +++ b/lib/gitlab/auth/identity.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + ## + # Identity class represents identity which we want to use in authorization policies. + # + # It decides if an identity is a single or composite identity and finds identity scope. + # + class Identity + COMPOSITE_IDENTITY_USERS_KEY = 'composite_identities' + COMPOSITE_IDENTITY_KEY_FORMAT = 'user:%s:composite_identity' + + IdentityError = Class.new(StandardError) + IdentityLinkMismatchError = Class.new(IdentityError) + UnexpectedIdentityError = Class.new(IdentityError) + TooManyIdentitiesLinkedError = Class.new(IdentityError) + MissingCompositeIdentityError = Class.new(::Gitlab::Access::AccessDeniedError) + + # TODO: why is this called 3 times in doorkeeper_access_spec.rb specs? + def self.link_from_oauth_token(oauth_token) + fabricate(oauth_token.user).tap do |identity| + identity.link!(oauth_token.scope_user) if identity&.composite? + end + end + + def self.fabricate(user) + new(user) if user.is_a?(::User) + end + + def initialize(user, store: ::Gitlab::SafeRequestStore) + raise UnexpectedIdentityError unless user.is_a?(::User) + + @user = user + @request_store = store + end + + def composite? + return false unless Feature.enabled?(:composite_identity, @user) + + @user.has_composite_identity? + end + + def linked? + @request_store.exist?(store_key) + end + + def valid? + return true unless composite? + + linked? + end + + def scoped_user + @request_store.fetch(store_key) do + raise MissingCompositeIdentityError, 'composite identity missing' + end + end + + def link!(scope_user) + return unless scope_user + + validate_link!(scope_user) + store_identity_link!(scope_user) + + self + end + + private + + def scoped_user_id + scoped_user.id + end + + def scoped_user_present? + @request_store.exist?(store_key) + end + + def validate_link!(scope_user) + return unless scoped_user_present? && saved_scoped_user_different_from_new_scope_user?(scope_user) + + raise IdentityLinkMismatchError, 'identity link change detected' + end + + def saved_scoped_user_different_from_new_scope_user?(scope_user) + scoped_user_id != scope_user.id + end + + def store_identity_link!(scope_user) + @request_store.store[store_key] = scope_user + + composite_identities.add(@user) + + raise TooManyIdentitiesLinkedError if composite_identities.size > 1 + end + + def composite_identities + @request_store.store[COMPOSITE_IDENTITY_USERS_KEY] ||= Set.new + end + + def store_key + @store_key ||= format(COMPOSITE_IDENTITY_KEY_FORMAT, @user.id) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 089a33f1232..e3b8cb1f0c8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -28996,7 +28996,7 @@ msgstr "" msgid "Insights|Configure a custom report for insights into your group processes such as amount of issues, bugs, and merge requests per month. %{linkStart}How do I configure an insights report?%{linkEnd}" msgstr "" -msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config in the YAML file or the enabled project features (issues, merge requests) in the project settings)." +msgid "Insights|Some items are not visible because the project was filtered out in the insights.yml file (see the projects.only config in the YAML file or the enabled project features (issues, merge requests) in the project settings)." msgstr "" msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)." diff --git a/package.json b/package.json index 54c1a1c5d96..105cee04794 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@mattiasbuelens/web-streams-adapter": "^0.1.0", "@rails/actioncable": "7.0.8-4", "@rails/ujs": "7.0.8-4", - "@sentry/browser": "8.38.0", + "@sentry/browser": "8.41.0", "@snowplow/browser-plugin-client-hints": "^3.24.2", "@snowplow/browser-plugin-form-tracking": "^3.24.2", "@snowplow/browser-plugin-ga-cookies": "^3.24.2", diff --git a/scripts/frontend/check_jest_vue3_quarantine.js b/scripts/frontend/check_jest_vue3_quarantine.js index de34a430033..426bcd6b579 100644 --- a/scripts/frontend/check_jest_vue3_quarantine.js +++ b/scripts/frontend/check_jest_vue3_quarantine.js @@ -1,5 +1,5 @@ const { spawnSync } = require('node:child_process'); -const { readFile, open } = require('node:fs/promises'); +const { readFile, open, stat } = require('node:fs/promises'); const parser = require('fast-xml-parser'); const defaultChalk = require('chalk'); const { getLocalQuarantinedFiles } = require('./jest_vue3_quarantine_utils'); @@ -7,6 +7,9 @@ const { getLocalQuarantinedFiles } = require('./jest_vue3_quarantine_utils'); // Always use basic color output const chalk = new defaultChalk.constructor({ level: 1 }); +let quarantinedFiles; +let filesThatChanged; + async function parseJUnitReport() { let junit; try { @@ -21,7 +24,7 @@ async function parseJUnitReport() { console.warn(e); // No JUnit report exists, or there was a parsing error. Either way, we // should not block the MR. - return { passed: [], total: 0 }; + return []; } const failuresByFile = new Map(); @@ -41,32 +44,28 @@ async function parseJUnitReport() { } } - const quarantinedFiles = new Set(await getLocalQuarantinedFiles()); const passed = []; for (const [file, failures] of failuresByFile.entries()) { if (failures === 0 && quarantinedFiles.has(file)) passed.push(file); } - return { - passed, - total: failuresByFile.size, - }; + return passed; } -function reportPassingSpecsShouldBeUnquarantined(passed) { +function reportSpecsShouldBeUnquarantined(files) { const docsLink = // eslint-disable-next-line no-restricted-syntax 'https://docs.gitlab.com/ee/development/testing_guide/testing_vue3.html#quarantine-list'; console.warn(' '); console.warn( - `The following ${passed.length} spec file(s) now pass(es) under Vue 3, and so must be removed from quarantine:`, + `The following ${files.length} spec files either now pass under Vue 3, or no longer exist, and so must be removed from quarantine:`, ); console.warn(' '); - console.warn(passed.join('\n')); + console.warn(files.join('\n')); console.warn(' '); console.warn( chalk.red( - `To fix this job, remove the file(s) listed above from the file ${chalk.underline('scripts/frontend/quarantined_vue3_specs.txt')}.`, + `To fix this job, remove the files listed above from the file ${chalk.underline('scripts/frontend/quarantined_vue3_specs.txt')}.`, ), ); console.warn(`For more information, please see ${docsLink}.`); @@ -84,8 +83,34 @@ async function changedFiles() { return files.flat(); } +function intersection(a, b) { + const result = new Set(); + + for (const element of a) { + if (b.has(element)) result.add(element); + } + + return result; +} + +async function getRemovedQuarantinedSpecs() { + const removedQuarantinedSpecs = []; + + for (const file of intersection(filesThatChanged, quarantinedFiles)) { + try { + // eslint-disable-next-line no-await-in-loop + await stat(file); + } catch (e) { + if (e.code === 'ENOENT') removedQuarantinedSpecs.push(file); + } + } + + return removedQuarantinedSpecs; +} + async function main() { - const filesThatChanged = await changedFiles(); + filesThatChanged = await changedFiles(); + quarantinedFiles = new Set(await getLocalQuarantinedFiles()); const jestStdout = (await open('jest_stdout', 'w')).createWriteStream(); const jestStderr = (await open('jest_stderr', 'w')).createWriteStream(); @@ -126,18 +151,18 @@ async function main() { }, ); - const { passed, total } = await parseJUnitReport(); + const passed = await parseJUnitReport(); + const removedQuarantinedSpecs = await getRemovedQuarantinedSpecs(); + const filesToReport = [...passed, ...removedQuarantinedSpecs]; - if (total.length === 0) { + if (filesToReport.length === 0) { // No tests ran, or there was some unexpected error. Either way, exit // successfully. return; } - if (passed.length > 0) { - process.exitCode = 1; - reportPassingSpecsShouldBeUnquarantined(passed); - } + process.exitCode = 1; + reportSpecsShouldBeUnquarantined(filesToReport); } main().catch((e) => { diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index 37586e42e48..fab3b963806 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -13,7 +13,6 @@ ee/spec/frontend/analytics/analytics_dashboards/components/analytics_dashboards_ ee/spec/frontend/analytics/analytics_dashboards/components/analytics_data_explorer_spec.js ee/spec/frontend/analytics/analytics_dashboards/components/list/feature_list_item_spec.js ee/spec/frontend/analytics/cycle_analytics/components/value_stream_aggregation_status_spec.js -ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js ee/spec/frontend/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_spec.js ee/spec/frontend/analytics/dashboards/ai_impact/components/metric_table_spec.js ee/spec/frontend/analytics/devops_reports/devops_adoption/components/devops_adoption_app_spec.js @@ -46,7 +45,6 @@ ee/spec/frontend/clusters/components/environments_spec.js ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/policies_section_spec.js ee/spec/frontend/compliance_dashboard/components/frameworks_report/report_spec.js ee/spec/frontend/compliance_dashboard/components/standards_adherence_report/base_table_spec.js -ee/spec/frontend/contextual_sidebar/components/trial_status_widget_spec.js ee/spec/frontend/dependencies/components/app_spec.js ee/spec/frontend/dependencies/components/dependency_location_spec.js ee/spec/frontend/dependencies/components/dependency_path_viewer_spec.js @@ -171,7 +169,6 @@ spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js -spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_pipeline_stage_spec.js spec/frontend/ci/pipeline_mini_graph/pipeline_mini_graph_spec.js spec/frontend/ci/pipelines_page/components/pipeline_multi_actions_spec.js spec/frontend/ci/pipelines_page/components/pipelines_artifacts_spec.js diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb index bbbabd7a6bb..c1da963e512 100644 --- a/spec/lib/banzai/filter/plantuml_filter_spec.rb +++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") input = '
Bob -> Sara : Hello
' - output = '' + output = '' doc = filter(input) expect(doc.to_s).to eq output @@ -19,7 +19,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") input = '
Bob -> Sara : Hello
' - output = '' + output = '' doc = filter(input) expect(doc.to_s).to eq output @@ -35,8 +35,17 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do expect(doc.to_s).to eq output end - it 'does not replace plantuml pre tag with img tag if url is invalid' do - stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") + it 'does not replace plantuml pre tag if there is no content' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") + + input = '
' + doc = filter(input) + + expect(doc.to_s).to eq input + end + + it 'does not replace plantuml pre tag if the url is not valid' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid://localhost:8080") input = '
Bob -> Sara : Hello
' output = '
Bob -> Sara : Hello
' diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 3a57540682e..7e1df6b10be 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -708,8 +708,22 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do end describe '#find_oauth_access_token' do - let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) } - let(:doorkeeper_access_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api', organization_id: organization.id) } + let(:scopes) { 'api' } + + let(:application) do + Doorkeeper::Application.create!( + name: 'MyApp', + redirect_uri: 'https://app.com', + owner: user) + end + + let(:doorkeeper_access_token) do + Doorkeeper::AccessToken.create!( + application_id: application.id, + resource_owner_id: user.id, + scopes: scopes, + organization_id: organization.id) + end context 'passed as header' do before do @@ -742,6 +756,48 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) end end + + context 'with composite identity', :request_store do + let(:user) { create(:user, username: 'gitlab-duo') } + + before do + allow_any_instance_of(::User).to receive(:has_composite_identity?) do |user| + user.username == 'gitlab-duo' + end + + set_bearer_token(doorkeeper_access_token.plaintext_token) + end + + context 'when scoped user is specified' do + let(:scopes) { "user:#{user.id}" } + + context 'when linking composite identitiy succeeds' do + it 'returns the oauth token' do + expect(find_oauth_access_token.token).to eq(doorkeeper_access_token.token) + end + end + + context 'when linking composite identity raises an error' do + before do + allow(Gitlab::Auth::Identity).to( + receive(:link_from_oauth_token).and_raise(::Gitlab::Auth::Identity::IdentityLinkMismatchError) + ) + end + + it 'raises an error' do + expect { find_oauth_access_token }.to raise_error(::Gitlab::Auth::Identity::IdentityLinkMismatchError) + end + end + end + + context 'when composite identity link can not be created' do + let(:scopes) { 'api' } + + it 'raises an exception' do + expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + end + end end describe '#find_personal_access_token_from_http_basic_auth' do diff --git a/spec/lib/gitlab/auth/identity_spec.rb b/spec/lib/gitlab/auth/identity_spec.rb new file mode 100644 index 00000000000..cabe0807251 --- /dev/null +++ b/spec/lib/gitlab/auth/identity_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::Identity, :request_store, feature_category: :system_access do + describe '.link_from_oauth_token' do + let_it_be(:actor_user) { create(:user) } + let_it_be(:delegating_user) { create(:user) } + let_it_be(:token_scopes) { [:api, :"user:#{delegating_user.id}"] } + let_it_be(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: token_scopes) } + + subject(:identity) { described_class.link_from_oauth_token(oauth_access_token) } + + context 'when composite identity is required for the actor' do + before do + allow(actor_user).to receive(:has_composite_identity?).and_return(true) + end + + it 'returns an identity' do + expect(identity).to be_composite + expect(identity).to be_linked + expect(identity).to be_valid + + expect(identity.scoped_user).to eq(delegating_user) + end + + context 'when oauth token does not have required scopes' do + let(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: [:api]) } + + it 'fabricates a composite identity which is not valid' do + expect(identity).to be_composite + expect(identity).not_to be_linked + expect(identity).not_to be_valid + end + end + + context 'when an identity link was already done for a different composite user' do + let_it_be(:different_user) { create(:user) } + let_it_be(:new_token_scopes) { [:api, :"user:#{different_user.id}"] } + let_it_be(:new_oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: new_token_scopes) } + + it 'raises an error' do + expect(identity).to be_valid + + expect { described_class.link_from_oauth_token(new_oauth_access_token) } + .to raise_error(::Gitlab::Auth::Identity::IdentityLinkMismatchError) + end + end + + context 'when actor user does not have composite identity enforced' do + before do + allow(actor_user).to receive(:has_composite_identity?).and_return(false) + end + + context 'when token has composite user scope' do + it 'returns an identity' do + expect(identity).not_to be_composite + expect(identity).not_to be_linked + end + end + + context 'when token does not have composite user scope' do + let_it_be(:token_scopes) { [:api] } + let_it_be(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: token_scopes) } + + it 'returns an identity' do + expect(identity).not_to be_composite + expect(identity).not_to be_linked + end + end + end + end + + context 'when composite identity is not required for the actor' do + it 'fabricates a valid identity' do + expect(identity).not_to be_composite + expect(identity).to be_valid + end + end + end + + describe '.fabricate' do + let_it_be(:user) { create(:user) } + + subject(:identity) { described_class.fabricate(user) } + + it 'returns a valid identity without a scoped user' do + expect(identity).to be_valid + + expect { identity.scoped_user } + .to raise_error(::Gitlab::Auth::Identity::MissingCompositeIdentityError) + end + end +end diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb index 1c998f487cb..2f1507a66b0 100644 --- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Auth::OAuth::IdentityLinker do let!(:identity) { user.identities.create!(provider: provider, extern_uid: uid) } it "doesn't create new identity" do - expect { subject.link }.not_to change { Identity.count } + expect { subject.link }.not_to change { ::Identity.count } end it "sets #changed? to false" do diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb index 390620379d6..6d16ab3b2f1 100644 --- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb +++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Count::ExactCountStrategy do create(:identity) end - let(:models) { [Project, Identity] } + let(:models) { [::Project, ::Identity] } subject { described_class.new(models).count } @@ -16,7 +16,7 @@ RSpec.describe Gitlab::Database::Count::ExactCountStrategy do it 'counts all models' do expect(models).to all(receive(:count).and_call_original) - expect(subject).to eq({ Project => 3, Identity => 1 }) + expect(subject).to eq({ ::Project => 3, ::Identity => 1 }) end it 'returns default value if count times out' do diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb index 37d3e13a7ab..0062f5e8f12 100644 --- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb +++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do create(:group) end - let(:models) { [Project, Ci::InstanceVariable, Identity, Group, Namespace] } + let(:models) { [Project, Ci::InstanceVariable, ::Identity, Group, Namespace] } let(:strategy) { described_class.new(models) } subject { strategy.count } diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb index e712ad09927..07f0faf904f 100644 --- a/spec/lib/gitlab/database/count_spec.rb +++ b/spec/lib/gitlab/database/count_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Count do create(:identity) end - let(:models) { [Project, Identity] } + let(:models) { [::Project, ::Identity] } describe '.approximate_counts' do context 'fallbacks' do @@ -32,15 +32,15 @@ RSpec.describe Gitlab::Database::Count do end it 'gets more results from second strategy if some counts are missing' do - expect(first_strategy).to receive(:count).and_return({ Project => 3 }) - expect(strategies[1]).to receive(:new).with([Identity]).and_return(second_strategy) - expect(second_strategy).to receive(:count).and_return({ Identity => 1 }) + expect(first_strategy).to receive(:count).and_return({ ::Project => 3 }) + expect(strategies[1]).to receive(:new).with([::Identity]).and_return(second_strategy) + expect(second_strategy).to receive(:count).and_return({ ::Identity => 1 }) - expect(subject).to eq({ Project => 3, Identity => 1 }) + expect(subject).to eq({ ::Project => 3, ::Identity => 1 }) end it 'does not get more results as soon as all counts are present' do - expect(first_strategy).to receive(:count).and_return({ Project => 3, Identity => 1 }) + expect(first_strategy).to receive(:count).and_return({ ::Project => 3, ::Identity => 1 }) expect(strategies[1]).not_to receive(:new) subject diff --git a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb index 54b3e260524..1f984206553 100644 --- a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb @@ -6,7 +6,7 @@ RSpec.describe ::Gitlab::Graphql::Authorize::ObjectAuthorization do describe '#ok?' do subject(:authorization) { described_class.new(%i[go_fast go_slow]) } - let(:user) { double(:User, id: 10001) } + let_it_be(:user) { create(:user) } let(:scope_validator) { instance_double(::Gitlab::Auth::ScopeValidator, valid_for?: true) } let(:policy) do diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb index cb0d8dd1bf1..7f50e4f504b 100644 --- a/spec/lib/gitlab/other_markup_spec.rb +++ b/spec/lib/gitlab/other_markup_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::OtherMarkup, feature_category: :wiki do RST output = <<~HTML - +

Caption with bold and italic

HTML diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 3bf6140a4e2..35732df0420 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ability do +RSpec.describe Ability, feature_category: :system_access do describe '#policy_for' do subject(:policy) { described_class.policy_for(user, subject, **options) } @@ -476,6 +476,89 @@ RSpec.describe Ability do end end + describe '.allowed?' do + let_it_be(:group) { create(:group, :private) } + let_it_be(:user) { create(:user) } + let_it_be(:delegated_user) { create(:user) } + let(:request_store_key) { format(::Gitlab::Auth::Identity::COMPOSITE_IDENTITY_KEY_FORMAT, user.id) } + + subject { described_class.allowed?(user, :read_group, group) } + + context 'with composite identity', :request_store do + before do + ::Gitlab::SafeRequestStore[request_store_key] = delegated_user + allow(user).to receive(:has_composite_identity?).and_return(true) + end + + context 'when both users are members' do + before_all do + group.add_developer(delegated_user) + group.add_developer(user) + end + + it 'returns true' do + expect(subject).to be_truthy + end + + context 'when delegating user is not set in RequestStore' do + before do + ::Gitlab::SafeRequestStore.delete(request_store_key) + end + + it 'raises an exception' do + expect { subject }.to raise_exception(::Gitlab::Auth::Identity::MissingCompositeIdentityError) + end + end + end + + context 'when only delegating user is a member' do + before_all do + group.add_developer(user) + end + + it 'returns false' do + expect(subject).to be_falsey + end + + context 'with disabled composite_identity feature flag' do + before do + stub_feature_flags(composite_identity: false) + end + + it 'returns true' do + expect(subject).to be_truthy + end + end + + context 'with unenforced composite identity' do + before do + allow(user).to receive(:has_composite_identity?).and_return(false) + end + + it 'returns true' do + expect(subject).to be_truthy + end + end + end + + context 'when only delegated user is a member' do + before_all do + group.add_developer(delegated_user) + end + + it 'returns false' do + expect(subject).to be_falsey + end + end + + context 'when neither user is a member' do + it 'returns false' do + expect(subject).to be_falsey + end + end + end + end + describe 'forgetting', :request_store do it 'allows us to discard specific values from the DeclarativePolicy cache' do user_a = build_stubbed(:user) diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb index 9a4b3a878eb..f80ec0cad33 100644 --- a/spec/models/notification_recipient_spec.rb +++ b/spec/models/notification_recipient_spec.rb @@ -14,6 +14,16 @@ RSpec.describe NotificationRecipient, feature_category: :team_planning do describe '#notifiable?' do let(:recipient) { described_class.new(user, :mention, target: target, project: project) } + context 'when user has a composite identity' do + before do + allow(recipient).to receive(:has_composite_identity?).and_return(true) + end + + it 'returns false' do + expect(recipient.notifiable?).to eq false + end + end + context 'when emails are disabled' do it 'returns false if group disabled' do expect(project.namespace).to receive(:emails_enabled?).and_return(false) diff --git a/spec/models/oauth_access_token_spec.rb b/spec/models/oauth_access_token_spec.rb index c61f80b76d7..f30e7b78833 100644 --- a/spec/models/oauth_access_token_spec.rb +++ b/spec/models/oauth_access_token_spec.rb @@ -77,4 +77,66 @@ RSpec.describe OauthAccessToken, feature_category: :system_access do end end end + + describe '#scope_user' do + let_it_be(:user) { create(:user) } + let_it_be_with_refind(:oauth_access_token) { create(:oauth_access_token) } + let(:user_id) { user.id } + + before do + allow(oauth_access_token).to receive(:scopes).and_return(scopes) + end + + context 'when scopes match expected format' do + context 'when scopes only include the composite scope' do + let(:scopes) { "user:#{user_id}" } + + it 'returns the user' do + expect(oauth_access_token.scope_user).to eq user + end + end + + context 'when scopes include another scope before composite scope' do + let(:scopes) { "other:scope user:#{user_id}" } + + it 'returns the user' do + expect(oauth_access_token.scope_user).to eq user + end + end + + context "when scopes include another scope after composite scope" do + let(:scopes) { "user:#{user_id} other:scope" } + + it 'returns the user' do + expect(oauth_access_token.scope_user).to eq user + end + end + + context 'when scopes include another scope before and after composite scope' do + let(:scopes) { "api user:#{user_id} read_api" } + + it 'returns the user' do + expect(oauth_access_token.scope_user).to eq user + end + end + end + + context 'when scopes do not match composite scope format' do + where(:scopes) do + [ + "user:#{non_existing_record_id}", + 'user:not_a_number', + 'some:other:scope', + nil, + "" + ] + end + + with_them do + it 'returns false' do + expect(oauth_access_token.scope_user).to be_nil + end + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f893783eff5..550dbf60117 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -8828,6 +8828,12 @@ RSpec.describe User, feature_category: :user_profile do end end + describe '#has_composite_identity?' do + it 'is false' do + expect(build(:user).has_composite_identity?).to be false + end + end + describe 'color_mode_id' do context 'when theme_id is 11' do let(:user) { build(:user, theme_id: 11) } diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 110aadea561..35066ecf4dc 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -8,6 +8,44 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) } let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api", organization_id: organization.id } + describe 'access token with composite identity scope', :request_store do + let!(:scopes) { "user:#{scope_user.id} api" } + let!(:scoped_token) { OauthAccessToken.create! application_id: application.id, user: user, scopes: scopes, organization_id: organization.id } + + let(:scope_user) { create(:user) } + let(:group) { create(:group, :private) } + let(:user) do + user = create(:user) + allow(user).to receive(:has_composite_identity?).and_return(true) + user + end + + context 'when user one has a composite identity token scoped to user two' do + before do + group.add_developer(user) # scoped user doesn't have access + allow(OauthAccessToken).to receive(:by_token).and_return(scoped_token) # this is required for the has_composite_identity? stub to work + end + + it 'restricts user access permissions' do + get api("/groups/#{group.id}"), params: { access_token: scoped_token.plaintext_token } + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when both users have access to a resource' do + before do + group.add_developer(scope_user) + end + + it 'allows access' do + get api("/groups/#{group.id}"), params: { access_token: token.plaintext_token } + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + describe "unauthenticated" do it "returns authentication success" do get api("/user"), params: { access_token: token.plaintext_token } diff --git a/spec/requests/api/virtual_registries/packages/maven_registries_spec.rb b/spec/requests/api/virtual_registries/packages/maven_registries_spec.rb index f8b0d4630b2..2bd66d24160 100644 --- a/spec/requests/api/virtual_registries/packages/maven_registries_spec.rb +++ b/spec/requests/api/virtual_registries/packages/maven_registries_spec.rb @@ -214,7 +214,7 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, :aggregate_failures, fea group.add_maintainer(user) end - it 'returns a bad request beacuse it is not a top level group' do + it 'returns a bad request because it is not a top level group' do api_request expect(response).to have_gitlab_http_status(:bad_request) diff --git a/spec/services/ci/runners/set_runner_associated_projects_service_spec.rb b/spec/services/ci/runners/set_runner_associated_projects_service_spec.rb index 78096132f1d..c038f6af26a 100644 --- a/spec/services/ci/runners/set_runner_associated_projects_service_spec.rb +++ b/spec/services/ci/runners/set_runner_associated_projects_service_spec.rb @@ -211,8 +211,7 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe let(:new_projects) { [project3, project4] } let(:owner_project) { new_projects.first } - it 'assigns associated projects and returns success response', - quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/481411' do + it 'assigns associated projects and returns success response' do expect(execute).to be_success runner.reload diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 38ffe2bfec8..5340d2c5ba6 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -535,6 +535,8 @@ module TestEnv end def component_ahead_of_target?(component_folder, expected_version) + return false unless Dir.exist?(File.join(component_folder, '.git')) + # The HEAD of the component_folder will be used as heuristic for the version # of the binaries, allowing to use Git to determine if HEAD is later than # the expected version. Note: Git considers HEAD to be an anchestor of HEAD. @@ -556,6 +558,8 @@ module TestEnv return false unless Dir.exist?(component_folder) + return false unless Dir.exist?(File.join(component_folder, '.git')) + sha, exit_status = Gitlab::Popen.popen(%W[#{Gitlab.config.git.bin_path} rev-parse HEAD], component_folder) return false if exit_status != 0 diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index c0ebcc5c474..4a205ae6bf6 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -299,7 +299,7 @@ module MarkdownMatchers end end - # PLantumlFilter + # PlantumlFilter matcher :parse_plantuml do set_default_markdown_messages diff --git a/yarn.lock b/yarn.lock index 6aa39f7535d..21157df828c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2502,76 +2502,63 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.38.0.tgz#d7f6d398778906efb0c017e63d3c59d3203dfa7d" - integrity sha512-5QMVcssrAcmjKT0NdFYcX0b0wwZovGAZ9L2GajErXtHkBenjI2sgR2+5J7n+QZGuk2SC1qhGmT1O9i3p3UEwew== +"@sentry-internal/browser-utils@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.41.0.tgz#9dc30a8c88aa6e1e542e5acae29ceabd1b377cc4" + integrity sha512-nU7Bn3jEUmf1QXRUT3j2ewUBlFJpe9vnAnjqpeVPDWTsVI52BwVNcJHuE37PrGs66OZ1ZkGMfKnQk43oCAa+oQ== dependencies: - "@sentry/core" "8.38.0" - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/feedback@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.38.0.tgz#726661a01f7ff40b93c8ee05c985fd0436a1c033" - integrity sha512-AW5HCCAlc3T1jcSuNhbFVNO1CHyJ5g5tsGKEP4VKgu+D1Gg2kZ5S2eFatLBUP/BD5JYb1A7p6XPuzYp1XfMq0A== +"@sentry-internal/feedback@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.41.0.tgz#9c3c95e6f7738a0d00fcb89061c284baef313ba2" + integrity sha512-bw+BrSNw8abOnu/IpD8YSbYubXkkT8jyNS7TM4e4UPZMuXcbtia7/r5d7kAiUfKv/sV5PNMlZLOk+EYJeLTANg== dependencies: - "@sentry/core" "8.38.0" - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/replay-canvas@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.38.0.tgz#26e9bc937dab73e1a26d57dc1015b7ff1f2d76c5" - integrity sha512-OxmlWzK9J8mRM+KxdSnQ5xuxq+p7TiBzTz70FT3HltxmeugvDkyp6803UcFqHOPHR35OYeVLOalym+FmvNn9kw== +"@sentry-internal/replay-canvas@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.41.0.tgz#9da984adc54fcd8ebe07cbbc13132fa78396dd01" + integrity sha512-lpgOBHWr1ZNxidD72A2pfoUMjIpwonOPYoQZWAHr86Oa3eIVQOyfklZlHW+gKPFl2/IEl9Lbtcke0JiDp3dkIQ== dependencies: - "@sentry-internal/replay" "8.38.0" - "@sentry/core" "8.38.0" - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry-internal/replay" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry-internal/replay@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.38.0.tgz#9a9b945a3c066f5610a363774e3c99420c3f4fce" - integrity sha512-mQPShKnIab7oKwkwrRxP/D8fZYHSkDY+cvqORzgi+wAwgnunytJQjz9g6Ww2lJu98rHEkr5SH4V4rs6PZYZmnQ== +"@sentry-internal/replay@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.41.0.tgz#b1112a52a0cf1727589ad4d42a8fac9f98f6d733" + integrity sha512-ByXEY7JI95y4Qr9fS3d28l9uuVU5Qa0HgL+xDmYElNx7CXz3Q9hFN6ibgUeC3h8BO5pDULxWNgAppl7FRY8N5w== dependencies: - "@sentry-internal/browser-utils" "8.38.0" - "@sentry/core" "8.38.0" - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry-internal/browser-utils" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry/browser@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.38.0.tgz#c562accdc2bbe0b0074d98bfe7ff460e39ce3109" - integrity sha512-AZR+b0EteNZEGv6JSdBD22S9VhQ7nrljKsSnzxobBULf3BpwmhmCzTbDrqWszKDAIDYmL+yQJIR2glxbknneWQ== +"@sentry/browser@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.41.0.tgz#f594012e6377a92db72127ef39aee812efe3a3b7" + integrity sha512-FfAU55eYwW2lG4M3dEw2472RvHrD5YWSfHCZvuRf/4skX38kFvKghZQ+epL+CVHTzvIRHOrbj8qQK6YLTGl9ew== dependencies: - "@sentry-internal/browser-utils" "8.38.0" - "@sentry-internal/feedback" "8.38.0" - "@sentry-internal/replay" "8.38.0" - "@sentry-internal/replay-canvas" "8.38.0" - "@sentry/core" "8.38.0" - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry-internal/browser-utils" "8.41.0" + "@sentry-internal/feedback" "8.41.0" + "@sentry-internal/replay" "8.41.0" + "@sentry-internal/replay-canvas" "8.41.0" + "@sentry/core" "8.41.0" + "@sentry/types" "8.41.0" -"@sentry/core@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.38.0.tgz#5d1b74770c79e489e786018a3e514cddeb777bcb" - integrity sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ== +"@sentry/core@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.41.0.tgz#e8a25cacd25fe4358f3179e85f3c2697427fe5a6" + integrity sha512-3v7u3t4LozCA5SpZY4yqUN2U3jSrkXNoLgz6L2SUUiydyCuSwXZIFEwpLJfgQyidpNDifeQbBI5E1O910XkPsA== dependencies: - "@sentry/types" "8.38.0" - "@sentry/utils" "8.38.0" + "@sentry/types" "8.41.0" -"@sentry/types@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.38.0.tgz#9c48734a8b4055bfd553a0141efec78e9680ed09" - integrity sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA== - -"@sentry/utils@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.38.0.tgz#2f91ca7d044f6e17b993c866ca02a981c4c1bc25" - integrity sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw== - dependencies: - "@sentry/types" "8.38.0" +"@sentry/types@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.41.0.tgz#db40c93bcedad26569c5dfe10a4b31253c349a10" + integrity sha512-eqdnGr9k9H++b9CjVUoTNUVahPVWeNnMy0YGkqS5+cjWWC+x43p56202oidGFmWo6702ub/xwUNH6M5PC4kq6A== "@sinclair/typebox@^0.27.8": version "0.27.8"