diff --git a/CHANGELOG.md b/CHANGELOG.md index 07894799eed99888afd77e2430ddfeddba7b5352..e8f447a17cf324fe6cd7b7a6211283cb2fe05e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 17.7.1 (2025-01-08) + +### Fixed (3 changes) + +- [Update acme-client to v2.0.19](https://gitlab.com/gitlab-org/security/gitlab/-/commit/17fb399704080e909a7ffd188dad4a0367a41cf6) +- [Add a migration to regenerate CI job token signing key](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1a69b334e54530b37ca623b8e0d5da6ed2961fb1) +- [Fix CI job token signing key not always generated](https://gitlab.com/gitlab-org/security/gitlab/-/commit/6fd10c2117db61d7c894944462c29425038301c0) + +### Security (4 changes) + +- [Prevent cyclic reference in work item hierarchy widget from frontend](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a545d1f48720d3d9908880d2a77ff0e76bcaa9a5) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4684)) +- [Allow external_provider config take precedence over external_groups](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c74b00da4e644dc5628f805587b2ec492e8bd044) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4680)) +- [Filter out sensitive parameters on Auth logs](https://gitlab.com/gitlab-org/security/gitlab/-/commit/e6f661b6f3ec52e0e9d37b89d171e5b949346804) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4686)) +- [Don't allow unauthorized users to close issues automatically](https://gitlab.com/gitlab-org/security/gitlab/-/commit/b1ef837d0b61f997dacd3b299da17c96616ba275) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4674)) + ## 17.7.0 (2024-12-18) ### Added (178 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 50dcdf6c3d7b969f8367e1ff7e27b836eefffc2e..087aa4f2f1cd3f059d973f837519e6977cafbba5 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -17.7.0 \ No newline at end of file +17.7.1 \ No newline at end of file diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 50dcdf6c3d7b969f8367e1ff7e27b836eefffc2e..087aa4f2f1cd3f059d973f837519e6977cafbba5 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -17.7.0 \ No newline at end of file +17.7.1 \ No newline at end of file diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 50dcdf6c3d7b969f8367e1ff7e27b836eefffc2e..087aa4f2f1cd3f059d973f837519e6977cafbba5 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -17.7.0 \ No newline at end of file +17.7.1 \ No newline at end of file diff --git a/Gemfile b/Gemfile index 6220b2ece5cde91af95c05089e3b736a58964a44..4be9aed49bdeb3bb7b867ab7f77d176b3e2a7611 100644 --- a/Gemfile +++ b/Gemfile @@ -130,10 +130,10 @@ gem 'rqrcode', '~> 2.2', feature_category: :system_access gem 'attr_encrypted', '~> 3.2.4', path: 'vendor/gems/attr_encrypted' # rubocop:todo Gemfile/MissingFeatureCategory # GitLab Pages -gem 'validates_hostname', '~> 1.0.13' # rubocop:todo Gemfile/MissingFeatureCategory -gem 'rubyzip', '~> 2.3.2', require: 'zip' # rubocop:todo Gemfile/MissingFeatureCategory +gem 'validates_hostname', '~> 1.0.13', feature_category: :pages +gem 'rubyzip', '~> 2.3.2', require: 'zip', feature_category: :pages # GitLab Pages letsencrypt support -gem 'acme-client', '~> 2.0.18' # rubocop:todo Gemfile/MissingFeatureCategory +gem 'acme-client', '~> 2.0.19', feature_category: :pages # Browser detection gem 'browser', '~> 5.3.1' # rubocop:todo Gemfile/MissingFeatureCategory diff --git a/Gemfile.checksum b/Gemfile.checksum index 385500c1a5e94e5837cfb765efd6a0d0952649bf..b53647db0e91a2371c787d2f9da63bcca820c6d8 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -1,7 +1,7 @@ [ {"name":"CFPropertyList","version":"3.0.5","platform":"ruby","checksum":"a78551cd4768d78ebca98488c27e33652ef818be64697a54676d34e6434674a4"}, {"name":"RedCloth","version":"4.3.4","platform":"ruby","checksum":"5231b2fdd91a933915cba330e5fd1a74025e77b56f57b7404c7191ebf2812297"}, -{"name":"acme-client","version":"2.0.18","platform":"ruby","checksum":"3feab341926ffc16eb65babe51ba4dad8180c13e21e774871344e0b3502ef275"}, +{"name":"acme-client","version":"2.0.19","platform":"ruby","checksum":"29647ab04cde309503cf553d3f9d0a25dcca6588caca57f55f12d6c8833244f8"}, {"name":"actioncable","version":"7.0.8.6","platform":"ruby","checksum":"f48b0ff8414dfbb2b38e639bf49e86677dfd1b16580ce9e0a3ffa1bb4c0a4057"}, {"name":"actionmailbox","version":"7.0.8.6","platform":"ruby","checksum":"8f9deed46e38fc97d86d34e9552ffd5aae81491b139f8df91ef2616ab5628857"}, {"name":"actionmailer","version":"7.0.8.6","platform":"ruby","checksum":"e3f058a5e378e72b8a90577aaa325f23e1d718b437fe9430411eb0324ba8f71d"}, diff --git a/Gemfile.lock b/Gemfile.lock index 1f664bb6a70bd6d8a335bf0161fb091441b3aa67..9991f04b2e0536c519156d21aca4d2236fdb5f46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -229,7 +229,8 @@ GEM CFPropertyList (3.0.5) rexml RedCloth (4.3.4) - acme-client (2.0.18) + acme-client (2.0.19) + base64 (~> 0.2.0) faraday (>= 1.0, < 3.0.0) faraday-retry (>= 1.0, < 3.0.0) actioncable (7.0.8.6) @@ -1970,7 +1971,7 @@ PLATFORMS DEPENDENCIES CFPropertyList (~> 3.0.0) RedCloth (~> 4.3.3) - acme-client (~> 2.0.18) + acme-client (~> 2.0.19) activerecord-explain-analyze (~> 0.1) activerecord-gitlab! addressable (~> 2.8) diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index a8f7e550bd0a8dfc5dd5930d8960e77e545c1b56..61ad7757317e1300d5ef80ccdbdaf4e43094811c 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -1,7 +1,7 @@ [ {"name":"CFPropertyList","version":"3.0.5","platform":"ruby","checksum":"a78551cd4768d78ebca98488c27e33652ef818be64697a54676d34e6434674a4"}, {"name":"RedCloth","version":"4.3.4","platform":"ruby","checksum":"5231b2fdd91a933915cba330e5fd1a74025e77b56f57b7404c7191ebf2812297"}, -{"name":"acme-client","version":"2.0.18","platform":"ruby","checksum":"3feab341926ffc16eb65babe51ba4dad8180c13e21e774871344e0b3502ef275"}, +{"name":"acme-client","version":"2.0.19","platform":"ruby","checksum":"29647ab04cde309503cf553d3f9d0a25dcca6588caca57f55f12d6c8833244f8"}, {"name":"actioncable","version":"7.1.4.2","platform":"ruby","checksum":"4235bd313a88a7ac594505247d3563603d4baaf080f5877c8bdf0af2f28354a3"}, {"name":"actionmailbox","version":"7.1.4.2","platform":"ruby","checksum":"8e5c3ea53177588a63cb333602ac0bbdd0f9e380bece15ff889e22aa1da960dd"}, {"name":"actionmailer","version":"7.1.4.2","platform":"ruby","checksum":"f5fe514a35034b7ccf34010bfe5f558dc3e22c7936be9b9b660f14dfd00f3c9e"}, @@ -344,7 +344,7 @@ {"name":"io-event","version":"1.6.5","platform":"ruby","checksum":"5da4c044ac5f411563da1a4743d28c8d30d7802e29370db42139a52b807b4ce2"}, {"name":"ipaddr","version":"1.2.5","platform":"ruby","checksum":"4e679c71d6d8ed99f925487082f70f9a958de155591caa0e7f6cef9aa160f17a"}, {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, -{"name":"irb","version":"1.14.2","platform":"ruby","checksum":"243040f6419115beb2404380a62e41861ddb03c0792c5873dec2a69a542723c6"}, +{"name":"irb","version":"1.14.3","platform":"ruby","checksum":"c457f1f2f1438ae9ce5c5be3981ae2138dec7fb894c7d73777eeeb0a6c0d0752"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, {"name":"jaro_winkler","version":"1.5.6","platform":"java","checksum":"3262aea433861fec3179184e9adc1933cca8bc15665957a143b56816f1a22f74"}, {"name":"jaro_winkler","version":"1.5.6","platform":"ruby","checksum":"007db7805527ada1cc12f2547676181d63b0a504ec4dd7a9a2eb2424521ccd81"}, @@ -533,8 +533,8 @@ {"name":"pry-byebug","version":"3.10.1","platform":"ruby","checksum":"c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8"}, {"name":"pry-rails","version":"0.3.9","platform":"ruby","checksum":"468662575abb6b67f4a9831219f99290d5eae7bf186e64dd810d0a3e4a8cc4b1"}, {"name":"pry-shell","version":"0.6.4","platform":"ruby","checksum":"ad024882d29912b071a7de65ebea538b242d2dc1498c60c7c2352ef94769f208"}, -{"name":"psych","version":"5.2.1","platform":"java","checksum":"ce33486096ad9518a479e7d95d715205266ea64655ae85ce01bec01035016128"}, -{"name":"psych","version":"5.2.1","platform":"ruby","checksum":"f6c8441d21e9df3f9790586eaf80ea472d7c98d1e89c92e68e1b6a85fb6eab66"}, +{"name":"psych","version":"5.2.2","platform":"java","checksum":"dae010e0bbc01e03da5c42cf538705759fb592f42eb9e06baf9bce932c2d7c4e"}, +{"name":"psych","version":"5.2.2","platform":"ruby","checksum":"a4a9477c85d3e858086c38cf64e7096abe40d1b1eed248b01020dec0ff9906ab"}, {"name":"public_suffix","version":"6.0.1","platform":"ruby","checksum":"61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f"}, {"name":"puma","version":"6.5.0","platform":"java","checksum":"a58eea585d291aa33796add9884208bc1591da5d8e61886f8ac74d080b298c40"}, {"name":"puma","version":"6.5.0","platform":"ruby","checksum":"94d1b75cab7f356d52e4f1b17b9040a090889b341dbeee6ee3703f441dc189f2"}, @@ -567,7 +567,7 @@ {"name":"rbs","version":"3.6.1","platform":"ruby","checksum":"ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0"}, {"name":"rbtrace","version":"0.5.1","platform":"ruby","checksum":"e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc"}, {"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"}, -{"name":"rdoc","version":"6.8.1","platform":"ruby","checksum":"0128002d1bfc4892bdd780940841e4ca41275f63781fd832d11bc8ba4461462c"}, +{"name":"rdoc","version":"6.10.0","platform":"ruby","checksum":"db665021883ac9df3ba29cdf71aece960749888db1bf9615b4a584cfa3fa3eda"}, {"name":"re2","version":"2.7.0","platform":"aarch64-linux","checksum":"778921298b6e8aba26a6230dd298c9b361b92e45024f81fa6aee788060fa307c"}, {"name":"re2","version":"2.7.0","platform":"arm-linux","checksum":"d328b5286d83ae265e13b855da8e348a976f80f91b748045b52073a570577954"}, {"name":"re2","version":"2.7.0","platform":"arm64-darwin","checksum":"7d993f27a1afac4001c539a829e2af211ced62604930c90df32a307cf74cb4a4"}, @@ -592,7 +592,7 @@ {"name":"regexp_parser","version":"2.6.0","platform":"ruby","checksum":"f163ba463a45ca2f2730e0902f2475bb0eefcd536dfc2f900a86d1e5a7d7a556"}, {"name":"regexp_property_values","version":"1.0.0","platform":"java","checksum":"5e26782b01241616855c4ee7bb8a62fce9387e484f2d3eaf04f2a0633708222e"}, {"name":"regexp_property_values","version":"1.0.0","platform":"ruby","checksum":"162499dc0bba1e66d334273a059f207a61981cc8cc69d2ca743594e7886d080f"}, -{"name":"reline","version":"0.5.12","platform":"ruby","checksum":"41ab36d3fd2aaa169e99f8b82a93b9585f51130529360e24388fcccc20a055a2"}, +{"name":"reline","version":"0.6.0","platform":"ruby","checksum":"57620375dcbe56ec09bac7192bfb7460c716bbf0054dc94345ecaa5438e539d2"}, {"name":"representable","version":"3.2.0","platform":"ruby","checksum":"cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace"}, {"name":"request_store","version":"1.5.1","platform":"ruby","checksum":"07a204d161590789f2b1d27f9f0eadcdecd6d868cb2f03240250e1bc747df78e"}, {"name":"responders","version":"3.0.1","platform":"ruby","checksum":"613fe28e498987f4feaa3230aa6313ca4bd5f0563a3da83511b0dd6cd8f47292"}, @@ -737,7 +737,7 @@ {"name":"thread_safe","version":"0.3.6","platform":"ruby","checksum":"9ed7072821b51c57e8d6b7011a8e282e25aeea3a4065eab326e43f66f063b05a"}, {"name":"thrift","version":"0.16.0","platform":"ruby","checksum":"d023286ea89e30444c9f1c28dd76107f87d8aaf85fe1742da1d8cd3b5417dcce"}, {"name":"tilt","version":"2.0.11","platform":"ruby","checksum":"7b180fc472cbdeb186c85d31c0f2d1e61a2c0d77e1d9fd0ca28482a9d972d6a0"}, -{"name":"timeout","version":"0.4.2","platform":"ruby","checksum":"8aca2d5ff98eb2f7a501c03f8c3622065932cc58bc58f725cd50a09e63b4cc19"}, +{"name":"timeout","version":"0.4.3","platform":"ruby","checksum":"9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e"}, {"name":"timfel-krb5-auth","version":"0.8.3","platform":"ruby","checksum":"ab388c9d747fa3cd95baf2cc1c03253e372d8c680adcc543670f4f099854bb80"}, {"name":"tins","version":"1.31.1","platform":"ruby","checksum":"51c4a347c25c630d310cbc2c040ffb84e266c8227f2ade881f1130ee4f9fbecf"}, {"name":"toml-rb","version":"2.2.0","platform":"ruby","checksum":"a1e2c54ac3cc9d49861004f75f0648b3622ac03a76abe105358c31553227d9a6"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index 6df4dab08d318d2368ba19a059be3551c8d8f6ea..04c40b8e7283a2e9a4b963cff3396b8d048b64d2 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -229,7 +229,8 @@ GEM CFPropertyList (3.0.5) rexml RedCloth (4.3.4) - acme-client (2.0.18) + acme-client (2.0.19) + base64 (~> 0.2.0) faraday (>= 1.0, < 3.0.0) faraday-retry (>= 1.0, < 3.0.0) actioncable (7.1.4.2) @@ -1032,7 +1033,7 @@ GEM io-event (1.6.5) ipaddr (1.2.5) ipaddress (0.8.3) - irb (1.14.2) + irb (1.14.3) rdoc (>= 4.0.0) reline (>= 0.4.2) jaeger-client (1.1.0) @@ -1473,7 +1474,7 @@ GEM pry (>= 0.13.0) tty-markdown tty-prompt - psych (5.2.1) + psych (5.2.2) date stringio public_suffix (6.0.1) @@ -1557,7 +1558,7 @@ GEM msgpack (>= 0.4.3) optimist (>= 3.0.0) rchardet (1.8.0) - rdoc (6.8.1) + rdoc (6.10.0) psych (>= 4.0.0) re2 (2.7.0) mini_portile2 (~> 2.8.5) @@ -1587,7 +1588,7 @@ GEM redis (>= 4, < 6) regexp_parser (2.6.0) regexp_property_values (1.0.0) - reline (0.5.12) + reline (0.6.0) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -1861,7 +1862,7 @@ GEM thread_safe (0.3.6) thrift (0.16.0) tilt (2.0.11) - timeout (0.4.2) + timeout (0.4.3) timfel-krb5-auth (0.8.3) tins (1.31.1) sync @@ -1998,7 +1999,7 @@ PLATFORMS DEPENDENCIES CFPropertyList (~> 3.0.0) RedCloth (~> 4.3.3) - acme-client (~> 2.0.18) + acme-client (~> 2.0.19) activerecord-explain-analyze (~> 0.1) activerecord-gitlab! addressable (~> 2.8) diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue index 847476a71563427f9f10bf45a786274045e8936c..23f466ee77297b29dff0e44de581a4dbc93d98f1 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue @@ -100,6 +100,11 @@ export default { required: false, default: null, }, + parentId: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -546,6 +551,7 @@ export default { :is-top-level="isTopLevel" :data-child-title="child.title" :data-child-type="child.workItemType.name" + :parent-id="parentId" class="!gl-border-x-0 !gl-border-b-1 !gl-border-t-0 !gl-border-solid !gl-pb-2 last:!gl-border-b-0 last:!gl-pb-0" @drag="$emit('drag', $event)" @drop="$emit('drop')" diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue index 5fb755ac89f895f52e87e3dbefad8b7dc32d3482..26103d7948a2bab8b68aba4fd76ce49a6429201b 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue @@ -79,6 +79,11 @@ export default { required: false, default: () => {}, }, + parentId: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -150,6 +155,12 @@ export default { ); }, shouldExpandChildren() { + // In case the parent is the same as the child, + // it is creating a cycle and recursively expanding the tree + // Issue details: https://gitlab.com/gitlab-org/gitlab/-/issues/498106 + if (this.parentId === this.childItem.id) { + return false; + } const rolledUpCountsByType = findHierarchyWidgets(this.childItem.widgets)?.rolledUpCountsByType || []; const nrOpenChildren = rolledUpCountsByType @@ -310,6 +321,7 @@ export default { :has-indirect-children="hasIndirectChildren" :dragged-item-type="draggedItemType" :allowed-children-by-type="allowedChildrenByType" + :parent-id="parentId" @drag="$emit('drag', $event)" @drop="$emit('drop')" @removeChild="$emit('removeChild', childItem)" diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue index 7b7e190504a6f97ed1933a10c50af923c52fd3e5..cab33edc8092ef3bbb422d6a6e6c1c0bab0e7157 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue @@ -451,6 +451,7 @@ export default { :has-indirect-children="hasIndirectChildren" :allowed-children-by-type="allowedChildrenByType" :dragged-item-type="draggedItemType" + :parent-id="workItemId" @drag="draggedItemType = $event" @drop="draggedItemType = null" @error="error = $event" diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2f36f5a6b4edef7bd96a4362b8c9735eb08563a9..75433488ba36a560d02abcb238449d85e0b138f1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -100,7 +100,7 @@ def self.endpoint_id_for_action(action_name) env: :blocklist, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath + path: request.filtered_path ) render plain: e.message, status: :forbidden diff --git a/app/controllers/concerns/invisible_captcha_on_signup.rb b/app/controllers/concerns/invisible_captcha_on_signup.rb index a704ff251b3d29b487cf4755c75ec6a0f161543f..67c09e7b17bfb4bdfd1ab09b70a754e02b33bd77 100644 --- a/app/controllers/concerns/invisible_captcha_on_signup.rb +++ b/app/controllers/concerns/invisible_captcha_on_signup.rb @@ -45,7 +45,7 @@ def log_request(message) env: :invisible_captcha_signup_bot_detected, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath + path: request.filtered_path } Gitlab::AuthLogger.error(request_information) diff --git a/app/models/group.rb b/app/models/group.rb index d38971c83f34c731c6fa5fe05bffa9c1db665b91..5cc5e0ab6be6db7f7405ca3629febde5c89699fa 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -502,12 +502,6 @@ def human_name full_name end - def to_human_reference(from = nil) - return unless cross_namespace_reference?(from) - - human_name - end - def visibility_level_allowed_by_parent?(level = self.visibility_level) return true unless parent_id && parent_id.nonzero? diff --git a/app/workers/issues/close_worker.rb b/app/workers/issues/close_worker.rb index 343f50cd7cfe59695f010ca2964b792a611ec5d0..caf600fe8456cd925d5d614c34389457a9647957 100644 --- a/app/workers/issues/close_worker.rb +++ b/app/workers/issues/close_worker.rb @@ -37,7 +37,18 @@ def perform(project_id, issue_id, issue_type, params = {}) author = User.find_by_id(params["closed_by"]) unless author - logger.info(structured_payload(message: "User not found.", user_id: params["closed_by"])) + logger.info(structured_payload(message: "Author not found.", user_id: params["closed_by"])) + return + end + + user = User.find_by_id(params["user_id"]) + + # Only authorizing if user is present for backwards compatibility. + # TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/work_items/509422 + if user && !issue.is_a?(ExternalIssue) && !user.can?(:update_issue, issue) + logger.info( + structured_payload(message: "User cannot update issue.", user_id: params["user_id"], issue_id: issue_id) + ) return end diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 25392cb27a1023ddc0fd3912bdea90814034a8d5..0616cd4d476b22f08bbfd56b7ec2d83bed94c372 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -59,7 +59,12 @@ def close_issues(project, user, author, commit, issues) Issues::CloseWorker.bulk_perform_async_with_contexts( issues, arguments_proc: ->(issue) { - [project.id, issue.id, issue.class.to_s, { closed_by: author.id, commit_hash: commit.to_hash }] + [ + project.id, + issue.id, + issue.class.to_s, + { closed_by: author.id, user_id: user.id, commit_hash: commit.to_hash } + ] }, context_proc: ->(issue) { { project: project } } ) diff --git a/config/application.rb b/config/application.rb index c3412f86480c9afc5832b69a084588773b18c9a5..476f0055d43bdfebdfc1b9bf0371bbed09a8585f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -214,7 +214,7 @@ class Application < Rails::Application # vulnerability: # https://gitlab.com/gitlab-org/labkit/blob/master/mask/matchers.go config.filter_parameters += [ - /token$/, + /token$/i, /password/, /secret/, /key$/, diff --git a/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb b/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb index 043f6414a6285c33c508166c626cae7e710e6435..5eb372af695b21a8d95cc6650c69cdf78f054774 100644 --- a/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb +++ b/db/migrate/20241017160504_generate_ci_job_token_signing_key.rb @@ -15,12 +15,14 @@ class ApplicationSetting < MigrationRecord end def up + ApplicationSetting.reset_column_information ApplicationSetting.find_each do |application_setting| application_setting.update(ci_job_token_signing_key: OpenSSL::PKey::RSA.new(2048).to_pem) end end def down + ApplicationSetting.reset_column_information ApplicationSetting.find_each do |application_setting| application_setting.update_columns( encrypted_ci_job_token_signing_key: nil, diff --git a/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb b/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb new file mode 100644 index 0000000000000000000000000000000000000000..a0151b6be4bd5d0a5856522caee67a5bd84dc094 --- /dev/null +++ b/db/migrate/20241017160505_regenerate_ci_job_token_signing_key.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Due to a schema cache problem reported in https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38397, +# the previous migration did not always populate the application_settings table with a CI job token +# signing key. Redo the migration if there is no key. +class RegenerateCiJobTokenSigningKey < Gitlab::Database::Migration[2.2] + milestone '17.7' + restrict_gitlab_migration gitlab_schema: :gitlab_main + + class ApplicationSetting < MigrationRecord + attr_encrypted :ci_job_token_signing_key, { + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_32, + algorithm: 'aes-256-gcm', + encode: false, + encode_iv: false + } + end + + def up + ApplicationSetting.reset_column_information + ApplicationSetting.find_each do |application_setting| + next if application_setting.ci_job_token_signing_key.present? + + application_setting.update(ci_job_token_signing_key: OpenSSL::PKey::RSA.new(2048).to_pem) + end + end + + def down + ApplicationSetting.reset_column_information + ApplicationSetting.find_each do |application_setting| + application_setting.update_columns( + encrypted_ci_job_token_signing_key: nil, + encrypted_ci_job_token_signing_key_iv: nil + ) + end + end +end diff --git a/db/schema_migrations/20241017160505 b/db/schema_migrations/20241017160505 new file mode 100644 index 0000000000000000000000000000000000000000..03379507ea8846826431445e24fdbc1ac252d6ea --- /dev/null +++ b/db/schema_migrations/20241017160505 @@ -0,0 +1 @@ +b72166c9449b798ee2d03a60aca42d300d172602678df65052fad564f98b4088 \ No newline at end of file diff --git a/ee/lib/search/elastic/references/work_item.rb b/ee/lib/search/elastic/references/work_item.rb index 2bf6964ed67f811714f9173cdb59da9858abbb7f..a2ea3641c407b43b8e664a0f2f9862cdd4f84d68 100644 --- a/ee/lib/search/elastic/references/work_item.rb +++ b/ee/lib/search/elastic/references/work_item.rb @@ -69,7 +69,7 @@ def model_klass private def populate_notes(target, data) - notes_internal, notes = target.notes.partition(&:internal) + notes_internal, notes = target.notes.user.partition(&:internal) internal_notes = notes_internal.sort_by(&:created_at).reverse.map(&:note).join("\n").presence data['notes_internal'] = internal_notes.truncate_bytes(NOTES_MAXIMUM_BYTES) if internal_notes diff --git a/ee/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/ee/spec/lib/gitlab/gfm/reference_rewriter_spec.rb deleted file mode 100644 index aeea856a4b5456311dc5689d3f90cc18c6a3e1d4..0000000000000000000000000000000000000000 --- a/ee/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Gfm::ReferenceRewriter, feature_category: :team_planning do - describe '#rewrite with table syntax' do - using RSpec::Parameterized::TableSyntax - - let_it_be(:parent_group1) { create(:group, path: "parent-group-one") } - let_it_be(:parent_group2) { create(:group, path: "parent-group-two") } - let_it_be(:user) { create(:user) } - - let_it_be(:source_project) { create(:project, path: 'old-project', group: parent_group1) } - let_it_be(:target_project1) { create(:project, path: 'new-project', group: parent_group1) } - let_it_be(:target_project2) { create(:project, path: 'new-project', group: parent_group2) } - let_it_be(:source_group) { create(:group, path: 'old-group', parent: parent_group1) } - let_it_be(:target_group1) { create(:group, path: 'new-group', parent: parent_group1) } - let_it_be(:target_group2) { create(:group, path: 'new-group', parent: parent_group2) } - - let_it_be(:work_item_project_first) { create(:issue, project: source_project) } - let_it_be(:work_item_group_first) { create(:issue, :group_level, namespace: source_group) } - - let_it_be(:merge_request) { create(:merge_request, source_project: source_project) } - - let_it_be(:project_label) { create(:label, id: 123, name: 'pr label1', project: source_project) } - let_it_be(:parent_group_label) { create(:group_label, id: 321, name: 'gr label1', group: parent_group1) } - - let_it_be(:project_milestone) { create(:milestone, title: 'project milestone', project: source_project) } - let_it_be(:parent_group_milestone) { create(:milestone, title: 'group milestone', group: parent_group1) } - - before_all do - parent_group1.add_reporter(user) - parent_group2.add_reporter(user) - end - - context 'with source as Project and target as Project within same parent group' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_project1 } # 'parent-group-one/new-project' - - where(:source_text, :destination_text) do - # group level work item reference - 'ref parent-group-one/old-group#1' | 'ref parent-group-one/old-group#1' - 'ref parent-group-one/old-group#1+' | 'ref parent-group-one/old-group#1+' - 'ref parent-group-one/old-group#1+s' | 'ref parent-group-one/old-group#1+s' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Project within different parent groups' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_project2 } # 'parent-group-two/new-project' - - where(:source_text, :destination_text) do - # group level work item reference - 'ref parent-group-one/old-group#1' | 'ref parent-group-one/old-group#1' - 'ref parent-group-one/old-group#1+' | 'ref parent-group-one/old-group#1+' - 'ref parent-group-one/old-group#1+s' | 'ref parent-group-one/old-group#1+s' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Group within same parent group' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_group1 } # 'parent-group-one/new-group' - - where(:source_text, :destination_text) do - # group level work item reference - 'ref parent-group-one/old-group#1' | 'ref parent-group-one/old-group#1' - 'ref parent-group-one/old-group#1+' | 'ref parent-group-one/old-group#1+' - 'ref parent-group-one/old-group#1+s' | 'ref parent-group-one/old-group#1+s' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Group within different parent groups' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_group2 } # 'parent-group-two/new-group' - - where(:source_text, :destination_text) do - # group level work item reference - 'ref parent-group-one/old-group#1' | 'ref parent-group-one/old-group#1' - 'ref parent-group-one/old-group#1+' | 'ref parent-group-one/old-group#1+' - 'ref parent-group-one/old-group#1+s' | 'ref parent-group-one/old-group#1+s' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Group and target as Project within same parent groups' do - let_it_be(:source_parent) { source_group } # 'parent-group-one/old-group' - let_it_be(:target_parent) { target_project1 } # 'parent-group-one/new-project' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref parent-group-one/old-project#1' | 'ref parent-group-one/old-project#1' - 'ref parent-group-one/old-project#1+' | 'ref parent-group-one/old-project#1+' - 'ref parent-group-one/old-project#1+s' | 'ref parent-group-one/old-project#1+s' - # group level work item reference - 'ref #1' | 'ref parent-group-one/old-group#1' - 'ref #1+' | 'ref parent-group-one/old-group#1+' - 'ref #1+s' | 'ref parent-group-one/old-group#1+s' - # merge request reference - 'ref parent-group-one/old-project!1' | 'ref parent-group-one/old-project!1' - 'ref parent-group-one/old-project!1+' | 'ref parent-group-one/old-project!1+' - 'ref parent-group-one/old-project!1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref parent-group-one/old-project~123' | 'ref parent-group-one/old-project~123' - 'ref parent-group-one/old-project~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-group~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-group~321' - # project level milestone reference - 'ref parent-group-one/old-project%"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Group and target as Project within different parent groups' do - let_it_be(:source_parent) { source_group } # 'parent-group-one/old-group' - let_it_be(:target_parent) { target_project2 } # 'parent-group-two/new-project' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref parent-group-one/old-project#1' | 'ref parent-group-one/old-project#1' - 'ref parent-group-one/old-project#1+' | 'ref parent-group-one/old-project#1+' - 'ref parent-group-one/old-project#1+s' | 'ref parent-group-one/old-project#1+s' - # group level work item reference - 'ref #1' | 'ref parent-group-one/old-group#1' - 'ref #1+' | 'ref parent-group-one/old-group#1+' - 'ref #1+s' | 'ref parent-group-one/old-group#1+s' - # merge request reference - 'ref parent-group-one/old-project!1' | 'ref parent-group-one/old-project!1' - 'ref parent-group-one/old-project!1+' | 'ref parent-group-one/old-project!1+' - 'ref parent-group-one/old-project!1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref parent-group-one/old-project~123' | 'ref parent-group-one/old-project~123' - 'ref parent-group-one/old-project~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-group~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-group~321' - # project level milestone reference - 'ref parent-group-one/old-project%"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Group and target as Group within same parent groups' do - let_it_be(:source_parent) { source_group } # 'parent-group-one/old-group' - let_it_be(:target_parent) { target_group1 } # 'parent-group-one/new-group' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref parent-group-one/old-project#1' | 'ref parent-group-one/old-project#1' - 'ref parent-group-one/old-project#1+' | 'ref parent-group-one/old-project#1+' - 'ref parent-group-one/old-project#1+s' | 'ref parent-group-one/old-project#1+s' - # group level work item reference - 'ref #1' | 'ref parent-group-one/old-group#1' - 'ref #1+' | 'ref parent-group-one/old-group#1+' - 'ref #1+s' | 'ref parent-group-one/old-group#1+s' - # merge request reference - 'ref parent-group-one/old-project!1' | 'ref parent-group-one/old-project!1' - 'ref parent-group-one/old-project!1+' | 'ref parent-group-one/old-project!1+' - 'ref parent-group-one/old-project!1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref parent-group-one/old-project~123' | 'ref parent-group-one/old-project~123' - 'ref parent-group-one/old-project~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-group~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-group~321' - # project level milestone reference - 'ref parent-group-one/old-project%"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Group and target as Group within different parent groups' do - let_it_be(:source_parent) { source_group } # 'parent-group-one/old-group' - let_it_be(:target_parent) { target_group2 } # 'parent-group-two/new-group' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref parent-group-one/old-project#1' | 'ref parent-group-one/old-project#1' - 'ref parent-group-one/old-project#1+' | 'ref parent-group-one/old-project#1+' - 'ref parent-group-one/old-project#1+s' | 'ref parent-group-one/old-project#1+s' - # group level work item reference - 'ref #1' | 'ref parent-group-one/old-group#1' - 'ref #1+' | 'ref parent-group-one/old-group#1+' - 'ref #1+s' | 'ref parent-group-one/old-group#1+s' - # merge request reference - 'ref parent-group-one/old-project!1' | 'ref parent-group-one/old-project!1' - 'ref parent-group-one/old-project!1+' | 'ref parent-group-one/old-project!1+' - 'ref parent-group-one/old-project!1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref parent-group-one/old-project~123' | 'ref parent-group-one/old-project~123' - 'ref parent-group-one/old-project~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-group~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-group~321' - # project level milestone reference - 'ref parent-group-one/old-project%"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - end -end diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 0541f60eebd09d7eab8bcf0eaced3df72931b40c..ebad7060f6b124ce6b97be1c8aa5af444114c623 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -95,7 +95,6 @@ let_it_be(:note1) { create(:note_on_issue, noteable: project_work_item, project: project, note: 'Some pig') } let_it_be(:note2) { create(:note_on_issue, noteable: project_work_item, project: project, note: 'Terrific') } let_it_be(:note3) { create(:note, :internal, noteable: project_work_item, project: project, note: "Radiant") } - let(:expected_hash) do base_work_item_hash.merge( root_namespace_id: project_work_item.namespace.root_ancestor.id, @@ -121,6 +120,12 @@ expect(indexed_json[:notes_internal]).to eq("Newest\n…") end + it 'does not include system notes' do + create(:note, :system, noteable: project_work_item, project: project, note: "Enchanting!") + + expect(indexed_json[:notes]).not_to include('Enchanting!') + end + context 'when add_work_item_type_correct_id migration is not complete' do before do set_elasticsearch_migration_to :add_work_item_type_correct_id, including: false diff --git a/jh/Gemfile.checksum b/jh/Gemfile.checksum index 9500bde80c37dd82bbf7e5cfa40f829a3b45b784..1281ace9fc3645451fbdebecc331035fe4ef4650 100644 --- a/jh/Gemfile.checksum +++ b/jh/Gemfile.checksum @@ -1,7 +1,7 @@ [ {"name":"CFPropertyList","version":"3.0.5","platform":"ruby","checksum":"a78551cd4768d78ebca98488c27e33652ef818be64697a54676d34e6434674a4"}, {"name":"RedCloth","version":"4.3.4","platform":"ruby","checksum":"5231b2fdd91a933915cba330e5fd1a74025e77b56f57b7404c7191ebf2812297"}, -{"name":"acme-client","version":"2.0.18","platform":"ruby","checksum":"3feab341926ffc16eb65babe51ba4dad8180c13e21e774871344e0b3502ef275"}, +{"name":"acme-client","version":"2.0.19","platform":"ruby","checksum":"29647ab04cde309503cf553d3f9d0a25dcca6588caca57f55f12d6c8833244f8"}, {"name":"actioncable","version":"7.0.8.6","platform":"ruby","checksum":"f48b0ff8414dfbb2b38e639bf49e86677dfd1b16580ce9e0a3ffa1bb4c0a4057"}, {"name":"actionmailbox","version":"7.0.8.6","platform":"ruby","checksum":"8f9deed46e38fc97d86d34e9552ffd5aae81491b139f8df91ef2616ab5628857"}, {"name":"actionmailer","version":"7.0.8.6","platform":"ruby","checksum":"e3f058a5e378e72b8a90577aaa325f23e1d718b437fe9430411eb0324ba8f71d"}, diff --git a/jh/Gemfile.lock b/jh/Gemfile.lock index c0830bcd4b4c5553cf06e4a899fd34ba6354e051..4850246dc98fe96f50c89a2fb1be66c8b54b6d6a 100644 --- a/jh/Gemfile.lock +++ b/jh/Gemfile.lock @@ -273,7 +273,8 @@ GEM CFPropertyList (3.0.5) rexml RedCloth (4.3.4) - acme-client (2.0.18) + acme-client (2.0.19) + base64 (~> 0.2.0) faraday (>= 1.0, < 3.0.0) faraday-retry (>= 1.0, < 3.0.0) actioncable (7.0.8.6) @@ -2026,7 +2027,7 @@ PLATFORMS DEPENDENCIES CFPropertyList (~> 3.0.0) RedCloth (~> 4.3.3) - acme-client (~> 2.0.18) + acme-client (~> 2.0.19) activerecord-explain-analyze (~> 0.1) activerecord-gitlab! addressable (~> 2.8) diff --git a/lib/banzai/filter/references/abstract_reference_filter.rb b/lib/banzai/filter/references/abstract_reference_filter.rb index 06ce1cb07ef4daa6a1b9b71c8adb8ae3a2134193..812475b944ca20abee06df6a2b31c6db0fe3541e 100644 --- a/lib/banzai/filter/references/abstract_reference_filter.rb +++ b/lib/banzai/filter/references/abstract_reference_filter.rb @@ -228,7 +228,7 @@ def object_link_filter(text, pattern, link_content: nil, link_reference: false) url.chomp!(matches[:format]) if matches.names.include?("format") - content = context[:link_text] || link_content || object_link_text(object, matches) + content = link_content || object_link_text(object, matches) link = %(<a href="#{url}" #{data} title="#{escape_once(title)}" diff --git a/lib/banzai/filter/references/issue_reference_filter.rb b/lib/banzai/filter/references/issue_reference_filter.rb index 8b3cca320e4ffe084493f211bdbaf48a5f7844bd..9a6dd834e84132a28406ffcafeec9aaffd472fd5 100644 --- a/lib/banzai/filter/references/issue_reference_filter.rb +++ b/lib/banzai/filter/references/issue_reference_filter.rb @@ -22,10 +22,6 @@ def url_for_object(issue, _parent) end def parent_records(parent, ids) - # we are treating all group level issues as work items so those would be handled - # by the WorkItemReferenceFilter - return Issue.none if parent.is_a?(Group) - parent.issues.where(iid: ids.to_a) .includes(:project, :namespace, ::Gitlab::Issues::TypeAssociationGetter.call) end diff --git a/lib/banzai/filter/references/milestone_reference_filter.rb b/lib/banzai/filter/references/milestone_reference_filter.rb index 566552f77697913af0c2cfd99488bbb6878cc625..af8050cf8d001cffc2c62ec64786855d12d62f05 100644 --- a/lib/banzai/filter/references/milestone_reference_filter.rb +++ b/lib/banzai/filter/references/milestone_reference_filter.rb @@ -158,15 +158,6 @@ def parent def requires_unescaping? true end - - def data_attributes_for(text, parent, object, link_content: false, link_reference: false) - object_parent = object.resource_parent - - return super unless object_parent.is_a?(Group) - return super if object_parent.id == parent.id - - super.merge({ group: object_parent.id, namespace: object_parent.id, project: nil }) - end end end end diff --git a/lib/banzai/filter/references/reference_cache.rb b/lib/banzai/filter/references/reference_cache.rb index 2d4760ee3306c51c7f97b59e1f144eea5667d8d5..03af5062cb6881875894c949aadab5fd7b35ea77 100644 --- a/lib/banzai/filter/references/reference_cache.rb +++ b/lib/banzai/filter/references/reference_cache.rb @@ -195,7 +195,29 @@ def cached_objects_for_paths(paths, absolute_path) def objects_for_paths(paths, absolute_path) search_paths = absolute_path ? paths.pluck(1..-1) : paths - Route.by_paths(search_paths).preload(source: [:route, { namespace: :route }]).map(&:source) + klass = parent_type.to_s.camelize.constantize + result = if parent_type == :namespace + klass.id_in(Route.by_paths(search_paths).select(:namespace_id)) + else + klass.where_full_path_in(search_paths) + end + + return result if parent_type == :group || parent_type == :namespace + return unless parent_type == :project + + projects = result.includes(namespace: :route) + .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") + + return projects unless absolute_path + + # If we make it to here, then we're handling absolute path(s). + # Which means we need to also search groups as well as projects. + # Possible future optimization might be to use Route along the lines of: + # Routable.where_full_path_in(paths).includes(:source) + # See `routable.rb` + groups = Group.where_full_path_in(search_paths) + + projects.to_a + groups.to_a end def refs_cache diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index c345ffa8351558189b4e7f293179795a3628c555..227d80f07fc8a5c905f0838acb87e7bec6f7e20a 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -11,6 +11,7 @@ module ApplicationRateLimiter LIMIT_USAGE_BUCKET = [0.25, 0.5, 0.75, 1].freeze class << self + include ::Gitlab::Utils::StrongMemoize # Application rate limits # # Threshold value can be either an Integer or a Proc @@ -224,7 +225,7 @@ def log_request(request, type, current_user, logger = Gitlab::AuthLogger) env: type, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath + path: request_path(request) } if current_user @@ -322,6 +323,31 @@ def scoped_user_in_allowlist?(scope, users_allowlist) scoped_user.username.downcase.in?(users_allowlist) end + + def request_path(request) + # req is an ActionDispatch::Request + if request.respond_to?(:filtered_path) + request.filtered_path + else + # req is a Grape::Request < Rack::Request + other_filtered_path(request) + end + end + + def other_filtered_path(request) + filtered_params = initialize_filtered_params.filter(request.GET) + + if filtered_params.any? + "#{request.path}?#{filtered_params.to_query}" + else + request.fullpath + end + end + + def initialize_filtered_params + ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters) + end + strong_memoize_attr :initialize_filtered_params end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 4ac005f598b33b4a8373bf011d2e6898dafed55e..4fa771b447c7d5f56610ca56577da53cf74b693f 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -178,7 +178,7 @@ def rate_limit!(rate_limiter, success:, login:, request:) env: :blocklist, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath, + path: request.filtered_path, login: login ) end diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb index 6f72f185c8df5f72e70aa6ae0e792285f746e62c..135dc7c7dc417ab266abd4c1ae708e49681f23ac 100644 --- a/lib/gitlab/auth/saml/user.rb +++ b/lib/gitlab/auth/saml/user.rb @@ -18,9 +18,7 @@ def find_user user ||= find_or_build_ldap_user if auto_link_ldap_user? user ||= build_new_user if signup_enabled? - if user - user.external = !(auth_hash.groups & saml_config.external_groups).empty? if external_users_enabled? - end + user&.external = external_user? user end @@ -57,6 +55,20 @@ def external_users_enabled? def auth_hash=(auth_hash) @auth_hash = Gitlab::Auth::Saml::AuthHash.new(auth_hash) end + + private + + def external_user? + if external_provider? + true + elsif external_users_enabled? + intersecting_external_groups? + end + end + + def intersecting_external_groups? + !(auth_hash.groups & saml_config.external_groups).empty? + end end end end diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb index eb4c6f1709670ebfed7ef57376c2326e80921ff9..de598879e206779c362d2091e94a6b284eaeb91e 100644 --- a/lib/gitlab/gfm/reference_rewriter.rb +++ b/lib/gitlab/gfm/reference_rewriter.rb @@ -72,7 +72,6 @@ def original_html end def unfold_reference(reference, match, target_parent) - format = match[:format].to_s before = @text[0...match.begin(0)] after = @text[match.end(0)..] @@ -86,26 +85,22 @@ def unfold_reference(reference, match, target_parent) raise RewriteError, "Unspecified reference detected for #{referable.class.name}" end - cross_reference += format new_text = before + cross_reference + after substitution_valid?(new_text) ? cross_reference : reference end def find_referable(reference) - extractor = Gitlab::ReferenceExtractor.new(source_parent_param[:project], @current_user) - extractor.analyze(reference, **source_parent_param) + extractor = Gitlab::ReferenceExtractor.new(@source_parent, @current_user) + extractor.analyze(reference) extractor.all.first end def build_cross_reference(referable, target_parent) - class_name = referable.class.base_class.name - - return referable.to_reference(target_parent) unless %w[Label Milestone].include?(class_name) - return referable.to_reference(@source_parent, target_container: target_parent) if referable.is_a?(GroupLabel) - return referable.to_reference(target_parent, full: true, absolute_path: true) if referable.is_a?(Milestone) - - full = @source_parent.is_a?(Group) ? true : false - referable.to_reference(target_parent, full: full) + if referable.respond_to?(:project) + referable.to_reference(target_parent) + else + referable.to_reference(@source_parent, target_container: target_parent) + end end def substitution_valid?(substituted) @@ -113,20 +108,8 @@ def substitution_valid?(substituted) end def markdown(text) - Banzai.render(text, **source_parent_param, no_original_data: true, no_sourcepos: true, link_text: 'placeholder') - end - - def source_parent_param - case @source_parent - when Project - { project: @source_parent } - when Group - { group: @source_parent, project: nil } - when Namespaces::ProjectNamespace - { project: @source_parent.project } - end + Banzai.render(text, project: @source_parent, no_original_data: true, no_sourcepos: true) end - strong_memoize_attr :source_parent_param end end end diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index b312f209307fb23c7e45a1787b253206fcf42a4e..c18ea0fd2ba1380e1a13ad3c5f4117f29f0cf111 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -27,7 +27,7 @@ def call(env) env: :blocklist, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath + path: request.filtered_path ) Rack::Response.new(e.message, 403).finish rescue Gitlab::Auth::MissingPersonalAccessTokenError diff --git a/lib/gitlab/middleware/path_traversal_check.rb b/lib/gitlab/middleware/path_traversal_check.rb index 7057acd0841e6684c0546036d239727a6541a75f..ac3dc8f88dd544a104acb2b5c7ce6e715542581a 100644 --- a/lib/gitlab/middleware/path_traversal_check.rb +++ b/lib/gitlab/middleware/path_traversal_check.rb @@ -34,7 +34,7 @@ def initialize(app) def call(env) return @app.call(env) unless Feature.enabled?(:check_path_traversal_middleware, Feature.current_request) - request = ::Rack::Request.new(env.dup) + request = ::ActionDispatch::Request.new(env.dup) log_params = {} return @app.call(env) unless path_traversal_attempt?(request, log_params) @@ -56,7 +56,7 @@ def call(env) def path_traversal_attempt?(request, log_params) with_duration_metric do |metric_labels| - original_fullpath = request.fullpath + original_fullpath = request.filtered_path exclude_query_parameters(request) decoded_fullpath = CGI.unescape(request.fullpath) diff --git a/qa/qa/runtime/user/store.rb b/qa/qa/runtime/user/store.rb index 405c43eb8124d3063cc5cca1a50ab9e01e87270e..f5612e70d4994ab9c09cddffd2f9c27a0a22cd73 100644 --- a/qa/qa/runtime/user/store.rb +++ b/qa/qa/runtime/user/store.rb @@ -151,7 +151,7 @@ def reset_test_user! # # @return [Boolean] def create_unique_test_user? - !Env.running_on_live_env? && !Env.personal_access_tokens_disabled? && admin_api_client + !Env.running_on_dot_com? && !Env.personal_access_tokens_disabled? && admin_api_client end # Create api client with provided token with fallback to UI creation of token diff --git a/qa/spec/runtime/user/store_spec.rb b/qa/spec/runtime/user/store_spec.rb index fc796f9ff9bca9b20e373f745677af96c2b1488a..037b88efc44846abfa8fb15ddd7bf44ca9136cc2 100644 --- a/qa/spec/runtime/user/store_spec.rb +++ b/qa/spec/runtime/user/store_spec.rb @@ -294,7 +294,7 @@ def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "r stub_env("GITLAB_PASSWORD", password) stub_env("GITLAB_QA_ACCESS_TOKEN", api_token) - allow(Runtime::Env).to receive(:running_on_live_env?).and_return(true) + allow(Runtime::Env).to receive(:running_on_dot_com?).and_return(true) end context "with personal access tokens disabled" do @@ -375,7 +375,7 @@ def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "r let(:user) { Resource::User.init { |usr| usr.api_client = instance_double(Runtime::API::Client) } } before do - allow(Runtime::Env).to receive(:running_on_live_env?).and_return(false) + allow(Runtime::Env).to receive(:running_on_dot_com?).and_return(false) described_class.instance_variable_set(:@admin_api_client, admin_api_client) described_class.instance_variable_set(:@test_user, user) @@ -399,7 +399,7 @@ def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "r stub_env("GITLAB_PASSWORD", password) stub_env("GITLAB_QA_ACCESS_TOKEN", nil) - allow(Runtime::Env).to receive(:running_on_live_env?).and_return(true) + allow(Runtime::Env).to receive(:running_on_dot_com?).and_return(true) end context "when api client has not been initialized" do @@ -502,7 +502,7 @@ def mock_user_get(token:, code: 200, body: { is_admin: true, id: 1, username: "r let(:user) { Resource::User.new } before do - allow(Runtime::Env).to receive(:running_on_live_env?).and_return(false) + allow(Runtime::Env).to receive(:running_on_dot_com?).and_return(false) allow(Resource::User).to receive(:fabricate!).and_yield(user).and_return(user) described_class.instance_variable_set(:@admin_api_client, admin_api_client) diff --git a/qa/spec/tools/ci/qa_changes_spec.rb b/qa/spec/tools/ci/qa_changes_spec.rb index 8b5dbb473562e9870293af0d71074418681948f8..e052ecc15a96ec868af1c0727f9a65f83da99600 100644 --- a/qa/spec/tools/ci/qa_changes_spec.rb +++ b/qa/spec/tools/ci/qa_changes_spec.rb @@ -110,11 +110,12 @@ before do stub_env('SELECTIVE_EXECUTION_IMPROVED', true) stub_env('QA_CODE_PATH_MAPPINGS_GCS_CREDENTIALS', gcs_creds) - allow(Fog::Storage::Google).to receive(:new) - .with(google_project: gcs_project_id, - google_json_key_string: gcs_creds) - .and_return(gcs_client) + stub_env('CI_MERGE_REQUEST_TARGET_BRANCH_NAME', "master") + allow(QA::Tools::Ci::CodePathsMapping).to receive(:new).and_return(code_paths_mapping) + allow(Fog::Storage::Google).to receive(:new) + .with(google_project: gcs_project_id, google_json_key_string: gcs_creds) + .and_return(gcs_client) end describe '#qa_tests' do diff --git a/spec/config/application_spec.rb b/spec/config/application_spec.rb index be136c11b5c5ee5d1fee0f785c4bae7f22fc4958..8b02592093a605e9c9872aec7abdccce9db72136 100644 --- a/spec/config/application_spec.rb +++ b/spec/config/application_spec.rb @@ -20,7 +20,10 @@ def request_for_url(input_url) where(:input_url, :output_query) do '/' | {} '/?safe=1' | { 'safe' => '1' } + '/?token=secret' | { 'token' => filtered } + '/?TOKEN=secret' | { 'TOKEN' => filtered } '/?private_token=secret' | { 'private_token' => filtered } + '/?PRIVATE_TOKEN=secret' | { 'PRIVATE_TOKEN' => filtered } '/?mixed=1&private_token=secret' | { 'mixed' => '1', 'private_token' => filtered } '/?note=secret¬eable=1&prefix_note=2' | { 'note' => filtered, 'noteable' => '1', 'prefix_note' => '2' } '/?note[note]=secret&target_type=1' | { 'note' => filtered, 'target_type' => '1' } diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js index c5b48a2014e4b7d23286aba63bdb667810010f7a..795fec3ebed229d5ba99a4fc3e3d1dea71e16abc 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js @@ -29,6 +29,7 @@ import { workItemHierarchyTreeFailureResponse, workItemHierarchyNoChildrenTreeResponse, workItemHierarchyTreeSingleClosedItemResponse, + workItemWithParentAsChild, } from '../../mock_data'; jest.mock('~/alert'); @@ -315,6 +316,7 @@ describe('WorkItemLinkChild', () => { }); }); }); + describe('drag & drop', () => { const allowedChildrenByType = { Issue: ['Task'], Epic: ['Epic', 'Issue'] }; const getWorkItemTreeNoChildrenQueryHandler = jest @@ -367,4 +369,17 @@ describe('WorkItemLinkChild', () => { }, ); }); + + describe('when parent is same as the grand child', () => { + it('hide the expand to avoid cyclic calls', () => { + createComponent({ + childItem: workItemWithParentAsChild, + props: { + parentId: 'gid://gitlab/WorkItem/1', + }, + }); + + expect(findExpandButton().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index fcce6b38b049e7536146ae3c57080fa7137df944..3fb36f3c5a6f0e796890faa529ca5239fe04eab4 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -2279,6 +2279,38 @@ export const workItemObjectiveWithChild = { __typename: 'WorkItem', }; +export const workItemWithParentAsChild = { + id: 'gid://gitlab/WorkItem/1', + iid: '1', + title: 'Cyclic parent 1', + description: 'Objective description', + state: 'OPEN', + confidential: false, + reference: 'test-project-path#12', + createdAt: '2022-08-03T12:41:54Z', + updatedAt: null, + closedAt: null, + workItemType: { + id: 'gid://gitlab/WorkItems::Type/2411', + name: 'Objective', + iconName: 'issue-type-objective', + __typename: 'WorkItemType', + }, + widgets: [ + { + type: 'HIERARCHY', + hasChildren: true, + parent: null, + rolledUpCountsByType: [], + children: { + nodes: [], + }, + __typename: 'WorkItemWidgetHierarchy', + }, + ], + __typename: 'WorkItem', +}; + export const workItemObjectiveWithoutChild = { id: 'gid://gitlab/WorkItem/12', iid: '12', diff --git a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb index ad07c361bf69ca4635cb2e3db58e15d02e0de346..d54c11f0b54fe2705f26b3a9ce786cb0cafb074f 100644 --- a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb @@ -793,34 +793,14 @@ let_it_be(:context) { { project: nil, group: another_group } } it 'can not find the label' do - reference = "#{another_group.full_path}~#{group_label.name}" + reference = "#{group.full_path}~#{group_label.name}" result = reference_filter("See #{reference}", context) expect(result.to_html).to include "See #{reference}" end - it 'finds the label with relative reference' do - label_name = group_label.name - reference = "#{group.full_path}~#{label_name}" - result = reference_filter("See #{reference}", context) - - if context[:label_url_method] == :group_url - expect(result.css('a').first.attr('href')).to eq(urls.group_url(group, label_name: label_name)) - else - expect(result.css('a').first.attr('href')).to eq(urls.issues_group_url(group, label_name: label_name)) - end - end - - it 'finds label in ancestors' do - label_name = parent_group_label.name - reference = "#{group.full_path}~#{label_name}" - result = reference_filter("See #{reference}", context) - - if context[:label_url_method] == :group_url - expect(result.css('a').first.attr('href')).to eq(urls.group_url(group, label_name: label_name)) - else - expect(result.css('a').first.attr('href')).to eq(urls.issues_group_url(group, label_name: label_name)) - end + it_behaves_like 'absolute group reference' do + let_it_be(:reference) { "#{group.full_path}~#{group_label.name}" } end it 'does not find label in ancestors' do @@ -829,10 +809,6 @@ expect(result.to_html).to include "See #{reference}" end - - it_behaves_like 'absolute group reference' do - let_it_be(:reference) { "#{group.full_path}~#{group_label.name}" } - end end end diff --git a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb index 8eaf629c24c651229fd7bed12ca27b6a16a59337..05dba4f774154a194552d34530355ee51fa34034 100644 --- a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb @@ -33,13 +33,8 @@ doc = reference_filter("Milestone #{reference}") link = doc.css('a').first - if milestone.project.present? - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - elsif milestone.group.present? - expect(link).to have_attribute('data-group') - expect(link.attr('data-group')).to eq milestone.group.id.to_s - end + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s end it 'includes a data-milestone attribute' do @@ -158,13 +153,8 @@ doc = reference_filter("Milestone #{link_reference}") link = doc.css('a').first - if milestone.project.present? - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - elsif milestone.group.present? - expect(link).to have_attribute('data-group') - expect(link.attr('data-group')).to eq milestone.group.id.to_s - end + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s end it 'includes a data-milestone attribute' do diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb index 52e068d2e84e987bdc60a696e510c89a22eb879d..b46e35f029cb865544b3cc3ddcaa68cbab4fcd5b 100644 --- a/spec/lib/gitlab/application_rate_limiter_spec.rb +++ b/spec/lib/gitlab/application_rate_limiter_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting do +RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting, feature_category: :system_access do + include StubRequests + let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } @@ -532,28 +534,27 @@ end describe '.log_request' do - let(:file_path) { 'master/README.md' } - let(:type) { :raw_blob_request_limit } - let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" } + let(:token_prefix) { Gitlab::ApplicationSettingFetcher.current_application_settings.personal_access_token_prefix } + let(:token_string) { "#{token_prefix}PAT1234" } + let(:relative_url) { "/#{project.full_path}/raw/?private_token=#{token_string}" } - let(:request) do - double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath) - end + let(:type) { :raw_blob_request_limit } + let(:request) { request_for_url(relative_url) } let(:base_attributes) do { message: 'Application_Rate_Limiter_Request', env: type, - remote_ip: '127.0.0.1', + remote_ip: request.ip, request_method: 'GET', - path: fullpath + path: request.filtered_path } end context 'without a current user' do let(:current_user) { nil } - it 'logs information to auth.log' do + it 'logs filtered information to auth.log' do expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once subject.log_request(request, type, current_user) @@ -570,7 +571,7 @@ }) end - it 'logs information to auth.log' do + it 'logs filtered information to auth.log' do expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once subject.log_request(request, type, current_user) diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb index f96c7fe0bc438a81b6e8dc5199c2b0ba411e63c5..4b5cc355ad463328cd2c396df38044d6d29b0adb 100644 --- a/spec/lib/gitlab/auth/saml/user_spec.rb +++ b/spec/lib/gitlab/auth/saml/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Auth::Saml::User do +RSpec.describe Gitlab::Auth::Saml::User, feature_category: :system_access do include LdapHelpers include LoginHelpers @@ -68,12 +68,22 @@ end end - context 'user was external, now should not be' do - it 'makes user internal' do - existing_user.update_attribute('external', true) - saml_user.save # rubocop:disable Rails/SaveBang - expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey + context 'when the external_provider config is set to saml' do + before do + stub_omniauth_saml_config(external_providers: [provider], block_auto_created_users: false) + end + + context 'when an existing saml external_user is removed from their external_group' do + before do + stub_saml_group_config([]) + end + + it 'retains the external:true attribute', :aggregate_failures do + saml_user.save # rubocop:disable Rails/SaveBang -- Gitlab::Auth::OAuth::User#save is a custom method + expect(gl_user).to eq existing_user + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end end end end @@ -418,6 +428,22 @@ expect(saml_user.find_user.external).to be_falsy end end + + context 'when the external_providers config includes saml' do + before do + stub_omniauth_saml_config(external_providers: [provider], block_auto_created_users: false) + stub_saml_group_config(%w[Freelancers]) + end + + it 'marks external:true for all users, regardless of the existence of external_groups', :aggregate_failures do + saml_user.find_user + + saml_user.save # rubocop:disable Rails/SaveBang -- Gitlab::Auth::OAuth::User#save is a custom method + expect(gl_user).to be_valid + expect(gl_user).to be_truthy + expect(gl_user.external).to be_truthy + end + end end describe '#bypass_two_factor?' do diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 333bdc22870070b9116f4452d8b0aac5696baf53..b1df7cf55d2d07b4a97fdf54e78e883b12571a02 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_category: :system_access do + include StubRequests + let_it_be(:project) { create(:project) } let(:auth_failure) { { actor: nil, project: nil, type: nil, authentication_abilities: nil } } @@ -296,7 +298,10 @@ end context 'when failure goes over threshold' do - let(:request) { instance_double(ActionDispatch::Request, fullpath: '/some/project.git/info/refs', request_method: 'GET', ip: 'ip', path: '/some_path/example') } + let(:token_prefix) { Gitlab::ApplicationSettingFetcher.current_application_settings.personal_access_token_prefix } + let(:token_string) { "#{token_prefix}PAT1234" } + let(:relative_url) { "/some/project.git/info/refs?private_token=#{token_string}" } + let(:request) { request_for_url(relative_url) } before do expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| @@ -304,13 +309,14 @@ end end - it 'logs a message' do + it 'logs a message with a filtered path' do expect(Gitlab::AuthLogger).to receive(:error).with( - message: include('IP has been temporarily banned from Git auth'), + message: "Rack_Attack: Git auth failures has exceeded the threshold. " \ + "IP has been temporarily banned from Git auth.", env: :blocklist, remote_ip: request.ip, request_method: request.request_method, - path: request.fullpath, + path: request.filtered_path, login: user.username ) diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 8b754c515b8a051dd090f43d173b64b2f148eb17..75427ac0402ee54f1bef2db139fd60aa4390e9e2 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Gfm::ReferenceRewriter, feature_category: :team_planning do +RSpec.describe Gitlab::Gfm::ReferenceRewriter do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } @@ -26,6 +26,14 @@ let!(:issue_second) { create(:issue, project: old_project) } let!(:merge_request) { create(:merge_request, source_project: old_project) } + context 'plain text description' do + let(:text) { 'Description that references #1, #2 and !1' } + + it { is_expected.to include issue_first.to_reference(new_project) } + it { is_expected.to include issue_second.to_reference(new_project) } + it { is_expected.to include merge_request.to_reference(new_project) } + end + context 'description with ignored elements' do let(:text) do "Hi. This references #1, but not `#2`\n" \ @@ -63,9 +71,60 @@ it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" } end + + context 'description with project labels' do + let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } + + context 'label referenced by id' do + let(:text) { '#1 and ~123' } + + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~123) } + end + + context 'label referenced by text' do + let(:text) { '#1 and ~"test"' } + + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~123) } + end + end + + context 'description with group labels' do + let(:old_group) { create(:group) } + let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } + + before do + old_project.update!(namespace: old_group) + end + + context 'label referenced by id' do + let(:text) { '#1 and ~321' } + + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~321) } + end + + context 'label referenced by text' do + let(:text) { '#1 and ~"group label"' } + + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~321) } + end + end end end + context 'when description contains a local reference' do + let(:local_issue) { create(:issue, project: old_project) } + let(:text) { "See ##{local_issue.iid}" } + + it { is_expected.to eq("See #{old_project.path}##{local_issue.iid}") } + end + + context 'when description contains a cross reference' do + let(:merge_request) { create(:merge_request) } + let(:text) { "See #{merge_request.project.full_path}!#{merge_request.iid}" } + + it { is_expected.to eq(text) } + end + context 'with a commit' do let(:old_project) { create(:project, :repository, name: 'old-project', group: group) } let(:commit) { old_project.commit } @@ -83,6 +142,26 @@ end end + context 'reference contains project milestone' do + let!(:milestone) do + create(:milestone, title: '9.0', project: old_project) + end + + let(:text) { 'milestone: %"9.0"' } + + it { is_expected.to eq %(milestone: #{old_project_ref}%"9.0") } + end + + context 'when referring to group milestone' do + let!(:milestone) do + create(:milestone, title: '10.0', group: group) + end + + let(:text) { 'milestone %"10.0"' } + + it { is_expected.to eq text } + end + context 'when referring to a group' do let(:text) { "group @#{group.full_path}" } @@ -99,7 +178,9 @@ before do create(:milestone, title: '9.0', project: old_project) - allow_any_instance_of(Milestone).to receive(:to_reference).and_return(nil) + allow_any_instance_of(Milestone) + .to receive(:to_reference) + .and_return(nil) end let(:text) { 'milestone: %"9.0"' } @@ -112,153 +193,4 @@ end end end - - describe '#rewrite with table syntax' do - using RSpec::Parameterized::TableSyntax - - let_it_be(:parent_group1) { create(:group, path: "parent-group-one") } - let_it_be(:parent_group2) { create(:group, path: "parent-group-two") } - let_it_be(:user) { create(:user) } - - let_it_be(:source_project) { create(:project, path: 'old-project', group: parent_group1) } - let_it_be(:target_project1) { create(:project, path: 'new-project', group: parent_group1) } - let_it_be(:target_project2) { create(:project, path: 'new-project', group: parent_group2) } - let_it_be(:target_group1) { create(:group, path: 'new-group', parent: parent_group1) } - let_it_be(:target_group2) { create(:group, path: 'new-group', parent: parent_group2) } - - let_it_be(:work_item_project_first) { create(:issue, project: source_project) } - - let_it_be(:merge_request) { create(:merge_request, source_project: source_project) } - - let_it_be(:project_label) { create(:label, id: 123, name: 'pr label1', project: source_project) } - let_it_be(:parent_group_label) { create(:group_label, id: 321, name: 'gr label1', group: parent_group1) } - - let_it_be(:project_milestone) { create(:milestone, title: 'project milestone', project: source_project) } - let_it_be(:parent_group_milestone) { create(:milestone, title: 'group milestone', group: parent_group1) } - - before_all do - parent_group1.add_reporter(user) - parent_group2.add_reporter(user) - end - - context 'with source as Project and target as Project within same parent group' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_project1 } # 'parent-group-one/new-project' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref #1' | 'ref old-project#1' - 'ref #1+' | 'ref old-project#1+' - 'ref #1+s' | 'ref old-project#1+s' - # merge request reference - 'ref !1' | 'ref old-project!1' - 'ref !1+' | 'ref old-project!1+' - 'ref !1+s' | 'ref old-project!1+s' - # project label reference - 'ref ~123' | 'ref old-project~123' - 'ref ~"pr label1"' | 'ref old-project~123' - # group level label reference - 'ref ~321' | 'ref old-project~321' - 'ref ~"gr label1"' | 'ref old-project~321' - # project level milestone reference - 'ref %"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Project within different parent groups' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_project2 } # 'parent-group-two/new-project' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref #1' | 'ref parent-group-one/old-project#1' - 'ref #1+' | 'ref parent-group-one/old-project#1+' - 'ref #1+s' | 'ref parent-group-one/old-project#1+s' - # merge request reference - 'ref !1' | 'ref parent-group-one/old-project!1' - 'ref !1+' | 'ref parent-group-one/old-project!1+' - 'ref !1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref ~123' | 'ref parent-group-one/old-project~123' - 'ref ~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-project~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-project~321' - # project level milestone reference - 'ref %"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Group within same parent group' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_group1 } # 'parent-group-one/new-group' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref #1' | 'ref parent-group-one/old-project#1' - 'ref #1+' | 'ref parent-group-one/old-project#1+' - 'ref #1+s' | 'ref parent-group-one/old-project#1+s' - # merge request reference - 'ref !1' | 'ref parent-group-one/old-project!1' - 'ref !1+' | 'ref parent-group-one/old-project!1+' - 'ref !1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref ~123' | 'ref parent-group-one/old-project~123' - 'ref ~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-project~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-project~321' - # project level milestone reference - 'ref %"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - - context 'with source as Project and target as Group within different parent groups' do - let_it_be(:source_parent) { source_project } # 'parent-group-one/old-project' - let_it_be(:target_parent) { target_group2 } # 'parent-group-two/new-group' - - where(:source_text, :destination_text) do - # project level work item reference - 'ref #1' | 'ref parent-group-one/old-project#1' - 'ref #1+' | 'ref parent-group-one/old-project#1+' - 'ref #1+s' | 'ref parent-group-one/old-project#1+s' - # merge request reference - 'ref !1' | 'ref parent-group-one/old-project!1' - 'ref !1+' | 'ref parent-group-one/old-project!1+' - 'ref !1+s' | 'ref parent-group-one/old-project!1+s' - # project label reference - 'ref ~123' | 'ref parent-group-one/old-project~123' - 'ref ~"pr label1"' | 'ref parent-group-one/old-project~123' - # group level label reference - 'ref ~321' | 'ref parent-group-one/old-project~321' - 'ref ~"gr label1"' | 'ref parent-group-one/old-project~321' - # project level milestone reference - 'ref %"project milestone"' | 'ref /parent-group-one/old-project%"project milestone"' - # group level milestone reference - 'ref %"group milestone"' | 'ref /parent-group-one%"group milestone"' - end - - with_them do - it_behaves_like 'rewrites references correctly' - end - end - end end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 2f38c83bb1fcdd55c3b4d1e8d598ba2d0f269cde..066218c86350aa31f912502c444f3abf5f593ef6 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -188,9 +188,23 @@ end context 'with a denylisted ip' do - it 'returns forbidden' do + let(:request) { ActionDispatch::Request.new(env) } + let(:attributes) do + { + message: 'Rack_Attack', + status: 403, + env: :blocklist, + remote_ip: env['REMOTE_ADDR'], + request_method: request.request_method, + path: request.filtered_path + } + end + + it 'returns forbidden', :aggregate_failures do err = Gitlab::Auth::IpBlocked.new expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(err) + expect(Gitlab::AuthLogger).to receive(:error).with(attributes) + response = go expect(response[0]).to eq(403) diff --git a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb index 5d9c9c86014c6b2de947214f9438b6a231fedac2..9cbe86130a73d6441532ca62f133c6e09f4c465f 100644 --- a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb +++ b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb @@ -10,7 +10,7 @@ let(:fake_app) { ->(_) { fake_response } } describe '#call' do - let(:fullpath) { ::Rack::Request.new(env).fullpath } + let(:fullpath) { ::ActionDispatch::Request.new(env).filtered_path } let(:decoded_fullpath) { CGI.unescape(fullpath) } let(:graphql_query) do diff --git a/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb b/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..73acdabdd71acf61a00366bcd3969d78648d2ae9 --- /dev/null +++ b/spec/migrations/regenerate_ci_job_token_signing_key_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe RegenerateCiJobTokenSigningKey, feature_category: :continuous_integration do + let(:application_settings) do + Class.new(ActiveRecord::Base) do + self.table_name = 'application_settings' + + attr_encrypted :ci_job_token_signing_key, { + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_32, + algorithm: 'aes-256-gcm', + encode: false, + encode_iv: false + } + end + end + + it 'generates a signing key' do + settings = application_settings.create! + settings.update!(ci_job_token_signing_key: nil) + + reversible_migration do |migration| + migration.before -> { + settings = application_settings.first + + expect(settings.ci_job_token_signing_key).to be_nil + expect(settings.encrypted_ci_job_token_signing_key).to be_nil + expect(settings.encrypted_ci_job_token_signing_key_iv).to be_nil + } + + migration.after -> { + settings = application_settings.first + + expect(settings.encrypted_ci_job_token_signing_key).to be_present + expect(settings.encrypted_ci_job_token_signing_key_iv).to be_present + expect { OpenSSL::PKey::RSA.new(settings.ci_job_token_signing_key) }.not_to raise_error + } + end + end + + context 'with existing key' do + let(:key) { OpenSSL::PKey::RSA.new(2048).to_pem } + + it 'does not touch existing keys' do + settings = application_settings.create! + settings.update!(ci_job_token_signing_key: key) + + migrate! + + settings = application_settings.first + + expect(settings.ci_job_token_signing_key).to eq(key) + expect(settings.encrypted_ci_job_token_signing_key).to be_present + expect(settings.encrypted_ci_job_token_signing_key_iv).to be_present + expect { OpenSSL::PKey::RSA.new(settings.ci_job_token_signing_key) }.not_to raise_error + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index cb74824eb1cff59d97a1ab0b075ff5690df4bc93..e63b07b2f0808e95dce8494323465c6744fbd64d 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1554,13 +1554,6 @@ it { expect(group.human_name).to eq(group.name) } end - describe '#to_human_reference' do - let_it_be(:new_group) { create(:group) } - - it { expect(group.to_human_reference).to be_nil } - it { expect(group.to_human_reference(new_group)).to eq(group.full_name) } - end - describe '#add_user' do let(:user) { create(:user) } diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb index e859d8a057f029abcc69f0c60b689a311bb3ed3f..a521c0051aac54ac907e5ced3557358e2891422f 100644 --- a/spec/models/label_note_spec.rb +++ b/spec/models/label_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe LabelNote, feature_category: :team_planning do +RSpec.describe LabelNote do include Gitlab::Routing.url_helpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb index f9fe12fbfbb29ec8c5d5f29826fc012063ceefea..877386e3dcbea996c4bb26407f65e11e5bbd1211 100644 --- a/spec/support/helpers/stub_requests.rb +++ b/spec/support/helpers/stub_requests.rb @@ -47,6 +47,13 @@ def stubbed_hostname(url, hostname: IP_ADDRESS_STUB) url.to_s end + def request_for_url(input_url) + env = Rack::MockRequest.env_for(input_url) + env['action_dispatch.parameter_filter'] = Gitlab::Application.config.filter_parameters + + ActionDispatch::Request.new(env) + end + private def parse_url(url) diff --git a/spec/support/shared_examples/lib/gitlab/gfm/reference_rewriter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gfm/reference_rewriter_shared_examples.rb deleted file mode 100644 index 16a276ac9c06695d7124c13bf6d33c66fbccedc0..0000000000000000000000000000000000000000 --- a/spec/support/shared_examples/lib/gitlab/gfm/reference_rewriter_shared_examples.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'rewrites references correctly' do - let(:noteable) { source_parent.work_items.first } - - let(:note_params) do - case source_parent - when ::Group - { namespace: source_parent, project: nil } - when ::Project - { project: source_parent } - when ::Namespaces::ProjectNamespace - { project: source_parent.project } - end - end - - let(:note) { create(:note, note: source_text, noteable: noteable, **note_params) } - - it 'checks source and target markdown text', :aggregate_failures do - new_text = described_class.new(note.note, note.note_html, source_parent, user).rewrite(target_parent) - source_text_html = generate_html_from_markdown(source_text, source_parent) - target_text_html = generate_html_from_markdown(destination_text, target_parent) - source_referable = find_referable(source_text, source_parent, user) - target_referable = find_referable(new_text, source_parent, user) - - expect(new_text).to eq(destination_text) - - # this checks that the rendered html actually did render, in contrary the result html would look smth like: - # <p dir="auto">ref #1</p> without an actual link to the referenced object - expect(source_text_html).to include("href=") - expect(target_text_html).to include("href=") - expect(referable_href(source_text_html)).to eq(referable_href(target_text_html)) - - # validate that expected referable can be extracted from source and destination texts - expect(source_referable.id).to eq(target_referable.id) - - # test rewriter with target as project namespace - if target_parent.is_a?(Project) - project_namespace = target_parent.project_namespace - new_text = described_class.new(note.note, note.note_html, source_parent, user).rewrite(project_namespace) - - expect(new_text).to eq(destination_text) - end - - # test rewriter with source as project namespace - if source_parent.is_a?(Project) - project_namespace = source_parent.project_namespace - new_text = described_class.new(note.note, note.note_html, project_namespace, user).rewrite(target_parent) - - expect(new_text).to eq(destination_text) - end - - # test rewriter with source and target as project namespace - if target_parent.is_a?(Project) && source_parent.is_a?(Project) - target_namespace = target_parent.project_namespace - source_namespace = source_parent.project_namespace - new_text = described_class.new(note.note, note.note_html, source_namespace, user).rewrite(target_namespace) - - expect(new_text).to eq(destination_text) - end - end -end - -def generate_html_from_markdown(text, parent) - Banzai.render(text, **parent_argument(parent), no_original_data: true, no_sourcepos: true) -end - -def parent_argument(parent) - case parent - when Project - { project: parent } - when Group - { group: parent, project: nil } - when Namespaces::ProjectNamespace - { project: parent.project } - end -end - -def find_referable(reference, parent, user) - extractor = Gitlab::ReferenceExtractor.new(parent_argument(parent)[:project], user) - extractor.analyze(reference, **parent_argument(parent)) - extractor.all.first -end - -def referable_href(text_html) - css = 'a' - xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css) - Nokogiri::HTML::DocumentFragment.parse(text_html).xpath(xpath).first.attribute('href').value -end diff --git a/spec/workers/issues/close_worker_spec.rb b/spec/workers/issues/close_worker_spec.rb index 488be7eb067a44b8caa990f556cbebd73a7a1854..52bec47d29a62b6fd6aef2b0f089907c5784d07a 100644 --- a/spec/workers/issues/close_worker_spec.rb +++ b/spec/workers/issues/close_worker_spec.rb @@ -4,48 +4,66 @@ RSpec.describe Issues::CloseWorker, feature_category: :team_planning do describe "#perform" do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:issue) { create(:issue, project: project, author: user) } - + let_it_be(:developer) { create(:user) } + let_it_be(:author) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository, developers: [developer]) } + let_it_be_with_reload(:issue) { create(:issue, project: project, author: author) } + + let(:project_id) { project.id } + let(:issue_id) { issue.id } + let(:current_user_id) { developer&.id } + let(:current_author_id) { author&.id } let(:commit) { project.commit } let(:opts) do - { "closed_by" => user&.id, "commit_hash" => commit.to_hash } + { "closed_by" => current_author_id, "user_id" => current_user_id, "commit_hash" => commit.to_hash } end - subject(:worker) { described_class.new } + subject(:perform_job) { described_class.new.perform(project_id, issue_id, issue.class.to_s, opts) } describe "#perform" do context "when the user can update the issues" do it "closes the issues" do - worker.perform(project.id, issue.id, issue.class.to_s, opts) + perform_job issue.reload - expect(issue.closed?).to eq(true) + expect(issue).to be_closed end it "closes external issues" do external_issue = ExternalIssue.new("foo", project) closer = instance_double(Issues::CloseService, execute: true) - expect(Issues::CloseService).to receive(:new).with(container: project, current_user: user).and_return(closer) + expect(Issues::CloseService).to receive(:new).with(container: project, current_user: author) + .and_return(closer) expect(closer).to receive(:execute).with(external_issue, commit: commit) - worker.perform(project.id, external_issue.id, external_issue.class.to_s, opts) + described_class.new.perform(project.id, external_issue.id, external_issue.class.to_s, opts) end end context "when the user can not update the issues" do - it "does not close the issues" do - other_user = create(:user) - opts = { "closed_by" => other_user.id, "commit_hash" => commit.to_hash } + let(:current_user_id) { create(:user).id } - worker.perform(project.id, issue.id, issue.class.to_s, opts) + it 'does not close the issues' do + perform_job issue.reload - expect(issue.closed?).to eq(false) + expect(issue).not_to be_closed + end + end + + # TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/work_items/509422 + context "when user is not provided to the worker (backwards compatibility)" do + let(:current_user_id) { nil } + + it 'does closes the issue' do + perform_job + + issue.reload + + expect(issue).to be_closed end end end @@ -54,31 +72,24 @@ it "does not call the close issue service" do expect(Issues::CloseService).not_to receive(:new) - expect { worker.perform(project.id, issue.id, issue.class.to_s, opts) } - .not_to raise_exception + expect { perform_job }.not_to raise_exception end end context "when the project does not exist" do - before do - allow(Project).to receive(:find_by_id).with(project.id).and_return(nil) - end + let(:project_id) { non_existing_record_id } it_behaves_like "when object does not exist" end - context "when the user does not exist" do - before do - allow(User).to receive(:find_by_id).with(user.id).and_return(nil) - end + context "when the author does not exist" do + let(:current_author_id) { non_existing_record_id } it_behaves_like "when object does not exist" end context "when the issue does not exist" do - before do - allow(Issue).to receive(:find_by_id).with(issue.id).and_return(nil) - end + let(:issue_id) { non_existing_record_id } it_behaves_like "when object does not exist" end diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index ae75fda72586728f4162bd7a066600ebe4ff2e79..92e7d0d0cee2585ed946d1e51a1ce57def0f4b63 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -4,6 +4,7 @@ RSpec.describe ProcessCommitWorker, feature_category: :source_code_management do let_it_be(:user) { create(:user) } + let_it_be(:author) { create(:user) } let(:auto_close_issues) { true } let(:project) { create(:project, :public, :repository, autoclose_referenced_issues: auto_close_issues) } @@ -84,15 +85,30 @@ context 'when commit is not a merge request merge commit' do context 'when commit has issue reference' do before do - allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}") + allow(commit).to receive_messages( + safe_message: "Closes #{issue.to_reference}", + author: author + ) end it 'closes issues that should be closed per the commit message' do expect { perform }.to change { Issues::CloseWorker.jobs.size }.by(1) end + it 'passes both author and user_id to CloseWorker' do + expect { perform }.to change { Issues::CloseWorker.jobs.size }.by(1) + + last_job = Issues::CloseWorker.jobs.last + expect(last_job['args']).to include( + project.id, + issue.id, + issue.class.to_s, + hash_including('closed_by' => commit.author.id, 'user_id' => user.id) + ) + end + it 'creates cross references' do - expect(commit).to receive(:create_cross_references!).with(user, [issue]) + expect(commit).to receive(:create_cross_references!).with(author, [issue]) perform end @@ -150,8 +166,12 @@ ) end - it 'closes issues that should be closed per the commit message' do - expect { perform }.to change { Issues::CloseWorker.jobs.size }.by(1) + it 'closes issues that should be closed per the commit message', :sidekiq_inline do + expect_next_instance_of(Issues::CloseService) do |close_service| + expect(close_service).to receive(:execute).with(issue, commit: commit) + end + + perform end end end diff --git a/yarn.lock b/yarn.lock index ac98e75441d9f1742385d22143d0a079c6060e21..290ed987d1a9e28de51b09f453195e82afad7226 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5061,9 +5061,9 @@ browserslist@^4.21.3, browserslist@^4.21.5, browserslist@^4.22.2, browserslist@^ update-browserslist-db "^1.1.0" browserslist@^4.24.0: - version "4.24.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" - integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== dependencies: caniuse-lite "^1.0.30001688" electron-to-chromium "^1.5.73" @@ -5250,9 +5250,9 @@ caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001646: integrity sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ== caniuse-lite@^1.0.30001688: - version "1.0.30001689" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz#67ca960dd5f443903e19949aeacc9d28f6e10910" - integrity sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g== + version "1.0.30001690" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" + integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== canvas-confetti@^1.4.0: version "1.4.0" @@ -6960,9 +6960,9 @@ electron-to-chromium@^1.5.4: integrity sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA== electron-to-chromium@^1.5.73: - version "1.5.74" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz#cb886b504a6467e4c00bea3317edb38393c53413" - integrity sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw== + version "1.5.79" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz#4424f23f319db7a653cf9ee76102e4ac283e6b3e" + integrity sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA== elkjs@^0.9.0: version "0.9.1" @@ -12014,7 +12014,7 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picocolors@^1.1.0: +picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -14870,12 +14870,12 @@ update-browserslist-db@^1.1.0: picocolors "^1.0.1" update-browserslist-db@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" + integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== dependencies: escalade "^3.2.0" - picocolors "^1.1.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1"