summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md67
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue1
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue14
-rw-r--r--app/assets/javascripts/diffs/i18n.js3
-rw-r--r--app/assets/javascripts/diffs/store/actions.js20
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js7
-rw-r--r--app/assets/javascripts/repository/index.js7
-rw-r--r--app/assets/javascripts/repository/utils/ref_switcher_utils.js27
-rw-r--r--app/controllers/concerns/confirm_email_warning.rb14
-rw-r--r--app/controllers/projects/blob_controller.rb10
-rw-r--r--app/controllers/projects/tree_controller.rb9
-rw-r--r--app/controllers/projects_controller.rb10
-rw-r--r--app/finders/environments/environment_names_finder.rb11
-rw-r--r--app/finders/notes_finder.rb8
-rw-r--r--app/helpers/dashboard_helper.rb2
-rw-r--r--app/models/concerns/issue_available_features.rb9
-rw-r--r--app/models/concerns/taskable.rb36
-rw-r--r--app/models/hooks/web_hook.rb18
-rw-r--r--app/models/issue.rb11
-rw-r--r--app/models/packages/npm/metadata_cache.rb14
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_feature.rb3
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb10
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/clusters/clusters/connect.html.haml1
-rw-r--r--app/views/clusters/clusters/new_cluster_docs.html.haml1
-rw-r--r--app/views/clusters/clusters/show.html.haml1
-rw-r--r--app/views/explore/projects/page_out_of_bounds.html.haml2
-rw-r--r--app/views/projects/blob/_breadcrumb.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--config/feature_flags/development/db_load_balance_audit_event_streaming_worker.yml8
-rw-r--r--db/docs/packages_npm_metadata_caches.yml10
-rw-r--r--db/migrate/20230313181536_create_packages_npm_metadata_caches.rb26
-rw-r--r--db/migrate/20230321085011_add_column_to_users_statistisc.rb7
-rw-r--r--db/migrate/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches.rb19
-rw-r--r--db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb26
-rw-r--r--db/post_migrate/20230330080731_remove_tmp_index_vuln_occurrences_on_report_type.rb21
-rw-r--r--db/schema_migrations/202302081318081
-rw-r--r--db/schema_migrations/202303131815361
-rw-r--r--db/schema_migrations/202303210850111
-rw-r--r--db/schema_migrations/202303221454031
-rw-r--r--db/schema_migrations/202303300807311
-rw-r--r--db/structure.sql39
-rw-r--r--doc/api/api_resources.md3
-rw-r--r--glfm_specification/output_example_snapshots/snapshot_spec.html16
-rw-r--r--lib/api/repositories.rb4
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb1
-rw-r--r--lib/extracts_ref.rb14
-rw-r--r--lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb17
-rw-r--r--lib/gitlab/regex.rb58
-rw-r--r--lib/gitlab/unicode.rb6
-rw-r--r--lib/gitlab/untrusted_regexp.rb11
-rw-r--r--lib/gitlab/url_sanitizer.rb90
-rw-r--r--lib/rouge/formatters/html_gitlab.rb9
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb9
-rw-r--r--qa/qa/resource/ci_variable.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb (renamed from qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident.rb)0
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb3
-rw-r--r--spec/controllers/concerns/confirm_email_warning_spec.rb34
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb33
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb23
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb23
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb34
-rw-r--r--spec/controllers/projects_controller_spec.rb63
-rw-r--r--spec/factories/issues.rb5
-rw-r--r--spec/factories/packages/npm/metadata_cache.rb10
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/features/admin/users/user_spec.rb30
-rw-r--r--spec/features/users/login_spec.rb29
-rw-r--r--spec/finders/environments/environment_names_finder_spec.rb26
-rw-r--r--spec/finders/notes_finder_spec.rb20
-rw-r--r--spec/frontend/diffs/store/actions_spec.js48
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js51
-rw-r--r--spec/frontend/repository/utils/ref_switcher_utils_spec.js29
-rw-r--r--spec/helpers/hooks_helper_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb84
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/regex_spec.rb10
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb32
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb31
-rw-r--r--spec/lib/rouge/formatters/html_gitlab_spec.rb21
-rw-r--r--spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb24
-rw-r--r--spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb37
-rw-r--r--spec/models/concerns/taskable_spec.rb6
-rw-r--r--spec/models/hooks/web_hook_spec.rb28
-rw-r--r--spec/models/issue_spec.rb36
-rw-r--r--spec/models/packages/npm/metadata_cache_spec.rb31
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/policies/project_policy_spec.rb50
-rw-r--r--spec/requests/api/repositories_spec.rb16
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb15
-rw-r--r--spec/services/web_hook_service_spec.rb4
-rw-r--r--spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb26
97 files changed, 1395 insertions, 322 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ea96e199d2..7fd8392f627 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,31 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 15.10.1 (2023-03-30)
+
+### Fixed (2 changes)
+
+- [Sync security policy rule schedules that may have been deleted by bug](gitlab-org/security/gitlab@5ac094761b5cfac26c44d63988359fbae263a415)
+- [Fix issue dashboard returning issues from archived projects](gitlab-org/security/gitlab@6127799167081845824e8759f358aac8f702adb8)
+
+### Security (15 changes)
+
+- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@c10a48134447128486e2254fc54d0af0d8e6fee0) ([merge request](gitlab-org/security/gitlab!3155))
+- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@4aa387fec0c995607f03e8c057d2c2a11168aca9) ([merge request](gitlab-org/security/gitlab!3158))
+- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@e9cf398f8c205ae1b8cafddbb2cfbcb214a84d51) ([merge request](gitlab-org/security/gitlab!3162))
+- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@fb55096b37ab82f49f2a0205f7ab8bdda14b0010) ([merge request](gitlab-org/security/gitlab!3153))
+- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@433996f41e89db3e2073314c0644a6f95ab67062) ([merge request](gitlab-org/security/gitlab!3146))
+- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@c9942785d9a26cf7bb96a81ccd14e5c6e5582bbe) ([merge request](gitlab-org/security/gitlab!3156))
+- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@a42d166e743edb966b0a581bf1325ffb7c96041b) ([merge request](gitlab-org/security/gitlab!3148))
+- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@58a823e09c27948d15432c344248a8436587f9af) ([merge request](gitlab-org/security/gitlab!3165))
+- [Check access to target project before looking for branch](gitlab-org/security/gitlab@804d9da677451889e0a7a0880f2c2f4c3c04faed) ([merge request](gitlab-org/security/gitlab!3151))
+- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@e21dbf4373a4c4e5179b073f5cba4318ee174918) ([merge request](gitlab-org/security/gitlab!3154))
+- [Use UntrustedRegexp to limit scan of HTML comments](gitlab-org/security/gitlab@874edf184764fa801866fbd4e89b9f7e87c570fd) ([merge request](gitlab-org/security/gitlab!3143))
+- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e88f78f19dc5ed01a74e6c0d4bb5c22f3a69b65b) ([merge request](gitlab-org/security/gitlab!3114))
+- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@94e4e543762998a9bbff75c5ffb5cd5da6bd2d88) ([merge request](gitlab-org/security/gitlab!3147))
+- [Fix security report authorization](gitlab-org/security/gitlab@10f33b260212ebf811acecf4b05af1311b44fb64) ([merge request](gitlab-org/security/gitlab!3145))
+- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@4ce175e4096c973a2d16b93fff6b60bc0144eee0) ([merge request](gitlab-org/security/gitlab!3132))
+
## 15.10.0 (2023-03-21)
### Added (155 changes)
@@ -726,6 +751,27 @@ entry.
- [Update submit buttons to use Pajamas component](gitlab-org/gitlab@4ffb92755e6be3268c78f02e471f5c2a21f437be) ([merge request](gitlab-org/gitlab!114246))
+## 15.9.4 (2023-03-30)
+
+### Security (16 changes)
+
+- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@98b1bd243f454bd28c262131be616ee2060c3a78) ([merge request](gitlab-org/security/gitlab!3104))
+- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@0f0c0f21dffe300a56abf1e07a2fefb17160faeb) ([merge request](gitlab-org/security/gitlab!3133))
+- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@12498f791f9c5fe833f5202b06cc818d4dcf965b) ([merge request](gitlab-org/security/gitlab!3124))
+- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@440a7989ff46ca333f86a38aefa47f74301e66fc) ([merge request](gitlab-org/security/gitlab!3163))
+- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@9dd0dff69d3941e827c461c67b9af10da07d69f8) ([merge request](gitlab-org/security/gitlab!3084))
+- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@cd20b44dd5b075827203330802e331b896448265) ([merge request](gitlab-org/security/gitlab!3140))
+- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@76975082c41870265e1285fa8f4e053eb6ff11ae) ([merge request](gitlab-org/security/gitlab!3136))
+- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@7fcc4a0d010d3a428e803f95ef47904c4c7178a8) ([merge request](gitlab-org/security/gitlab!3149))
+- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@4e7313536e4cdb3ecef37100b5a73720eabfbc79) ([merge request](gitlab-org/security/gitlab!3108))
+- [Check access to target project before looking for branch](gitlab-org/security/gitlab@f55edf39e52af9eecb19caf8ed5d4cb8524ef64d) ([merge request](gitlab-org/security/gitlab!3040))
+- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@be73600e8c43c22cda1ace5910eb2052b2741972) ([merge request](gitlab-org/security/gitlab!3120))
+- [Use UntrustedRegexp to limit scan of HTML comments](gitlab-org/security/gitlab@d5e65583debcae71787e171643275bc9b9d4393e) ([merge request](gitlab-org/security/gitlab!3142))
+- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@54045b508a9ba9ae18f5992b77970240774b28a7) ([merge request](gitlab-org/security/gitlab!3111))
+- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@bc033cd3a98c9a1468545811a8180604f7f8aee3) ([merge request](gitlab-org/security/gitlab!3101))
+- [Fix security report authorization](gitlab-org/security/gitlab@a01cf9d8383ffc4c0e29514f71d49bf345e1f7c2) ([merge request](gitlab-org/security/gitlab!3106))
+- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@3cefb16a5e369ee99f4c3ccbaa02cead6faf1a99) ([merge request](gitlab-org/security/gitlab!3130))
+
## 15.9.3 (2023-03-09)
### Fixed (4 changes)
@@ -1482,6 +1528,27 @@ entry.
- [Remove Gitlab::Redis::DuplicateJobs](gitlab-org/gitlab@73d863b0a49175cce7649c0936b2e16157f61665) ([merge request](gitlab-org/gitlab!109122))
- [Clean-up feature flag `hash_based_cache_for_protected_branches`](gitlab-org/gitlab@96e8a07564bac07a100556e00ce4af3f21dca293) ([merge request](gitlab-org/gitlab!108724))
+## 15.8.5 (2023-03-30)
+
+### Security (16 changes)
+
+- [Fix rubocop offenses in lib/gitlab/url_sanitizer.rb](gitlab-org/security/gitlab@ddc04cf7059e411e20033b95e1297381d64d4b22) ([merge request](gitlab-org/security/gitlab!3175))
+- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@a22ce3851128eb900dbabe9e38c07889967a2915) ([merge request](gitlab-org/security/gitlab!3032))
+- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@fad24ae9d8fa0e7bd9eff0c9e6914c8267451b4d) ([merge request](gitlab-org/security/gitlab!3134))
+- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@85be0fbfc98cdb774d68070479e35be22f6ba40a) ([merge request](gitlab-org/security/gitlab!3125))
+- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@2df2fa2dc4b9015d044d0ddc5d26e17e9e5f85c0) ([merge request](gitlab-org/security/gitlab!3164))
+- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@53f7f06843eea4d666d361f5a1d349bd1e3f4312) ([merge request](gitlab-org/security/gitlab!3085))
+- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@9fa9dbff463f6015ffaf8d082db3d41ae623763e) ([merge request](gitlab-org/security/gitlab!3141))
+- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@20d77d4d680d13f916fb69de0d79802753421c8f) ([merge request](gitlab-org/security/gitlab!3137))
+- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@0fed113756b27a3a078f87f29711b225e1ed4cce) ([merge request](gitlab-org/security/gitlab!3150))
+- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@2285088f37aca877b1dcd59c728cdf33171b30cb) ([merge request](gitlab-org/security/gitlab!3109))
+- [Check access to target project before looking for branch](gitlab-org/security/gitlab@37b8d855d87c88170322e6a6d4c285fee6c6cb64) ([merge request](gitlab-org/security/gitlab!3038))
+- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@66f8cc2eb13509397b980d53a4b67ca03d8903f7) ([merge request](gitlab-org/security/gitlab!3121))
+- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e1859de393b4794e1356d6318e56ede4b557c059) ([merge request](gitlab-org/security/gitlab!3112))
+- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@13f9c6231cea956f73355c5b5b820163f523e7d8) ([merge request](gitlab-org/security/gitlab!3100))
+- [Fix security report authorization](gitlab-org/security/gitlab@19baab85c7a5a64a09e3e4808e8550fc72e18323) ([merge request](gitlab-org/security/gitlab!3105))
+- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@be5491c5db05161e4b14d53900dd19b66848de48) ([merge request](gitlab-org/security/gitlab!3131))
+
## 15.8.4 (2023-03-02)
### Security (12 changes)
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
index a0651841397..b8a9ceb261d 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
@@ -517,7 +517,6 @@ export default {
ref="deleteCiVariable"
variant="danger"
category="secondary"
- data-qa-selector="ci_variable_delete_button"
@click="deleteVarAndClose"
>{{ __('Delete variable') }}</gl-button
>
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
index 5e367ff33b2..6f6c55e07c7 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_table.vue
@@ -175,12 +175,7 @@ export default {
v-if="glFeatures.ciVariablesPages"
class="ci-variable-actions gl-display-flex gl-justify-content-end gl-my-3"
>
- <gl-button
- v-if="!isTableEmpty"
- data-qa-selector="reveal_ci_variable_value_button"
- @click="toggleHiddenState"
- >{{ valuesButtonText }}</gl-button
- >
+ <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button>
<gl-button
v-gl-modal-directive="$options.modalId"
class="gl-mx-3"
@@ -317,12 +312,7 @@ export default {
@click="setSelectedVariable()"
>{{ __('Add variable') }}</gl-button
>
- <gl-button
- v-if="!isTableEmpty"
- data-qa-selector="reveal_ci_variable_value_button"
- @click="toggleHiddenState"
- >{{ valuesButtonText }}</gl-button
- >
+ <gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button>
</div>
<div v-else class="gl-display-flex gl-justify-content-center gl-mt-6">
<gl-keyset-pagination
diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js
index d8812de12d4..95155181c78 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -4,6 +4,9 @@ export const GENERIC_ERROR = __('Something went wrong on our end. Please try aga
export const LOAD_SINGLE_DIFF_FAILED = s__(
'MergeRequest|Encountered an issue while trying to fetch the single file diff.',
);
+export const DISCUSSION_SINGLE_DIFF_FAILED = s__(
+ "MergeRequest|Can't fetch the single file diff for the discussion. Please reload this page.",
+);
export const DIFF_FILE_HEADER = {
optionsDropdownTitle: __('Options'),
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 6c461021f86..f6552d39193 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -50,7 +50,7 @@ import {
TRACKING_SINGLE_FILE_MODE,
TRACKING_MULTIPLE_FILES_MODE,
} from '../constants';
-import { LOAD_SINGLE_DIFF_FAILED } from '../i18n';
+import { DISCUSSION_SINGLE_DIFF_FAILED, LOAD_SINGLE_DIFF_FAILED } from '../i18n';
import eventHub from '../event_hub';
import { isCollapsed } from '../utils/diff_file';
import { markFileReview, setReviewsForMergeRequest } from '../utils/file_reviews';
@@ -896,6 +896,24 @@ export function moveToNeighboringCommit({ dispatch, state }, { direction }) {
}
}
+export const rereadNoteHash = ({ state, dispatch }) => {
+ const urlHash = window?.location?.hash;
+
+ if (isUrlHashNoteLink(urlHash)) {
+ dispatch('setCurrentDiffFileIdFromNote', urlHash.split('_').pop())
+ .then(() => {
+ if (state.viewDiffsFileByFile) {
+ dispatch('fetchFileByFile');
+ }
+ })
+ .catch(() => {
+ createAlert({
+ message: DISCUSSION_SINGLE_DIFF_FAILED,
+ });
+ });
+ }
+};
+
export const setCurrentDiffFileIdFromNote = ({ commit, getters, rootGetters }, noteId) => {
const note = rootGetters.notesById[noteId];
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index c4f806f1723..79d54773436 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -15,7 +15,7 @@ import '~/sourcegraph/load';
import createStore from '~/code_navigation/store';
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
import RefSelector from '~/ref/components/ref_selector.vue';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
Vue.use(Vuex);
Vue.use(VueApollo);
@@ -34,7 +34,7 @@ const initRefSwitcher = () => {
if (!refSwitcherEl) return false;
- const { projectId, projectRootPath, ref } = refSwitcherEl.dataset;
+ const { projectId, projectRootPath, ref, refType } = refSwitcherEl.dataset;
return new Vue({
el: refSwitcherEl,
@@ -42,7 +42,8 @@ const initRefSwitcher = () => {
return createElement(RefSelector, {
props: {
projectId,
- value: ref,
+ value: refType ? joinPaths('refs', refType, ref) : ref,
+ useSymbolicRefNames: true,
queryParams: { sort: 'updated_desc' },
},
on: {
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 60ec93fb73e..7050587a907 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
-import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
+import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import PerformancePlugin from '~/performance/vue_performance_plugin';
@@ -128,7 +128,7 @@ export default function setupVueRepositoryList() {
if (!refSwitcherEl) return false;
- const { projectId, projectRootPath } = refSwitcherEl.dataset;
+ const { projectId, projectRootPath, refType } = refSwitcherEl.dataset;
return new Vue({
el: refSwitcherEl,
@@ -136,7 +136,8 @@ export default function setupVueRepositoryList() {
return createElement(RefSelector, {
props: {
projectId,
- value: ref,
+ value: refType ? joinPaths('refs', refType, ref) : ref,
+ useSymbolicRefNames: true,
queryParams: { sort: 'updated_desc' },
},
on: {
diff --git a/app/assets/javascripts/repository/utils/ref_switcher_utils.js b/app/assets/javascripts/repository/utils/ref_switcher_utils.js
index c62f7f709c4..bcad4a2c822 100644
--- a/app/assets/javascripts/repository/utils/ref_switcher_utils.js
+++ b/app/assets/javascripts/repository/utils/ref_switcher_utils.js
@@ -16,22 +16,29 @@ const getNamespaceTargetRegex = (ref) => new RegExp(`(/-/(blob|tree))/${ref}/(.*
* @param {string} selectedRef - The selected ref from the ref dropdown.
*/
export function generateRefDestinationPath(projectRootPath, ref, selectedRef) {
- const currentPath = window.location.pathname;
- const encodedHash = '%23';
+ const url = new URL(window.location.href);
+ const currentPath = url.pathname;
+ let refType = null;
let namespace = '/-/tree';
let target;
+ let actualRef = selectedRef;
+
+ const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/);
+ if (matches) {
+ [, refType, actualRef] = matches;
+ }
+ if (refType) {
+ url.searchParams.set('ref_type', refType);
+ } else {
+ url.searchParams.delete('ref_type');
+ }
+
const NAMESPACE_TARGET_REGEX = getNamespaceTargetRegex(ref);
const match = NAMESPACE_TARGET_REGEX.exec(currentPath);
if (match) {
[, namespace, , target] = match;
}
+ url.pathname = joinPaths(projectRootPath, namespace, actualRef, target);
- const destinationPath = joinPaths(
- projectRootPath,
- namespace,
- encodeURI(selectedRef).replace(/#/g, encodedHash),
- target,
- );
-
- return `${destinationPath}${window.location.hash}`;
+ return url.toString();
}
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
index 8b7371cbc17..2efea461a35 100644
--- a/app/controllers/concerns/confirm_email_warning.rb
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
module ConfirmEmailWarning
+ include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
included do
@@ -17,11 +18,9 @@ module ConfirmEmailWarning
return unless current_user
return if current_user.confirmed?
- email = current_user.unconfirmed_email || current_user.email
-
flash.now[:warning] = format(
confirm_warning_message,
- email: email,
+ email: email_to_display,
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
update_link: view_context.link_to(_('Update it'), profile_path)
).html_safe
@@ -29,7 +28,16 @@ module ConfirmEmailWarning
private
+ def email
+ current_user.unconfirmed_email || current_user.email
+ end
+ strong_memoize_attr :email
+
def confirm_warning_message
_("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.")
end
+
+ def email_to_display
+ html_escape(email)
+ end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index e108150e432..ab1ac0beb36 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
before_action :commit, except: [:new, :create]
+ before_action :check_for_ambiguous_ref, only: [:show]
before_action :blob, except: [:new, :create]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
@@ -156,6 +157,15 @@ class Projects::BlobController < Projects::ApplicationController
end
end
+ def check_for_ambiguous_ref
+ @ref_type = ref_type
+
+ if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
+ branch = @project.repository.find_branch(@ref)
+ redirect_to project_blob_path(@project, File.join(branch.target, @path))
+ end
+ end
+
def commit
@commit ||= @repository.commit(@ref)
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 6e7630fa1b2..fb8caf2ae32 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -27,6 +27,15 @@ class Projects::TreeController < Projects::ApplicationController
def show
return render_404 unless @commit
+ @ref_type = ref_type
+ if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
+ branch = @project.repository.find_branch(@ref)
+ if branch
+ redirect_to project_tree_path(@project, branch.target)
+ return
+ end
+ end
+
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path))
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 9cbefe101b9..0e6128ea650 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -172,11 +172,19 @@ class ProjectsController < Projects::ApplicationController
flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
end
+ if ambiguous_ref?(@project, @ref)
+ branch = @project.repository.find_branch(@ref)
+
+ # The files view would render a ref other than the default branch
+ # This redirect can be removed once the view is fixed
+ redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref"))
+ return
+ end
+
respond_to do |format|
format.html do
@notification_setting = current_user.notification_settings_for(@project) if current_user
@project = @project.present(current_user: current_user)
-
render_landing_page
end
diff --git a/app/finders/environments/environment_names_finder.rb b/app/finders/environments/environment_names_finder.rb
index d4928f0fc84..ffb689f45e2 100644
--- a/app/finders/environments/environment_names_finder.rb
+++ b/app/finders/environments/environment_names_finder.rb
@@ -32,18 +32,9 @@ module Environments
end
def namespace_environments
- # We assume reporter access is needed for the :read_environment permission
- # here. This expection is also present in
- # IssuableFinder::Params#min_access_level, which is used for filtering out
- # merge requests that don't have the right permissions.
- #
- # We use this approach so we don't need to load every project into memory
- # just to verify if we can see their environments. Doing so would not be
- # efficient, and possibly mess up pagination if certain projects are not
- # meant to be visible.
projects = project_or_group
.all_projects
- .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
+ .filter_by_feature_visibility(:environments, current_user)
Environment.for_project(projects)
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index c542ffbce7e..81017290f12 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -30,6 +30,7 @@ class NotesFinder
notes = init_collection
notes = since_fetch_at(notes)
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
+ notes = redact_internal(notes)
sort(notes)
end
@@ -181,6 +182,13 @@ class NotesFinder
notes.order_by(sort)
end
+
+ def redact_internal(notes)
+ subject = @project || target
+ return notes if Ability.allowed?(@current_user, :read_internal_note, subject)
+
+ notes.not_internal
+ end
end
NotesFinder.prepend_mod_with('NotesFinder')
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 0352f5a1dfc..7c239f78088 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -41,7 +41,7 @@ module DashboardHelper
if doc_href.present?
link_to_doc = link_to(
- sprite_icon('question'),
+ sprite_icon('question-o'),
doc_href,
class: 'gl-ml-2',
title: _('Documentation'),
diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb
index 209456f8b67..c5d194a93e7 100644
--- a/app/models/concerns/issue_available_features.rb
+++ b/app/models/concerns/issue_available_features.rb
@@ -27,7 +27,14 @@ module IssueAvailableFeatures
raise ArgumentError, 'invalid feature'
end
- self.class.available_features_for_issue_types[feature].include?(issue_type)
+ type_for_issue = if Feature.enabled?(:issue_type_uses_work_item_types_table)
+ # The default will only be used in places where an issue is only build and not saved
+ work_item_type_with_default.base_type
+ else
+ issue_type
+ end
+
+ self.class.available_features_for_issue_types[feature].include?(type_for_issue)
end
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index f9eba4cc2fe..dee1c820f23 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -24,25 +24,37 @@ module Taskable
(\s.+) # followed by whitespace and some text.
}x.freeze
+ ITEM_PATTERN_UNTRUSTED =
+ '^' \
+ '(?:(?:>\s{0,4})*)' \
+ '(?P<prefix>(?:\s*(?:[-+*]|(?:\d+\.)))+)' \
+ '\s+' \
+ '(?P<checkbox>' \
+ "#{COMPLETE_PATTERN.source}|#{INCOMPLETE_PATTERN.source}" \
+ ')' \
+ '(?P<label>\s.+)'.freeze
+
# ignore tasks in code or html comment blocks. HTML blocks
# are ok as we allow tasks inside <detail> blocks
- REGEX = %r{
- #{::Gitlab::Regex.markdown_code_or_html_comments}
- |
- (?<task_item>
- #{ITEM_PATTERN}
- )
- }mx.freeze
+ REGEX =
+ "#{::Gitlab::Regex.markdown_code_or_html_comments_untrusted}" \
+ "|" \
+ "(?P<task_item>" \
+ "#{ITEM_PATTERN_UNTRUSTED}" \
+ ")".freeze
def self.get_tasks(content)
items = []
- content.to_s.scan(REGEX) do
- next unless $~[:task_item]
+ regex = Gitlab::UntrustedRegexp.new(REGEX, multiline: true)
+ regex.scan(content.to_s).each do |match|
+ next unless regex.extract_named_group(:task_item, match)
+
+ prefix = regex.extract_named_group(:prefix, match)
+ checkbox = regex.extract_named_group(:checkbox, match)
+ label = regex.extract_named_group(:label, match)
- $~[:task_item].scan(ITEM_PATTERN) do |prefix, checkbox, label|
- items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
- end
+ items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
end
items
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7e55ffe2e5e..25ccdc2b4f1 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -36,7 +36,7 @@ class WebHook < ApplicationRecord
after_initialize :initialize_url_variables
before_validation :reset_token
- before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }
+ before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }, on: :update
before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
@@ -105,7 +105,7 @@ class WebHook < ApplicationRecord
# See app/validators/json_schemas/web_hooks_url_variables.json
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
- def interpolated_url
+ def interpolated_url(url = self.url, url_variables = self.url_variables)
return url unless url.include?('{')
vars = url_variables
@@ -131,7 +131,19 @@ class WebHook < ApplicationRecord
end
def reset_url_variables
- self.url_variables = {} if url_changed? && !encrypted_url_variables_changed?
+ interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were)
+
+ return if url_variables_were.empty? || interpolated_url_was == interpolated_url
+
+ self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
+ end
+
+ def decrypt_url_was
+ self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was))
+ end
+
+ def url_variables_were
+ self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was)
end
def initialize_url_variables
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 160894a0c4f..d7b72fa9dad 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -52,6 +52,9 @@ class Issue < ApplicationRecord
# Types of issues that should be displayed on issue board lists
TYPES_FOR_BOARD_LIST = %w(issue incident).freeze
+ # This default came from the enum `issue_type` column. Defined as default in the DB
+ DEFAULT_ISSUE_TYPE = :issue
+
belongs_to :project
belongs_to :namespace, inverse_of: :issues
@@ -713,6 +716,12 @@ class Issue < ApplicationRecord
project || namespace
end
+ # Persisted records will always have a work_item_type. This method is useful
+ # in places where we use a non persisted issue to perform feature checks
+ def work_item_type_with_default
+ work_item_type || WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE)
+ end
+
private
def due_date_after_start_date
@@ -783,6 +792,8 @@ class Issue < ApplicationRecord
def ensure_work_item_type
return if work_item_type_id.present? || work_item_type_id_change&.last.present?
+ # TODO: We should switch to DEFAULT_ISSUE_TYPE here when the issue_type column is dropped
+ # https://gitlab.com/gitlab-org/gitlab/-/work_items/402700?iid_path=true
self.work_item_type = WorkItems::Type.default_by_type(issue_type)
end
diff --git a/app/models/packages/npm/metadata_cache.rb b/app/models/packages/npm/metadata_cache.rb
new file mode 100644
index 00000000000..2d116f2e9c0
--- /dev/null
+++ b/app/models/packages/npm/metadata_cache.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Packages
+ module Npm
+ class MetadataCache < ApplicationRecord
+ belongs_to :project, inverse_of: :npm_metadata_caches
+
+ validates :file, :package_name, :project, :size, presence: true
+ validates :package_name, uniqueness: { scope: :project_id }
+ validates :package_name, format: { with: Gitlab::Regex.package_name_regex }
+ validates :package_name, format: { with: Gitlab::Regex.npm_package_name_regex }
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index ea3e9088fb7..6bc09930f3f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -261,6 +261,8 @@ class Project < ApplicationRecord
has_many :debian_distributions,
class_name: 'Packages::Debian::ProjectDistribution',
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :npm_metadata_caches,
+ class_name: 'Packages::Npm::MetadataCache'
has_one :packages_cleanup_policy,
class_name: 'Packages::Cleanup::Policy',
inverse_of: :project
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 053ccfac050..52e623db7b0 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -36,7 +36,8 @@ class ProjectFeature < ApplicationRecord
merge_requests: Gitlab::Access::REPORTER,
metrics_dashboard: Gitlab::Access::REPORTER,
container_registry: Gitlab::Access::REPORTER,
- package_registry: Gitlab::Access::REPORTER
+ package_registry: Gitlab::Access::REPORTER,
+ environments: Gitlab::Access::REPORTER
}.freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 68f12657447..8a0bbbba4d9 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -421,7 +421,6 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:metrics_dashboard) }.policy do
- enable :read_prometheus
enable :read_deployment
end
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index 235dc6678df..e9abafceb13 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -54,7 +54,15 @@ module MergeRequests
end
def validate_service
- errors << 'User is required' if current_user.nil?
+ if current_user.nil?
+ errors << 'User is required'
+ return
+ end
+
+ unless current_user&.can?(:read_code, target_project)
+ errors << 'User access was denied'
+ return
+ end
unless target_project.merge_requests_enabled?
errors << "Merge requests are not enabled for project #{target_project.full_path}"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 8afddd99451..e8adf9444fc 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -126,7 +126,7 @@
- if show_version_check?
.float-right
.js-gitlab-version-check-badge{ data: { "size": "lg", "actionable": "true", "version": gitlab_version_check.to_json } }
- = link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
+ = link_to(sprite_icon('question-o'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
%p
= link_to _('GitLab'), general_admin_application_settings_path
%span.float-right
diff --git a/app/views/clusters/clusters/connect.html.haml b/app/views/clusters/clusters/connect.html.haml
index 82750974803..a6e1837badf 100644
--- a/app/views/clusters/clusters/connect.html.haml
+++ b/app/views/clusters/clusters/connect.html.haml
@@ -1,4 +1,3 @@
-- @content_class = 'limit-container-width' unless fluid_layout
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
- breadcrumb_title _('Connect a cluster')
- page_title _('Connect a Kubernetes Cluster')
diff --git a/app/views/clusters/clusters/new_cluster_docs.html.haml b/app/views/clusters/clusters/new_cluster_docs.html.haml
index bff371b8d51..72c70f35e22 100644
--- a/app/views/clusters/clusters/new_cluster_docs.html.haml
+++ b/app/views/clusters/clusters/new_cluster_docs.html.haml
@@ -1,4 +1,3 @@
-- @content_class = 'limit-container-width' unless fluid_layout
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
- breadcrumb_title _('Create a cluster')
- page_title _('Create a Kubernetes cluster')
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 58e0ef96333..7660a8e4ac1 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -1,4 +1,3 @@
-- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
- breadcrumb_title @cluster.name
- page_title _('Kubernetes Cluster')
diff --git a/app/views/explore/projects/page_out_of_bounds.html.haml b/app/views/explore/projects/page_out_of_bounds.html.haml
index 104257cdef7..1b65cdb0c56 100644
--- a/app/views/explore/projects/page_out_of_bounds.html.haml
+++ b/app/views/explore/projects/page_out_of_bounds.html.haml
@@ -8,5 +8,5 @@
%h5= _("Maximum page reached")
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
- = render Pajamas::ButtonComponent.new(href: request.params.merge(page: @max_page_number)) do
+ = render Pajamas::ButtonComponent.new(href: safe_params.merge(page: @max_page_number)) do
= _("Back to page %{number}") % { number: @max_page_number }
diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml
index e77367a7b42..79b13dc861a 100644
--- a/app/views/projects/blob/_breadcrumb.html.haml
+++ b/app/views/projects/blob/_breadcrumb.html.haml
@@ -2,7 +2,7 @@
.nav-block
.tree-ref-container
.tree-ref-holder
- #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project), ref: current_ref } }
+ #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project), ref: current_ref, ref_type: @ref_type.to_s } }
%ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 6cd3c584f2a..d494d9cc36d 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -2,7 +2,7 @@
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
.tree-ref-holder.gl-max-w-26
- #js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } }
+ #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
diff --git a/config/feature_flags/development/db_load_balance_audit_event_streaming_worker.yml b/config/feature_flags/development/db_load_balance_audit_event_streaming_worker.yml
new file mode 100644
index 00000000000..a7b60acefdf
--- /dev/null
+++ b/config/feature_flags/development/db_load_balance_audit_event_streaming_worker.yml
@@ -0,0 +1,8 @@
+---
+name: db_load_balance_audit_event_streaming_worker
+introduced_by_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1811
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/402493
+milestone: '15.11'
+type: development
+group: group::scalability
+default_enabled: false
diff --git a/db/docs/packages_npm_metadata_caches.yml b/db/docs/packages_npm_metadata_caches.yml
new file mode 100644
index 00000000000..3329c75b7ee
--- /dev/null
+++ b/db/docs/packages_npm_metadata_caches.yml
@@ -0,0 +1,10 @@
+---
+table_name: packages_npm_metadata_caches
+classes:
+- Packages::Npm::MetadataCache
+feature_categories:
+- package_registry
+description: Store the metadata of npm packages to use later as a cache
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114312
+milestone: '15.11'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230313181536_create_packages_npm_metadata_caches.rb b/db/migrate/20230313181536_create_packages_npm_metadata_caches.rb
new file mode 100644
index 00000000000..fde33342f04
--- /dev/null
+++ b/db/migrate/20230313181536_create_packages_npm_metadata_caches.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class CreatePackagesNpmMetadataCaches < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ INDEX_NAME = 'index_npm_metadata_caches_on_package_name_project_id_unique'
+
+ def up
+ create_table :packages_npm_metadata_caches do |t|
+ t.timestamps_with_timezone
+
+ t.datetime_with_timezone :last_downloaded_at
+ t.bigint :project_id, index: true
+ t.integer :file_store, default: 1
+ t.integer :size, null: false
+ t.text :file, null: false, limit: 255
+ t.text :package_name, null: false # rubocop:disable Migration/AddLimitToTextColumns
+
+ t.index %i[package_name project_id], name: INDEX_NAME, unique: true, where: 'project_id IS NOT NULL'
+ end
+ end
+
+ def down
+ drop_table :packages_npm_metadata_caches
+ end
+end
diff --git a/db/migrate/20230321085011_add_column_to_users_statistisc.rb b/db/migrate/20230321085011_add_column_to_users_statistisc.rb
new file mode 100644
index 00000000000..8eef2ff5765
--- /dev/null
+++ b/db/migrate/20230321085011_add_column_to_users_statistisc.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddColumnToUsersStatistisc < Gitlab::Database::Migration[2.1]
+ def change
+ add_column :users_statistics, :with_highest_role_guest_with_custom_role, :integer, default: 0, null: false
+ end
+end
diff --git a/db/migrate/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches.rb b/db/migrate/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches.rb
new file mode 100644
index 00000000000..7c542279bd0
--- /dev/null
+++ b/db/migrate/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddProjectIdForeignKeyToPackagesNpmMetadataCaches < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ SOURCE_TABLE = :packages_npm_metadata_caches
+ TARGET_TABLE = :projects
+ COLUMN = :project_id
+
+ def up
+ add_concurrent_foreign_key SOURCE_TABLE, TARGET_TABLE, column: COLUMN, on_delete: :nullify
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key SOURCE_TABLE, column: COLUMN
+ end
+ end
+end
diff --git a/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb b/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb
new file mode 100644
index 00000000000..73e6f257498
--- /dev/null
+++ b/db/post_migrate/20230208131808_nullify_last_error_from_project_mirror_data.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class NullifyLastErrorFromProjectMirrorData < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'NullifyLastErrorFromProjectMirrorData'
+ INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+ SUB_BATCH_SIZE = 1_000
+
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :project_mirror_data,
+ :id,
+ job_interval: INTERVAL,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :project_mirror_data, :id, [])
+ end
+end
diff --git a/db/post_migrate/20230330080731_remove_tmp_index_vuln_occurrences_on_report_type.rb b/db/post_migrate/20230330080731_remove_tmp_index_vuln_occurrences_on_report_type.rb
new file mode 100644
index 00000000000..ebfda8104ce
--- /dev/null
+++ b/db/post_migrate/20230330080731_remove_tmp_index_vuln_occurrences_on_report_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveTmpIndexVulnOccurrencesOnReportType < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = 'tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99'
+ REPORT_TYPES = {
+ cluster_image_scanning: 7,
+ custom: 99
+ }
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name :vulnerability_occurrences, INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :vulnerability_occurrences, :id,
+ where: "report_type IN (#{REPORT_TYPES.values.join(', ')})",
+ name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230208131808 b/db/schema_migrations/20230208131808
new file mode 100644
index 00000000000..24c5b21f6ad
--- /dev/null
+++ b/db/schema_migrations/20230208131808
@@ -0,0 +1 @@
+784f8f189eee7b5cf3136f0a859874a1d170d2b148f4c260f968b144816f1322 \ No newline at end of file
diff --git a/db/schema_migrations/20230313181536 b/db/schema_migrations/20230313181536
new file mode 100644
index 00000000000..4131e34c725
--- /dev/null
+++ b/db/schema_migrations/20230313181536
@@ -0,0 +1 @@
+d8a040d40d19bb75c8e5fc8bb867ea6354ceda22c9dfe5724a4231a4b005e373 \ No newline at end of file
diff --git a/db/schema_migrations/20230321085011 b/db/schema_migrations/20230321085011
new file mode 100644
index 00000000000..c35be85e583
--- /dev/null
+++ b/db/schema_migrations/20230321085011
@@ -0,0 +1 @@
+fc277fb3f02c01f57355cf6381a9883e6f67c339303242ea34c5a1b567b227d0 \ No newline at end of file
diff --git a/db/schema_migrations/20230322145403 b/db/schema_migrations/20230322145403
new file mode 100644
index 00000000000..d2db5495d7b
--- /dev/null
+++ b/db/schema_migrations/20230322145403
@@ -0,0 +1 @@
+7ec944ccdd85380bba7d17fbd9dbf37bea918d0ac7fbe03142f4a4c6561a77a9 \ No newline at end of file
diff --git a/db/schema_migrations/20230330080731 b/db/schema_migrations/20230330080731
new file mode 100644
index 00000000000..18a8e9dba88
--- /dev/null
+++ b/db/schema_migrations/20230330080731
@@ -0,0 +1 @@
+5bc014685295ca8af21450de34e39fb54e6cef2fc53943cce84ea24370a9955f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b4473fab752..87278d20592 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19561,6 +19561,28 @@ CREATE TABLE packages_npm_metadata (
CONSTRAINT chk_rails_e5cbc301ae CHECK ((char_length((package_json)::text) < 20000))
);
+CREATE TABLE packages_npm_metadata_caches (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ last_downloaded_at timestamp with time zone,
+ project_id bigint,
+ file_store integer DEFAULT 1,
+ size integer NOT NULL,
+ file text NOT NULL,
+ package_name text NOT NULL,
+ CONSTRAINT check_57aa07a4b2 CHECK ((char_length(file) <= 255))
+);
+
+CREATE SEQUENCE packages_npm_metadata_caches_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE packages_npm_metadata_caches_id_seq OWNED BY packages_npm_metadata_caches.id;
+
CREATE TABLE packages_nuget_dependency_link_metadata (
dependency_link_id bigint NOT NULL,
target_framework text NOT NULL,
@@ -23522,7 +23544,8 @@ CREATE TABLE users_statistics (
with_highest_role_owner integer DEFAULT 0 NOT NULL,
bots integer DEFAULT 0 NOT NULL,
blocked integer DEFAULT 0 NOT NULL,
- with_highest_role_minimal_access integer DEFAULT 0 NOT NULL
+ with_highest_role_minimal_access integer DEFAULT 0 NOT NULL,
+ with_highest_role_guest_with_custom_role integer DEFAULT 0 NOT NULL
);
CREATE SEQUENCE users_statistics_id_seq
@@ -25149,6 +25172,8 @@ ALTER TABLE ONLY packages_dependency_links ALTER COLUMN id SET DEFAULT nextval('
ALTER TABLE ONLY packages_maven_metadata ALTER COLUMN id SET DEFAULT nextval('packages_maven_metadata_id_seq'::regclass);
+ALTER TABLE ONLY packages_npm_metadata_caches ALTER COLUMN id SET DEFAULT nextval('packages_npm_metadata_caches_id_seq'::regclass);
+
ALTER TABLE ONLY packages_package_file_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_package_file_build_infos_id_seq'::regclass);
ALTER TABLE ONLY packages_package_files ALTER COLUMN id SET DEFAULT nextval('packages_package_files_id_seq'::regclass);
@@ -27379,6 +27404,9 @@ ALTER TABLE ONLY packages_helm_file_metadata
ALTER TABLE ONLY packages_maven_metadata
ADD CONSTRAINT packages_maven_metadata_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY packages_npm_metadata_caches
+ ADD CONSTRAINT packages_npm_metadata_caches_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY packages_npm_metadata
ADD CONSTRAINT packages_npm_metadata_pkey PRIMARY KEY (package_id);
@@ -31223,6 +31251,8 @@ CREATE INDEX index_notification_settings_on_source_and_level_and_user ON notific
CREATE UNIQUE INDEX index_notifications_on_user_id_and_source_id_and_source_type ON notification_settings USING btree (user_id, source_id, source_type);
+CREATE UNIQUE INDEX index_npm_metadata_caches_on_package_name_project_id_unique ON packages_npm_metadata_caches USING btree (package_name, project_id) WHERE (project_id IS NOT NULL);
+
CREATE INDEX index_ns_root_stor_stats_on_registry_size_estimated ON namespace_root_storage_statistics USING btree (registry_size_estimated);
CREATE UNIQUE INDEX index_ns_user_callouts_feature ON user_namespace_callouts USING btree (user_id, feature_name, namespace_id);
@@ -31373,6 +31403,8 @@ CREATE INDEX index_packages_maven_metadata_on_package_id_and_path ON packages_ma
CREATE INDEX index_packages_maven_metadata_on_path ON packages_maven_metadata USING btree (path);
+CREATE INDEX index_packages_npm_metadata_caches_on_project_id ON packages_npm_metadata_caches USING btree (project_id);
+
CREATE INDEX index_packages_nuget_dl_metadata_on_dependency_link_id ON packages_nuget_dependency_link_metadata USING btree (dependency_link_id);
CREATE INDEX index_packages_on_available_pypi_packages ON packages_packages USING btree (project_id, id) WHERE ((status = ANY (ARRAY[0, 1])) AND (package_type = 5) AND (version IS NOT NULL));
@@ -32743,8 +32775,6 @@ CREATE INDEX tmp_idx_for_vulnerability_feedback_migration ON vulnerability_feedb
CREATE INDEX tmp_idx_package_files_on_non_zero_size ON packages_package_files USING btree (package_id, size) WHERE (size IS NOT NULL);
-CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
-
CREATE INDEX tmp_index_ci_job_artifacts_on_expire_at_where_locked_unknown ON ci_job_artifacts USING btree (expire_at, job_id) WHERE ((locked = 2) AND (expire_at IS NOT NULL));
CREATE INDEX tmp_index_ci_job_artifacts_on_id_expire_at_file_type_trace ON ci_job_artifacts USING btree (id) WHERE (((date_part('day'::text, timezone('UTC'::text, expire_at)) = ANY (ARRAY[(21)::double precision, (22)::double precision, (23)::double precision])) AND (date_part('minute'::text, timezone('UTC'::text, expire_at)) = ANY (ARRAY[(0)::double precision, (30)::double precision, (45)::double precision])) AND (date_part('second'::text, timezone('UTC'::text, expire_at)) = (0)::double precision)) OR (file_type = 3));
@@ -34828,6 +34858,9 @@ ALTER TABLE ONLY merge_requests
ALTER TABLE ONLY ml_experiments
ADD CONSTRAINT fk_ad89c59858 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY packages_npm_metadata_caches
+ ADD CONSTRAINT fk_ada23b1d30 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY merge_request_metrics
ADD CONSTRAINT fk_ae440388cc FOREIGN KEY (latest_closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index b7c1def0ba4..6c04a73a760 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -163,7 +163,8 @@ The following API resources are available outside of project and group contexts
| [Geo Nodes](geo_nodes.md) **(PREMIUM SELF)** | `/geo_nodes` |
| [Group Activity Analytics](group_activity_analytics.md) | `/analytics/group_activity/{issues_count}` |
| [Group repository storage moves](group_repository_storage_moves.md) **(PREMIUM SELF)** | `/group_repository_storage_moves` |
-| [Import repository from GitHub](import.md) | `/import/github` |
+| [Import repository from GitHub](import.md#import-repository-from-github) | `/import/github` |
+| [Import repository from Bitbucket Server](import.md#import-repository-from-bitbucket-server) | `/import/bitbucket_server` |
| [Instance clusters](instance_clusters.md) **(FREE SELF)** | `/admin/clusters` |
| [Instance-level CI/CD variables](instance_level_ci_variables.md) **(FREE SELF)** | `/admin/ci/variables` |
| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
diff --git a/glfm_specification/output_example_snapshots/snapshot_spec.html b/glfm_specification/output_example_snapshots/snapshot_spec.html
index 96131037648..e1ef404ed03 100644
--- a/glfm_specification/output_example_snapshots/snapshot_spec.html
+++ b/glfm_specification/output_example_snapshots/snapshot_spec.html
@@ -7028,7 +7028,7 @@ references and their corresponding code points.</p>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;  &amp;amp; © Æ Ď</span>
+<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt; &amp;amp; © Æ Ď</span>
<span id="LC2" class="line" lang="plaintext">¾ ℋ ⅆ</span>
<span id="LC3" class="line" lang="plaintext">∲ ≧̸&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
@@ -7344,11 +7344,11 @@ stripped in this way:</p>
<div>
<div><a href="#example-343">Example 343</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
+<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; b &lt;/code&gt;&lt;/p&gt;</span></code></pre>
+<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; b &lt;/code&gt;&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
</div>
@@ -7356,12 +7356,12 @@ stripped in this way:</p>
<div>
<div><a href="#example-344">Example 344</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
+<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
<span id="LC2" class="line" lang="plaintext">` `</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; &lt;/code&gt;</span>
+<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; &lt;/code&gt;</span>
<span id="LC2" class="line" lang="plaintext">&lt;code&gt; &lt;/code&gt;&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
@@ -7832,11 +7832,11 @@ not part of a [left-flanking delimiter run]:</p>
<div>
<div><a href="#example-363">Example 363</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
+<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;* a *&lt;/p&gt;</span></code></pre>
+<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;* a *&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
</div>
@@ -9790,7 +9790,7 @@ Other [Unicode whitespace] like non-breaking space doesn't work.</p>
<div>
<div><a href="#example-515">Example 515</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
-<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
+<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 70535496b12..6f8d34ea387 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -203,6 +203,10 @@ module API
render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
end
+ unless can?(current_user, :read_code, target_project)
+ forbidden!("You don't have access to this fork's parent project")
+ end
+
cache_key = compare_cache_key(current_user, user_project, target_project, declared_params)
cache_action(cache_key, expires_in: 1.minute) do
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index a5862fbaac4..1833d8239d6 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -58,6 +58,7 @@ module Banzai
def records_for_nodes(nodes)
node_includes = [
+ :work_item_type,
:namespace,
:author,
:assignees,
diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb
index dba1aad639c..49c9772f760 100644
--- a/lib/extracts_ref.rb
+++ b/lib/extracts_ref.rb
@@ -5,7 +5,8 @@
# Can be extended for different types of repository object, e.g. Project or Snippet
module ExtractsRef
InvalidPathError = Class.new(StandardError)
-
+ BRANCH_REF_TYPE = 'heads'
+ TAG_REF_TYPE = 'tags'
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
#
@@ -91,7 +92,7 @@ module ExtractsRef
def ref_type
return unless params[:ref_type].present?
- params[:ref_type] == 'tags' ? 'tags' : 'heads'
+ params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE
end
private
@@ -154,4 +155,13 @@ module ExtractsRef
def repository_container
raise NotImplementedError
end
+
+ def ambiguous_ref?(project, ref)
+ return true if project.repository.ambiguous_ref?(ref)
+
+ return false unless ref&.starts_with?('refs/')
+
+ unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '')
+ project.repository.commit(unprefixed_ref).present?
+ end
end
diff --git a/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb b/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb
new file mode 100644
index 00000000000..6ea5c17353b
--- /dev/null
+++ b/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Nullifies last_error value from project_mirror_data table as they
+ # potentially included sensitive data.
+ # https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3041
+ class NullifyLastErrorFromProjectMirrorData < BatchedMigrationJob
+ feature_category :source_code_management
+ operation_name :update_all
+
+ def perform
+ each_sub_batch { |rel| rel.update_all(last_error: nil) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 5b235639ae8..de6eba9b9c9 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -453,6 +453,17 @@ module Gitlab
)
}mx.freeze
+ # Code blocks:
+ # ```
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # ```
+ MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED =
+ '(?P<code>' \
+ '^```\n' \
+ '(?:\n|.)*?' \
+ '\n```\ *$' \
+ ')'.freeze
+
MARKDOWN_HTML_BLOCK_REGEX = %r{
(?<html>
# HTML block:
@@ -466,27 +477,19 @@ module Gitlab
)
}mx.freeze
- MARKDOWN_HTML_COMMENT_LINE_REGEX = %r{
- (?<html_comment_line>
- # HTML comment line:
- # <!-- some commented text -->
-
- ^<!--\ .*?\ -->\ *$
- )
- }mx.freeze
-
- MARKDOWN_HTML_COMMENT_BLOCK_REGEX = %r{
- (?<html_comment_block>
- # HTML comment block:
- # <!-- some commented text
- # additional text
- # -->
+ # HTML comment line:
+ # <!-- some commented text -->
+ MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED =
+ '(?P<html_comment_line>' \
+ '^<!--\ .*?\ -->\ *$' \
+ ')'.freeze
- ^<!--.*\n
- .+?
- \n-->\ *$
- )
- }mx.freeze
+ MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED =
+ '(?P<html_comment_block>' \
+ '^<!--.*?\n' \
+ '(?:\n|.)*?' \
+ '\n.*?-->\ *$' \
+ ')'.freeze
def markdown_code_or_html_blocks
@markdown_code_or_html_blocks ||= %r{
@@ -496,14 +499,13 @@ module Gitlab
}mx.freeze
end
- def markdown_code_or_html_comments
- @markdown_code_or_html_comments ||= %r{
- #{MARKDOWN_CODE_BLOCK_REGEX}
- |
- #{MARKDOWN_HTML_COMMENT_LINE_REGEX}
- |
- #{MARKDOWN_HTML_COMMENT_BLOCK_REGEX}
- }mx.freeze
+ def markdown_code_or_html_comments_untrusted
+ @markdown_code_or_html_comments_untrusted ||=
+ "#{MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED}" \
+ "|" \
+ "#{MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED}" \
+ "|" \
+ "#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED}"
end
# Based on Jira's project key format
diff --git a/lib/gitlab/unicode.rb b/lib/gitlab/unicode.rb
index b49c5647dab..f291ea1b4ee 100644
--- a/lib/gitlab/unicode.rb
+++ b/lib/gitlab/unicode.rb
@@ -9,6 +9,12 @@ module Gitlab
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
BIDI_REGEXP = /\p{Bidi Control}/.freeze
+ # Regular expression for identifying space characters
+ #
+ # In web browsers space characters can be confused with simple
+ # spaces which may be misleading
+ SPACE_REGEXP = /\p{Space_Separator}/.freeze
+
class << self
# Warning message used to highlight bidi characters in the GUI
def bidi_warning
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
index 96e74f00c78..7c7bda3a8f9 100644
--- a/lib/gitlab/untrusted_regexp.rb
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -47,6 +47,17 @@ module Gitlab
RE2.Replace(text, regexp, rewrite)
end
+ # #scan returns an array of the groups captured, rather than MatchData.
+ # Use this to give the capture group name and grab the proper value
+ def extract_named_group(name, match)
+ return unless match
+
+ match_position = regexp.named_capturing_groups[name.to_s]
+ raise RegexpError, "Invalid named capture group: #{name}" unless match_position
+
+ match[match_position - 1]
+ end
+
def ==(other)
self.source == other.source
end
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index e3bf11b00b4..79e124a58f5 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -2,15 +2,37 @@
module Gitlab
class UrlSanitizer
+ include Gitlab::Utils::StrongMemoize
+
ALLOWED_SCHEMES = %w[http https ssh git].freeze
ALLOWED_WEB_SCHEMES = %w[http https].freeze
+ SCHEMIFIED_SCHEME = 'glschemelessuri'
+ SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://".freeze
+ # URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or
+ # relative URLs. This section will match schemeless URIs with userinfo
+ # e.g. user:pass@gitlab.com but will not match scp-style URIs e.g.
+ # user@server:path/to/file)
+ #
+ # The userinfo part is very loose compared to URI's implementation so we
+ # also match non-escaped userinfo e.g foo:b?r@gitlab.com which should be
+ # encoded as foo:b%3Fr@gitlab.com
+ URI_REGEXP = %r{
+ (?:
+ #{URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)}
+ |
+ (?:(?:(?!@)[%#{URI::REGEXP::PATTERN::UNRESERVED}#{URI::REGEXP::PATTERN::RESERVED}])+(?:@))
+ (?# negative lookahead ensures this isn't an SCP-style URL: [host]:[rel_path|abs_path] server:path/to/file)
+ (?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH}))
+ #{URI::REGEXP::PATTERN::HOSTPORT}
+ )
+ }x
def self.sanitize(content)
- regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
-
- content.gsub(regexp) { |url| new(url).masked_url }
- rescue Addressable::URI::InvalidURIError
- content.gsub(regexp, '')
+ content.gsub(URI_REGEXP) do |url|
+ new(url).masked_url
+ rescue Addressable::URI::InvalidURIError
+ ''
+ end
end
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
@@ -37,34 +59,45 @@ module Gitlab
@url = parse_url(url)
end
+ def credentials
+ @credentials ||= { user: @url.user.presence, password: @url.password.presence }
+ end
+
+ def user
+ credentials[:user]
+ end
+
def sanitized_url
- @sanitized_url ||= safe_url.to_s
+ safe_url = @url.dup
+ safe_url.password = nil
+ safe_url.user = nil
+ reverse_schemify(safe_url.to_s)
end
+ strong_memoize_attr :sanitized_url
def masked_url
url = @url.dup
url.password = "*****" if url.password.present?
url.user = "*****" if url.user.present?
- url.to_s
- end
-
- def credentials
- @credentials ||= { user: @url.user.presence, password: @url.password.presence }
- end
-
- def user
- credentials[:user]
+ reverse_schemify(url.to_s)
end
+ strong_memoize_attr :masked_url
def full_url
- @full_url ||= generate_full_url.to_s
+ return reverse_schemify(@url.to_s) unless valid_credentials?
+
+ url = @url.dup
+ url.password = encode_percent(credentials[:password]) if credentials[:password].present?
+ url.user = encode_percent(credentials[:user]) if credentials[:user].present?
+ reverse_schemify(url.to_s)
end
+ strong_memoize_attr :full_url
private
def parse_url(url)
- url = url.to_s.strip
- match = url.match(%r{\A(?:git|ssh|http(?:s?))\://(?:(.+)(?:@))?(.+)})
+ url = schemify(url.to_s.strip)
+ match = url.match(%r{\A(?:(?:#{SCHEMIFIED_SCHEME}|git|ssh|http(?:s?)):)?//(?:(.+)(?:@))?(.+)}o)
raw_credentials = match[1] if match
if raw_credentials.present?
@@ -83,24 +116,19 @@ module Gitlab
url
end
- def generate_full_url
- return @url unless valid_credentials?
-
- @url.dup.tap do |generated|
- generated.password = encode_percent(credentials[:password]) if credentials[:password].present?
- generated.user = encode_percent(credentials[:user]) if credentials[:user].present?
- end
+ def schemify(url)
+ # Prepend the placeholder scheme unless the URL has a scheme or is relative
+ url.prepend(SCHEMIFY_PLACEHOLDER) unless url.starts_with?(%r{(?:#{URI::REGEXP::PATTERN::SCHEME}:)?//}o)
+ url
end
- def safe_url
- safe_url = @url.dup
- safe_url.password = nil
- safe_url.user = nil
- safe_url
+ def reverse_schemify(url)
+ url.slice!(SCHEMIFY_PLACEHOLDER) if url.starts_with?(SCHEMIFY_PLACEHOLDER)
+ url
end
def valid_credentials?
- credentials && credentials.is_a?(Hash) && credentials.any?
+ credentials.is_a?(Hash) && credentials.values.any?
end
def encode_percent(string)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 436739bed12..a7e95a96b8b 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -25,7 +25,10 @@ module Rouge
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
line.each do |token, value|
- yield highlight_unicode_control_characters(span(token, value.chomp! || value))
+ value = value.chomp! || value
+ value = replace_space_characters(value)
+
+ yield highlight_unicode_control_characters(span(token, value))
end
yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present?
@@ -42,6 +45,10 @@ module Rouge
%(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>)
end
+ def replace_space_characters(text)
+ text.gsub(Gitlab::Unicode::SPACE_REGEXP, ' ')
+ end
+
def highlight_unicode_control_characters(text)
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 572d1c219d9..2e34602fc73 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2775,6 +2775,9 @@ msgstr ""
msgid "AdminArea|Users with highest role"
msgstr ""
+msgid "AdminArea|Users with highest role guest and with a %{strongOpen}Custom Role%{strongClose}."
+msgstr ""
+
msgid "AdminArea|Users without a Group and Project"
msgstr ""
@@ -27322,6 +27325,9 @@ msgstr ""
msgid "MergeRequest|Approved by @%{username}"
msgstr ""
+msgid "MergeRequest|Can't fetch the single file diff for the discussion. Please reload this page."
+msgstr ""
+
msgid "MergeRequest|Can't show this merge request because of an internal error. Contact your administrator."
msgstr ""
@@ -43764,6 +43770,9 @@ msgstr ""
msgid "The default branch for this project has been changed. Please update your bookmarks."
msgstr ""
+msgid "The default branch of this project clashes with another ref"
+msgstr ""
+
msgid "The dependency list details information about the components used within your project."
msgstr ""
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index 8df0aaa9f27..1315ed8ca73 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -11,7 +11,6 @@ module QA
element :ci_variable_key_field
element :ci_variable_value_field
element :ci_variable_save_button
- element :ci_variable_delete_button
end
def fill_variable(key, value, masked = false)
@@ -37,14 +36,6 @@ module QA
def click_ci_variable_save_button
click_element :ci_variable_save_button
end
-
- def click_reveal_ci_variable_value_button
- click_element :reveal_ci_variable_value_button
- end
-
- def click_ci_variable_delete_button
- click_element :ci_variable_delete_button
- end
end
end
end
diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb
index b632446623d..4123b8fe62b 100644
--- a/qa/qa/resource/ci_variable.rb
+++ b/qa/qa/resource/ci_variable.rb
@@ -18,19 +18,6 @@ module QA
@variable_type = 'env_var'
end
- def fabricate!
- project.visit!
-
- Page::Project::Menu.perform(&:go_to_ci_cd_settings)
-
- Page::Project::Settings::CiCd.perform do |setting|
- setting.expand_ci_variables do |page|
- page.click_add_variable
- page.fill_variable(key, value, masked)
- end
- end
- end
-
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
deleted file mode 100644
index 8474e5c1b37..00000000000
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Verify' do
- describe 'Add or Remove CI variable via UI', :smoke, product_group: :pipeline_security do
- let(:project) do
- Resource::Project.fabricate_via_api_unless_fips! do |project|
- project.name = 'project-with-ci-variables'
- project.description = 'project with CI variables'
- end
- end
-
- before do
- Flow::Login.sign_in
- project.visit!
- add_ci_variable
- end
-
- it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348027' do
- Page::Project::Settings::CiVariables.perform do |ci_variable|
- expect(ci_variable).to have_text('VARIABLE_KEY')
- expect(ci_variable).not_to have_text('some_CI_variable')
-
- ci_variable.click_reveal_ci_variable_value_button
-
- expect(ci_variable).to have_text('some_CI_variable')
- end
- end
-
- it 'user removes a CI variable', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348026' do
- Page::Project::Settings::CiVariables.perform do |ci_variable|
- ci_variable.click_edit_ci_variable
- ci_variable.click_ci_variable_delete_button
-
- expect(ci_variable).to have_text('There are no variables yet', wait: 60)
- end
- end
-
- private
-
- def add_ci_variable
- Resource::CiVariable.fabricate_via_browser_ui! do |ci_variable|
- ci_variable.project = project
- ci_variable.key = 'VARIABLE_KEY'
- ci_variable.value = 'some_CI_variable'
- ci_variable.masked = false
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident.rb b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb
index fe3cd5a432b..fe3cd5a432b 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/recovery_alert_closes_correct_incident_spec.rb
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index 4101bd7f658..4e68ffdda2a 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do
enable_ssl_verification: false,
url_variables: [
{ key: 'token', value: 'some secret value' },
+ { key: 'baz', value: 'qux' },
{ key: 'foo', value: nil }
]
}
@@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do
expect(flash[:notice]).to include('was updated')
expect(hook).to have_attributes(hook_params.except(:url_variables))
expect(hook).to have_attributes(
- url_variables: { 'token' => 'some secret value', 'baz' => 'woo' }
+ url_variables: { 'token' => 'some secret value', 'baz' => 'qux' }
)
end
end
diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb
index fca99d37000..7cfbd86cdcb 100644
--- a/spec/controllers/concerns/confirm_email_warning_spec.rb
+++ b/spec/controllers/concerns/confirm_email_warning_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ConfirmEmailWarning do
+RSpec.describe ConfirmEmailWarning, feature_category: :system_access do
before do
stub_application_setting_enum('email_confirmation_setting', 'soft')
end
@@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do
it { is_expected.to set_confirm_warning_for(user.email) }
end
end
+
+ context 'when user is being impersonated' do
+ let(:impersonator) { create(:admin) }
+
+ before do
+ allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id })
+
+ get :index
+ end
+
+ it { is_expected.to set_confirm_warning_for(user.email) }
+
+ context 'when impersonated user email has html in their email' do
+ let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
+
+ it { is_expected.to set_confirm_warning_for("malicious@test.com&lt;form&gt;&lt;input/title=&#39;&lt;script&gt;alert(document.domain)&lt;/script&gt;&#39;&gt;") }
+ end
+ end
+
+ context 'when user is not being impersonated' do
+ before do
+ get :index
+ end
+
+ it { is_expected.to set_confirm_warning_for(user.email) }
+
+ context 'when user email has html in their email' do
+ let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
+
+ it { is_expected.to set_confirm_warning_for("malicious@test.com&lt;form&gt;&lt;input/title=&#39;&lt;script&gt;alert(document.domain)&lt;/script&gt;&#39;&gt;") }
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 3f14317a84b..b07cb7a228d 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -2,15 +2,16 @@
require 'spec_helper'
-RSpec.describe Projects::BlobController do
+RSpec.describe Projects::BlobController, feature_category: :source_code_management do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) }
let(:previous_default_branch) { nil }
describe "GET show" do
- def request
- get(:show, params: { namespace_id: project.namespace, project_id: project, id: id })
+ let(:params) { { namespace_id: project.namespace, project_id: project, id: id } }
+ let(:request) do
+ get(:show, params: params)
end
render_views
@@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do
context 'with file path' do
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
-
+ project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
+ project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
request
end
+ context 'when the ref is ambiguous' do
+ let(:ref) { 'ambiguous_ref' }
+ let(:path) { 'README.md' }
+ let(:id) { "#{ref}/#{path}" }
+ let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
+
+ context 'and explicitly requesting a branch' do
+ let(:ref_type) { 'heads' }
+
+ it 'redirects to blob#show with sha for the branch' do
+ expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}"))
+ end
+ end
+
+ context 'and explicitly requesting a tag' do
+ let(:ref_type) { 'tags' }
+
+ it 'responds with success' do
+ expect(response).to be_ok
+ end
+ end
+ end
+
context "valid branch, valid file" do
let(:id) { 'master/README.md' }
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 38f72c769f3..d16e5eea2e9 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
- let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:project) { create(:project) }
let(:user) { create(:user) }
@@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
expect(response).to redirect_to(new_user_session_path)
end
end
+
+ context 'with a public project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
+ end
+
+ context 'with guest user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index 68d50cf19f0..6b0c164e432 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::Environments::PrometheusApiController do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:project) { create(:project) }
let_it_be(:proxyable) { create(:environment, project: project) }
before do
@@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do
expect(response).to redirect_to(new_user_session_path)
end
end
+
+ context 'with a public project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
+ end
+
+ context 'with guest user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 2b3adc719c1..61998d516e8 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Projects::TreeController do
+RSpec.describe Projects::TreeController, feature_category: :source_code_management do
let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) }
let(:previous_default_branch) { nil }
let(:user) { create(:user) }
@@ -15,15 +15,41 @@ RSpec.describe Projects::TreeController do
end
describe "GET show" do
+ let(:params) do
+ {
+ namespace_id: project.namespace.to_param, project_id: project, id: id
+ }
+ end
+
# Make sure any errors accessing the tree in our views bubble up to this spec
render_views
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
+ project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
+ get :show, params: params
+ end
- get :show, params: {
- namespace_id: project.namespace.to_param, project_id: project, id: id
- }
+ context 'when the ref is ambiguous' do
+ let(:id) { 'ambiguous_ref' }
+ let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
+
+ context 'and explicitly requesting a branch' do
+ let(:ref_type) { 'heads' }
+
+ it 'redirects to blob#show with sha for the branch' do
+ expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id))
+ end
+ end
+
+ context 'and explicitly requesting a tag' do
+ let(:ref_type) { 'tags' }
+
+ it 'responds with success' do
+ expect(response).to be_ok
+ end
+ end
end
context "valid branch, no path" do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index cd6d3990309..5ece9f09e5f 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -163,6 +163,69 @@ RSpec.describe ProjectsController, feature_category: :projects do
expect(assigns(:notification_setting).level).to eq("watch")
end
end
+
+ context 'when there is a tag with the same name as the default branch' do
+ let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) }
+ let(:tree_with_default_branch) do
+ branch = tagged_project.repository.find_branch(tagged_project.default_branch)
+ project_tree_path(tagged_project, branch.target)
+ end
+
+ before do
+ tagged_project.repository.create_file(
+ tagged_project.creator,
+ 'file_for_tag',
+ 'content for file',
+ message: "Automatically created file",
+ branch_name: 'branch-to-tag'
+ )
+
+ tagged_project.repository.add_tag(
+ tagged_project.creator,
+ tagged_project.default_branch, # tag name
+ 'branch-to-tag' # target
+ )
+ end
+
+ it 'redirects to tree view for the default branch' do
+ get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project }
+ expect(response).to redirect_to(tree_with_default_branch)
+ end
+ end
+
+ context 'when the default branch name can resolve to another ref' do
+ let!(:project_with_default_branch) do
+ create(:project, :public, :custom_repo, files: ['somefile']).tap do |p|
+ p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master')
+ p.change_head("refs/heads/#{other_ref}")
+ end.reload
+ end
+
+ let(:other_ref) { 'branch-name' }
+
+ context 'but there is no other ref' do
+ it 'responds with ok' do
+ get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
+ expect(response).to be_ok
+ end
+ end
+
+ context 'and that other ref exists' do
+ let(:tree_with_default_branch) do
+ branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch)
+ project_tree_path(project_with_default_branch, branch.target)
+ end
+
+ before do
+ project_with_default_branch.repository.create_branch(other_ref, 'master')
+ end
+
+ it 'redirects to tree view for the default branch' do
+ get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
+ expect(response).to redirect_to(tree_with_default_branch)
+ end
+ end
+ end
end
describe "when project repository is disabled" do
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 70a4a3ec822..54a3fc57a5d 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -66,6 +66,11 @@ FactoryBot.define do
end
end
+ trait :requirement do
+ issue_type { :requirement }
+ association :work_item_type, :default, :requirement
+ end
+
trait :task do
issue_type { :task }
association :work_item_type, :default, :task
diff --git a/spec/factories/packages/npm/metadata_cache.rb b/spec/factories/packages/npm/metadata_cache.rb
new file mode 100644
index 00000000000..b06915bcb46
--- /dev/null
+++ b/spec/factories/packages/npm/metadata_cache.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :npm_metadata_cache, class: 'Packages::Npm::MetadataCache' do
+ project
+ sequence(:package_name) { |n| "@#{project.root_namespace.path}/package-#{n}" }
+ file { 'unnamed' }
+ size { 100.kilobytes }
+ end
+end
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index a7f562df92d..3e70b897df6 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -7,7 +7,7 @@ FactoryBot.define do
project
trait :url_variables do
- url_variables { { 'abc' => 'supers3cret' } }
+ url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } }
end
trait :token do
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index 71e2429c4fe..403fd49fc65 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
icon = first('[data-testid="incognito-icon"]')
expect(icon).not_to be nil
end
+
+ context 'when viewing the confirm email warning', :js do
+ let_it_be(:another_user) { create(:user, :unconfirmed) }
+
+ let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') }
+ let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } }
+
+ context 'with an email that does not contain HTML' do
+ before do
+ subject
+ end
+
+ it 'displays the warning alert including the email' do
+ expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify")
+ end
+ end
+
+ context 'with an email that contains HTML' do
+ let(:malicious_email) { "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>" }
+ let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) }
+
+ before do
+ subject
+ end
+
+ it 'displays the impersonation alert, excludes email, and disables links' do
+ expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify")
+ end
+ end
+ end
end
context 'ending impersonation' do
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index d990c57fbf1..e6e5a1f9894 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -208,17 +208,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
end
describe 'with two-factor authentication', :js do
- def enter_code(code)
- if page.has_content?("Sign in via 2FA code")
- click_on("Sign in via 2FA code")
- enter_code(code)
- else
- fill_in 'user_otp_attempt', with: code
- click_button 'Verify code'
- end
+ def enter_code(code, only_two_factor_webauthn_enabled: false)
+ click_on("Sign in via 2FA code") if only_two_factor_webauthn_enabled
+
+ fill_in 'user_otp_attempt', with: code
+ click_button 'Verify code'
end
- shared_examples_for 'can login with recovery codes' do
+ shared_examples_for 'can login with recovery codes' do |only_two_factor_webauthn_enabled: false|
context 'using backup code' do
let(:codes) { user.generate_otp_backup_codes! }
@@ -235,7 +232,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
.to increment(:user_authenticated_counter)
.and increment(:user_two_factor_authenticated_counter)
- enter_code(codes.sample)
+ enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
expect(page).to have_current_path root_path, ignore_query: true
end
@@ -245,7 +242,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
.to increment(:user_authenticated_counter)
.and increment(:user_two_factor_authenticated_counter)
- expect { enter_code(codes.sample) }
+ expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
end
@@ -256,13 +253,13 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
.and increment(:user_session_destroyed_counter)
random_code = codes.delete(codes.sample)
- expect { enter_code(random_code) }
+ expect { enter_code(random_code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
gitlab_sign_out
gitlab_sign_in(user)
- expect { enter_code(codes.sample) }
+ expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
end
@@ -272,7 +269,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
.and increment(:user_two_factor_authenticated_counter)
expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
- enter_code(codes.sample)
+ enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
end
end
@@ -287,7 +284,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
user.save!(touch: false)
expect(user.reload.otp_backup_codes.size).to eq 9
- enter_code(code)
+ enter_code(code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
expect(page).to have_content('Invalid two-factor code.')
end
end
@@ -382,7 +379,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
context 'when user with only Webauthn enabled' do
let(:user) { create(:user, :two_factor_via_webauthn, registrations_count: 1) }
- include_examples 'can login with recovery codes'
+ include_examples 'can login with recovery codes', only_two_factor_webauthn_enabled: true
end
end
diff --git a/spec/finders/environments/environment_names_finder_spec.rb b/spec/finders/environments/environment_names_finder_spec.rb
index 438f9e9ea7c..c2336c59119 100644
--- a/spec/finders/environments/environment_names_finder_spec.rb
+++ b/spec/finders/environments/environment_names_finder_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
describe '#execute' do
let!(:group) { create(:group) }
let!(:public_project) { create(:project, :public, namespace: group) }
+ let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) }
let!(:private_project) { create(:project, :private, namespace: group) }
let!(:user) { create(:user) }
@@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do
create(:environment, name: 'gprd', project: public_project)
create(:environment, name: 'gprd', project: private_project)
create(:environment, name: 'gcny', project: private_project)
+ create(:environment, name: 'gprivprd', project: public_project_with_private_environments)
+ create(:environment, name: 'gprivstg', project: public_project_with_private_environments)
+
+ public_project_with_private_environments.update!(namespace: group)
+ public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE)
end
context 'using a group' do
@@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
names = described_class.new(group, user).execute
- expect(names).to eq(%w[gcny gprd gstg])
+ expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
end
end
@@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
names = described_class.new(group, user).execute
- expect(names).to eq(%w[gcny gprd gstg])
+ expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
end
end
@@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
end
+ context 'with a public project reporter which has private environments' do
+ it 'returns environment names for public projects' do
+ public_project_with_private_environments.add_reporter(user)
+
+ names = described_class.new(group, user).execute
+
+ expect(names).to eq(%w[gprd gprivprd gprivstg gstg])
+ end
+ end
+
context 'with a group guest' do
- it 'returns environment names for all public projects' do
+ it 'returns environment names for public projects' do
group.add_guest(user)
names = described_class.new(group, user).execute
@@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
context 'with a non-member' do
- it 'returns environment names for all public projects' do
+ it 'returns environment names for only public projects with public environments' do
names = described_class.new(group, user).execute
expect(names).to eq(%w[gprd gstg])
@@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
context 'without a user' do
- it 'returns environment names for all public projects' do
+ it 'returns environment names for only public projects with public environments' do
names = described_class.new(group).execute
expect(names).to eq(%w[gprd gstg])
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 792a14e3064..1255a882114 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -106,6 +106,26 @@ RSpec.describe NotesFinder do
end
end
+ context 'for notes on public issue in public project' do
+ let_it_be(:public_project) { create(:project, :public) }
+ let_it_be(:guest_member) { create(:user) }
+ let_it_be(:reporter_member) { create(:user) }
+ let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) }
+ let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) }
+ let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) }
+ let_it_be(:public_note) { create(:note_on_issue, project: public_project) }
+
+ it 'shows all notes when the current_user has reporter access' do
+ notes = described_class.new(reporter_member, project: public_project).execute
+ expect(notes).to contain_exactly internal_note, public_note
+ end
+
+ it 'shows only public notes when the current_user has guest access' do
+ notes = described_class.new(guest_member, project: public_project).execute
+ expect(notes).to contain_exactly public_note
+ end
+ end
+
context 'for target type' do
let(:project) { create(:project, :repository) }
let!(:note1) { create :note_on_issue, project: project }
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 351e62fb6e0..b988472f947 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -1598,6 +1598,54 @@ describe('DiffsStoreActions', () => {
);
});
+ describe('rereadNoteHash', () => {
+ beforeEach(() => {
+ window.location.hash = 'note_123';
+ });
+
+ it('dispatches setCurrentDiffFileIdFromNote if the hash is a note URL', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ {},
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ );
+ });
+
+ it('dispatches fetchFileByFile if the app is in fileByFile mode', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ { viewDiffsFileByFile: true },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }, { type: 'fetchFileByFile' }],
+ );
+ });
+
+ it('does not try to fetch the diff file if the app is not in fileByFile mode', () => {
+ window.location.hash = 'note_123';
+
+ return testAction(
+ diffActions.rereadNoteHash,
+ {},
+ { viewDiffsFileByFile: false },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ );
+ });
+
+ it('does nothing if the hash is not a note URL', () => {
+ window.location.hash = 'abcdef1234567890';
+
+ return testAction(diffActions.rereadNoteHash, {}, {}, [], []);
+ });
+ });
+
describe('setCurrentDiffFileIdFromNote', () => {
it('commits SET_CURRENT_DIFF_FILE', () => {
const commit = jest.fn();
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index ff1d860fd53..b06463e73a7 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
@@ -64,39 +65,23 @@ describe('Author Select', () => {
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
describe('user is searching via "filter by commit message"', () => {
- it('disables dropdown container', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ hasSearchParam: true });
+ beforeEach(() => {
+ setWindowLocation(`?search=foo`);
+ createComponent();
+ });
- await nextTick();
+ it('does not disable dropdown container', () => {
expect(findDropdownContainer().attributes('disabled')).toBeUndefined();
});
- it('has correct tooltip message', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ hasSearchParam: true });
-
- await nextTick();
+ it('has correct tooltip message', () => {
expect(findDropdownContainer().attributes('title')).toBe(
'Searching by both author and message is currently not supported.',
);
});
- it('disables dropdown', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ hasSearchParam: false });
-
- await nextTick();
- expect(findDropdown().attributes('disabled')).toBeUndefined();
- });
-
- it('hasSearchParam if user types a truthy string', () => {
- wrapper.vm.setSearchParam('false');
-
- expect(wrapper.vm.hasSearchParam).toBe(true);
+ it('disables dropdown', () => {
+ expect(findDropdown().attributes('disabled')).toBe('true');
});
});
@@ -106,9 +91,8 @@ describe('Author Select', () => {
});
it('displays the current selected author', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ currentAuthor });
+ setWindowLocation(`?author=${currentAuthor}`);
+ createComponent();
await nextTick();
expect(findDropdown().attributes('text')).toBe(currentAuthor);
@@ -146,12 +130,14 @@ describe('Author Select', () => {
expect(findDropdownItems().at(0).text()).toBe('Any Author');
});
- it('displays the project authors', async () => {
- await nextTick();
+ it('displays the project authors', () => {
expect(findDropdownItems()).toHaveLength(authors.length + 1);
});
it('has the correct props', async () => {
+ setWindowLocation(`?author=${currentAuthor}`);
+ createComponent();
+
const [{ avatar_url: avatarUrl, username }] = authors;
const result = {
avatarUrl,
@@ -159,16 +145,11 @@ describe('Author Select', () => {
isChecked: true,
};
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ currentAuthor });
-
await nextTick();
expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result));
});
- it("display the author's name", async () => {
- await nextTick();
+ it("display the author's name", () => {
expect(findDropdownItems().at(1).text()).toBe(currentAuthor);
});
diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
index 7f708f13eaa..220dbf17398 100644
--- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js
+++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
@@ -1,5 +1,6 @@
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'spec/test_constants';
import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
const projectRootPath = 'root/Project1';
@@ -16,16 +17,38 @@ describe('generateRefDestinationPath', () => {
${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
- `('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
+ `('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
setWindowLocation(currentPath);
- expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(result);
+ expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(
+ `${TEST_HOST}/${result}`,
+ );
+ });
+
+ describe('when using symbolic ref names', () => {
+ it.each`
+ currentPath | nextRef | result
+ ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`}
+ ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
+ ${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
+ ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
+ ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
+ ${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`}
+ `(
+ 'generates the correct destination path for $currentPath with ref type when it can be extracted',
+ ({ currentPath, result, nextRef }) => {
+ setWindowLocation(currentPath);
+ expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe(
+ `${TEST_HOST}/${result}`,
+ );
+ },
+ );
});
it('encodes the selected ref', () => {
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
- result,
+ `${TEST_HOST}/${result}`,
);
});
});
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
index a6cfbfe86ca..d8fa64e099a 100644
--- a/spec/helpers/hooks_helper_spec.rb
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe HooksHelper do
it 'returns proper data' do
expect(subject).to match(
url: project_hook.url,
- url_variables: Gitlab::Json.dump([{ key: 'abc' }])
+ url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }])
)
end
end
diff --git a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
index 80061539a0b..2b86a4f8cfc 100644
--- a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
@@ -202,7 +202,7 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor
filter(link, context)
end.count
- expect(control_count).to eq 10
+ expect(control_count).to eq 11
expect do
filter("#{link} #{link2}", context)
diff --git a/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb
new file mode 100644
index 00000000000..62f908ed79b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/nullify_last_error_from_project_mirror_data_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength
+ it 'nullifies last_error column on all rows' do
+ namespaces = table(:namespaces)
+ projects = table(:projects)
+ project_import_states = table(:project_mirror_data)
+
+ group = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
+
+ project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
+ project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
+ project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
+
+ project_1 = projects.create!(
+ namespace_id: group.id,
+ project_namespace_id: project_namespace_1.id,
+ name: 'test1'
+ )
+ project_2 = projects.create!(
+ namespace_id: group.id,
+ project_namespace_id: project_namespace_2.id,
+ name: 'test2'
+ )
+ project_3 = projects.create!(
+ namespace_id: group.id,
+ project_namespace_id: project_namespace_3.id,
+ name: 'test3'
+ )
+
+ project_import_state_1 = project_import_states.create!(
+ project_id: project_1.id,
+ status: 0,
+ last_update_started_at: 1.hour.ago,
+ last_update_scheduled_at: 1.hour.ago,
+ last_update_at: 1.hour.ago,
+ last_successful_update_at: 2.days.ago,
+ last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength
+ correlation_id_value: SecureRandom.uuid,
+ jid: SecureRandom.uuid
+ )
+
+ project_import_states.create!(
+ project_id: project_2.id,
+ status: 1,
+ last_update_started_at: 1.hour.ago,
+ last_update_scheduled_at: 1.hour.ago,
+ last_update_at: 1.hour.ago,
+ last_successful_update_at: nil,
+ next_execution_timestamp: 1.day.from_now,
+ last_error: '',
+ correlation_id_value: SecureRandom.uuid,
+ jid: SecureRandom.uuid
+ )
+
+ project_import_state_3 = project_import_states.create!(
+ project_id: project_3.id,
+ status: 2,
+ last_update_started_at: 1.hour.ago,
+ last_update_scheduled_at: 1.hour.ago,
+ last_update_at: 1.hour.ago,
+ last_successful_update_at: 1.hour.ago,
+ next_execution_timestamp: 1.day.from_now,
+ last_error: nil,
+ correlation_id_value: SecureRandom.uuid,
+ jid: SecureRandom.uuid
+ )
+
+ migration = described_class.new(
+ start_id: project_import_state_1.id,
+ end_id: project_import_state_3.id,
+ batch_table: :project_mirror_data,
+ batch_column: :id,
+ sub_batch_size: 1,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection
+ )
+
+ w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord
+ expect { migration.perform }.to change(&w_last_error_count).from(2).to(0)
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 8bdd4e89598..715749a4195 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -711,6 +711,7 @@ project:
- packages
- package_files
- rpm_repository_files
+- npm_metadata_caches
- packages_cleanup_policy
- alerting_setting
- project_setting
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index d885051b93b..e51e62d5f0a 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -1171,7 +1171,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
end
context 'HTML comment lines' do
- subject { described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX }
+ subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED, multiline: true) }
let(:expected) { [['<!-- an HTML comment -->'], ['<!-- another HTML comment -->']] }
let(:markdown) do
@@ -1189,20 +1189,20 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
it { is_expected.to match(%(<!-- single line comment -->)) }
it { is_expected.not_to match(%(<!--\nblock comment\n-->)) }
it { is_expected.not_to match(%(must start in first column <!-- comment -->)) }
- it { expect(markdown.scan(subject)).to eq expected }
+ it { expect(subject.scan(markdown)).to eq expected }
end
context 'HTML comment blocks' do
- subject { described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX }
+ subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED, multiline: true) }
- let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\n-->) }
+ let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\nmore text -->) }
let(:markdown) do
<<~MARKDOWN
Regular text
<!-- the start of an HTML comment
- [ ] list item commented out
- -->
+ more text -->
MARKDOWN
end
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 270c4beec97..66675b20107 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -137,6 +137,38 @@ RSpec.describe Gitlab::UntrustedRegexp do
end
end
+ describe '#extract_named_group' do
+ let(:re) { described_class.new('(?P<name>\w+) (?P<age>\d+)|(?P<name_only>\w+)') }
+ let(:text) { 'Bob 40' }
+
+ it 'returns values for both named groups' do
+ matched = re.scan(text).first
+
+ expect(re.extract_named_group(:name, matched)).to eq 'Bob'
+ expect(re.extract_named_group(:age, matched)).to eq '40'
+ end
+
+ it 'returns nil if there was no match for group' do
+ matched = re.scan('Bob').first
+
+ expect(re.extract_named_group(:name, matched)).to be_nil
+ expect(re.extract_named_group(:age, matched)).to be_nil
+ expect(re.extract_named_group(:name_only, matched)).to eq 'Bob'
+ end
+
+ it 'returns nil if match is nil' do
+ matched = '(?P<age>\d+)'.scan(text).first
+
+ expect(re.extract_named_group(:age, matched)).to be_nil
+ end
+
+ it 'raises if name is not a capture group' do
+ matched = re.scan(text).first
+
+ expect { re.extract_named_group(:foo, matched) }.to raise_error('Invalid named capture group: foo')
+ end
+ end
+
describe '#match' do
context 'when there are matches' do
it 'returns a match object' do
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 0ffbf5f81e7..c02cbef8328 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -10,29 +10,36 @@ RSpec.describe Gitlab::UrlSanitizer do
# We want to try with multi-line content because is how error messages are formatted
described_class.sanitize(%Q{
remote: Not Found
- fatal: repository '#{url}' not found
+ fatal: repository `#{url}` not found
})
end
where(:input, :output) do
- 'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/'
- 'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/'
- 'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git'
-
- # git protocol does not support authentication but clean any details anyway
- 'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git'
- 'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git'
+ # http(s), ssh, git, relative, and schemeless URLs should all be masked correctly
+ urls = ['http://', 'https://', 'ssh://', 'git://', '//', ''].flat_map do |protocol|
+ [
+ ["#{protocol}test.com", "#{protocol}test.com"],
+ ["#{protocol}test.com/", "#{protocol}test.com/"],
+ ["#{protocol}test.com/path/to/repo.git", "#{protocol}test.com/path/to/repo.git"],
+ ["#{protocol}user@test.com", "#{protocol}*****@test.com"],
+ ["#{protocol}user:pass@test.com", "#{protocol}*****:*****@test.com"],
+ ["#{protocol}user:@test.com", "#{protocol}*****@test.com"],
+ ["#{protocol}:pass@test.com", "#{protocol}:*****@test.com"]
+ ]
+ end
# SCP-style URLs are left unmodified
- 'user@server:project.git' | 'user@server:project.git'
- 'user:pass@server:project.git' | 'user:pass@server:project.git'
+ urls << ['user@server:project.git', 'user@server:project.git']
+ urls << ['user:@server:project.git', 'user:@server:project.git']
+ urls << [':pass@server:project.git', ':pass@server:project.git']
+ urls << ['user:pass@server:project.git', 'user:pass@server:project.git']
# return an empty string for invalid URLs
- 'ssh://' | ''
+ urls << ['ssh://', '']
end
with_them do
- it { expect(sanitize_url(input)).to include("repository '#{output}' not found") }
+ it { expect(sanitize_url(input)).to include("repository `#{output}` not found") }
end
end
diff --git a/spec/lib/rouge/formatters/html_gitlab_spec.rb b/spec/lib/rouge/formatters/html_gitlab_spec.rb
index 79bfdb262c0..6fc1b395fc8 100644
--- a/spec/lib/rouge/formatters/html_gitlab_spec.rb
+++ b/spec/lib/rouge/formatters/html_gitlab_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Rouge::Formatters::HTMLGitlab do
+RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_management do
describe '#format' do
subject { described_class.format(tokens, **options) }
@@ -67,5 +67,24 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do
is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times
end
end
+
+ context 'when space characters and zero-width spaces are used' do
+ let(:lang) { 'ruby' }
+ let(:tokens) { lexer.lex(code, continue: false) }
+
+ let(:code) do
+ <<~JS
+ def\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000hello
+ JS
+ end
+
+ it 'replaces the space characters with spaces' do
+ is_expected.to eq(
+ "<span id=\"LC1\" class=\"line\" lang=\"ruby\">" \
+ "<span class=\"k\">def</span><span class=\"err\"> </span><span class=\"n\">hello</span>" \
+ "</span>"
+ )
+ end
+ end
end
end
diff --git a/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb b/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb
new file mode 100644
index 00000000000..647c583aa39
--- /dev/null
+++ b/spec/migrations/20230322145403_add_project_id_foreign_key_to_packages_npm_metadata_caches_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe AddProjectIdForeignKeyToPackagesNpmMetadataCaches,
+ feature_category: :package_registry do
+ let(:table) { described_class::SOURCE_TABLE }
+ let(:column) { described_class::COLUMN }
+ let(:foreign_key) { -> { described_class.new.foreign_keys_for(table, column).first } }
+
+ it 'creates and drops the foreign key' do
+ reversible_migration do |migration|
+ migration.before -> do
+ expect(foreign_key.call).to be(nil)
+ end
+
+ migration.after -> do
+ expect(foreign_key.call).to have_attributes(column: column.to_s)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb
new file mode 100644
index 00000000000..6c5679b674e
--- /dev/null
+++ b/spec/migrations/nullify_last_error_from_project_mirror_data_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do
+ let(:migration) { described_class::MIGRATION }
+
+ before do
+ migrate!
+ end
+
+ describe '#up' do
+ it 'schedules background jobs for each batch of projects' do
+ expect(migration).to(
+ have_scheduled_batched_migration(
+ table_name: :project_mirror_data,
+ column_name: :id,
+ interval: described_class::INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ )
+ end
+ end
+
+ describe '#down' do
+ before do
+ schema_migrate_down!
+ end
+
+ it 'deletes all batched migration records' do
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/models/concerns/taskable_spec.rb b/spec/models/concerns/taskable_spec.rb
index 14f346f353b..20de8995d13 100644
--- a/spec/models/concerns/taskable_spec.rb
+++ b/spec/models/concerns/taskable_spec.rb
@@ -35,11 +35,7 @@ RSpec.describe Taskable, feature_category: :team_planning do
TaskList::Item.new('- [ ]', 'First item'),
TaskList::Item.new('- [x]', 'Second item'),
TaskList::Item.new('* [x]', 'First item'),
- TaskList::Item.new('* [ ]', 'Second item'),
- TaskList::Item.new('+ [ ]', 'No-break space (U+00A0)'),
- TaskList::Item.new('+ [ ]', 'Figure space (U+2007)'),
- TaskList::Item.new('+ [ ]', 'Narrow no-break space (U+202F)'),
- TaskList::Item.new('+ [ ]', 'Thin space (U+2009)')
+ TaskList::Item.new('* [ ]', 'Second item')
]
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 8fd76d2a835..48dfaff74d8 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -242,6 +242,22 @@ RSpec.describe WebHook, feature_category: :integrations do
expect(hook.url_variables).to eq({})
end
+ it 'resets url variables if url is changed and url variables are appended' do
+ hook.url = 'http://suspicious.example.com/{abc}/{foo}'
+ hook.url_variables = hook.url_variables.merge('foo' => 'bar')
+
+ expect(hook).not_to be_valid
+ expect(hook.url_variables).to eq({})
+ end
+
+ it 'resets url variables if url is changed and url variables are removed' do
+ hook.url = 'http://suspicious.example.com/{abc}'
+ hook.url_variables = hook.url_variables.except("def")
+
+ expect(hook).not_to be_valid
+ expect(hook.url_variables).to eq({})
+ end
+
it 'does not reset url variables if both url and url variables are changed' do
hook.url = 'http://example.com/{one}/{two}'
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
@@ -249,6 +265,18 @@ RSpec.describe WebHook, feature_category: :integrations do
expect(hook).to be_valid
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
end
+
+ context 'without url variables' do
+ subject(:hook) { build_stubbed(:project_hook, project: project, url: 'http://example.com') }
+
+ it 'does not reset url variables' do
+ hook.url = 'http://example.com/{one}/{two}'
+ hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
+
+ expect(hook).to be_valid
+ expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
+ end
+ end
end
it "only consider these branch filter strategies are valid" do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index eaac88d2964..dec62815366 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1773,6 +1773,36 @@ RSpec.describe Issue, feature_category: :team_planning do
it 'raises error when feature is invalid' do
expect { issue.issue_type_supports?(:unkown_feature) }.to raise_error(ArgumentError)
end
+
+ context 'when issue_type_uses_work_item_types_table feature flag is disabled' do
+ before do
+ stub_feature_flags(issue_type_uses_work_item_types_table: false)
+ end
+
+ it 'uses the issue_type column' do
+ expect(issue).to receive(:issue_type).and_call_original
+ expect(issue).not_to receive(:work_item_type).and_call_original
+
+ issue.issue_type_supports?(:assignee)
+ end
+ end
+
+ context 'when issue_type_uses_work_item_types_table feature flag is enabled' do
+ it 'uses the work_item_types table' do
+ expect(issue).not_to receive(:issue_type).and_call_original
+ expect(issue).to receive(:work_item_type).and_call_original
+
+ issue.issue_type_supports?(:assignee)
+ end
+
+ context 'when the issue is not persisted' do
+ it 'uses the default work item type' do
+ non_persisted_issue = build(:issue)
+
+ expect(non_persisted_issue.issue_type_supports?(:assignee)).to be_truthy
+ end
+ end
+ end
end
describe '#supports_time_tracking?' do
@@ -1920,4 +1950,10 @@ RSpec.describe Issue, feature_category: :team_planning do
end
end
end
+
+ describe '#work_item_type_with_default' do
+ subject { Issue.new.work_item_type_with_default }
+
+ it { is_expected.to eq(WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)) }
+ end
end
diff --git a/spec/models/packages/npm/metadata_cache_spec.rb b/spec/models/packages/npm/metadata_cache_spec.rb
new file mode 100644
index 00000000000..fdee0bedc5b
--- /dev/null
+++ b/spec/models/packages/npm/metadata_cache_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::MetadataCache, type: :model, feature_category: :package_registry do
+ let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project).inverse_of(:npm_metadata_caches) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:file) }
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:size) }
+
+ describe '#package_name' do
+ it { is_expected.to validate_presence_of(:package_name) }
+ it { is_expected.to validate_uniqueness_of(:package_name).scoped_to(:project_id) }
+ it { is_expected.to allow_value('my.app-11.07.2018').for(:package_name) }
+ it { is_expected.to allow_value('@group-1/package').for(:package_name) }
+ it { is_expected.to allow_value('@any-scope/package').for(:package_name) }
+ it { is_expected.to allow_value('unscoped-package').for(:package_name) }
+ it { is_expected.not_to allow_value('my(dom$$$ain)com.my-app').for(:package_name) }
+ it { is_expected.not_to allow_value('@inv@lid-scope/package').for(:package_name) }
+ it { is_expected.not_to allow_value('@scope/../../package').for(:package_name) }
+ it { is_expected.not_to allow_value('@scope%2e%2e%fpackage').for(:package_name) }
+ it { is_expected.not_to allow_value('@scope/sub/package').for(:package_name) }
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d4020708918..5c387faec33 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -143,6 +143,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
it { is_expected.to have_many(:rpm_repository_files).class_name('Packages::Rpm::RepositoryFile').inverse_of(:project).dependent(:destroy) }
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
+ it { is_expected.to have_many(:npm_metadata_caches).class_name('Packages::Npm::MetadataCache') }
it { is_expected.to have_one(:packages_cleanup_policy).class_name('Packages::Cleanup::Policy').inverse_of(:project) }
it { is_expected.to have_many(:pipeline_artifacts).dependent(:restrict_with_error) }
it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 3759539677a..50f425f4efe 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -727,6 +727,39 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
end
end
+ describe 'read_prometheus', feature_category: :metrics do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
+ end
+
+ let(:policy) { :read_prometheus }
+
+ where(:project_visibility, :role, :allowed) do
+ :public | :anonymous | false
+ :public | :guest | false
+ :public | :reporter | true
+ :internal | :anonymous | false
+ :internal | :guest | false
+ :internal | :reporter | true
+ :private | :anonymous | false
+ :private | :guest | false
+ :private | :reporter | true
+ end
+
+ with_them do
+ let(:current_user) { public_send(role) }
+ let(:project) { public_send("#{project_visibility}_project") }
+
+ if params[:allowed]
+ it { is_expected.to be_allowed(policy) }
+ else
+ it { is_expected.not_to be_allowed(policy) }
+ end
+ end
+ end
+
describe 'update_max_artifacts_size' do
context 'when no user' do
let(:current_user) { anonymous }
@@ -1002,7 +1035,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { guest }
it { is_expected.to be_allowed(:metrics_dashboard) }
- it { is_expected.to be_allowed(:read_prometheus) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
@@ -1012,7 +1045,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { anonymous }
it { is_expected.to be_allowed(:metrics_dashboard) }
- it { is_expected.to be_allowed(:read_prometheus) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) }
@@ -1038,12 +1071,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
end
@@ -1066,7 +1101,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { guest }
it { is_expected.to be_allowed(:metrics_dashboard) }
- it { is_expected.to be_allowed(:read_prometheus) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
@@ -1076,6 +1111,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
end
end
@@ -1098,12 +1134,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
end
@@ -1122,12 +1160,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
+ it { is_expected.to be_disallowed(:read_prometheus) }
end
end
end
@@ -2068,7 +2108,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
:public | ProjectFeature::ENABLED | :anonymous | true
:public | ProjectFeature::PRIVATE | :maintainer | true
:public | ProjectFeature::PRIVATE | :developer | true
- :public | ProjectFeature::PRIVATE | :guest | true
+ :public | ProjectFeature::PRIVATE | :guest | false
:public | ProjectFeature::PRIVATE | :anonymous | false
:public | ProjectFeature::DISABLED | :maintainer | false
:public | ProjectFeature::DISABLED | :developer | false
@@ -2080,7 +2120,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
:internal | ProjectFeature::ENABLED | :anonymous | false
:internal | ProjectFeature::PRIVATE | :maintainer | true
:internal | ProjectFeature::PRIVATE | :developer | true
- :internal | ProjectFeature::PRIVATE | :guest | true
+ :internal | ProjectFeature::PRIVATE | :guest | false
:internal | ProjectFeature::PRIVATE | :anonymous | false
:internal | ProjectFeature::DISABLED | :maintainer | false
:internal | ProjectFeature::DISABLED | :developer | false
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index b146dda5030..8853eff0b3e 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -572,6 +572,22 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
context 'when authenticated', 'as a developer' do
it_behaves_like 'repository compare' do
let(:current_user) { user }
+
+ context 'when user does not have read access to the parent project' do
+ let_it_be(:group) { create(:group) }
+ let(:forked_project) { fork_project(project, current_user, repository: true, namespace: group) }
+
+ before do
+ forked_project.add_guest(current_user)
+ end
+
+ it 'returns 403 error' do
+ get api(route, current_user), params: { from: 'improve/awesome', to: 'feature', from_project_id: forked_project.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq("403 Forbidden - You don't have access to this fork's parent project")
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 7ca795ecd1a..49ec8b09939 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -861,6 +861,21 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
end
end
+ describe 'when user does not have access to target project' do
+ let(:push_options) { { create: true, target: 'my-branch' } }
+ let(:changes) { default_branch_changes }
+
+ before do
+ allow(user1).to receive(:can?).with(:read_code, project).and_return(false)
+ end
+
+ it 'records an error', :sidekiq_inline do
+ service.execute
+
+ expect(service.errors).to eq(["User access was denied"])
+ end
+ end
+
describe 'when MRs are not enabled' do
let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } }
let(:push_options) { { create: true } }
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 5736bf885be..b4250fcf04d 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -130,8 +130,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state,
context 'there is userinfo' do
before do
project_hook.update!(
- url: 'http://{one}:{two}@example.com',
- url_variables: { 'one' => 'a', 'two' => 'b' }
+ url: 'http://{foo}:{bar}@example.com',
+ url_variables: { 'foo' => 'a', 'bar' => 'b' }
)
stub_full_request('http://example.com', method: :post)
end
diff --git a/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb
new file mode 100644
index 00000000000..1ace28be5b4
--- /dev/null
+++ b/spec/views/explore/projects/page_out_of_bounds.html.haml_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'explore/projects/page_out_of_bounds.html.haml', feature_category: :projects do
+ let(:page_limit) { 10 }
+ let(:unsafe_param) { 'hacked_using_unsafe_param!' }
+
+ before do
+ assign(:max_page_number, page_limit)
+
+ controller.params[:action] = 'index'
+ controller.params[:host] = unsafe_param
+ controller.params[:protocol] = unsafe_param
+ controller.params[:sort] = 'name_asc'
+ end
+
+ it 'removes unsafe params from the link' do
+ render
+
+ href = "/explore/projects?page=#{page_limit}&sort=name_asc"
+ button_text = format(_("Back to page %{number}"), number: page_limit)
+ expect(rendered).to have_link(button_text, href: href)
+ expect(rendered).not_to include(unsafe_param)
+ end
+end