diff options
66 files changed, 543 insertions, 188 deletions
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md index e0de8fab4a7..7cb8871f5bc 100644 --- a/.gitlab/issue_templates/Feature Flag Roll Out.md +++ b/.gitlab/issue_templates/Feature Flag Roll Out.md @@ -37,7 +37,7 @@ If applicable, any groups/projects that are happy to have this feature turned on - [ ] Coordinate a time to enable the flag with `#production` and `#g_delivery` on slack. - [ ] Announce on the issue an estimated time this will be enabled on GitLab.com - [ ] Enable on GitLab.com by running chatops command in `#production` -- [ ] Cross post chatops slack command to `#support_gitlab-com` and in your team channel +- [ ] Cross post chatops slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel - [ ] Announce on the issue that the flag has been enabled - [ ] Remove feature flag and add changelog entry - [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue index 4d767f1a578..b4a59fd0178 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -1,5 +1,5 @@ <script> -import { mapActions, mapState } from 'vuex'; +import { mapActions, mapGetters, mapState } from 'vuex'; import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import { s__ } from '~/locale'; @@ -15,8 +15,15 @@ export default { GlLink, }, i18n: { - unavailableFeatureText: s__( - 'ContainerRegistry|Currently, the Container Registry tag expiration feature is not available for projects created before GitLab version 12.8. For updates and more information, visit Issue %{linkStart}#196124%{linkEnd}', + unavailableFeatureTitle: s__( + `ContainerRegistry|Container Registry tag expiration and retention policy is disabled`, + ), + unavailableFeatureIntroText: s__( + `ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled.`, + ), + unavailableUserFeatureText: s__(`ContainerRegistry|Please contact your administrator.`), + unavailableAdminFeatureText: s__( + `ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature.`, ), fetchSettingsErrorText: FETCH_SETTINGS_ERROR_MESSAGE, }, @@ -26,10 +33,19 @@ export default { }; }, computed: { - ...mapState(['isDisabled']), + ...mapState(['isAdmin', 'adminSettingsPath']), + ...mapGetters({ isDisabled: 'getIsDisabled' }), showSettingForm() { return !this.isDisabled && !this.fetchSettingsError; }, + showDisabledFormMessage() { + return this.isDisabled && !this.fetchSettingsError; + }, + unavailableFeatureMessage() { + return this.isAdmin + ? this.$options.i18n.unavailableAdminFeatureText + : this.$options.i18n.unavailableUserFeatureText; + }, }, mounted() { this.fetchSettings().catch(() => { @@ -59,16 +75,21 @@ export default { </ul> <settings-form v-if="showSettingForm" /> <template v-else> - <gl-alert v-if="isDisabled" :dismissible="false"> - <p> - <gl-sprintf :message="$options.i18n.unavailableFeatureText"> - <template #link="{content}"> - <gl-link href="https://gitlab.com/gitlab-org/gitlab/issues/196124" target="_blank"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </p> + <gl-alert + v-if="showDisabledFormMessage" + :dismissible="false" + :title="$options.i18n.unavailableFeatureTitle" + variant="tip" + > + {{ $options.i18n.unavailableFeatureIntroText }} + + <gl-sprintf :message="unavailableFeatureMessage"> + <template #link="{ content }"> + <gl-link :href="adminSettingsPath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> </gl-alert> <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false"> <gl-sprintf :message="$options.i18n.fetchSettingsErrorText" /> diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js index d0379d05164..be1f62334fa 100644 --- a/app/assets/javascripts/registry/settings/store/actions.js +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -5,11 +5,7 @@ export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_ST export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); export const receiveSettingsSuccess = ({ commit }, data) => { - if (data) { - commit(types.SET_SETTINGS, data); - } else { - commit(types.SET_IS_DISABLED, true); - } + commit(types.SET_SETTINGS, data); }; export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS); diff --git a/app/assets/javascripts/registry/settings/store/getters.js b/app/assets/javascripts/registry/settings/store/getters.js index 639becebeec..ef4b4f0ba02 100644 --- a/app/assets/javascripts/registry/settings/store/getters.js +++ b/app/assets/javascripts/registry/settings/store/getters.js @@ -19,3 +19,7 @@ export const getSettings = (state, getters) => ({ }); export const getIsEdited = state => !isEqual(state.original, state.settings); + +export const getIsDisabled = state => { + return !(state.original || state.enableHistoricEntries); +}; diff --git a/app/assets/javascripts/registry/settings/store/mutation_types.js b/app/assets/javascripts/registry/settings/store/mutation_types.js index 2d071567c1f..db499ffa761 100644 --- a/app/assets/javascripts/registry/settings/store/mutation_types.js +++ b/app/assets/javascripts/registry/settings/store/mutation_types.js @@ -3,4 +3,3 @@ export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; export const TOGGLE_LOADING = 'TOGGLE_LOADING'; export const SET_SETTINGS = 'SET_SETTINGS'; export const RESET_SETTINGS = 'RESET_SETTINGS'; -export const SET_IS_DISABLED = 'SET_IS_DISABLED'; diff --git a/app/assets/javascripts/registry/settings/store/mutations.js b/app/assets/javascripts/registry/settings/store/mutations.js index f562137db1a..bb7071b020b 100644 --- a/app/assets/javascripts/registry/settings/store/mutations.js +++ b/app/assets/javascripts/registry/settings/store/mutations.js @@ -1,3 +1,4 @@ +import { parseBoolean } from '~/lib/utils/common_utils'; import * as types from './mutation_types'; export default { @@ -8,19 +9,19 @@ export default { keepN: JSON.parse(initialState.keepNOptions), olderThan: JSON.parse(initialState.olderThanOptions), }; + state.enableHistoricEntries = parseBoolean(initialState.enableHistoricEntries); + state.isAdmin = parseBoolean(initialState.isAdmin); + state.adminSettingsPath = initialState.adminSettingsPath; }, [types.UPDATE_SETTINGS](state, data) { state.settings = { ...state.settings, ...data.settings }; }, [types.SET_SETTINGS](state, settings) { - state.settings = settings; + state.settings = settings ?? state.settings; state.original = Object.freeze(settings); }, - [types.SET_IS_DISABLED](state, isDisabled) { - state.isDisabled = isDisabled; - }, [types.RESET_SETTINGS](state) { - state.settings = { ...state.original }; + state.settings = Object.assign({}, state.original); }, [types.TOGGLE_LOADING](state) { state.isLoading = !state.isLoading; diff --git a/app/assets/javascripts/registry/settings/store/state.js b/app/assets/javascripts/registry/settings/store/state.js index 582e18e5465..fccc0991c1c 100644 --- a/app/assets/javascripts/registry/settings/store/state.js +++ b/app/assets/javascripts/registry/settings/store/state.js @@ -8,9 +8,17 @@ export default () => ({ */ isLoading: false, /* - * Boolean to determine if the user is allowed to interact with the form + * Boolean to determine if the user is an admin */ - isDisabled: false, + isAdmin: false, + /* + * String containing the full path to the admin config page for CI/CD + */ + adminSettingsPath: '', + /* + * Boolean to determine if project created before 12.8 can use this feature + */ + enableHistoricEntries: false, /* * This contains the data shown and manipulated in the UI * Has the following structure: @@ -24,9 +32,9 @@ export default () => ({ */ settings: {}, /* - * Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel' + * Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel', initialized to null */ - original: {}, + original: null, /* * Contains the options used to populate the form selects */ diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index bffaa096210..2f6640232dd 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -202,7 +202,7 @@ const fileExtensionIcons = { flv: 'movie', vob: 'movie', ogv: 'movie', - ogg: 'movie', + ogg: 'music', gifv: 'movie', avi: 'movie', mov: 'movie', diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c5c586ea489..b2496427924 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -496,6 +496,10 @@ class ApplicationController < ActionController::Base html_request? && !devise_controller? end + def public_visibility_restricted? + Gitlab::CurrentSettings.restricted_visibility_levels.include? Gitlab::VisibilityLevel::PUBLIC + end + def set_usage_stats_consent_flag return unless current_user return if sessionless_user? diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb index 8eee3742d89..9d33135d4c1 100644 --- a/app/controllers/explore/application_controller.rb +++ b/app/controllers/explore/application_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Explore::ApplicationController < ApplicationController - skip_before_action :authenticate_user! + skip_before_action :authenticate_user!, unless: :public_visibility_restricted? layout 'explore' end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 97895d6461c..91bba1eb617 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class HelpController < ApplicationController - skip_before_action :authenticate_user! + skip_before_action :authenticate_user!, unless: :public_visibility_restricted? layout 'help' diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 1b36f60c316..b341cc795a0 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -51,6 +51,10 @@ module ExploreHelper links.any? { |link| explore_nav_link?(link) } end + def public_visibility_restricted? + Gitlab::CurrentSettings.restricted_visibility_levels.include? Gitlab::VisibilityLevel::PUBLIC + end + private def get_explore_nav_links diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index a4b6e613100..f3f4cdc857f 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -74,7 +74,7 @@ module ServicesHelper def scoped_integration_path(integration) if @project.present? - project_settings_integration_path(@project, integration) + project_service_path(@project, integration) elsif @group.present? group_settings_integration_path(@group, integration) else diff --git a/app/models/project_services/alerts_service.rb b/app/models/project_services/alerts_service.rb index 2f7902d9617..16bf37fd189 100644 --- a/app/models/project_services/alerts_service.rb +++ b/app/models/project_services/alerts_service.rb @@ -15,6 +15,8 @@ class AlertsService < Service before_validation :ensure_token, if: :activated? def url + return if instance? || template? + url_helpers.project_alerts_notify_url(project, format: :json) end diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index 86e2eeda21f..f8715b57d6e 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -56,7 +56,12 @@ module Groups end def tree_exporter - Gitlab::ImportExport::Group::TreeSaver.new(group: @group, current_user: @current_user, shared: @shared, params: @params) + Gitlab::ImportExport::Group::LegacyTreeSaver.new( + group: @group, + current_user: @current_user, + shared: @shared, + params: @params + ) end def file_saver diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index d36e08f44a4..6a261bbbc46 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -38,7 +38,9 @@ %hr.footer-fixed .container.footer-container .footer-links - = link_to _("Explore"), explore_root_path - = link_to _("Help"), help_path + - if !public_visibility_restricted? + = link_to _("Explore"), explore_root_path + = link_to _("Help"), help_path = link_to _("About GitLab"), "https://about.gitlab.com/" + = footer_message diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml index ff2b00ea376..6ac80a5aba3 100644 --- a/app/views/layouts/devise_empty.html.haml +++ b/app/views/layouts/devise_empty.html.haml @@ -14,7 +14,8 @@ %hr .container .footer-links - = link_to _("Explore"), explore_root_path - = link_to _("Help"), help_path + - if !public_visibility_restricted? + = link_to _("Explore"), explore_root_path + = link_to _("Help"), help_path = link_to _("About GitLab"), "https://about.gitlab.com/" = footer_message diff --git a/app/views/projects/registry/settings/_index.haml b/app/views/projects/registry/settings/_index.haml index 0e0341a9923..c0cef8503e0 100644 --- a/app/views/projects/registry/settings/_index.haml +++ b/app/views/projects/registry/settings/_index.haml @@ -1,4 +1,7 @@ #js-registry-settings{ data: { project_id: @project.id, cadence_options: cadence_options.to_json, keep_n_options: keep_n_options.to_json, - older_than_options: older_than_options.to_json} } + older_than_options: older_than_options.to_json, + is_admin: current_user&.admin.to_s, + admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), + enable_historic_entries: Gitlab::CurrentSettings.try(:container_expiration_policies_enable_historic_entries).to_s} } diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index a0d9d29a7ae..bd9defe5f74 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -10,7 +10,7 @@ - if @service.respond_to?(:detailed_description) %p= @service.detailed_description .col-lg-9 - = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| + = form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = render 'shared/service_settings', form: form, service: @service - if @service.editable? .footer-block.row-content-block diff --git a/app/views/projects/services/alerts/_help.html.haml b/app/views/projects/services/alerts/_help.html.haml index be910203125..ef3ab8d8d04 100644 --- a/app/views/projects/services/alerts/_help.html.haml +++ b/app/views/projects/services/alerts/_help.html.haml @@ -1,3 +1,3 @@ .js-alerts-service-settings{ data: { activated: @service.activated?.to_s, - form_path: project_service_path(@project, @service.to_param), - authorization_key: @service.token, url: @service.url, learn_more_url: 'https://docs.gitlab.com/ee/user/project/integrations/generic_alerts.html' } } + form_path: scoped_integration_path(@service), + authorization_key: @service.token, url: @service.url || _('<namespace / project>'), learn_more_url: 'https://docs.gitlab.com/ee/user/project/integrations/generic_alerts.html' } } diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 395df502ddb..cf73a7055c6 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -1,4 +1,5 @@ -- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: @project.full_name } +- pretty_name = @project&.full_name || _('<project name>') +- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: pretty_name } %p= s_("ProjectService|To set up this service:") %ul.list-unstyled.indent-list @@ -20,7 +21,7 @@ .form-group = label_tag :display_name, _('Display name'), class: 'col-12 col-form-label label-bold' .col-12.input-group - = text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly' + = text_field_tag :display_name, "GitLab / #{pretty_name}", class: 'form-control form-control-sm', readonly: 'readonly' .input-group-append = clipboard_button(target: '#display_name', class: 'input-group-text') @@ -38,8 +39,9 @@ %p = s_('MattermostService|Suggestions:') %code= 'gitlab' - %code= @project.path # Path contains no spaces, but dashes - %code= @project.full_path + - if @project + %code= @project.path # Path contains no spaces, but dashes + %code= @project.full_path .form-group = label_tag :request_url, s_('MattermostService|Request URL'), class: 'col-12 col-form-label label-bold' diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index 7f6717e298c..93ea17a3a3d 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -1,5 +1,5 @@ -- pretty_name = defined?(@project) ? @project.full_name : 'namespace / path' -- run_actions_text = "Perform common operations on GitLab project: #{pretty_name}" +- pretty_name = @project&.full_name || _('<project name>') +- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: pretty_name } .info-well .well-segment @@ -31,8 +31,10 @@ %p = _("Suggestions:") %code= 'gitlab' - %code= @project.path # Path contains no spaces, but dashes - %code= @project.full_path + %code= 'project' + - if @project + %code= @project.path # Path contains no spaces, but dashes + %code= @project.full_path .form-group = label_tag :url, 'URL', class: 'col-12 col-form-label label-bold' diff --git a/changelogs/unreleased/55241-rate-limit-issue-creation-api.yml b/changelogs/unreleased/55241-rate-limit-issue-creation-api.yml new file mode 100644 index 00000000000..e685e83fd2e --- /dev/null +++ b/changelogs/unreleased/55241-rate-limit-issue-creation-api.yml @@ -0,0 +1,5 @@ +--- +title: Introduce rate limit for creating issues via API +merge_request: 28130 +author: +type: performance diff --git a/changelogs/unreleased/fix-admin-mode-sidekiq-admin-ui.yml b/changelogs/unreleased/fix-admin-mode-sidekiq-admin-ui.yml new file mode 100644 index 00000000000..3d940372c6b --- /dev/null +++ b/changelogs/unreleased/fix-admin-mode-sidekiq-admin-ui.yml @@ -0,0 +1,5 @@ +--- +title: Protect sidekiq admin UI with admin mode +merge_request: 28164 +author: Diego Louzán +type: fixed diff --git a/changelogs/unreleased/winniehell-ogg-music.yml b/changelogs/unreleased/winniehell-ogg-music.yml new file mode 100644 index 00000000000..7504bb3af79 --- /dev/null +++ b/changelogs/unreleased/winniehell-ogg-music.yml @@ -0,0 +1,5 @@ +--- +title: Use music icon for files with .ogg extension +merge_request: 29514 +author: +type: fixed diff --git a/config/routes/sidekiq.rb b/config/routes/sidekiq.rb index 0fa23f2b3d0..36ec8bc1d54 100644 --- a/config/routes/sidekiq.rb +++ b/config/routes/sidekiq.rb @@ -1,4 +1,3 @@ -constraint = lambda { |request| request.env['warden'].authenticate? && request.env['warden'].user.admin? } -constraints constraint do +constraints ::Constraints::AdminConstrainer.new do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end diff --git a/doc/api/README.md b/doc/api/README.md index 24b81852dc5..3c8d3dc4902 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -390,7 +390,7 @@ CAUTION: **Caution:** For performance reasons since [GitLab 11.8](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23931) and **behind the `api_kaminari_count_with_limit` -[feature flag](../development/feature_flags.md)**, if the number of resources is +[feature flag](../development/feature_flags/index.md)**, if the number of resources is more than 10,000, the `X-Total` and `X-Total-Pages` headers as well as the `rel="last"` `Link` are not present in the response headers. diff --git a/doc/api/issues.md b/doc/api/issues.md index 89b81c9ee97..14f81d7d327 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -740,6 +740,14 @@ the `weight` parameter: **Note**: The `closed_by` attribute was [introduced in GitLab 10.6](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17042). This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists. +## Rate limits + +To help avoid abuse, users are limited to: + +| Request Type | Limit | +| ---------------- | --------------------------- | +| Create | 300 issues per minute | + ## Edit issue Updates an existing project issue. This call is also used to mark an issue as diff --git a/doc/development/README.md b/doc/development/README.md index b505fa38618..16858b0c58e 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -63,7 +63,7 @@ Complementary reads: styleguide if you are contributing to the [GraphQL API](../api/graphql/index.md) - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers - [Working with Gitaly](gitaly.md) -- [Manage feature flags](feature_flags.md) +- [Manage feature flags](feature_flags/index.md) - [Licensed feature availability](licensed_feature_availability.md) - [View sent emails or preview mailers](emails.md) - [Shell commands](shell_commands.md) in the GitLab codebase diff --git a/doc/development/changelog.md b/doc/development/changelog.md index bc1ba4eb50b..2007c26403c 100644 --- a/doc/development/changelog.md +++ b/doc/development/changelog.md @@ -5,9 +5,9 @@ file, as well as information and history about our changelog process. ## Overview -Each bullet point, or **entry**, in our [`CHANGELOG.md`][changelog.md] file is -generated from a single data file in the [`changelogs/unreleased/`][unreleased] -(or corresponding EE) folder. The file is expected to be a [YAML] file in the +Each bullet point, or **entry**, in our [`CHANGELOG.md`](https://gitlab.com/gitlab-org/gitlab/blob/master/CHANGELOG.md) file is +generated from a single data file in the [`changelogs/unreleased/`](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/changelogs/) +(or corresponding EE) folder. The file is expected to be a [YAML](https://en.wikipedia.org/wiki/YAML) file in the following format: ```yaml @@ -27,15 +27,12 @@ valid options are: added, fixed, changed, deprecated, removed, security, perform Community contributors and core team members are encouraged to add their name to the `author` field. GitLab team members **should not**. -[changelog.md]: https://gitlab.com/gitlab-org/gitlab/blob/master/CHANGELOG.md -[unreleased]: https://gitlab.com/gitlab-org/gitlab-foss/tree/master/changelogs/ -[YAML]: https://en.wikipedia.org/wiki/YAML - ## What warrants a changelog entry? - Any change that introduces a database migration, whether it's regular, post, or data migration, **must** have a changelog entry. -- [Security fixes] **must** have a changelog entry, without `merge_request` value +- [Security fixes](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md) + **must** have a changelog entry, without `merge_request` value and with `type` set to `security`. - Any user-facing change **should** have a changelog entry. Example: "GitLab now uses system fonts for all text." @@ -269,13 +266,14 @@ as the other was merged. When we had dozens of merge requests fighting for the same changelog entry location, this quickly became a major source of merge conflicts and delays in development. -This led us to a [boring solution] of "add your entry in a random location in +This led us to a [boring solution](https://about.gitlab.com/handbook/values/#boring-solutions) of "add your entry in a random location in the list." This actually worked pretty well as we got further along in each monthly release cycle, but at the start of a new cycle, when a new version section was added and there were fewer places to "randomly" add an entry, the conflicts became a problem again until we had a sufficient number of entries. -On top of all this, it created an entirely different headache for [release managers] +On top of all this, it created an entirely different headache for +[release managers](https://gitlab.com/gitlab-org/release/docs/blob/master/quickstart/release-manager.md) when they cherry-picked a commit into a stable branch for a patch release. If the commit included an entry in the `CHANGELOG`, it would include the entire changelog for the latest version in `master`, so the release manager would have @@ -283,16 +281,11 @@ to manually remove the later entries. They often would have had to do this multiple times per patch release. This was compounded when we had to release multiple patches at once due to a security issue. -We needed to automate all of this manual work. So we [started brainstorming]. +We needed to automate all of this manual work. So we +[started brainstorming](https://gitlab.com/gitlab-org/gitlab-foss/issues/17826). After much discussion we settled on the current solution of one file per entry, and then compiling the entries into the overall `CHANGELOG.md` file during the -[release process]. - -[boring solution]: https://about.gitlab.com/handbook/values/#boring-solutions -[release managers]: https://gitlab.com/gitlab-org/release/docs/blob/master/quickstart/release-manager.md -[started brainstorming]: https://gitlab.com/gitlab-org/gitlab-foss/issues/17826 -[release process]: https://gitlab.com/gitlab-org/release-tools -[Security fixes]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md +[release process](https://gitlab.com/gitlab-org/release-tools). --- diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 52a0672259f..5220f29bd60 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -490,15 +490,11 @@ A good example of collaboration on an MR touching multiple parts of the codebase ### Credits -Largely based on the [thoughtbot code review guide]. - -[thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review +Largely based on the [thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review). --- [Return to Development documentation](README.md) -[projects]: https://about.gitlab.com/handbook/engineering/projects/ -[build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build [^1]: Please note that specs other than JavaScript specs are considered backend code. [^2]: We encourage you to seek guidance from a database maintainer if your merge request is potentially introducing expensive queries. It is most efficient to comment on the line of code in question with the SQL queries so they can give their advice. diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index 5df357eee9e..c8705a174af 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -402,7 +402,8 @@ below will make it easy to manage this, without unnecessary overhead. Every monthly release has a corresponding issue on the CE issue tracker to keep track of functionality broken by that release and any fixes that need to be -included in a patch release (see [8.3 Regressions] as an example). +included in a patch release (see +[8.3 Regressions](https://gitlab.com/gitlab-org/gitlab-foss/issues/4127) as an example). As outlined in the issue description, the intended workflow is to post one note with a reference to an issue describing the regression, and then to update that @@ -412,11 +413,9 @@ If you're a contributor who doesn't have the required permissions to update other users' notes, please post a new note with a reference to both the issue and the merge request. -The release manager will [update the notes] in the regression issue as fixes are -addressed. - -[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-foss/issues/4127 -[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue +The release manager will +[update the notes](https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue) +in the regression issue as fixes are addressed. ## Technical and UX debt diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index deb86569879..0007f6d6e2f 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -1349,11 +1349,9 @@ Replace `reconfigure` with `restart` where appropriate. In [step 2 of the installation guide](../../install/installation.md#2-ruby), we install Ruby from source. Whenever there is a new version that needs to be updated, remember to change it throughout the codeblock and also replace -the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby +the sha256sum (it can be found in the [downloads page](https://www.ruby-lang.org/en/downloads/) of the Ruby website). -[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website" - ### Configuration documentation for source and Omnibus installations GitLab currently officially supports two installation methods: installations @@ -1380,7 +1378,7 @@ the style below as a guide: external_url "https://gitlab.example.com" ``` -1. Save the file and [reconfigure] GitLab for the changes to take effect. +1. Save the file and [reconfigure](path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab for the changes to take effect. --- @@ -1393,10 +1391,7 @@ the style below as a guide: host: "gitlab.example.com" ``` -1. Save the file and [restart] GitLab for the changes to take effect. - -[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure -[restart]: path/to/administration/restart_gitlab.md#installations-from-source +1. Save the file and [restart](path/to/administration/restart_gitlab.md#installations-from-source) GitLab for the changes to take effect. ```` In this case: diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index b386d0e78ff..bd70d5b87ba 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -9,7 +9,8 @@ ## Act as CE when unlicensed -Since the implementation of [GitLab CE features to work with unlicensed EE instance][ee-as-ce] +Since the implementation of +[GitLab CE features to work with unlicensed EE instance](https://gitlab.com/gitlab-org/gitlab/issues/2500) GitLab Enterprise Edition should work like GitLab Community Edition when no license is active. So EE features always should be guarded by `project.feature_available?` or `group.feature_available?` (or @@ -24,8 +25,6 @@ setting the [`FOSS_ONLY` environment variable](https://gitlab.com/gitlab-org/git to something that evaluates as `true`. The same works for running tests (for example `FOSS_ONLY=1 yarn jest`). -[ee-as-ce]: https://gitlab.com/gitlab-org/gitlab/issues/2500 - ## Separation of EE code All EE code should be put inside the `ee/` top-level directory. The @@ -53,11 +52,9 @@ is applied not only to models. Here's a list of other examples: - `ee/app/views/foo/_bar.html.haml` This works because for every path that is present in CE's eager-load/auto-load -paths, we add the same `ee/`-prepended path in [`config/application.rb`]. +paths, we add the same `ee/`-prepended path in [`config/application.rb`](https://gitlab.com/gitlab-org/gitlab/blob/925d3d4ebc7a2c72964ce97623ae41b8af12538d/config/application.rb#L42-52). This also applies to views. -[`config/application.rb`]: https://gitlab.com/gitlab-org/gitlab/blob/925d3d4ebc7a2c72964ce97623ae41b8af12538d/config/application.rb#L42-52 - ### EE features based on CE features For features that build on existing CE features, write a module in the `EE` diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md index fa9942e8d81..aa32285b27b 100644 --- a/doc/development/feature_flags/controls.md +++ b/doc/development/feature_flags/controls.md @@ -29,6 +29,11 @@ Monitor stage, Health group. For all production environment Chatops commands, use the `#production` channel. +As per the template, where a feature would have a (potentially) significant user +impact and the feature is being enabled instance wide prior to release, please copy +the Slack message and repost in the `#support_gitlab-com` channel for added visibility +and awareness, preferably with a link to the issue, MR, or docs. + Regardless of the channel in which the Chatops command is ran, any feature flag change that affects GitLab.com will automatically be logged in an issue. The issue is created in the [gl-infra/feature-flag-log](https://gitlab.com/gitlab-com/gl-infra/feature-flag-log/issues?scope=all&utf8=%E2%9C%93&state=closed) project, and it will at minimum log the Slack handle of person enabling a feature flag, the time, and the name of the flag being changed. diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md index e16fe7eba5b..230288844d7 100644 --- a/doc/development/file_storage.md +++ b/doc/development/file_storage.md @@ -1,6 +1,6 @@ # File Storage in GitLab -We use the [CarrierWave] gem to handle file upload, store and retrieval. +We use the [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) gem to handle file upload, store and retrieval. File uploads should be accelerated by workhorse, for details please refer to [uploads development documentation](uploads.md). @@ -46,14 +46,14 @@ they are still not 100% standardized. You can see them below: CI Artifacts and LFS Objects behave differently in CE and EE. In CE they inherit the `GitlabUploader` while in EE they inherit the `ObjectStorage` and store files in and S3 API compatible object store. -In the case of Issues/MR/Notes Markdown attachments, there is a different approach using the [Hashed Storage] layout, +In the case of Issues/MR/Notes Markdown attachments, there is a different approach using the [Hashed Storage](../administration/repository_storage_types.md) layout, instead of basing the path into a mutable variable `:project_path_with_namespace`, it's possible to use the hash of the project ID instead, if project migrates to the new approach (introduced in 10.2). -> Note: We provide an [all-in-one Rake task] to migrate all uploads to object +> Note: We provide an [all-in-one Rake task](../administration/raketasks/uploads/migrate.md) to migrate all uploads to object > storage in one go. If a new Uploader class or model type is introduced, make -> sure you add a Rake task invocation corresponding to it to the [category -> list]. +> sure you add a Rake task invocation corresponding to it to the +> [category list](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/tasks/gitlab/uploads/migrate.rake). ### Path segments @@ -144,8 +144,3 @@ class Thing < ActiveRecord::Base ... end ``` - -[CarrierWave]: https://github.com/carrierwaveuploader/carrierwave -[Hashed Storage]: ../administration/repository_storage_types.md -[all-in-one rake task]: ../administration/raketasks/uploads/migrate.md -[category list]: https://gitlab.com/gitlab-org/gitlab/blob/master/lib/tasks/gitlab/uploads/migrate.rake diff --git a/doc/development/interacting_components.md b/doc/development/interacting_components.md index 3db260d5f85..4639bd7cc20 100644 --- a/doc/development/interacting_components.md +++ b/doc/development/interacting_components.md @@ -9,7 +9,7 @@ when making _backend_ changes that might involve multiple features or [component ## Uploads -GitLab supports uploads to [object storage]. That means every feature and +GitLab supports uploads to [object storage](https://docs.gitlab.com/charts/advanced/external-object-storage/). That means every feature and change that affects uploads should also be tested against [object storage], which is _not_ enabled by default in [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit). @@ -25,5 +25,3 @@ See also [File Storage in GitLab](file_storage.md). GitLab supports a great amount of features for [merge requests](../user/project/merge_requests/index.md). One of them is the ability to create merge requests from and to [forks](../gitlab-basics/fork-project.md), which should also be highly considered and tested upon development phase. - -[object storage]: https://docs.gitlab.com/charts/advanced/external-object-storage/ diff --git a/doc/development/performance.md b/doc/development/performance.md index a7ae283432f..5068103ff16 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -8,7 +8,7 @@ consistent performance of GitLab. The process of solving performance problems is roughly as follows: 1. Make sure there's an issue open somewhere (for example, on the GitLab CE issue - tracker), and create one if there is not. See [#15607][#15607] for an example. + tracker), and create one if there is not. See [#15607](https://gitlab.com/gitlab-org/gitlab-foss/issues/15607) for an example. 1. Measure the performance of the code in a production environment such as GitLab.com (see the [Tooling](#tooling) section below). Performance should be measured over a period of _at least_ 24 hours. @@ -495,7 +495,7 @@ just memory but also unnecessary time spent in CPU and I/O for processing lines ## Anti-Patterns -This is a collection of [anti-patterns][anti-pattern] that should be avoided +This is a collection of [anti-patterns](https://en.wikipedia.org/wiki/Anti-pattern) that should be avoided unless these changes have a measurable, significant, and positive impact on production environments. @@ -539,6 +539,3 @@ Assuming you are working with ActiveRecord models, you might also find these lin You may find some useful examples in this snippet: <https://gitlab.com/gitlab-org/gitlab-foss/snippets/33946> - -[#15607]: https://gitlab.com/gitlab-org/gitlab-foss/issues/15607 -[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 109cd635dc0..d510dff82dd 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -205,7 +205,7 @@ On the EC2 dashboard, look for Load Balancer in the left navigation bar: 1. Click **Configure Health Check** and set up a health check for your EC2 instances. 1. For **Ping Protocol**, select HTTP. 1. For **Ping Port**, enter 80. - 1. For **Ping Path**, enter `/explore`. (We use `/explore` as it's a public endpoint that does + 1. For **Ping Path**, enter `/users/sign_in`. (We use `/users/sign_in` as it's a public endpoint that does not require authorization.) 1. Keep the default **Advanced Details** or adjust them according to your needs. 1. Click **Add EC2 Instances** but, as we don't have any instances to add yet, come back diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index d7b2222fa06..0ca2da1db63 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -69,6 +69,16 @@ you are privileged to. If the public level is restricted, user profiles are only visible to logged in users. +## Visibility of pages + +By default, the following directories are visible to unauthenticated users: + +- Public access (`/public`). +- Explore (`/explore`). +- Help (`/help`). + +However, if the access level of the `/public` directory is restricted, these directories are visible only to logged in users. + ## Restricting the use of public or internal projects You can restrict the use of visibility levels for users when they create a project or a diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index 704dd89ede2..f827fed833b 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -91,7 +91,7 @@ For more details on group visibility, see [Public access](../../../public_access ## Restricted visibility levels -To set the available visibility levels for new projects and snippets: +To set the available visibility levels for projects, snippets, and selected pages: 1. Check the desired visibility levels. 1. Click **Save changes**. diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png Binary files differnew file mode 100644 index 00000000000..8983d685a24 --- /dev/null +++ b/doc/user/project/import/img/jira/import_issues_from_jira_button_v12_10.png diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png Binary files differnew file mode 100644 index 00000000000..0ac5e9bdb91 --- /dev/null +++ b/doc/user/project/import/img/jira/import_issues_from_jira_form_v12_10.png diff --git a/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png b/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png Binary files differnew file mode 100644 index 00000000000..cf5f0dd59cd --- /dev/null +++ b/doc/user/project/import/img/jira/import_issues_from_jira_projects_v12_10.png diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index a114e23b3e2..a0da68eb513 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -15,6 +15,7 @@ 1. [By uploading a manifest file (AOSP)](manifest.md) 1. [From Gemnasium](gemnasium.md) 1. [From Phabricator](phabricator.md) +1. [From Jira (issues only)](jira.md) In addition to the specific migration documentation above, you can import any Git repository via HTTP from the New Project page. Be aware that if the diff --git a/doc/user/project/import/jira.md b/doc/user/project/import/jira.md new file mode 100644 index 00000000000..49224001fe6 --- /dev/null +++ b/doc/user/project/import/jira.md @@ -0,0 +1,57 @@ +# Import your Jira project issues to GitLab + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2766) in GitLab 12.10. + +Using GitLab Jira importer, you can import your Jira issues to GitLab.com or to +your self-managed GitLab instance. + +Jira issues import is an MVC, project-level feature, meaning that issues from multiple +Jira projects can be imported into a GitLab project. MVC version imports issue title and description +as well as some other issue metadata as a section in the issue description. + +## Prerequisites + +### Permissions + +In order to be able to import issues from a Jira project you need to have read access on Jira +issues and a [Maintainer or higher](../../permissions.md#project-members-permissions) role in the +GitLab project that you wish to import into. + +### Jira integration + +This feature uses the existing GitLab [Jira integration](../integrations/jira.md). + +Make sure you have the integration set up before trying to import Jira issues. + +## Import Jira issues to GitLab + +To import Jira issues to a GitLab project, follow the steps below. + +NOTE: **Note:** +Importing Jira issues is done as an asynchronous background job, which +may result in delays based on import queues load, system load, or other factors. +Importing large projects may take several minutes depending on the size of the import. + +1. On the **{issues}** **Issues** page, click the **Import Issues** (**{import}**) button. +1. Select **Import from Jira**. + + ![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png) + + The following form appears. + + ![Import issues from Jira form](img/jira/import_issues_from_jira_form_v12_10.png) + + If you've previously set up the [Jira integration](../integrations/jira.md), you now see the Jira + projects that you have access to in the dropdown. + +1. Select the Jira project that you wish to import issues from. + + ![Import issues from Jira form](img/jira/import_issues_from_jira_projects_v12_10.png) + +1. Click **Import Issues**. You're presented with a confirmation that import has started. + While the import is running in the background, you can navigate away from the import status page + to the issues page, and you'll see the new issues appearing in the issues list. + +1. To check the status of your import, go back to the Jira import page. + + ![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png) diff --git a/lib/api/helpers/rate_limiter.rb b/lib/api/helpers/rate_limiter.rb new file mode 100644 index 00000000000..5a531b5324a --- /dev/null +++ b/lib/api/helpers/rate_limiter.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module API + module Helpers + module RateLimiter + def check_rate_limit!(key, scope) + if rate_limiter.throttled?(key, scope: scope) + log_request(key) + render_exceeded_limit_error! + end + end + + private + + def rate_limiter + ::Gitlab::ApplicationRateLimiter + end + + def render_exceeded_limit_error! + render_api_error!({ error: _('This endpoint has been requested too many times. Try again later.') }, 429) + end + + def log_request(key) + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + end + end + end +end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index a78202877fb..f27afd0055f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -4,6 +4,7 @@ module API class Issues < Grape::API include PaginationParams helpers Helpers::IssuesHelpers + helpers Helpers::RateLimiter helpers ::Gitlab::IssuableMetadata before { authenticate_non_get! } @@ -211,6 +212,8 @@ module API post ':id/issues' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42320') + check_rate_limit! :issues_create, [current_user, :issues_create] + authorize! :create_issue, user_project params.delete(:created_at) unless current_user.can?(:set_issue_created_at, user_project) diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index ef6a8f1a396..9fd9d13a20c 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -2,15 +2,8 @@ module API class ProjectExport < Grape::API - helpers do - def throttled?(action) - rate_limiter.throttled?(action, scope: [current_user, action, user_project]) - end + helpers Helpers::RateLimiter - def rate_limiter - ::Gitlab::ApplicationRateLimiter - end - end before do not_found! unless Gitlab::CurrentSettings.project_export_enabled? authorize_admin_project @@ -32,9 +25,7 @@ module API detail 'This feature was introduced in GitLab 10.6.' end get ':id/export/download' do - if throttled?(:project_download_export) - render_api_error!({ error: 'This endpoint has been requested too many times. Try again later.' }, 429) - end + check_rate_limit! :project_download_export, [current_user, :project_download_export, user_project] if user_project.export_file_exists? present_carrierwave_file!(user_project.export_file) @@ -54,9 +45,7 @@ module API end end post ':id/export' do - if throttled?(:project_export) - render_api_error!({ error: 'This endpoint has been requested too many times. Try again later.' }, 429) - end + check_rate_limit! :project_export, [current_user, :project_export, user_project] project_export_params = declared_params(include_missing: false) after_export_params = project_export_params.delete(:upload) || {} diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index ffa9dd13754..0e83686cab2 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -8,19 +8,12 @@ module API helpers Helpers::ProjectsHelpers helpers Helpers::FileUploadHelpers + helpers Helpers::RateLimiter helpers do def import_params declared_params(include_missing: false) end - - def throttled?(key, scope) - rate_limiter.throttled?(key, scope: scope) - end - - def rate_limiter - ::Gitlab::ApplicationRateLimiter - end end before do @@ -69,13 +62,7 @@ module API post 'import' do require_gitlab_workhorse! - key = "project_import".to_sym - - if throttled?(key, [current_user, key]) - rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) - - render_api_error!({ error: _('This endpoint has been requested too many times. Try again later.') }, 429) - end + check_rate_limit! :project_import, [current_user, :project_import] Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42437') diff --git a/lib/constraints/admin_constrainer.rb b/lib/constraints/admin_constrainer.rb new file mode 100644 index 00000000000..59c855a1b73 --- /dev/null +++ b/lib/constraints/admin_constrainer.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Constraints + class AdminConstrainer + def matches?(request) + if Feature.enabled?(:user_mode_in_session) + admin_mode_enabled?(request) + else + user_is_admin?(request) + end + end + + private + + def user_is_admin?(request) + request.env['warden'].authenticate? && request.env['warden'].user.admin? + end + + def admin_mode_enabled?(request) + Gitlab::Session.with_session(request.session) do + request.env['warden'].authenticate? && Gitlab::Auth::CurrentUserMode.new(request.env['warden'].user).admin_mode? + end + end + end +end diff --git a/lib/gitlab/import_export/group/tree_saver.rb b/lib/gitlab/import_export/group/legacy_tree_saver.rb index fd1eb329ad2..3776ef0d8f5 100644 --- a/lib/gitlab/import_export/group/tree_saver.rb +++ b/lib/gitlab/import_export/group/legacy_tree_saver.rb @@ -3,7 +3,7 @@ module Gitlab module ImportExport module Group - class TreeSaver + class LegacyTreeSaver attr_reader :full_path, :shared def initialize(group:, current_user:, shared:, params: {}) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9965f8c7d47..49a064da6f8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -788,12 +788,18 @@ msgstr "" msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address." msgstr "" +msgid "<namespace / project>" +msgstr "" + msgid "<no name set>" msgstr "" msgid "<no scopes selected>" msgstr "" +msgid "<project name>" +msgstr "" + msgid "<strong>%{group_name}</strong> group members" msgstr "" @@ -5456,6 +5462,9 @@ msgstr "" msgid "Container repositories sync capacity" msgstr "" +msgid "ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature." +msgstr "" + msgid "ContainerRegistry|%{imageName} tags" msgstr "" @@ -5471,6 +5480,9 @@ msgstr "" msgid "ContainerRegistry|Container Registry" msgstr "" +msgid "ContainerRegistry|Container Registry tag expiration and retention policy is disabled" +msgstr "" + msgid "ContainerRegistry|Copy build command" msgstr "" @@ -5480,9 +5492,6 @@ msgstr "" msgid "ContainerRegistry|Copy push command" msgstr "" -msgid "ContainerRegistry|Currently, the Container Registry tag expiration feature is not available for projects created before GitLab version 12.8. For updates and more information, visit Issue %{linkStart}#196124%{linkEnd}" -msgstr "" - msgid "ContainerRegistry|Docker connection error" msgstr "" @@ -5531,6 +5540,9 @@ msgstr "" msgid "ContainerRegistry|Number of tags to retain:" msgstr "" +msgid "ContainerRegistry|Please contact your administrator." +msgstr "" + msgid "ContainerRegistry|Push an image" msgstr "" @@ -5587,6 +5599,9 @@ msgstr "" msgid "ContainerRegistry|Tags deleted successfully" msgstr "" +msgid "ContainerRegistry|The Container Registry tag expiration and retention policies for this project have not been enabled." +msgstr "" + msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator." msgstr "" diff --git a/spec/controllers/explore/groups_controller_spec.rb b/spec/controllers/explore/groups_controller_spec.rb index 5a32d8ca0d3..eccbd7fa14d 100644 --- a/spec/controllers/explore/groups_controller_spec.rb +++ b/spec/controllers/explore/groups_controller_spec.rb @@ -22,4 +22,18 @@ describe Explore::GroupsController do expect(assigns(:groups)).to contain_exactly(member_of_group, public_group) end + + context 'restricted visibility level is public' do + before do + sign_out(user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'redirects to login page' do + get :index + + expect(response).to redirect_to new_user_session_path + end + end end diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index c2cd29eb036..00cc2d5a81c 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -171,5 +171,17 @@ describe Explore::ProjectsController do get :index, params: { sort: sorting_param } end end + + context 'restricted visibility level is public' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'redirects to login page' do + get :index + + expect(response).to redirect_to new_user_session_path + end + end end end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index e010cac2f73..f03fee8d3ae 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -79,6 +79,20 @@ describe HelpController do expect(assigns[:help_index]).to eq '[protocol-relative](//example.com)' end end + + context 'restricted visibility set to public' do + before do + sign_out(user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'redirects to sign_in path' do + get :index + + expect(response).to redirect_to(new_user_session_path) + end + end end describe 'GET #show' do diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb index eff63d6a788..50ec44580d2 100644 --- a/spec/features/explore/groups_spec.rb +++ b/spec/features/explore/groups_spec.rb @@ -89,5 +89,17 @@ describe 'Explore Groups', :js do end it_behaves_like 'renders group in public groups area' + + context 'when visibility is restricted to public' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'redirects to the sign in page' do + visit explore_groups_path + + expect(page).to have_current_path(new_user_session_path) + end + end end end diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index c64709c0b55..6adf51a1cf6 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -16,6 +16,17 @@ describe 'User explores projects' do include_examples 'shows public projects' end + + context 'when visibility is restricted to public' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + visit(explore_projects_path) + end + + it 'redirects to login page' do + expect(page).to have_current_path(new_user_session_path) + end + end end context 'when signed in' do diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js index c83cc0c00dd..95f784c9727 100644 --- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js @@ -1,10 +1,11 @@ import { shallowMount } from '@vue/test-utils'; -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import component from '~/registry/settings/components/registry_settings_app.vue'; import SettingsForm from '~/registry/settings/components/settings_form.vue'; import { createStore } from '~/registry/settings/store/'; -import { SET_IS_DISABLED } from '~/registry/settings/store/mutation_types'; +import { SET_SETTINGS, SET_INITIAL_STATE } from '~/registry/settings/store/mutation_types'; import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants'; +import { stringifiedFormOptions } from '../../shared/mock_data'; describe('Registry Settings App', () => { let wrapper; @@ -13,14 +14,14 @@ describe('Registry Settings App', () => { const findSettingsComponent = () => wrapper.find(SettingsForm); const findAlert = () => wrapper.find(GlAlert); - const mountComponent = ({ dispatchMock = 'mockResolvedValue', isDisabled = false } = {}) => { - store = createStore(); - store.commit(SET_IS_DISABLED, isDisabled); + const mountComponent = ({ dispatchMock = 'mockResolvedValue' } = {}) => { const dispatchSpy = jest.spyOn(store, 'dispatch'); - if (dispatchMock) { - dispatchSpy[dispatchMock](); - } + dispatchSpy[dispatchMock](); + wrapper = shallowMount(component, { + stubs: { + GlSprintf, + }, mocks: { $toast: { show: jest.fn(), @@ -30,11 +31,16 @@ describe('Registry Settings App', () => { }); }; + beforeEach(() => { + store = createStore(); + }); + afterEach(() => { wrapper.destroy(); }); it('renders', () => { + store.commit(SET_SETTINGS, { foo: 'bar' }); mountComponent(); expect(wrapper.element).toMatchSnapshot(); }); @@ -45,13 +51,15 @@ describe('Registry Settings App', () => { }); it('renders the setting form', () => { + store.commit(SET_SETTINGS, { foo: 'bar' }); mountComponent(); expect(findSettingsComponent().exists()).toBe(true); }); - describe('isDisabled', () => { + describe('the form is disabled', () => { beforeEach(() => { - mountComponent({ isDisabled: true }); + store.commit(SET_SETTINGS, undefined); + mountComponent(); }); it('the form is hidden', () => { @@ -59,9 +67,27 @@ describe('Registry Settings App', () => { }); it('shows an alert', () => { - expect(findAlert().html()).toContain( - 'Currently, the Container Registry tag expiration feature is not available', + const text = findAlert().text(); + expect(text).toContain( + 'The Container Registry tag expiration and retention policies for this project have not been enabled.', ); + expect(text).toContain('Please contact your administrator.'); + }); + + describe('an admin is visiting the page', () => { + beforeEach(() => { + store.commit(SET_INITIAL_STATE, { + ...stringifiedFormOptions, + isAdmin: true, + adminSettingsPath: 'foo', + }); + }); + + it('shows the admin part of the alert message', () => { + const sprintf = findAlert().find(GlSprintf); + expect(sprintf.text()).toBe('administration settings'); + expect(sprintf.find(GlLink).attributes('href')).toBe('foo'); + }); }); }); diff --git a/spec/frontend/registry/settings/store/actions_spec.js b/spec/frontend/registry/settings/store/actions_spec.js index 5038dc82416..f92d10d087f 100644 --- a/spec/frontend/registry/settings/store/actions_spec.js +++ b/spec/frontend/registry/settings/store/actions_spec.js @@ -20,7 +20,7 @@ describe('Actions Registry Store', () => { ); describe('receiveSettingsSuccess', () => { - it('calls SET_SETTINGS when data is present', () => { + it('calls SET_SETTINGS', () => { testAction( actions.receiveSettingsSuccess, 'foo', @@ -29,15 +29,6 @@ describe('Actions Registry Store', () => { [], ); }); - it('calls SET_IS_DISABLED when data is not present', () => { - testAction( - actions.receiveSettingsSuccess, - null, - {}, - [{ type: types.SET_IS_DISABLED, payload: true }], - [], - ); - }); }); describe('fetchSettings', () => { diff --git a/spec/frontend/registry/settings/store/getters_spec.js b/spec/frontend/registry/settings/store/getters_spec.js index 44631b97a39..944057ebc9f 100644 --- a/spec/frontend/registry/settings/store/getters_spec.js +++ b/spec/frontend/registry/settings/store/getters_spec.js @@ -29,7 +29,7 @@ describe('Getters registry settings store', () => { }); }); - describe('getIsDisabled', () => { + describe('getIsEdited', () => { it('returns false when original is equal to settings', () => { const same = { foo: 'bar' }; expect(getters.getIsEdited({ original: same, settings: same })).toBe(false); @@ -41,4 +41,18 @@ describe('Getters registry settings store', () => { ); }); }); + + describe('getIsDisabled', () => { + it.each` + original | enableHistoricEntries | result + ${undefined} | ${false} | ${true} + ${{ foo: 'bar' }} | ${undefined} | ${false} + ${{}} | ${false} | ${false} + `( + 'returns $result when original is $original and enableHistoricEntries is $enableHistoricEntries', + ({ original, enableHistoricEntries, result }) => { + expect(getters.getIsDisabled({ original, enableHistoricEntries })).toBe(result); + }, + ); + }); }); diff --git a/spec/frontend/registry/settings/store/mutations_spec.js b/spec/frontend/registry/settings/store/mutations_spec.js index 8ab0196fd4d..1d85e38eb36 100644 --- a/spec/frontend/registry/settings/store/mutations_spec.js +++ b/spec/frontend/registry/settings/store/mutations_spec.js @@ -12,14 +12,19 @@ describe('Mutations Registry Store', () => { describe('SET_INITIAL_STATE', () => { it('should set the initial state', () => { - const expectedState = { ...mockState, projectId: 'foo', formOptions }; - mutations[types.SET_INITIAL_STATE](mockState, { + const payload = { projectId: 'foo', + enableHistoricEntries: false, + adminSettingsPath: 'foo', + isAdmin: true, + }; + const expectedState = { ...mockState, ...payload, formOptions }; + mutations[types.SET_INITIAL_STATE](mockState, { + ...payload, ...stringifiedFormOptions, }); - expect(mockState.projectId).toEqual(expectedState.projectId); - expect(mockState.formOptions).toEqual(expectedState.formOptions); + expect(mockState).toEqual(expectedState); }); }); @@ -41,6 +46,13 @@ describe('Mutations Registry Store', () => { expect(mockState.settings).toEqual(expectedState.settings); expect(mockState.original).toEqual(expectedState.settings); }); + + it('should keep the default state when settings is not present', () => { + const originalSettings = { ...mockState.settings }; + mutations[types.SET_SETTINGS](mockState); + expect(mockState.settings).toEqual(originalSettings); + expect(mockState.original).toEqual(undefined); + }); }); describe('RESET_SETTINGS', () => { @@ -50,6 +62,13 @@ describe('Mutations Registry Store', () => { mutations[types.RESET_SETTINGS](mockState); expect(mockState.settings).toEqual(mockState.original); }); + + it('if original is undefined it should initialize to empty object', () => { + mockState.settings = { foo: 'bar' }; + mockState.original = undefined; + mutations[types.RESET_SETTINGS](mockState); + expect(mockState.settings).toEqual({}); + }); }); describe('TOGGLE_LOADING', () => { @@ -58,11 +77,4 @@ describe('Mutations Registry Store', () => { expect(mockState.isLoading).toEqual(true); }); }); - - describe('SET_IS_DISABLED', () => { - it('should set isDisabled', () => { - mutations[types.SET_IS_DISABLED](mockState, true); - expect(mockState.isDisabled).toEqual(true); - }); - }); }); diff --git a/spec/lib/constraints/admin_constrainer_spec.rb b/spec/lib/constraints/admin_constrainer_spec.rb new file mode 100644 index 00000000000..da178f9e71a --- /dev/null +++ b/spec/lib/constraints/admin_constrainer_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true +# +require 'spec_helper' + +describe Constraints::AdminConstrainer, :do_not_mock_admin_mode do + let(:user) { create(:user) } + + let(:session) { {} } + let(:env) { { 'warden' => double(:warden, authenticate?: true, user: user) } } + let(:request) { double(:request, session: session, env: env) } + + around do |example| + Gitlab::Session.with_session(session) do + example.run + end + end + + describe '#matches' do + context 'feature flag :user_mode_in_session is enabled' do + context 'when user is a regular user' do + it 'forbids access' do + expect(subject.matches?(request)).to be(false) + end + end + + context 'when user is an admin' do + let(:user) { create(:admin) } + + context 'admin mode is disabled' do + it 'forbids access' do + expect(subject.matches?(request)).to be(false) + end + end + + context 'admin mode is enabled' do + before do + current_user_mode = Gitlab::Auth::CurrentUserMode.new(user) + current_user_mode.request_admin_mode! + current_user_mode.enable_admin_mode!(password: user.password) + end + + it 'allows access' do + expect(subject.matches?(request)).to be(true) + end + end + end + end + + context 'feature flag :user_mode_in_session is disabled' do + before do + stub_feature_flags(user_mode_in_session: false) + end + + context 'when user is a regular user' do + it 'forbids access' do + expect(subject.matches?(request)).to be(false) + end + end + + context 'when user is an admin' do + let(:user) { create(:admin) } + + it 'allows access' do + expect(subject.matches?(request)).to be(true) + end + end + end + end +end diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb index eb790662d8c..a0490c255aa 100644 --- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::ImportExport::Group::TreeSaver do +describe Gitlab::ImportExport::Group::LegacyTreeSaver do describe 'saves the group tree into a json object' do let(:shared) { Gitlab::ImportExport::Shared.new(group) } let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) } @@ -28,7 +28,7 @@ describe Gitlab::ImportExport::Group::TreeSaver do # except: # context 'with description override' do # context 'group members' do - # ^ These are specific for the Group::TreeSaver + # ^ These are specific for the Group::LegacyTreeSaver context 'JSON' do let(:saved_group_json) do group_tree_saver.save diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index be48113c215..1444f43003f 100644 --- a/spec/requests/api/issues/post_projects_issues_spec.rb +++ b/spec/requests/api/issues/post_projects_issues_spec.rb @@ -381,6 +381,20 @@ describe API::Issues do end.not_to change { project.labels.count } end end + + context 'when request exceeds the rate limit' do + before do + allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) + end + + it 'prevents users from creating more issues' do + post api("/projects/#{project.id}/issues", user), + params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } + + expect(response).to have_gitlab_http_status(:too_many_requests) + expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') + end + end end describe 'POST /projects/:id/issues with spam filtering' do diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb index e026d2166d6..56c7121cc34 100644 --- a/spec/services/groups/import_export/export_service_spec.rb +++ b/spec/services/groups/import_export/export_service_spec.rb @@ -50,7 +50,7 @@ describe Groups::ImportExport::ExportService do end it 'saves the models' do - expect(Gitlab::ImportExport::Group::TreeSaver).to receive(:new).and_call_original + expect(Gitlab::ImportExport::Group::LegacyTreeSaver).to receive(:new).and_call_original service.execute end |