diff options
38 files changed, 597 insertions, 135 deletions
diff --git a/.gitlab/ci/gitlab-gems.gitlab-ci.yml b/.gitlab/ci/gitlab-gems.gitlab-ci.yml index a773e9c7f90..cc8a058d354 100644 --- a/.gitlab/ci/gitlab-gems.gitlab-ci.yml +++ b/.gitlab/ci/gitlab-gems.gitlab-ci.yml @@ -29,3 +29,6 @@ include: - local: .gitlab/ci/templates/gem.gitlab-ci.yml inputs: gem_name: "gitlab-http" + - local: .gitlab/ci/templates/gem.gitlab-ci.yml + inputs: + gem_name: "gitlab-backup-cli" @@ -40,6 +40,7 @@ gem 'gitlab-safe_request_store', path: 'gems/gitlab-safe_request_store' # ruboco # GitLab Monorepo Gems group :monorepo do gem 'gitlab-utils', path: 'gems/gitlab-utils' # rubocop:todo Gemfile/MissingFeatureCategory + gem 'gitlab-backup-cli', path: 'gems/gitlab-backup-cli', feature_category: :backup_restore end # Responders respond_to and respond_with @@ -265,7 +266,7 @@ gem 're2', '2.3.0' # rubocop:todo Gemfile/MissingFeatureCategory # Misc -gem 'semver_dialects', '~> 1.2.1' # rubocop:todo Gemfile/MissingFeatureCategory +gem 'semver_dialects', '~> 1.5', feature_category: :static_application_security_testing gem 'version_sorter', '~> 2.3' # rubocop:todo Gemfile/MissingFeatureCategory gem 'csv_builder', path: 'gems/csv_builder' # rubocop:todo Gemfile/MissingFeatureCategory diff --git a/Gemfile.checksum b/Gemfile.checksum index 5c4be5c85ce..553e0131dda 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -104,6 +104,7 @@ {"name":"date","version":"3.3.3","platform":"java","checksum":"584e0a582d1eb2207b4eaac089d8a43f2ca10bea02682f286099642f15c56cce"}, {"name":"date","version":"3.3.3","platform":"ruby","checksum":"819792019d5712b748fb15f6dfaaedef14b0328723ef23583ea35f186774530f"}, {"name":"dead_end","version":"3.1.1","platform":"ruby","checksum":"1011df7f7c0149be004e11cbbc37747760227c55305cd902fd3c06e1394b2f5b"}, +{"name":"deb_version","version":"1.0.2","platform":"ruby","checksum":"c21f911d7f2fd1d61219caae254fc078e6598e477fdff8a05a18bec6c72ee713"}, {"name":"debug_inspector","version":"1.1.0","platform":"ruby","checksum":"eaa5a2d0195e1d65fb4164e8e7e466cca2e7eb53bc5e608cf12b8bf02c3a8606"}, {"name":"deckar01-task_list","version":"2.3.3","platform":"ruby","checksum":"918abaf3f81e6c0d224c2b7bef593d7f84ee5847a0692726d24e3fb272c2c758"}, {"name":"declarative","version":"0.0.20","platform":"ruby","checksum":"8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9"}, @@ -580,7 +581,7 @@ {"name":"sd_notify","version":"0.1.1","platform":"ruby","checksum":"cbc7ac6caa7cedd26b30a72b5eeb6f36050dc0752df263452ea24fb5a4ad3131"}, {"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"}, {"name":"selenium-webdriver","version":"4.15.0","platform":"ruby","checksum":"36134e883c4df98f1b7e8519a3753c77427b74621147f8245aa6cac306d52297"}, -{"name":"semver_dialects","version":"1.2.1","platform":"ruby","checksum":"60a1f67659f79c51a667e8858ec9b089c1e4ce4f6d2a0f0b4ac101916946eb23"}, +{"name":"semver_dialects","version":"1.5.0","platform":"ruby","checksum":"0080f1abafc9c1af82d34e890d7c317b9eacb56b9e03040107ef5d1a51ca49ae"}, {"name":"sentry-rails","version":"5.8.0","platform":"ruby","checksum":"c11b2d909de2c2bfda793c45f64180fd784d54c46886338b683ee3f8efa7731b"}, {"name":"sentry-raven","version":"3.1.2","platform":"ruby","checksum":"103d3b122958810d34898ce2e705bcf549ddb9d855a70ce9a3970ee2484f364a"}, {"name":"sentry-ruby","version":"5.8.0","platform":"ruby","checksum":"caeb121433be379fb94e991a45265a287b13a9a9083e7264f539752369d37110"}, @@ -646,7 +647,7 @@ {"name":"test-prof","version":"1.2.3","platform":"ruby","checksum":"c52a40194cb30f399ed3eb6beb4c45b5daad8b8eb418e8ef69089e4dc7e01fd6"}, {"name":"test_file_finder","version":"0.2.1","platform":"ruby","checksum":"a5e9b369d80c76aefbb609acf5e11d89a048f35e565de3cc261c20112f0fcdb3"}, {"name":"text","version":"1.3.1","platform":"ruby","checksum":"2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4"}, -{"name":"thor","version":"1.2.2","platform":"ruby","checksum":"2f93c652828cba9fcf4f65f5dc8c306f1a7317e05aad5835a13740122c17f24c"}, +{"name":"thor","version":"1.3.0","platform":"ruby","checksum":"1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3"}, {"name":"thread_safe","version":"0.3.6","platform":"java","checksum":"bb28394cd0924c068981adee71f36a81c85c92e7d74d3f62372bd51489a0e0c2"}, {"name":"thread_safe","version":"0.3.6","platform":"ruby","checksum":"9ed7072821b51c57e8d6b7011a8e282e25aeea3a4065eab326e43f66f063b05a"}, {"name":"thrift","version":"0.16.0","platform":"ruby","checksum":"d023286ea89e30444c9f1c28dd76107f87d8aaf85fe1742da1d8cd3b5417dcce"}, diff --git a/Gemfile.lock b/Gemfile.lock index 6fbd0fb6c01..e2ebb913813 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,6 +24,12 @@ PATH typhoeus (~> 1.0, >= 1.0.1) PATH + remote: gems/gitlab-backup-cli + specs: + gitlab-backup-cli (0.0.1) + thor (~> 1.3) + +PATH remote: gems/gitlab-http specs: gitlab-http (0.1.0) @@ -422,6 +428,7 @@ GEM database_cleaner-core (2.0.1) date (3.3.3) dead_end (3.1.1) + deb_version (1.0.2) debug_inspector (1.1.0) deckar01-task_list (2.3.3) html-pipeline @@ -1493,9 +1500,10 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - semver_dialects (1.2.1) + semver_dialects (1.5.0) + deb_version (~> 1.0.1) pastel (~> 0.8.0) - thor (~> 1.2.0) + thor (~> 1.3) tty-command (~> 0.10.1) sentry-rails (5.8.0) railties (>= 5.0) @@ -1617,7 +1625,7 @@ GEM test_file_finder (0.2.1) faraday (>= 1.0, < 3.0, != 2.0.0) text (1.3.1) - thor (1.2.2) + thor (1.3.0) thread_safe (0.3.6) thrift (0.16.0) tilt (2.0.11) @@ -1849,6 +1857,7 @@ DEPENDENCIES gettext_i18n_rails (~> 1.11.0) gettext_i18n_rails_js (~> 2.0.0) gitaly (~> 16.5.0.pre.rc1) + gitlab-backup-cli! gitlab-chronic (~> 0.10.5) gitlab-dangerfiles (~> 4.6.0) gitlab-experiment (~> 0.8.0) @@ -2028,7 +2037,7 @@ DEPENDENCIES sd_notify (~> 0.1.0) seed-fu (~> 2.3.7) selenium-webdriver (~> 4.15) - semver_dialects (~> 1.2.1) + semver_dialects (~> 1.5) sentry-rails (~> 5.8.0) sentry-raven (~> 3.1) sentry-ruby (~> 5.8.0) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6f15bc553bf..cee56dca538 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -29,7 +29,8 @@ class ProjectsController < Projects::ApplicationController before_action :authorize_read_code!, only: [:refs] # Authorize - before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] + before_action :authorize_admin_project_or_custom_permissions!, only: :edit + before_action :authorize_admin_project!, only: [:update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] before_action :authorize_archive_project!, only: [:archive, :unarchive] before_action :event_filter, only: [:show, :activity] @@ -598,6 +599,11 @@ class ProjectsController < Projects::ApplicationController def render_edit render 'edit' end + + # Overridden in EE + def authorize_admin_project_or_custom_permissions! + authorize_admin_project! + end end ProjectsController.prepend_mod_with('ProjectsController') diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index d07e4f9e298..5e5f9ab7385 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -48,6 +48,12 @@ class ProjectMember < Member end end + def permissible_access_level_roles_for_project_access_token(current_user, project) + permissible_access_level_roles(current_user, project).filter do |_, value| + value <= project.project_authorizations.find_by(user: current_user).access_level + end + end + def access_level_roles Gitlab::Access.options end diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb index 1c496aa5e77..824b1a8c377 100644 --- a/app/services/resource_access_tokens/create_service.rb +++ b/app/services/resource_access_tokens/create_service.rb @@ -17,6 +17,8 @@ module ResourceAccessTokens access_level = params[:access_level] || Gitlab::Access::MAINTAINER return error("Could not provision owner access to project access token") if do_not_allow_owner_access_level_for_project_bot?(access_level) + return error("Access level of the token can't be greater the access level of the user who created the token") unless validate_access_level(access_level) + return error(s_('AccessTokens|Access token limit reached')) if reached_access_token_limit? user = create_user @@ -125,6 +127,14 @@ module ResourceAccessTokens ServiceResponse.success(payload: { access_token: access_token }) end + def validate_access_level(access_level) + return true unless resource.is_a?(Project) + return true if current_user.bot? + return true if current_user.can?(:manage_owners, resource) + + current_user.authorized_project?(resource, access_level.to_i) + end + def do_not_allow_owner_access_level_for_project_bot?(access_level) resource.is_a?(Project) && access_level.to_i == Gitlab::Access::OWNER && diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 4e84a6ef7e7..fd0dc1178f7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -5,116 +5,119 @@ - reduce_visibility_form_id = 'reduce-visibility-form' - @force_desktop_expanded_sidebar = true -= render Pajamas::AlertComponent.new(title: _('GitLab Pages has moved'), +- if can?(current_user, :admin_project, @project) + = render Pajamas::AlertComponent.new(title: _('GitLab Pages has moved'), alert_options: { class: 'gl-my-5', data: { feature_id: Users::CalloutsHelper::PAGES_MOVED_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' } }) do |c| - - c.with_body do - = _('To go to GitLab Pages, on the left sidebar, select %{pages_link}.').html_safe % {pages_link: link_to('Deploy > Pages', project_pages_path(@project)).html_safe} - -%section.settings.general-settings.no-animate.expanded#js-general-settings - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = _('Collapse') - %p.gl-text-secondary= _('Update your project name, topics, description, and avatar.') - .settings-content= render 'projects/settings/general' - -%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded), data: { testid: 'visibility-features-permissions-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default emoji reactions.') - - .settings-content - = form_for @project, html: { multipart: true, class: "sharing-permissions-form", id: reduce_visibility_form_id }, authenticity_token: true do |f| - %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' } - %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe - .js-project-permissions-form{ data: visibility_confirm_modal_data(@project, reduce_visibility_form_id) } -- if show_merge_request_settings_callout?(@project) - %section.settings.expanded - = render Pajamas::AlertComponent.new(variant: :info, + - c.with_body do + = _('To go to GitLab Pages, on the left sidebar, select %{pages_link}.').html_safe % {pages_link: link_to('Deploy > Pages', project_pages_path(@project)).html_safe} + + %section.settings.general-settings.no-animate.expanded#js-general-settings + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = _('Collapse') + %p.gl-text-secondary= _('Update your project name, topics, description, and avatar.') + .settings-content= render 'projects/settings/general' + + %section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded), data: { testid: 'visibility-features-permissions-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default emoji reactions.') + + .settings-content + = form_for @project, html: { multipart: true, class: "sharing-permissions-form", id: reduce_visibility_form_id }, authenticity_token: true do |f| + %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' } + %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe + .js-project-permissions-form{ data: visibility_confirm_modal_data(@project, reduce_visibility_form_id) } + - if show_merge_request_settings_callout?(@project) + %section.settings.expanded + = render Pajamas::AlertComponent.new(variant: :info, title: _('Merge requests and approvals settings have moved.'), alert_options: { class: 'js-merge-request-settings-callout gl-my-5', data: { feature_id: Users::CalloutsHelper::MERGE_REQUEST_SETTINGS_MOVED_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' } }) do |c| - - c.with_body do - = _('On the left sidebar, select %{merge_requests_link} to view them.').html_safe % { merge_requests_link: link_to('Settings > Merge requests', project_settings_merge_requests_path(@project)).html_safe } - -%section.settings.no-animate{ class: ('expanded' if expanded), data: { testid: 'badges-settings-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = s_('ProjectSettings|Badges') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary - = s_('ProjectSettings|Customize this project\'s badges.') - = link_to s_('ProjectSettings|What are badges?'), help_page_path('user/project/badges') - .settings-content - = render 'shared/badges/badge_settings' - -= render_if_exists 'compliance_management/compliance_framework/project_settings', expanded: expanded - -= render_if_exists 'projects/settings/default_issue_template' - -= render 'projects/service_desk_settings' - -%section.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded), data: { testid: 'advanced-settings-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary= s_('ProjectSettings|Housekeeping, export, archive, change path, transfer, and delete.') - - .settings-content - = render_if_exists 'projects/settings/restore', project: @project - - = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-mt-0' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| - - c.with_header do - .gl-new-card-title-wrapper - %h4.gl-new-card-title= _('Housekeeping') - %p.gl-new-card-description - = _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.') - = link_to _('Learn more.'), help_page_path('administration/housekeeping'), target: '_blank', rel: 'noopener noreferrer' - - - c.with_body do - .gl-display-flex.gl-flex-wrap.gl-gap-3 - = render Pajamas::ButtonComponent.new(method: :post, href: housekeeping_project_path(@project)) do - = _('Run housekeeping') - #js-project-prune-unreachable-objects-button{ data: { prune_objects_path: housekeeping_project_path(@project, prune: true), prune_objects_doc_path: help_page_path('administration/housekeeping', anchor: 'prune-unreachable-objects') } } - - = render 'export', project: @project - - = render_if_exists 'projects/settings/archive' - - = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card rename-repository' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| - - c.with_header do - .gl-new-card-title-wrapper - %h4.gl-new-card-title.warning-title= _('Change path') - %p.gl-new-card-description - - link = link_to('', help_page_path('user/project/settings/index', anchor: 'rename-a-repository'), target: '_blank', rel: 'noopener noreferrer') - = safe_format(_("A project’s repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}"), tag_pair(link, :link_start, :link_end)) - - - c.with_body do - = render 'projects/errors' - = gitlab_ui_form_for @project do |f| - .form-group - %p - %span.gl-font-weight-bold= _("Be careful. Renaming a project's repository can have unintended side effects.") - = _('You will need to update your local repositories to point to the new location.') - - if @project.deployment_platform.present? - %p= _('Your deployment services will be broken, you will need to manually fix the services after renaming.') - = f.label :path, _('Path'), class: 'label-bold' + - c.with_body do + = _('On the left sidebar, select %{merge_requests_link} to view them.').html_safe % { merge_requests_link: link_to('Settings > Merge requests', project_settings_merge_requests_path(@project)).html_safe } + + %section.settings.no-animate{ class: ('expanded' if expanded), data: { testid: 'badges-settings-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only + = s_('ProjectSettings|Badges') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary + = s_('ProjectSettings|Customize this project\'s badges.') + = link_to s_('ProjectSettings|What are badges?'), help_page_path('user/project/badges') + .settings-content + = render 'shared/badges/badge_settings' + + = render_if_exists 'compliance_management/compliance_framework/project_settings', expanded: expanded + + = render_if_exists 'projects/settings/default_issue_template' + + = render 'projects/service_desk_settings' + + %section.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded), data: { testid: 'advanced-settings-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary= s_('ProjectSettings|Housekeeping, export, archive, change path, transfer, and delete.') + + .settings-content + = render_if_exists 'projects/settings/restore', project: @project + + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-mt-0' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| + - c.with_header do + .gl-new-card-title-wrapper + %h4.gl-new-card-title= _('Housekeeping') + %p.gl-new-card-description + = _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.') + = link_to _('Learn more.'), help_page_path('administration/housekeeping'), target: '_blank', rel: 'noopener noreferrer' + + - c.with_body do + .gl-display-flex.gl-flex-wrap.gl-gap-3 + = render Pajamas::ButtonComponent.new(method: :post, href: housekeeping_project_path(@project)) do + = _('Run housekeeping') + #js-project-prune-unreachable-objects-button{ data: { prune_objects_path: housekeeping_project_path(@project, prune: true), prune_objects_doc_path: help_page_path('administration/housekeeping', anchor: 'prune-unreachable-objects') } } + + = render 'export', project: @project + + = render_if_exists 'projects/settings/archive' + + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card rename-repository' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| + - c.with_header do + .gl-new-card-title-wrapper + %h4.gl-new-card-title.warning-title= _('Change path') + %p.gl-new-card-description + - link = link_to('', help_page_path('user/project/settings/index', anchor: 'rename-a-repository'), target: '_blank', rel: 'noopener noreferrer') + = safe_format(_("A project’s repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}"), tag_pair(link, :link_start, :link_end)) + + - c.with_body do + = render 'projects/errors' + = gitlab_ui_form_for @project do |f| .form-group - .input-group - .input-group-prepend - .input-group-text - #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/ - = f.text_field :path, class: 'form-control gl-form-input-xl', data: { testid: 'project-path-field' } - = f.submit _('Change path'), class: "btn-danger", data: { testid: 'change-path-button' }, pajamas_button: true - - = render 'transfer', project: @project - - = render 'remove_fork', project: @project - - = render 'remove', project: @project + %p + %span.gl-font-weight-bold= _("Be careful. Renaming a project's repository can have unintended side effects.") + = _('You will need to update your local repositories to point to the new location.') + - if @project.deployment_platform.present? + %p= _('Your deployment services will be broken, you will need to manually fix the services after renaming.') + = f.label :path, _('Path'), class: 'label-bold' + .form-group + .input-group + .input-group-prepend + .input-group-text + #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/ + = f.text_field :path, class: 'form-control gl-form-input-xl', data: { testid: 'project-path-field' } + = f.submit _('Change path'), class: "btn-danger", data: { testid: 'change-path-button' }, pajamas_button: true + + = render 'transfer', project: @project + + = render 'remove_fork', project: @project + + = render 'remove', project: @project +- elsif can?(current_user, :archive_project, @project) + = render_if_exists 'projects/settings/archive' .save-project-loader.hide .center diff --git a/app/views/projects/settings/access_tokens/_form.html.haml b/app/views/projects/settings/access_tokens/_form.html.haml index 919462a0f62..ee993962c7a 100644 --- a/app/views/projects/settings/access_tokens/_form.html.haml +++ b/app/views/projects/settings/access_tokens/_form.html.haml @@ -7,7 +7,7 @@ resource: @project, token: @resource_access_token, scopes: @scopes, - access_levels: ProjectMember.permissible_access_level_roles(current_user, @project), + access_levels: ProjectMember.permissible_access_level_roles_for_project_access_token(current_user, @project), default_access_level: Gitlab::Access::GUEST, prefix: :resource_access_token, description_prefix: :project_access_token, diff --git a/bin/gitlab-backup-cli b/bin/gitlab-backup-cli new file mode 100755 index 00000000000..4037684be07 --- /dev/null +++ b/bin/gitlab-backup-cli @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +$:.unshift File.expand_path("../../lib", __FILE__) + +# We require APP_PATH when the rails environment is required only, +# this allows for faster CLI execution when rails is not needed +APP_PATH = File.expand_path('../config/application', __dir__) + +require_relative '../config/boot' + +require 'gitlab/backup/cli' + +Gitlab::Backup::Cli::Runner.start(ARGV) diff --git a/db/migrate/20231024123444_add_archive_project_to_member_roles.rb b/db/migrate/20231024123444_add_archive_project_to_member_roles.rb new file mode 100644 index 00000000000..27ff86450e8 --- /dev/null +++ b/db/migrate/20231024123444_add_archive_project_to_member_roles.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddArchiveProjectToMemberRoles < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + add_column :member_roles, :archive_project, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20231024123444 b/db/schema_migrations/20231024123444 new file mode 100644 index 00000000000..578f1cef1bd --- /dev/null +++ b/db/schema_migrations/20231024123444 @@ -0,0 +1 @@ +db84d40c9afd9121aa24617167fa82b86cabc98bf274e61057eef02e1fafd7c3
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 333cf6c65a4..1055e902056 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18525,6 +18525,7 @@ CREATE TABLE member_roles ( admin_merge_request boolean DEFAULT false NOT NULL, admin_group_member boolean DEFAULT false NOT NULL, manage_project_access_tokens boolean DEFAULT false NOT NULL, + archive_project boolean DEFAULT false NOT NULL, CONSTRAINT check_4364846f58 CHECK ((char_length(description) <= 255)), CONSTRAINT check_9907916995 CHECK ((char_length(name) <= 255)) ); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2edb3b4e351..c7921115e1a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5134,6 +5134,7 @@ Input type: `MemberRoleCreateInput` | <a id="mutationmemberrolecreateadmingroupmember"></a>`adminGroupMember` | [`Boolean`](#boolean) | Permission to admin group members. | | <a id="mutationmemberrolecreateadminmergerequest"></a>`adminMergeRequest` | [`Boolean`](#boolean) | Permission to admin merge requests. | | <a id="mutationmemberrolecreateadminvulnerability"></a>`adminVulnerability` | [`Boolean`](#boolean) | Permission to admin vulnerability. | +| <a id="mutationmemberrolecreatearchiveproject"></a>`archiveProject` | [`Boolean`](#boolean) | Permission to archive projects. | | <a id="mutationmemberrolecreatebaseaccesslevel"></a>`baseAccessLevel` | [`MemberAccessLevel!`](#memberaccesslevel) | Base access level for the custom role. | | <a id="mutationmemberrolecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationmemberrolecreatedescription"></a>`description` | [`String`](#string) | Description of the member role. | @@ -20322,6 +20323,7 @@ Represents a member role. | <a id="memberroleadmingroupmember"></a>`adminGroupMember` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin group members. | | <a id="memberroleadminmergerequest"></a>`adminMergeRequest` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin merge requests. | | <a id="memberroleadminvulnerability"></a>`adminVulnerability` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin vulnerability. | +| <a id="memberrolearchiveproject"></a>`archiveProject` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Permission to archive projects. | | <a id="memberrolebaseaccesslevel"></a>`baseAccessLevel` **{warning-solid}** | [`AccessLevel!`](#accesslevel) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Base access level for the custom role. | | <a id="memberroledescription"></a>`description` | [`String`](#string) | Description of the member role. | | <a id="memberroleenabledpermissions"></a>`enabledPermissions` **{warning-solid}** | [`[MemberRolePermission!]`](#memberrolepermission) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Array of all permissions enabled for the custom role. | @@ -29406,6 +29408,7 @@ Member role permission. | <a id="memberrolepermissionadmin_group_member"></a>`ADMIN_GROUP_MEMBER` | Allows admin access to group members. | | <a id="memberrolepermissionadmin_merge_request"></a>`ADMIN_MERGE_REQUEST` | Allows admin access to the merge requests. | | <a id="memberrolepermissionadmin_vulnerability"></a>`ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. | +| <a id="memberrolepermissionarchive_project"></a>`ARCHIVE_PROJECT` | Allows to archive projects. | | <a id="memberrolepermissionmanage_project_access_tokens"></a>`MANAGE_PROJECT_ACCESS_TOKENS` | Allows manage access to the project access tokens. | | <a id="memberrolepermissionread_code"></a>`READ_CODE` | Allows read-only access to the source code. | | <a id="memberrolepermissionread_dependency"></a>`READ_DEPENDENCY` | Allows read-only access to the dependencies. | diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index cc50a8e225a..63de583de25 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -13,12 +13,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Read dependency added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) in GitLab 16.3. > - [Name and description fields added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126423) in GitLab 16.3. > - [Admin merge request introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) in GitLab 16.4 [with a flag](../administration/feature_flags.md) named `admin_merge_request`. Disabled by default. +> - [Feature flag `admin_merge_request` removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132578) in GitLab 16.5. > - [Admin group members introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `admin_group_member`. Disabled by default. The feature flag has been removed in GitLab 16.6. > - [Manage project access tokens introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132342) in GitLab 16.5 in [with a flag](../administration/feature_flags.md) named `manage_project_access_tokens`. Disabled by default. +> - [Archive project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project`. Disabled by default. FLAG: -On self-managed GitLab, by default these two features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_merge_request` and `admin_member_custom_role`. -On GitLab.com, this feature is not available. +On self-managed GitLab, by default these features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_group_member`, `manage_project_access_tokens` and `archive_project`. +On GitLab.com, these features are not available. ## List all member roles of a group @@ -48,6 +50,7 @@ If successful, returns [`200`](rest/index.md#status-codes) and the following res | `[].read_vulnerability` | boolean | Permission to read project vulnerabilities. | | `[].admin_group_member` | boolean | Permission to admin members of a group. | | `[].manage_project_access_tokens` | boolean | Permission to manage project access tokens. | +| `[].archive_project` | boolean | Permission to archive projects. | Example request: @@ -70,7 +73,8 @@ Example response: "read_code": true, "read_dependency": false, "read_vulnerability": false, - "manage_project_access_tokens": false + "manage_project_access_tokens": false, + "archive_project": false }, { "id": 3, @@ -83,7 +87,8 @@ Example response: "read_code": false, "read_dependency": true, "read_vulnerability": true, - "manage_project_access_tokens": false + "manage_project_access_tokens": false, + "archive_project": false } ] ``` diff --git a/doc/user/custom_roles.md b/doc/user/custom_roles.md index 1b827d82792..bbb48724078 100644 --- a/doc/user/custom_roles.md +++ b/doc/user/custom_roles.md @@ -15,6 +15,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Ability to create and remove a custom role with the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393235) in GitLab 16.4. > - Ability to manage group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) in GitLab 16.5. > - Ability to manage project access tokens [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `manage_project_access_tokens`. +> - Ability to archive projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project`. Disabled by default. Custom roles allow group Owners or instance administrators to create roles specific to the needs of their organization. @@ -97,6 +98,7 @@ These requirements are documented in the `Required permission` column in the fol | `admin_merge_request` | GitLab 16.4 and later | Not applicable | View and approve [merge requests](project/merge_requests/index.md), and view the associated merge request code. <br> Does not allow users to view or change merge request approval rules. | | `manage_project_access_tokens` | GitLab 16.5 and later | Not applicable | Create, delete, and list [project access tokens](project/settings/project_access_tokens.md). | | `admin_group_member` | GitLab 16.5 and later | Not applicable | Add or remove [group members](group/manage.md). | +| `archive_project` | GitLab 16.6 and later | Not applicable | Archive and unarchive [projects](project/settings/index.md#archive-a-project). | ## Billing and seat usage diff --git a/gems/gitlab-backup-cli/.gitignore b/gems/gitlab-backup-cli/.gitignore new file mode 100644 index 00000000000..b04a8c840df --- /dev/null +++ b/gems/gitlab-backup-cli/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/gems/gitlab-backup-cli/.gitlab-ci.yml b/gems/gitlab-backup-cli/.gitlab-ci.yml new file mode 100644 index 00000000000..884ca22a853 --- /dev/null +++ b/gems/gitlab-backup-cli/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - local: gems/gem.gitlab-ci.yml + inputs: + gem_name: "gitlab-backup-cli" diff --git a/gems/gitlab-backup-cli/.rspec b/gems/gitlab-backup-cli/.rspec new file mode 100644 index 00000000000..34c5164d9b5 --- /dev/null +++ b/gems/gitlab-backup-cli/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/gems/gitlab-backup-cli/.rubocop.yml b/gems/gitlab-backup-cli/.rubocop.yml new file mode 100644 index 00000000000..cc0632f32dd --- /dev/null +++ b/gems/gitlab-backup-cli/.rubocop.yml @@ -0,0 +1,19 @@ +--- +inherit_gem: + gitlab-styles: + - rubocop-bundler.yml + - rubocop-fips.yml + - rubocop-gemspec.yml + - rubocop-layout.yml + - rubocop-lint.yml + - rubocop-metrics.yml + - rubocop-naming.yml + - rubocop-performance.yml + - rubocop-rspec.yml + - rubocop-security.yml + - rubocop-style.yml + +AllCops: + TargetRubyVersion: 3.0 + SuggestExtensions: false + NewCops: disable diff --git a/gems/gitlab-backup-cli/Gemfile b/gems/gitlab-backup-cli/Gemfile new file mode 100644 index 00000000000..e5c133f93f2 --- /dev/null +++ b/gems/gitlab-backup-cli/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in gitlab-backup-cli.gemspec +gemspec diff --git a/gems/gitlab-backup-cli/Gemfile.lock b/gems/gitlab-backup-cli/Gemfile.lock new file mode 100644 index 00000000000..73c2b942865 --- /dev/null +++ b/gems/gitlab-backup-cli/Gemfile.lock @@ -0,0 +1,114 @@ +PATH + remote: . + specs: + gitlab-backup-cli (0.0.1) + thor (~> 1.3) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.1.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + ast (2.4.2) + base64 (0.2.0) + bigdecimal (3.1.4) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + diff-lcs (1.5.0) + drb (2.2.0) + ruby2_keywords + gitlab-styles (11.0.0) + rubocop (~> 1.57.1) + rubocop-graphql (~> 0.18) + rubocop-performance (~> 1.15) + rubocop-rails (~> 2.17) + rubocop-rspec (~> 2.22) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + json (2.6.3) + language_server-protocol (3.17.0.3) + minitest (5.20.0) + mutex_m (0.2.0) + parallel (1.23.0) + parser (3.2.2.4) + ast (~> 2.4.1) + racc + racc (1.7.3) + rack (3.0.8) + rainbow (3.1.1) + rake (13.1.0) + regexp_parser (2.8.2) + rexml (3.2.6) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + rubocop (1.57.2) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.4) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.30.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.19.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.24.0) + rubocop (~> 1.33) + rubocop-graphql (0.19.0) + rubocop (>= 0.87, < 2) + rubocop-performance (1.19.1) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.20.0) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.25.0) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-progressbar (1.13.0) + ruby2_keywords (0.0.5) + thor (1.3.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) + +PLATFORMS + arm64-darwin-21 + arm64-darwin-22 + ruby + x86_64-linux + +DEPENDENCIES + gitlab-backup-cli! + gitlab-styles (~> 11.0) + rake (~> 13.0) + rspec (~> 3.0) + rubocop-rails (<= 2.20) + +BUNDLED WITH + 2.4.21 diff --git a/gems/gitlab-backup-cli/LICENSE.txt b/gems/gitlab-backup-cli/LICENSE.txt new file mode 100644 index 00000000000..3def1245ee6 --- /dev/null +++ b/gems/gitlab-backup-cli/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023-present GitLab B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/gems/gitlab-backup-cli/README.md b/gems/gitlab-backup-cli/README.md new file mode 100644 index 00000000000..0d6b12b65aa --- /dev/null +++ b/gems/gitlab-backup-cli/README.md @@ -0,0 +1,7 @@ +# Gitlab::Backup::Cli + +This gem will contain the Backup CLI logic. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/gems/gitlab-backup-cli/Rakefile b/gems/gitlab-backup-cli/Rakefile new file mode 100644 index 00000000000..cca71754493 --- /dev/null +++ b/gems/gitlab-backup-cli/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +task default: %i[spec rubocop] diff --git a/gems/gitlab-backup-cli/bin/console b/gems/gitlab-backup-cli/bin/console new file mode 100755 index 00000000000..3aa2eb40f21 --- /dev/null +++ b/gems/gitlab-backup-cli/bin/console @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "gitlab/backup/cli" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +require "irb" +IRB.start(__FILE__) diff --git a/gems/gitlab-backup-cli/bin/setup b/gems/gitlab-backup-cli/bin/setup new file mode 100755 index 00000000000..dce67d860af --- /dev/null +++ b/gems/gitlab-backup-cli/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/gems/gitlab-backup-cli/gitlab-backup-cli.gemspec b/gems/gitlab-backup-cli/gitlab-backup-cli.gemspec new file mode 100644 index 00000000000..40ed07d68e4 --- /dev/null +++ b/gems/gitlab-backup-cli/gitlab-backup-cli.gemspec @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative "lib/gitlab/backup/cli/version" + +Gem::Specification.new do |spec| + spec.name = "gitlab-backup-cli" + spec.version = Gitlab::Backup::Cli::VERSION + spec.authors = ["Gabriel Mazetto"] + spec.email = ["brodock@gmail.com"] + + spec.summary = "GitLab Backup CLI" + spec.description = "GitLab Backup CLI" + spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-backup-cli" + spec.license = "MIT" + spec.required_ruby_version = ">= 3.0" + + spec.metadata["rubygems_mfa_required"] = "true" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + + spec.files = Dir['lib/**/*.rb'] + spec.test_files = Dir['spec/**/*'] + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || + f.start_with?(*%w[bin/ test/ spec/ features/ .git .gitlab-ci .rspec .rubocop.yml Gemfile]) + end + end + + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency "thor", "~> 1.3" + + spec.add_development_dependency "gitlab-styles", "~> 11.0" + spec.add_development_dependency "rake", "~> 13.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "rubocop-rails", "<= 2.20" # https://github.com/rubocop/rubocop-rails/issues/1173 +end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb new file mode 100644 index 00000000000..5f0654c9d03 --- /dev/null +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Gitlab + module Backup + # GitLab Backup CLI + module Cli + autoload :VERSION, 'gitlab/backup/cli/version' + autoload :Runner, 'gitlab/backup/cli/runner' + + class Error < StandardError; end + # Your code goes here... + end + end +end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/runner.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/runner.rb new file mode 100644 index 00000000000..ceb77a62499 --- /dev/null +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/runner.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'thor' + +module Gitlab + module Backup + module Cli + # GitLab Backup CLI + # + # This supersedes the previous backup rake files and will be + # the default interface to handle backups + class Runner < Thor + def self.exit_on_failure? + true + end + + map %w[--version -v] => :version + desc 'version', 'Display the version information' + + def version + puts "GitLab Backup CLI (#{VERSION})" + end + + private + + def rails_environment! + require APP_PATH + + Rails.application.load_tasks + end + end + end + end +end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/version.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/version.rb new file mode 100644 index 00000000000..32bf3de31da --- /dev/null +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/version.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Gitlab + module Backup + module Cli + VERSION = "0.0.1" + end + end +end diff --git a/gems/gitlab-backup-cli/sig/gitlab/backup/cli.rbs b/gems/gitlab-backup-cli/sig/gitlab/backup/cli.rbs new file mode 100644 index 00000000000..25540c06400 --- /dev/null +++ b/gems/gitlab-backup-cli/sig/gitlab/backup/cli.rbs @@ -0,0 +1,7 @@ +module Gitlab + module Backup + module Cli + VERSION: String + end + end +end diff --git a/gems/gitlab-backup-cli/sig/gitlab/backup/cli/runner.rbs b/gems/gitlab-backup-cli/sig/gitlab/backup/cli/runner.rbs new file mode 100644 index 00000000000..56b031b82bc --- /dev/null +++ b/gems/gitlab-backup-cli/sig/gitlab/backup/cli/runner.rbs @@ -0,0 +1,15 @@ +module Gitlab + module Backup + module Cli + class Runner + def self.exit_on_failure?: -> bool + + def version: -> void + + private + + def rails_environment!: -> void + end + end + end +end diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli_spec.rb new file mode 100644 index 00000000000..db3aaeccb05 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe Gitlab::Backup::Cli do + it "has a version number" do + expect(Gitlab::Backup::Cli::VERSION).not_to be nil + end +end diff --git a/gems/gitlab-backup-cli/spec/spec_helper.rb b/gems/gitlab-backup-cli/spec/spec_helper.rb new file mode 100644 index 00000000000..6c4ae2df96c --- /dev/null +++ b/gems/gitlab-backup-cli/spec/spec_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "gitlab/backup/cli" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb index 8fed1c46425..077eebf58b9 100644 --- a/lib/sidebars/projects/menus/settings_menu.rb +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -57,10 +57,6 @@ module Sidebars monitor_menu_item, usage_quotas_menu_item ] - elsif context.current_user && can?(context.current_user, :manage_resource_access_tokens, context.project) - [ - access_tokens_menu_item - ] else [] end diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb index 81ca9670ac6..605cec8be5e 100644 --- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb @@ -59,18 +59,6 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu, feature_category: :navig let(:item_id) { :access_tokens } it_behaves_like 'access rights checks' - - describe 'when the user is not an admin but has manage_resource_access_tokens' do - before do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :admin_project, project).and_return(false) - allow(Ability).to receive(:allowed?).with(user, :manage_resource_access_tokens, project).and_return(true) - end - - it 'includes access token menu item' do - expect(subject.title).to eql('Access Tokens') - end - end end describe 'Repository' do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index a2b5bde8890..a9725a796bf 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -51,6 +51,50 @@ RSpec.describe ProjectMember, feature_category: :groups_and_projects do end end + describe '.permissible_access_level_roles_for_project_access_token' do + let_it_be(:owner) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + + before do + project.add_owner(owner) + project.add_maintainer(maintainer) + project.add_developer(developer) + end + + subject(:access_levels) { described_class.permissible_access_level_roles_for_project_access_token(user, project) } + + context 'when member can manage owners' do + let(:user) { owner } + + it 'returns Gitlab::Access.options_with_owner' do + expect(access_levels).to eq(Gitlab::Access.options_with_owner) + end + end + + context 'when member cannot manage owners' do + let(:user) { maintainer } + + it 'returns Gitlab::Access.options' do + expect(access_levels).to eq(Gitlab::Access.options) + end + end + + context 'when the user is a developer' do + let(:user) { developer } + + it 'returns Gitlab::Access.options' do + expect(access_levels).to eq({ + "Guest" => 10, + "Reporter" => 20, + "Developer" => 30 + }) + end + end + end + describe '#real_source_type' do subject { create(:project_member).real_source_type } |