diff --git a/Gemfile b/Gemfile index 3454cb62576..b63396ac9d4 100644 --- a/Gemfile +++ b/Gemfile @@ -20,11 +20,7 @@ gem 'bootsnap', '~> 1.16.0', require: false # Pin openssl to match the version bundled with our supported Rubies. # See https://stdgems.org/openssl/#gem-version. gem 'openssl', '2.2.2' -# This gem was originally bundled with Ruby 2.7, but is unbundled as of Ruby 3. -# Since the latest version caused problems with GitLab, we pin this to an older -# version for now. -# See https://gitlab.com/gitlab-org/gitlab/-/issues/376417 -gem 'ipaddr', '1.2.2' +gem 'ipaddr', '~> 1.2.5' # Responders respond_to and respond_with gem 'responders', '~> 3.0' diff --git a/Gemfile.checksum b/Gemfile.checksum index 52f88f4fdb2..945e08df7eb 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -302,7 +302,7 @@ {"name":"ice_nine","version":"0.11.2","platform":"ruby","checksum":"5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db"}, {"name":"imagen","version":"0.1.8","platform":"ruby","checksum":"fde7b727d4fe79c6bb5ac46c1f7184bf87a6d54df54d712ad2be039d2f93a162"}, {"name":"invisible_captcha","version":"2.0.0","platform":"ruby","checksum":"a381edcb1d1b8744e9dc398ecad142c3e2ab077604645f85eeb02f9ea535c042"}, -{"name":"ipaddr","version":"1.2.2","platform":"ruby","checksum":"27916ee6367d549850d3675bc020f1f1ddafbbe1cfc58635f17dfa56c42f9f79"}, +{"name":"ipaddr","version":"1.2.5","platform":"ruby","checksum":"4e679c71d6d8ed99f925487082f70f9a958de155591caa0e7f6cef9aa160f17a"}, {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, {"name":"jaro_winkler","version":"1.5.4","platform":"java","checksum":"0454333a50b44a09745878bfe57859893631ff7dfe48c029827894944514fe7c"}, diff --git a/Gemfile.lock b/Gemfile.lock index 2f45b663daa..a3574c04bc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -819,7 +819,7 @@ GEM parser (>= 2.5, != 2.5.1.1) invisible_captcha (2.0.0) rails (>= 5.0) - ipaddr (1.2.2) + ipaddr (1.2.5) ipaddress (0.8.3) jaeger-client (1.1.0) opentracing (~> 0.3) @@ -1767,7 +1767,7 @@ DEPENDENCIES httparty (~> 0.20.0) icalendar invisible_captcha (~> 2.0.0) - ipaddr (= 1.2.2) + ipaddr (~> 1.2.5) ipaddress (~> 0.8.3) ipynbdiff! jira-ruby (~> 2.1.4) diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index 1a998f89c68..c7693fb993d 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -6,7 +6,6 @@ @import './pages/hierarchy'; @import './pages/issues'; @import './pages/labels'; -@import './pages/login'; @import './pages/merge_requests'; @import './pages/note_form'; @import './pages/notes'; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/page_bundles/login.scss similarity index 99% rename from app/assets/stylesheets/pages/login.scss rename to app/assets/stylesheets/page_bundles/login.scss index 360ea20733d..495b7d58788 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/page_bundles/login.scss @@ -1,4 +1,5 @@ -@import 'framework/variables'; +@import 'mixins_and_variables_and_functions'; + /* Login Page */ .login-page { .container { diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index 57f61508178..7b6e3a5c521 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -756,222 +756,6 @@ input:-ms-input-placeholder { svg { fill: currentColor; } -.login-page .container { - max-width: 960px; -} -.login-page .navbar-gitlab .container { - max-width: none; -} -.login-page .flash-container { - margin-bottom: 16px; - position: relative; - top: 8px; -} -.login-page .brand-holder { - font-size: 18px; - line-height: 1.5; -} -.login-page .brand-holder p { - font-size: 16px; - color: #888; -} -.login-page .brand-holder h3 { - font-size: 22px; -} -.login-page .brand-holder img { - max-width: 100%; - margin-bottom: 30px; -} -.login-page .brand-holder a { - font-weight: 600; -} -.login-page p { - font-size: 13px; -} -.login-page .signin-text p { - margin-bottom: 0; - line-height: 1.5; -} -.login-page .borderless .login-box, -.login-page .borderless .omniauth-container { - box-shadow: none; -} -.login-page .borderless .g-recaptcha > div { - margin-left: auto; - margin-right: auto; -} -.login-page .login-box, -.login-page .omniauth-container { - box-shadow: 0 0 0 1px #dcdcde; - border-radius: 0.25rem; -} -.login-page .login-box .login-heading h3, -.login-page .omniauth-container .login-heading h3 { - font-weight: 400; - line-height: 1.5; - margin: 0 0 10px; -} -.login-page .login-box .login-footer, -.login-page .omniauth-container .login-footer { - margin-top: 10px; -} -.login-page .login-box .login-footer p:last-child, -.login-page .omniauth-container .login-footer p:last-child { - margin-bottom: 0; -} -.login-page .login-box a.forgot, -.login-page .omniauth-container a.forgot { - float: right; - padding-top: 6px; -} -.login-page .login-box .nav .active a, -.login-page .omniauth-container .nav .active a { - background: transparent; -} -.login-page .login-box .login-body, -.login-page .omniauth-container .login-body { - font-size: 13px; -} -.login-page .login-box .login-body input + p, -.login-page .login-box .login-body input ~ p.field-validation, -.login-page .omniauth-container .login-body input + p, -.login-page .omniauth-container .login-body input ~ p.field-validation { - margin-top: 5px; -} -.login-page .login-box .login-body .username .validation-success, -.login-page .omniauth-container .login-body .username .validation-success { - color: #217645; -} -.login-page .login-box .login-body .username .validation-error, -.login-page .omniauth-container .login-body .username .validation-error { - color: #dd2b0e; -} -.login-page .omniauth-container { - border-radius: 0.25rem; - font-size: 13px; -} -.login-page .omniauth-container p { - margin: 0; -} -.login-page .omniauth-container form { - padding: 0; - border: 0; - background: none; -} -.login-page .new-session-tabs { - display: flex; - box-shadow: 0 0 0 1px #dcdcde; - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} -.login-page .new-session-tabs.nav-links-unboxed { - border-color: transparent; - box-shadow: none; -} -.login-page .new-session-tabs.nav-links-unboxed .nav-item { - border-left: 0; - border-right: 0; - border-bottom: 1px solid #dcdcde; - background-color: transparent; -} -.login-page .new-session-tabs.custom-provider-tabs { - flex-wrap: wrap; -} -.login-page .new-session-tabs.custom-provider-tabs li { - min-width: 85px; - flex-basis: auto; -} -.login-page .new-session-tabs.custom-provider-tabs li:nth-child(n + 5) { - border-top: 1px solid #dcdcde; -} -.login-page .new-session-tabs.custom-provider-tabs a { - font-size: 16px; -} -.login-page .new-session-tabs li { - flex: 1; - text-align: center; - border-left: 1px solid #dcdcde; -} -.login-page .new-session-tabs li:first-of-type { - border-left: 0; - border-top-left-radius: 4px; -} -.login-page .new-session-tabs li:last-of-type { - border-top-right-radius: 4px; -} -.login-page .new-session-tabs li:not(.active) { - background-color: #fbfafd; -} -.login-page .new-session-tabs li a { - width: 100%; - font-size: 18px; -} -.login-page .new-session-tabs li.active > a { - cursor: default; -} -.login-page .form-control:active, -.login-page .form-control:focus { - background-color: #fff; -} -.login-page .submit-container { - margin-top: 16px; -} -.login-page input[type="submit"] { - margin-bottom: 0; - display: block; - width: 100%; -} -.login-page .devise-errors h2 { - margin-top: 0; - font-size: 14px; - color: #ae1800; -} -@media (max-width: 575.98px) { - .login-page .col-md-5.float-right { - float: none !important; - margin-bottom: 45px; - } -} -.devise-layout-html { - margin: 0; - padding: 0; - height: 100%; -} -.devise-layout-html body { - height: calc(100% - 51px); - margin: 0; - padding: 0; -} -.devise-layout-html body.navless { - height: calc(100% - 11px); -} -.devise-layout-html body .page-wrap { - min-height: 100%; - position: relative; -} -.devise-layout-html body .footer-container, -.devise-layout-html body hr.footer-fixed { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 40px; - background: #fff; -} -.devise-layout-html body .login-page-broadcast { - margin-top: 40px; -} -.devise-layout-html body .navless-container { - padding: 0 15px 65px; -} -.devise-layout-html body .flash-container { - padding-bottom: 65px; -} -@media (max-width: 575.98px) { - .devise-layout-html body .flash-container { - padding-bottom: 0; - } -} .gl-display-flex { display: flex; diff --git a/app/graphql/types/achievements/user_achievement_type.rb b/app/graphql/types/achievements/user_achievement_type.rb index d2146807445..bf161d2f1e5 100644 --- a/app/graphql/types/achievements/user_achievement_type.rb +++ b/app/graphql/types/achievements/user_achievement_type.rb @@ -5,7 +5,7 @@ module Types class UserAchievementType < BaseObject graphql_name 'UserAchievement' - authorize :read_achievement + authorize :read_user_achievement field :id, ::Types::GlobalIDType[::Achievements::UserAchievement], diff --git a/app/policies/achievements/user_achievement_policy.rb b/app/policies/achievements/user_achievement_policy.rb index b500d0a25c8..05650a05490 100644 --- a/app/policies/achievements/user_achievement_policy.rb +++ b/app/policies/achievements/user_achievement_policy.rb @@ -3,5 +3,10 @@ module Achievements class UserAchievementPolicy < ::BasePolicy delegate { @subject.achievement.namespace } + delegate { @subject.user } + + rule { can?(:read_user_profile) | can?(:admin_achievement) }.enable :read_user_achievement + + rule { ~can?(:read_achievement) }.prevent :read_user_achievement end end diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml index 3950170e486..a24ef5d8ea4 100644 --- a/app/views/admin/sessions/new.html.haml +++ b/app/views/admin/sessions/new.html.haml @@ -1,4 +1,5 @@ - page_title _('Enter Admin Mode') +- add_page_specific_style 'page_bundles/login' .row.justify-content-center .col-md-5.new-session-forms-container diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml index d05cc51af41..65455f2ccf0 100644 --- a/app/views/admin/sessions/two_factor.html.haml +++ b/app/views/admin/sessions/two_factor.html.haml @@ -1,4 +1,5 @@ - page_title _('Enter 2FA for Admin Mode') +- add_page_specific_style 'page_bundles/login' .row.justify-content-center .col-md-5.new-session-forms-container diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 3532c6638ce..36a9a284e91 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,3 +1,4 @@ +- add_page_specific_style 'page_bundles/login' !!! 5 %html.devise-layout-html{ class: system_message_class } = render "layouts/head", { startup_filename: 'signin' } diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml index cadba3f91e9..89aba85984f 100644 --- a/app/views/layouts/devise_empty.html.haml +++ b/app/views/layouts/devise_empty.html.haml @@ -1,3 +1,4 @@ +- add_page_specific_style 'page_bundles/login' !!! 5 %html.devise-layout-html{ lang: "en", class: system_message_class } = render "layouts/head" diff --git a/app/views/layouts/signup_onboarding.html.haml b/app/views/layouts/signup_onboarding.html.haml index 4d0bb36d4b5..8cbea686d51 100644 --- a/app/views/layouts/signup_onboarding.html.haml +++ b/app/views/layouts/signup_onboarding.html.haml @@ -1,6 +1,7 @@ !!! 5 %html.devise-layout-html.navless{ class: system_message_class } - add_page_specific_style 'page_bundles/signup' + - add_page_specific_style 'page_bundles/login' = render "layouts/head" %body.signup-page{ class: "#{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'signup_page' } } = render "layouts/header/logo_with_title" diff --git a/app/views/layouts/simple_registration.html.haml b/app/views/layouts/simple_registration.html.haml index dc7ec25c96e..a68941b031f 100644 --- a/app/views/layouts/simple_registration.html.haml +++ b/app/views/layouts/simple_registration.html.haml @@ -1,6 +1,7 @@ !!! 5 %html{ lang: "en" } = render "layouts/head" + - add_page_specific_style 'page_bundles/login' %body.login-page.application.navless{ class: user_application_theme, data: { page: body_data_page } } = render "layouts/header/logo_with_title" = render "layouts/broadcast" diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml index 2796f0c0a7e..45c23aa7190 100644 --- a/app/views/registrations/welcome/show.html.haml +++ b/app/views/registrations/welcome/show.html.haml @@ -1,6 +1,7 @@ - @html_class = "subscriptions-layout-html" - page_title _('Your profile') - add_page_specific_style 'page_bundles/signup' +- add_page_specific_style 'page_bundles/login' - gitlab_experience_text = _('To personalize your GitLab experience, we\'d like to know a bit more about you') - content_for :page_specific_javascripts do = render "layouts/google_tag_manager_head" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 6ba37072230..4607e2e974c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -51,7 +51,8 @@ .avatar-holder = link_to avatar_icon_for_user(@user, 400, current_user: current_user), target: '_blank', rel: 'noopener noreferrer' do = render Pajamas::AvatarComponent.new(@user, alt: "", size: 96, avatar_options: { itemprop: "image" }) - #js-user-achievements{ data: { root_url: root_url, user_id: @user.id } } + - if Ability.allowed?(current_user, :read_user_profile, @user) + #js-user-achievements{ data: { root_url: root_url, user_id: @user.id } } .gl-display-inline-block.gl-vertical-align-top.gl-text-left.gl-max-w-80 - if @user.blocked? || !@user.confirmed? .user-info diff --git a/config/application.rb b/config/application.rb index 781b6e042b1..f041d435c7f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -294,6 +294,7 @@ module Gitlab config.assets.precompile << "page_bundles/jira_connect.css" config.assets.precompile << "page_bundles/jira_connect_users.css" config.assets.precompile << "page_bundles/learn_gitlab.css" + config.assets.precompile << "page_bundles/login.css" config.assets.precompile << "page_bundles/marketing_popover.css" config.assets.precompile << "page_bundles/members.css" config.assets.precompile << "page_bundles/merge_conflicts.css" diff --git a/db/post_migrate/20230315084704_finalize_issues_iid_scoping_to_namespace.rb b/db/post_migrate/20230315084704_finalize_issues_iid_scoping_to_namespace.rb new file mode 100644 index 00000000000..f9d4013d5f3 --- /dev/null +++ b/db/post_migrate/20230315084704_finalize_issues_iid_scoping_to_namespace.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FinalizeIssuesIidScopingToNamespace < Gitlab::Database::Migration[2.1] + MIGRATION = 'IssuesInternalIdScopeUpdater' + + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + ensure_batched_background_migration_is_finished( + job_class_name: MIGRATION, + table_name: :internal_ids, + column_name: :id, + job_arguments: [], + finalize: true) + end + + def down; end +end diff --git a/db/schema_migrations/20230315084704 b/db/schema_migrations/20230315084704 new file mode 100644 index 00000000000..a41414de7b1 --- /dev/null +++ b/db/schema_migrations/20230315084704 @@ -0,0 +1 @@ +b0091fc76ead45dab7a0cd4d2b0a65858703cb18a98cca7715b88bceac8c2ed0 \ No newline at end of file diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 83e187fe1b5..3e8894d2463 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -327,6 +327,7 @@ ETag ETags Etsy Excon +exfiltrate exfiltration ExifTool expirable diff --git a/doc/index.md b/doc/index.md index 68105bcfb30..852ecccdd37 100644 --- a/doc/index.md +++ b/doc/index.md @@ -74,7 +74,7 @@ If you are coming to GitLab from another platform, the following information is | Topic | Description | |:----------------------------------------------------|:------------| | [Importing to GitLab](user/project/import/index.md) | Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz, and SVN into GitLab. | -| [Migrating from SVN](user/project/import/svn.md) | Convert a SVN repository to Git and GitLab. | +| [Migrating from SVN](user/project/import/index.md#import-from-subversion) | Convert a SVN repository to Git and GitLab. | ## Build an integration with GitLab diff --git a/doc/update/index.md b/doc/update/index.md index 0c1297b4524..40150c91163 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -617,6 +617,12 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap migration might take multiple hours or days to complete on larger GitLab instances. Make sure the migration has completed successfully before upgrading to 15.7.0 or later. - Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information. +- A redesigned sign-in page is enabled by default in GitLab 15.4 and later, with improvements shipping in later releases. For more information, see [epic 8557](https://gitlab.com/groups/gitlab-org/-/epics/8557). + It can be disabled with a feature flag. Start [a Rails console](../administration/operations/rails_console.md) and run: + + ```ruby + Feature.disable(:restyle_login_page) + ``` ### 15.3.4 diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 3a09e1078ca..d71e31561d6 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -425,7 +425,8 @@ For more information, see ## Users with minimal access **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942) in GitLab 13.4. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942) in GitLab 13.4. +> - Support for inviting users with minimal access role [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106438) in GitLab 15.9. Owners can add members with a "minimal access" role to a root group. Such users do not: diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index 200c2609914..41ac5354656 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -39,6 +39,20 @@ Keep in mind the limitations of [migrating using file exports](../settings/impor When migrating from self-managed to GitLab.com, user associations (such as comment author) are changed to the user who is importing the projects. +## Security + +Only import projects from sources you trust. If you import a project from an untrusted source, +an attacker could steal your sensitive data. For example, an imported project +with a malicious `.gitlab-ci.yml` file could allow an attacker to exfiltrate group CI/CD variables. + +GitLab self-managed administrators can reduce their attack surface by disabling import sources they don't need: + +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > General**. +1. Expand **Visibility and access controls**. +1. Scroll to **Import sources**. +1. Clear checkboxes for importers that are not required. + ## Available project importers You can import projects from: @@ -65,7 +79,7 @@ You can then [connect your external repository to get CI/CD benefits](../../../c GitLab can not automatically migrate Subversion repositories to Git. Converting Subversion repositories to Git can be difficult, but several tools exist including: -- [`git svn`](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git), for very small and simple repositories. +- [`git svn`](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git), for very small and basic repositories. - [`reposurgeon`](http://www.catb.org/~esr/reposurgeon/repository-editing.html), for larger and more complex repositories. ## Migrate using the API @@ -82,10 +96,9 @@ over a series of Docker pulls and pushes. Re-run any CI pipelines to retrieve an ## Migrate between two self-managed GitLab instances -To migrate from an existing self-managed GitLab instance to a new self-managed GitLab instance, it's -best to [back up](../../../raketasks/backup_restore.md) -the existing instance and restore it on the new instance. For example, this is useful when migrating -a self-managed instance from an old server to a new server. +To migrate from an existing self-managed GitLab instance to a new self-managed GitLab instance, +you should [back up](../../../raketasks/backup_restore.md) +the existing instance and restore it on the new instance. For example, you could use this method to migrate a self-managed instance from an old server to a new server. The backups produced don't depend on the operating system running GitLab. You can therefore use the restore method to switch between different operating system distributions or versions, as long @@ -159,17 +172,3 @@ For more information, see: including settings that need checking afterwards and other limitations. For support, customers must enter into a paid engagement with GitLab Professional Services. - -## Security - -Only import projects from sources you trust. If you import a project from an untrusted source, it -may be possible for an attacker to steal your sensitive data. For example, an imported project -with a malicious `.gitlab-ci.yml` file could allow an attacker to exfiltrate group CI/CD variables. - -GitLab self-managed administrators can reduce their attack surface by disabling import sources they don't need: - -1. On the top bar, select **Main menu > Admin**. -1. On the left sidebar, select **Settings > General**. -1. Expand **Visibility and access controls**. -1. Scroll to **Import sources**. -1. Clear checkboxes for importers that are not required. diff --git a/lib/gitlab/url_blockers/ip_allowlist_entry.rb b/lib/gitlab/url_blockers/ip_allowlist_entry.rb index b293afe166c..ff4eb86ec41 100644 --- a/lib/gitlab/url_blockers/ip_allowlist_entry.rb +++ b/lib/gitlab/url_blockers/ip_allowlist_entry.rb @@ -12,11 +12,32 @@ module Gitlab end def match?(requested_ip, requested_port = nil) - return false unless ip.include?(requested_ip) + requested_ip = IPAddr.new(requested_ip) if requested_ip.is_a?(String) + + return false unless ip_include?(requested_ip) return true if port.nil? port == requested_port end + + private + + # Prior to ipaddr v1.2.3, if the allow list were the IPv4 to IPv6 + # mapped address ::ffff:169.254.168.100 and the requested IP were + # 169.254.168.100 or ::ffff:169.254.168.100, the IP would be + # considered in the allow list. However, with + # https://github.com/ruby/ipaddr/pull/31, IPAddr#include? will + # only match if the IP versions are the same. This method + # preserves backwards compatibility if the versions differ by + # checking inclusion by coercing an IPv4 address to its IPv6 + # mapped address. + def ip_include?(requested_ip) + return true if ip.include?(requested_ip) + return ip.include?(requested_ip.ipv4_mapped) if requested_ip.ipv4? && ip.ipv6? + return ip.ipv4_mapped.include?(requested_ip) if requested_ip.ipv6? && ip.ipv4? + + false + end end end end diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index 49f69a46395..544591ff2ab 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -1,4 +1,4 @@ -import { GlEmptyState, GlTabs, GlTab, GlSprintf } from '@gitlab/ui'; +import { GlEmptyState, GlModal, GlTabs, GlTab, GlSprintf } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -7,7 +7,7 @@ import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; - +import { stubComponent } from 'helpers/stub_component'; import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue'; import PackagesApp from '~/packages_and_registries/package_registry/pages/details.vue'; import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue'; @@ -66,6 +66,7 @@ describe('PackagesApp', () => { }; const { __typename, ...packageWithoutTypename } = packageData(); + const showMock = jest.fn(); function createComponent({ resolver = jest.fn().mockResolvedValue(packageDetailsQuery()), @@ -86,17 +87,11 @@ describe('PackagesApp', () => { stubs: { PackageTitle, DeletePackages, - GlModal: { - template: ` -
- -

-
- `, + GlModal: stubComponent(GlModal, { methods: { - show: jest.fn(), + show: showMock, }, - }, + }), GlSprintf, GlTabs, GlTab, @@ -251,7 +246,7 @@ describe('PackagesApp', () => { await findDeleteButton().trigger('click'); - expect(findDeleteModal().find('p').text()).toBe( + expect(findDeleteModal().text()).toBe( 'You are about to delete version 1.0.0 of @gitlab-org/package-15. Are you sure?', ); }); @@ -331,13 +326,15 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show'); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).not.toHaveBeenCalled(); - expect(showDeleteFileSpy).toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); + + await waitForPromises(); + + expect(findDeleteFileModal().text()).toBe( + 'You are about to delete foo-1.0.1.tgz. This is a destructive action that may render your package unusable. Are you sure?', + ); }); it('when its the only file opens delete package confirmation modal', async () => { @@ -360,17 +357,13 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show'); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', [fileToDelete]); - expect(showDeletePackageSpy).toHaveBeenCalled(); - expect(showDeleteFileSpy).not.toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); await waitForPromises(); - expect(findDeleteModal().find('p').text()).toBe( + expect(findDeleteModal().text()).toBe( 'Deleting the last package asset will remove version 1.0.0 of @gitlab-org/package-15. Are you sure?', ); }); @@ -542,15 +535,13 @@ describe('PackagesApp', () => { await waitForPromises(); - const showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show'); - findPackageFiles().vm.$emit('delete-files', packageFiles()); - expect(showDeletePackageSpy).toHaveBeenCalled(); + expect(showMock).toHaveBeenCalledTimes(1); await waitForPromises(); - expect(findDeleteModal().find('p').text()).toBe( + expect(findDeleteModal().text()).toBe( 'Deleting all package assets will remove version 1.0.0 of @gitlab-org/package-15. Are you sure?', ); }); diff --git a/spec/graphql/types/achievements/user_achievement_type_spec.rb b/spec/graphql/types/achievements/user_achievement_type_spec.rb index 6b1512ff841..b7fe4d815f7 100644 --- a/spec/graphql/types/achievements/user_achievement_type_spec.rb +++ b/spec/graphql/types/achievements/user_achievement_type_spec.rb @@ -20,5 +20,5 @@ RSpec.describe GitlabSchema.types['UserAchievement'], feature_category: :user_pr it { expect(described_class.graphql_name).to eq('UserAchievement') } it { expect(described_class).to have_graphql_fields(fields) } - it { expect(described_class).to require_graphql_authorizations(:read_achievement) } + it { expect(described_class).to require_graphql_authorizations(:read_user_achievement) } end diff --git a/spec/lib/gitlab/url_blockers/ip_allowlist_entry_spec.rb b/spec/lib/gitlab/url_blockers/ip_allowlist_entry_spec.rb index 8dcb402dfb2..c56e5ce4e7a 100644 --- a/spec/lib/gitlab/url_blockers/ip_allowlist_entry_spec.rb +++ b/spec/lib/gitlab/url_blockers/ip_allowlist_entry_spec.rb @@ -2,7 +2,7 @@ require 'fast_spec_helper' -RSpec.describe Gitlab::UrlBlockers::IpAllowlistEntry do +RSpec.describe Gitlab::UrlBlockers::IpAllowlistEntry, feature_category: :integrations do let(:ipv4) { IPAddr.new('192.168.1.1') } describe '#initialize' do @@ -65,11 +65,31 @@ RSpec.describe Gitlab::UrlBlockers::IpAllowlistEntry do end it 'matches IPv6 within IPv6 range' do - ipv6_range = IPAddr.new('fd84:6d02:f6d8:c89e::/124') + ipv6_range = IPAddr.new('::ffff:192.168.1.0/8') ip_allowlist_entry = described_class.new(ipv6_range) expect(ip_allowlist_entry).to be_match(ipv6_range.to_range.last.to_s, 8080) expect(ip_allowlist_entry).not_to be_match('fd84:6d02:f6d8:f::f', 8080) end + + it 'matches IPv4 to IPv6 mapped addresses in allow list' do + ipv6_range = IPAddr.new('::ffff:192.168.1.1') + ip_allowlist_entry = described_class.new(ipv6_range) + + expect(ip_allowlist_entry).to be_match(ipv4, 8080) + expect(ip_allowlist_entry).to be_match(ipv6_range.to_range.last.to_s, 8080) + expect(ip_allowlist_entry).not_to be_match('::ffff:192.168.1.0', 8080) + expect(ip_allowlist_entry).not_to be_match('::ffff:169.254.168.101', 8080) + end + + it 'matches IPv4 to IPv6 mapped addresses in requested IP' do + ipv4_range = IPAddr.new('192.168.1.1/24') + ip_allowlist_entry = described_class.new(ipv4_range) + + expect(ip_allowlist_entry).to be_match(ipv4, 8080) + expect(ip_allowlist_entry).to be_match('::ffff:192.168.1.0', 8080) + expect(ip_allowlist_entry).to be_match('::ffff:192.168.1.1', 8080) + expect(ip_allowlist_entry).not_to be_match('::ffff:169.254.170.100/8', 8080) + end end end diff --git a/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb new file mode 100644 index 00000000000..1834e8c6e0e --- /dev/null +++ b/spec/migrations/finalize_issues_iid_scoping_to_namespace_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe FinalizeIssuesIidScopingToNamespace, :migration, feature_category: :team_planning do + let(:batched_migrations) { table(:batched_background_migrations) } + + let!(:migration) { described_class::MIGRATION } + + describe '#up' do + shared_examples 'finalizes the migration' do + it 'finalizes the migration' do + allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner| + expect(runner).to receive(:finalize).with('"IssuesInternalIdScopeUpdater"', :internal_ids, :id, [nil, "up"]) + end + end + end + + context 'when migration is missing' do + it 'warns migration not found' do + expect(Gitlab::AppLogger) + .to receive(:warn).with(/Could not find batched background migration for the given configuration:/) + + migrate! + end + end + + context 'with migration present' do + let!(:migration) do + batched_migrations.create!( + job_class_name: 'IssuesInternalIdScopeUpdater', + table_name: :internal_ids, + column_name: :id, + job_arguments: [nil, 'up'], + interval: 2.minutes, + min_value: 1, + max_value: 2, + batch_size: 1000, + sub_batch_size: 200, + gitlab_schema: :gitlab_main, + status: 3 # finished + ) + end + + context 'when migration finished successfully' do + it 'does not raise exception' do + expect { migrate! }.not_to raise_error + end + end + + context 'with different migration statuses' do + using RSpec::Parameterized::TableSyntax + + where(:status, :description) do + 0 | 'paused' + 1 | 'active' + 4 | 'failed' + 5 | 'finalizing' + end + + with_them do + before do + migration.update!(status: status) + end + + it_behaves_like 'finalizes the migration' + end + end + end + end +end diff --git a/spec/policies/achievements/user_achievement_policy_spec.rb b/spec/policies/achievements/user_achievement_policy_spec.rb new file mode 100644 index 00000000000..47f6188e178 --- /dev/null +++ b/spec/policies/achievements/user_achievement_policy_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::UserAchievementPolicy, feature_category: :user_profile do + let(:maintainer) { create(:user) } + + let(:group) { create(:group, :public) } + + let(:current_user) { create(:user) } + let(:achievement) { create(:achievement, namespace: group) } + let(:achievement_owner) { create(:user) } + let(:user_achievement) { create(:user_achievement, achievement: achievement, user: achievement_owner) } + + before do + group.add_maintainer(maintainer) + end + + subject { described_class.new(current_user, user_achievement) } + + it 'is readable to everyone when user has public profile' do + is_expected.to be_allowed(:read_user_achievement) + end + + context 'when user has private profile' do + before do + achievement_owner.update!(private_profile: true) + end + + context 'for achievement owner' do + let(:current_user) { achievement_owner } + + it 'is visible' do + is_expected.to be_allowed(:read_user_achievement) + end + end + + context 'for group maintainer' do + let(:current_user) { maintainer } + + it 'is visible' do + is_expected.to be_allowed(:read_user_achievement) + end + end + + context 'for others' do + it 'is hidden' do + is_expected.not_to be_allowed(:read_user_achievement) + end + end + end + + context 'when group is private' do + let(:group) { create(:group, :private) } + + context 'for achievement owner' do + let(:current_user) { achievement_owner } + + it 'is hidden' do + is_expected.not_to be_allowed(:read_user_achievement) + end + end + + context 'for group maintainer' do + let(:current_user) { maintainer } + + it 'is visible' do + is_expected.to be_allowed(:read_user_achievement) + end + end + + context 'for others' do + it 'is hidden' do + is_expected.not_to be_allowed(:read_user_achievement) + end + end + end +end