Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-15 15:09:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-15 15:09:06 +0300
commit6ae38bb3b5dc719fb6a046dcbcce4671176395a2 (patch)
treeaef1c4118d8371e43c878f366755f19dd702121a
parent9c72b346ac3b24cca9233a6ebf298659b408513f (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml3
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue52
-rw-r--r--app/assets/javascripts/incidents/constants.js1
-rw-r--r--app/assets/javascripts/incidents/list.js5
-rw-r--r--app/assets/javascripts/pages/admin/credentials/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/security/credentials/index.js3
-rw-r--r--app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue2
-rw-r--r--app/controllers/admin/users_controller.rb17
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/finders/keys_finder.rb2
-rw-r--r--app/helpers/profiles_helper.rb15
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/ssh_keys_helper.rb18
-rw-r--r--app/helpers/users_helper.rb13
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/concerns/has_repository.rb6
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/user.rb6
-rw-r--r--app/policies/global_policy.rb1
-rw-r--r--app/serializers/deployment_entity.rb1
-rw-r--r--app/services/groups/transfer_service.rb21
-rw-r--r--app/services/groups/update_service.rb32
-rw-r--r--app/services/projects/create_service.rb9
-rw-r--r--app/services/users/approve_service.rb37
-rw-r--r--app/views/admin/users/_approve_user.html.haml7
-rw-r--r--app/views/admin/users/_block_user.html.haml11
-rw-r--r--app/views/admin/users/_head.html.haml17
-rw-r--r--app/views/admin/users/_user.html.haml17
-rw-r--r--app/views/admin/users/_user_approve_effects.html.haml11
-rw-r--r--app/views/admin/users/index.html.haml5
-rw-r--r--app/views/admin/users/show.html.haml39
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml2
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml14
-rw-r--r--app/workers/all_queues.yml16
-rw-r--r--app/workers/disallow_two_factor_for_group_worker.rb19
-rw-r--r--app/workers/disallow_two_factor_for_subgroups_worker.rb30
-rw-r--r--changelogs/unreleased/233785-track-audit-event-searches.yml5
-rw-r--r--changelogs/unreleased/251963-respect-group-s-default-branch-name-when-present.yml5
-rw-r--r--changelogs/unreleased/267000-create-from-template-inherit-integrations.yml5
-rw-r--r--changelogs/unreleased/alipniagov-update-workhorse-to-8-51.yml5
-rw-r--r--changelogs/unreleased/bugfix_coverage_fuzzing_linux_arch.yml5
-rw-r--r--changelogs/unreleased/upgrate-gitlab-pages-to-1-28-0.yml5
-rw-r--r--config/feature_flags/development/canary_ingress_weight_control.yml7
-rw-r--r--config/routes/admin.rb1
-rw-r--r--config/sidekiq_queues.yml4
-rw-r--r--doc/api/search.md3
-rw-r--r--doc/integration/jira_development_panel.md11
-rw-r--r--doc/user/group/iterations/index.md9
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md27
-rw-r--r--lib/api/search.rb4
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml1
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb15
-rw-r--r--lib/gitlab/search_results.rb4
-rw-r--r--locale/gitlab.pot111
-rw-r--r--qa/qa/page/profile/ssh_keys.rb2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb52
-rw-r--r--spec/controllers/profiles_controller_spec.rb13
-rw-r--r--spec/factories/groups.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb75
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb444
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js42
-rw-r--r--spec/frontend/incidents/mocks/incidents.json6
-rw-r--r--spec/helpers/projects/incidents_helper_spec.rb2
-rw-r--r--spec/helpers/search_helper_spec.rb29
-rw-r--r--spec/helpers/users_helper_spec.rb24
-rw-r--r--spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb1
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb28
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb37
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb5
-rw-r--r--spec/models/group_spec.rb22
-rw-r--r--spec/models/project_spec.rb32
-rw-r--r--spec/models/user_spec.rb45
-rw-r--r--spec/policies/global_policy_spec.rb18
-rw-r--r--spec/requests/api/search_spec.rb31
-rw-r--r--spec/serializers/deployment_entity_spec.rb4
-rw-r--r--spec/services/groups/transfer_service_spec.rb33
-rw-r--r--spec/services/groups/update_service_spec.rb19
-rw-r--r--spec/services/projects/create_service_spec.rb33
-rw-r--r--spec/services/users/approve_service_spec.rb106
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb77
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb3
-rw-r--r--spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb26
-rw-r--r--spec/workers/disallow_two_factor_for_group_worker_spec.rb22
-rw-r--r--spec/workers/disallow_two_factor_for_subgroups_worker_spec.rb17
90 files changed, 1512 insertions, 422 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 8d032d82c7f..57d4a2a4cb7 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -353,6 +353,7 @@
.frontend:rules:compile-test-assets:
rules:
- changes: *code-backstage-qa-patterns
+ - <<: *if-merge-request-title-run-all-rspec
.frontend:rules:compile-test-assets-as-if-foss:
rules:
@@ -500,6 +501,7 @@
rules:
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
+ - <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-migration:
rules:
@@ -891,6 +893,7 @@
- <<: *if-default-refs
changes: *code-backstage-patterns
when: on_success
+ - <<: *if-merge-request-title-run-all-rspec
.test-metadata:rules:update-tests-metadata:
rules:
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 5db08bf2dc5..cfc730712d5 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.27.0
+1.28.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 20f1d546bd3..f66efe38785 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.50.0
+8.51.0
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 50f2136325d..3ecd911e814 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -39,6 +39,7 @@ import {
DEFAULT_PAGE_SIZE,
INCIDENT_STATUS_TABS,
TH_CREATED_AT_TEST_ID,
+ TH_INCIDENT_SLA_TEST_ID,
TH_SEVERITY_TEST_ID,
TH_PUBLISHED_TEST_ID,
INCIDENT_DETAILS_PATH,
@@ -67,7 +68,7 @@ export default {
{
key: 'severity',
label: s__('IncidentManagement|Severity'),
- thClass,
+ thClass: `${thClass} w-15p`,
tdClass: `${tdClass} sortable-cell`,
sortable: true,
thAttr: TH_SEVERITY_TEST_ID,
@@ -75,23 +76,38 @@ export default {
{
key: 'title',
label: s__('IncidentManagement|Incident'),
- thClass: `gl-pointer-events-none gl-w-half`,
+ thClass: `gl-pointer-events-none`,
tdClass,
},
{
key: 'createdAt',
label: s__('IncidentManagement|Date created'),
- thClass,
+ thClass: `${thClass} gl-w-eighth`,
tdClass: `${tdClass} sortable-cell`,
sortable: true,
thAttr: TH_CREATED_AT_TEST_ID,
},
{
+ key: 'incidentSla',
+ label: s__('IncidentManagement|Time to SLA'),
+ thClass: `gl-pointer-events-none gl-text-right gl-w-eighth`,
+ tdClass: `${tdClass} gl-text-right`,
+ thAttr: TH_INCIDENT_SLA_TEST_ID,
+ },
+ {
key: 'assignees',
label: s__('IncidentManagement|Assignees'),
- thClass: 'gl-pointer-events-none',
+ thClass: 'gl-pointer-events-none w-15p',
tdClass,
},
+ {
+ key: 'published',
+ label: s__('IncidentManagement|Published'),
+ thClass: `${thClass} w-15p`,
+ tdClass: `${tdClass} sortable-cell`,
+ sortable: true,
+ thAttr: TH_PUBLISHED_TEST_ID,
+ },
],
components: {
GlLoadingIcon,
@@ -107,6 +123,8 @@ export default {
GlTabs,
GlTab,
PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'),
+ ServiceLevelAgreementCell: () =>
+ import('ee_component/incidents/components/service_level_agreement_cell.vue'),
GlBadge,
GlEmptyState,
SeverityToken,
@@ -126,6 +144,7 @@ export default {
'textQuery',
'authorUsernamesQuery',
'assigneeUsernamesQuery',
+ 'slaFeatureAvailable',
],
apollo: {
incidents: {
@@ -231,21 +250,12 @@ export default {
);
},
availableFields() {
- return this.publishedAvailable
- ? [
- ...this.$options.fields,
- ...[
- {
- key: 'published',
- label: s__('IncidentManagement|Published'),
- thClass,
- tdClass: `${tdClass} sortable-cell`,
- sortable: true,
- thAttr: TH_PUBLISHED_TEST_ID,
- },
- ],
- ]
- : this.$options.fields;
+ const isHidden = {
+ published: !this.publishedAvailable,
+ incidentSla: !this.slaFeatureAvailable,
+ };
+
+ return this.$options.fields.filter(({ key }) => !isHidden[key]);
},
isEmpty() {
return !this.incidents.list?.length;
@@ -526,6 +536,10 @@ export default {
<time-ago-tooltip :time="item.createdAt" />
</template>
+ <template v-if="slaFeatureAvailable" #cell(incidentSla)="{ item }">
+ <service-level-agreement-cell :sla-due-at="item.slaDueAt" data-testid="incident-sla" />
+ </template>
+
<template #cell(assignees)="{ item }">
<div data-testid="incident-assignees">
<template v-if="hasAssignees(item.assignees)">
diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js
index bdabf1c3e42..4fccefb66c5 100644
--- a/app/assets/javascripts/incidents/constants.js
+++ b/app/assets/javascripts/incidents/constants.js
@@ -46,5 +46,6 @@ export const trackIncidentCreateNewOptions = {
export const DEFAULT_PAGE_SIZE = 20;
export const TH_CREATED_AT_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' };
export const TH_SEVERITY_TEST_ID = { 'data-testid': 'incident-management-severity-sort' };
+export const TH_INCIDENT_SLA_TEST_ID = { 'data-testid': 'incident-management-sla' };
export const TH_PUBLISHED_TEST_ID = { 'data-testid': 'incident-management-published-sort' };
export const INCIDENT_DETAILS_PATH = 'incident';
diff --git a/app/assets/javascripts/incidents/list.js b/app/assets/javascripts/incidents/list.js
index aeec4a258b9..15af7432436 100644
--- a/app/assets/javascripts/incidents/list.js
+++ b/app/assets/javascripts/incidents/list.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { parseBoolean } from '~/lib/utils/common_utils';
import IncidentsList from './components/incidents_list.vue';
Vue.use(VueApollo);
@@ -19,6 +20,7 @@ export default () => {
textQuery,
authorUsernamesQuery,
assigneeUsernamesQuery,
+ slaFeatureAvailable,
} = domEl.dataset;
const apolloProvider = new VueApollo({
@@ -33,11 +35,12 @@ export default () => {
incidentType,
newIssuePath,
issuePath,
- publishedAvailable,
+ publishedAvailable: parseBoolean(publishedAvailable),
emptyListSvgPath,
textQuery,
authorUsernamesQuery,
assigneeUsernamesQuery,
+ slaFeatureAvailable: parseBoolean(slaFeatureAvailable),
},
apolloProvider,
components: {
diff --git a/app/assets/javascripts/pages/admin/credentials/index.js b/app/assets/javascripts/pages/admin/credentials/index.js
new file mode 100644
index 00000000000..868c8e33077
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/credentials/index.js
@@ -0,0 +1,3 @@
+import initConfirmModal from '~/confirm_modal';
+
+initConfirmModal();
diff --git a/app/assets/javascripts/pages/groups/security/credentials/index.js b/app/assets/javascripts/pages/groups/security/credentials/index.js
new file mode 100644
index 00000000000..868c8e33077
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/security/credentials/index.js
@@ -0,0 +1,3 @@
+import initConfirmModal from '~/confirm_modal';
+
+initConfirmModal();
diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
index 62b7e02c52a..1af1bc18e3e 100644
--- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
+++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
@@ -77,7 +77,7 @@ export default {
<template>
<labels-select
class="block labels js-labels-block"
- :allow-label-remove="true"
+ :allow-label-remove="allowLabelEdit"
:allow-label-create="allowLabelCreate"
:allow-label-edit="allowLabelEdit"
:allow-multiselect="true"
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 55057ba8144..d280f158c14 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -6,6 +6,7 @@ class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate
before_action :ensure_destroy_prerequisites_met, only: [:destroy]
+ before_action :check_admin_approval_feature_available!, only: [:approve]
feature_category :users
@@ -62,6 +63,16 @@ class Admin::UsersController < Admin::ApplicationController
end
end
+ def approve
+ result = Users::ApproveService.new(current_user).execute(user)
+
+ if result[:status] == :success
+ redirect_back_or_admin_user(notice: _("Successfully approved"))
+ else
+ redirect_back_or_admin_user(alert: result[:message])
+ end
+ end
+
def activate
return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user must be unblocked to be activated")) if user.blocked?
@@ -82,7 +93,7 @@ class Admin::UsersController < Admin::ApplicationController
def block
result = Users::BlockService.new(current_user).execute(user)
- if result[:status] = :success
+ if result[:status] == :success
redirect_back_or_admin_user(notice: _("Successfully blocked"))
else
redirect_back_or_admin_user(alert: _("Error occurred. User was not blocked"))
@@ -287,6 +298,10 @@ class Admin::UsersController < Admin::ApplicationController
def log_impersonation_event
Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
end
+
+ def check_admin_approval_feature_available!
+ access_denied! unless Feature.enabled?(:admin_approval_for_new_user_signups)
+ end
end
Admin::UsersController.prepend_if_ee('EE::Admin::UsersController')
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 0687c057d47..c85c83688a4 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -2,6 +2,7 @@
class ProfilesController < Profiles::ApplicationController
include ActionView::Helpers::SanitizeHelper
+ include Gitlab::Tracking
before_action :user
before_action :authorize_change_username!, only: :update_username
@@ -65,6 +66,8 @@ class ProfilesController < Profiles::ApplicationController
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id)
.order("created_at DESC")
.page(params[:page])
+
+ Gitlab::Tracking.event(self.class.name, 'search_audit_event')
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/keys_finder.rb b/app/finders/keys_finder.rb
index e7e78d71a58..9c357e12205 100644
--- a/app/finders/keys_finder.rb
+++ b/app/finders/keys_finder.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
class KeysFinder
+ delegate :find, :find_by_id, to: :execute
+
InvalidFingerprint = Class.new(StandardError)
GitLabAccessDeniedError = Class.new(StandardError)
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index 44d869fbd8f..5a42e581867 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -29,19 +29,4 @@ module ProfilesHelper
def user_profile?
params[:controller] == 'users'
end
-
- def ssh_key_delete_modal_data(key, is_admin)
- {
- path: path_to_key(key, is_admin),
- method: 'delete',
- qa_selector: 'delete_ssh_key_button',
- modal_attributes: {
- 'data-qa-selector': 'ssh_key_delete_modal',
- title: _('Are you sure you want to delete this SSH key?'),
- message: _('This action cannot be undone, and will permanently delete the %{key} SSH key') % { key: key.title },
- okVariant: 'danger',
- okTitle: _('Delete')
- }
- }
- end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 63601485daf..a032b1b2bba 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -100,7 +100,7 @@ module SearchHelper
end
def search_service
- @search_service ||= ::SearchService.new(current_user, params)
+ @search_service ||= ::SearchService.new(current_user, params.merge(confidential: Gitlab::Utils.to_boolean(params[:confidential])))
end
private
diff --git a/app/helpers/ssh_keys_helper.rb b/app/helpers/ssh_keys_helper.rb
new file mode 100644
index 00000000000..381db893943
--- /dev/null
+++ b/app/helpers/ssh_keys_helper.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module SshKeysHelper
+ def ssh_key_delete_modal_data(key, path)
+ {
+ path: path,
+ method: 'delete',
+ qa_selector: 'delete_ssh_key_button',
+ modal_attributes: {
+ 'data-qa-selector': 'ssh_key_delete_modal',
+ title: _('Are you sure you want to delete this SSH key?'),
+ message: _('This action cannot be undone, and will permanently delete the %{key} SSH key') % { key: key.title },
+ okVariant: 'danger',
+ okTitle: _('Delete')
+ }
+ }
+ end
+end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index c1bca6b4c41..f47937e6d57 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -84,7 +84,7 @@ module UsersHelper
def user_badges_in_admin_section(user)
[].tap do |badges|
- badges << { text: s_('AdminUsers|Blocked'), variant: 'danger' } if user.blocked?
+ badges << blocked_user_badge(user) if user.blocked?
badges << { text: s_('AdminUsers|Admin'), variant: 'success' } if user.admin?
badges << { text: s_('AdminUsers|External'), variant: 'secondary' } if user.external?
badges << { text: s_("AdminUsers|It's you!"), variant: nil } if current_user == user
@@ -106,8 +106,19 @@ module UsersHelper
end
end
+ def can_force_email_confirmation?(user)
+ !user.confirmed?
+ end
+
private
+ def blocked_user_badge(user)
+ pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' }
+ return pending_approval_badge if user.blocked_pending_approval?
+
+ { text: s_('AdminUsers|Blocked'), variant: 'danger' }
+ end
+
def get_profile_tabs
tabs = []
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index e6e5e671fe8..8a7bd5a7ad9 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -120,6 +120,7 @@ module ApplicationSettingImplementation
repository_checks_enabled: true,
repository_storages_weighted: { default: 100 },
repository_storages: ['default'],
+ require_admin_approval_after_user_signup: false,
require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
rsa_key_restriction: 0,
diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb
index eccf21551f0..978a54bdee7 100644
--- a/app/models/concerns/has_repository.rb
+++ b/app/models/concerns/has_repository.rb
@@ -84,7 +84,11 @@ module HasRepository
end
def default_branch_from_preferences
- empty_repo? ? Gitlab::CurrentSettings.default_branch_name : nil
+ return unless empty_repo?
+
+ group_branch_default_name = group&.default_branch_name if respond_to?(:group)
+
+ group_branch_default_name || Gitlab::CurrentSettings.default_branch_name
end
def reload_default_branch
diff --git a/app/models/group.rb b/app/models/group.rb
index 5a18441e0ad..5d0934899d1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -552,6 +552,10 @@ class Group < Namespace
owners.first || parent&.default_owner || owner
end
+ def default_branch_name
+ namespace_settings&.default_branch_name
+ end
+
def access_level_roles
GroupMember.access_level_roles
end
diff --git a/app/models/user.rb b/app/models/user.rb
index ae8da5726d6..8db449116eb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -293,6 +293,7 @@ class User < ApplicationRecord
transition active: :blocked
transition deactivated: :blocked
transition ldap_blocked: :blocked
+ transition blocked_pending_approval: :blocked
end
event :ldap_block do
@@ -338,7 +339,8 @@ class User < ApplicationRecord
# Scopes
scope :admins, -> { where(admin: true) }
- scope :blocked, -> { with_states(:blocked, :ldap_blocked, :blocked_pending_approval) }
+ scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
+ scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
scope :external, -> { where(external: true) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :active, -> { with_state(:active).non_internal }
@@ -538,6 +540,8 @@ class User < ApplicationRecord
admins
when 'blocked'
blocked
+ when 'blocked_pending_approval'
+ blocked_pending_approval
when 'two_factor_disabled'
without_two_factor
when 'two_factor_enabled'
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index de69636b078..c1ea4dddb51 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -98,6 +98,7 @@ class GlobalPolicy < BasePolicy
rule { admin }.policy do
enable :read_custom_attribute
enable :update_custom_attribute
+ enable :approve_user
end
# We can't use `read_statistics` because the user may have different permissions for different projects
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index dc7c4654208..a37011d0100 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -17,6 +17,7 @@ class DeploymentEntity < Grape::Entity
end
end
+ expose :status
expose :created_at
expose :deployed_at
expose :tag
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 70f5c7e2ea7..aad574aeaf5 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -38,6 +38,7 @@ module Groups
# Overridden in EE
def post_update_hooks(updated_project_ids)
refresh_project_authorizations
+ refresh_descendant_groups if @new_parent_group
end
def ensure_allowed_transfer
@@ -101,6 +102,8 @@ module Groups
@group.visibility_level = @new_parent_group.visibility_level
end
+ update_two_factor_authentication if @new_parent_group
+
@group.parent = @new_parent_group
@group.clear_memoization(:self_and_ancestors_ids)
@@ -129,8 +132,26 @@ module Groups
projects_to_update
.update_all(visibility_level: @new_parent_group.visibility_level)
end
+
+ def update_two_factor_authentication
+ return if namespace_parent_allows_two_factor_auth
+
+ @group.require_two_factor_authentication = false
+ end
+
+ def refresh_descendant_groups
+ return if namespace_parent_allows_two_factor_auth
+
+ if @group.descendants.where(require_two_factor_authentication: true).any?
+ DisallowTwoFactorForSubgroupsWorker.perform_async(@group.id)
+ end
+ end
# rubocop: enable CodeReuse/ActiveRecord
+ def namespace_parent_allows_two_factor_auth
+ @new_parent_group.namespace_settings.allow_mfa_for_subgroups
+ end
+
def ensure_ownership
return if @new_parent_group
return unless @group.owners.empty?
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 08d113bc0f3..d5fe89cfd59 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -4,6 +4,8 @@ module Groups
class UpdateService < Groups::BaseService
include UpdateVisibilityLevel
+ SETTINGS_PARAMS = [:allow_mfa_for_subgroups].freeze
+
def execute
reject_parent_id!
remove_unallowed_params
@@ -21,6 +23,8 @@ module Groups
return false unless update_shared_runners
+ handle_changes
+
before_assignment_hook(group, params)
handle_namespace_settings
@@ -89,6 +93,19 @@ module Groups
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id)
end
+
+ update_two_factor_requirement_for_subgroups
+ end
+
+ def update_two_factor_requirement_for_subgroups
+ settings = group.namespace_settings
+
+ return if settings.allow_mfa_for_subgroups
+
+ if settings.previous_changes.include?(:allow_mfa_for_subgroups)
+ # enque in batches members update
+ DisallowTwoFactorForSubgroupsWorker.perform_async(group.id)
+ end
end
def reject_parent_id!
@@ -101,6 +118,21 @@ module Groups
params.delete(:default_branch_protection) unless can?(current_user, :update_default_branch_protection, group)
end
+ def handle_changes
+ handle_settings_update
+ end
+
+ def handle_settings_update
+ settings_params = params.slice(*allowed_settings_params)
+ allowed_settings_params.each { |param| params.delete(param) }
+
+ ::NamespaceSettings::UpdateService.new(current_user, group, settings_params).execute
+ end
+
+ def allowed_settings_params
+ SETTINGS_PARAMS
+ end
+
def valid_share_with_group_lock_change?
return true unless changing_share_with_group_lock?
return true if can?(current_user, :change_share_with_group_lock, group)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 6fc8e8f8935..8f18a23aa0f 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -147,7 +147,7 @@ module Projects
def create_readme
commit_attrs = {
- branch_name: Gitlab::CurrentSettings.default_branch_name.presence || 'master',
+ branch_name: @project.default_branch || 'master',
commit_message: 'Initial commit',
file_path: 'README.md',
file_content: "# #{@project.name}\n\n#{@project.description}"
@@ -165,10 +165,9 @@ module Projects
@project.create_or_update_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
if @project.save
- unless @project.gitlab_project_import?
- Service.create_from_active_default_integrations(@project, :project_id, with_templates: true)
- @project.create_labels
- end
+ Service.create_from_active_default_integrations(@project, :project_id, with_templates: true)
+
+ @project.create_labels unless @project.gitlab_project_import?
unless @project.import?
raise 'Failed to create repository' unless @project.create_repository
diff --git a/app/services/users/approve_service.rb b/app/services/users/approve_service.rb
new file mode 100644
index 00000000000..228cfbd6947
--- /dev/null
+++ b/app/services/users/approve_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Users
+ class ApproveService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ return error(_('You are not allowed to approve a user')) unless allowed?
+ return error(_('The user you are trying to approve is not pending an approval')) unless approval_required?(user)
+
+ if user.activate
+ # Resends confirmation email if the user isn't confirmed yet.
+ # Please see Devise's implementation of `resend_confirmation_instructions` for detail.
+ user.resend_confirmation_instructions
+ user.accept_pending_invitations! if user.active_for_authentication?
+
+ success
+ else
+ error(user.errors.full_messages.uniq.join('. '))
+ end
+ end
+
+ private
+
+ attr_reader :current_user
+
+ def allowed?
+ can?(current_user, :approve_user)
+ end
+
+ def approval_required?(user)
+ user.blocked_pending_approval?
+ end
+ end
+end
diff --git a/app/views/admin/users/_approve_user.html.haml b/app/views/admin/users/_approve_user.html.haml
new file mode 100644
index 00000000000..b4d960d909c
--- /dev/null
+++ b/app/views/admin/users/_approve_user.html.haml
@@ -0,0 +1,7 @@
+.card.border-info
+ .card-header.gl-bg-blue-500.gl-text-white
+ = s_('AdminUsers|This user has requested access')
+ .card-body
+ = render partial: 'admin/users/user_approve_effects'
+ %br
+ = link_to s_('AdminUsers|Approve user'), approve_admin_user_path(user), method: :put, class: "btn gl-button btn-info", data: { confirm: s_('AdminUsers|Are you sure?') }
diff --git a/app/views/admin/users/_block_user.html.haml b/app/views/admin/users/_block_user.html.haml
new file mode 100644
index 00000000000..b07a72c3e28
--- /dev/null
+++ b/app/views/admin/users/_block_user.html.haml
@@ -0,0 +1,11 @@
+.card.border-warning
+ .card-header.bg-warning.text-white
+ = s_('AdminUsers|Block this user')
+ .card-body
+ = render partial: 'admin/users/user_block_effects'
+ %br
+ %button.btn.gl-button.btn-warning{ data: { 'gl-modal-action': 'block',
+ content: s_('AdminUsers|You can always unblock their account, their data will remain intact.'),
+ url: block_admin_user_path(user),
+ username: sanitize_name(user.name) } }
+ = s_('AdminUsers|Block user')
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index a60dbd51935..4abcdef7e27 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -1,13 +1,20 @@
%h3.page-title
= @user.name
- - if @user.blocked?
- %span.cred (Blocked)
+ - if @user.blocked_pending_approval?
+ %span.cred
+ = s_('AdminUsers|(Pending approval)')
+ - elsif @user.blocked?
+ %span.cred
+ = s_('AdminUsers|(Blocked)')
- if @user.internal?
- %span.cred (Internal)
+ %span.cred
+ = s_('AdminUsers|(Internal)')
- if @user.admin
- %span.cred (Admin)
+ %span.cred
+ = s_('AdminUsers|(Admin)')
- if @user.deactivated?
- %span.cred (Deactivated)
+ %span.cred
+ = s_('AdminUsers|(Deactivated)')
= render_if_exists 'admin/users/audtior_user_badge'
.float-right
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index 284307d1d54..70ab95bfa61 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -35,15 +35,22 @@
%span.small
= s_('AdminUsers|Cannot unblock LDAP blocked users')
- elsif user.blocked?
- = link_to _('Unblock'), unblock_admin_user_path(user), method: :put
+ - if user.blocked_pending_approval?
+ = link_to s_('AdminUsers|Approve'), approve_admin_user_path(user), method: :put
+ %button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'block',
+ url: block_admin_user_path(user),
+ username: sanitize_name(user.name) } }
+ = s_('AdminUsers|Block')
+ - else
+ = link_to _('Unblock'), unblock_admin_user_path(user), method: :put
- else
- %button.btn.gl-button.btn-default-tertiary{ data: { 'gl-modal-action': 'block',
+ %button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'block',
url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Block')
- if user.can_be_deactivated?
%li
- %button.btn.gl-button.btn-default-tertiary{ data: { 'gl-modal-action': 'deactivate',
+ %button.btn.btn-default-tertiary{ data: { 'gl-modal-action': 'deactivate',
url: deactivate_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Deactivate')
@@ -57,13 +64,13 @@
%li.divider
- if user.can_be_removed?
%li
- %button.delete-user-button.btn.gl-button.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
+ %button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user')
%li
- %button.delete-user-button.btn.gl-button.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
+ %button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
diff --git a/app/views/admin/users/_user_approve_effects.html.haml b/app/views/admin/users/_user_approve_effects.html.haml
new file mode 100644
index 00000000000..54e51bf3467
--- /dev/null
+++ b/app/views/admin/users/_user_approve_effects.html.haml
@@ -0,0 +1,11 @@
+%p
+ = s_('AdminUsers|Approved users can:')
+%ul
+ %li
+ = s_('AdminUsers|Log in')
+ %li
+ = s_('AdminUsers|Access Git repositories')
+ %li
+ = s_('AdminUsers|Access the API')
+ %li
+ = s_('AdminUsers|Be added to groups and projects')
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index cf6f9248461..2a5a97becaf 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -30,6 +30,11 @@
= link_to admin_users_path(filter: "blocked") do
= s_('AdminUsers|Blocked')
%small.badge.badge-pill= limited_counter_with_delimiter(User.blocked)
+ - if Feature.enabled?(:admin_approval_for_new_user_signups)
+ = nav_link(html_options: { class: "#{active_when(params[:filter] == 'blocked_pending_approval')} filter-blocked-pending-approval" }) do
+ = link_to admin_users_path(filter: "blocked_pending_approval") do
+ = s_('AdminUsers|Pending approval')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked_pending_approval)
= nav_link(html_options: { class: active_when(params[:filter] == 'deactivated') }) do
= link_to admin_users_path(filter: "deactivated") do
= s_('AdminUsers|Deactivated')
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 0ae4d987aab..9c6f151a6b1 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -137,7 +137,7 @@
.col-md-6
- unless @user == current_user
- - unless @user.confirmed?
+ - if can_force_email_confirmation?(@user)
.card.border-info
.card-header.bg-info.text-white
Confirm user
@@ -173,28 +173,23 @@
= s_('AdminUsers|Deactivate user')
- if @user.blocked?
- .card.border-info
- .card-header.bg-info.text-white
- This user is blocked
- .card-body
- %p A blocked user cannot:
- %ul
- %li Log in
- %li Access Git repositories
- %br
- = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: 'Are you sure?' }
+ - if @user.blocked_pending_approval?
+ = render 'admin/users/approve_user', user: @user
+ = render 'admin/users/block_user', user: @user
+ - else
+ .card.border-info
+ .card-header.gl-bg-blue-500.gl-text-white
+ This user is blocked
+ .card-body
+ %p A blocked user cannot:
+ %ul
+ %li Log in
+ %li Access Git repositories
+ %br
+ = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: s_('AdminUsers|Are you sure?') }
- elsif !@user.internal?
- .card.border-warning
- .card-header.bg-warning.text-white
- Block this user
- .card-body
- = render partial: 'admin/users/user_block_effects'
- %br
- %button.btn.gl-button.btn-warning{ data: { 'gl-modal-action': 'block',
- content: 'You can always unblock their account, their data will remain intact.',
- url: block_admin_user_path(@user),
- username: sanitize_name(@user.name) } }
- = s_('AdminUsers|Block user')
+ = render 'admin/users/block_user', user: @user
+
- if @user.access_locked?
.card.border-info
.card-header.bg-info.text-white
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 3f0c1596396..eaf00ce6709 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -27,6 +27,6 @@
= s_('Profiles|Created%{time_ago}'.html_safe) % { time_ago: time_ago_with_tooltip(key.created_at, html_class: 'gl-ml-2')}
- if key.can_delete?
.gl-ml-3
- = button_to '#', class: "btn btn-default gl-button btn-default-tertiary js-confirm-modal-button", data: ssh_key_delete_modal_data(key, is_admin) do
+ = button_to '#', class: "btn btn-default gl-button btn-default-tertiary js-confirm-modal-button", data: ssh_key_delete_modal_data(key, path_to_key(key, is_admin)) do
%span.sr-only= _('Delete')
= sprite_icon('remove')
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 2bc7e9eccb8..22d795ca831 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -38,4 +38,4 @@
.col-md-12
.float-right
- if @key.can_delete?
- = button_to _('Delete'), '#', class: "btn btn-danger gl-button delete-key js-confirm-modal-button", data: ssh_key_delete_modal_data(@key, is_admin)
+ = button_to _('Delete'), '#', class: "btn btn-danger gl-button delete-key js-confirm-modal-button", data: ssh_key_delete_modal_data(@key, path_to_key(@key, is_admin))
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index da684c29372..9c5cfe35cda 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -3,7 +3,7 @@
%div
- if @user.errors.any?
- .alert.alert-danger
+ .gl-alert.gl-alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 3d929fe637f..c6d39f5bba0 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,5 +1,5 @@
- @content_class = "limit-container-width" unless fluid_layout
-- default_branch_name = Gitlab::CurrentSettings.default_branch_name.presence || "master"
+- default_branch_name = @project.default_branch || "master"
- breadcrumb_title _("Details")
- page_title _("Details")
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 3eb27f002ef..f21ec45eefb 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -5,21 +5,23 @@
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0
- %li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') }
+ %li.issuable-mr.gl-display-none.gl-display-sm-block.has-tooltip{ title: _('Related merge requests') }
= image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
= issuable_mr
- if upvotes > 0
- %li.issuable-upvotes.d-none.d-sm-block.has-tooltip{ title: _('Upvotes') }
- = sprite_icon('thumb-up', css_class: "vertical-align-middle")
+ %li.issuable-upvotes.gl-display-none.gl-display-sm-block.has-tooltip{ title: _('Upvotes') }
+ = sprite_icon('thumb-up', css_class: "gl-vertical-align-middle")
= upvotes
- if downvotes > 0
- %li.issuable-downvotes.d-none.d-sm-block.has-tooltip{ title: _('Downvotes') }
- = sprite_icon('thumb-down', css_class: "vertical-align-middle")
+ %li.issuable-downvotes.gl-display-none.gl-display-sm-block.has-tooltip{ title: _('Downvotes') }
+ = sprite_icon('thumb-down', css_class: "gl-vertical-align-middle")
= downvotes
-%li.issuable-comments.d-none.d-sm-block
+= render_if_exists 'shared/issuable/blocking_issues_count', issuable: issuable
+
+%li.issuable-comments.gl-display-none.gl-display-sm-block
= link_to issuable_path, class: ['has-tooltip', ('no-comments' if note_count == 0)], title: _('Comments') do
= sprite_icon('comments', css_class: 'gl-vertical-align-text-bottom')
= note_count
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 1e2cded0618..cf27f4d9c27 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1409,6 +1409,22 @@
:weight: 1
:idempotent:
:tags: []
+- :name: disallow_two_factor_for_group
+ :feature_category: :subgroups
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
+- :name: disallow_two_factor_for_subgroups
+ :feature_category: :subgroups
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: email_receiver
:feature_category: :issue_tracking
:has_external_dependencies:
diff --git a/app/workers/disallow_two_factor_for_group_worker.rb b/app/workers/disallow_two_factor_for_group_worker.rb
new file mode 100644
index 00000000000..b3cc7a44672
--- /dev/null
+++ b/app/workers/disallow_two_factor_for_group_worker.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class DisallowTwoFactorForGroupWorker
+ include ApplicationWorker
+ include ExceptionBacktrace
+
+ feature_category :subgroups
+ idempotent!
+
+ def perform(group_id)
+ begin
+ group = Group.find(group_id)
+ rescue ActiveRecord::RecordNotFound
+ return
+ end
+
+ group.update!(require_two_factor_authentication: false)
+ end
+end
diff --git a/app/workers/disallow_two_factor_for_subgroups_worker.rb b/app/workers/disallow_two_factor_for_subgroups_worker.rb
new file mode 100644
index 00000000000..1ca227030e2
--- /dev/null
+++ b/app/workers/disallow_two_factor_for_subgroups_worker.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class DisallowTwoFactorForSubgroupsWorker
+ include ApplicationWorker
+ include ExceptionBacktrace
+
+ INTERVAL = 2.seconds.to_i
+
+ feature_category :subgroups
+ idempotent!
+
+ def perform(group_id)
+ begin
+ group = Group.find(group_id)
+ rescue ActiveRecord::RecordNotFound
+ return
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ subgroups = group.descendants.where(require_two_factor_authentication: true) # rubocop: disable CodeReuse/ActiveRecord
+ subgroups.find_each(batch_size: 100).with_index do |subgroup, index|
+ delay = index * INTERVAL
+
+ with_context(namespace: subgroup) do
+ DisallowTwoFactorForGroupWorker.perform_in(delay, subgroup.id)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/changelogs/unreleased/233785-track-audit-event-searches.yml b/changelogs/unreleased/233785-track-audit-event-searches.yml
new file mode 100644
index 00000000000..b6464a6d6e2
--- /dev/null
+++ b/changelogs/unreleased/233785-track-audit-event-searches.yml
@@ -0,0 +1,5 @@
+---
+title: Track audit event searches via Snowplow
+merge_request: 44888
+author:
+type: other
diff --git a/changelogs/unreleased/251963-respect-group-s-default-branch-name-when-present.yml b/changelogs/unreleased/251963-respect-group-s-default-branch-name-when-present.yml
new file mode 100644
index 00000000000..593be2d1450
--- /dev/null
+++ b/changelogs/unreleased/251963-respect-group-s-default-branch-name-when-present.yml
@@ -0,0 +1,5 @@
+---
+title: Respect Group's default branch name when present
+merge_request: 44370
+author:
+type: changed
diff --git a/changelogs/unreleased/267000-create-from-template-inherit-integrations.yml b/changelogs/unreleased/267000-create-from-template-inherit-integrations.yml
new file mode 100644
index 00000000000..e2e8f47b745
--- /dev/null
+++ b/changelogs/unreleased/267000-create-from-template-inherit-integrations.yml
@@ -0,0 +1,5 @@
+---
+title: Projects created from templates inherits integrations.
+merge_request: 44932
+author:
+type: changed
diff --git a/changelogs/unreleased/alipniagov-update-workhorse-to-8-51.yml b/changelogs/unreleased/alipniagov-update-workhorse-to-8-51.yml
new file mode 100644
index 00000000000..22c00c97892
--- /dev/null
+++ b/changelogs/unreleased/alipniagov-update-workhorse-to-8-51.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.51.0
+merge_request: 45256
+author:
+type: other
diff --git a/changelogs/unreleased/bugfix_coverage_fuzzing_linux_arch.yml b/changelogs/unreleased/bugfix_coverage_fuzzing_linux_arch.yml
new file mode 100644
index 00000000000..69f97bc9e08
--- /dev/null
+++ b/changelogs/unreleased/bugfix_coverage_fuzzing_linux_arch.yml
@@ -0,0 +1,5 @@
+---
+title: Remove linux arch only rule for coverage fuzzing
+merge_request: 42316
+author:
+type: fixed
diff --git a/changelogs/unreleased/upgrate-gitlab-pages-to-1-28-0.yml b/changelogs/unreleased/upgrate-gitlab-pages-to-1-28-0.yml
new file mode 100644
index 00000000000..ab5258051ae
--- /dev/null
+++ b/changelogs/unreleased/upgrate-gitlab-pages-to-1-28-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade GitLab Pages to 1.28.0
+merge_request: 45257
+author:
+type: added
diff --git a/config/feature_flags/development/canary_ingress_weight_control.yml b/config/feature_flags/development/canary_ingress_weight_control.yml
new file mode 100644
index 00000000000..681ffc98cb5
--- /dev/null
+++ b/config/feature_flags/development/canary_ingress_weight_control.yml
@@ -0,0 +1,7 @@
+---
+name: canary_ingress_weight_control
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43816
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/260295
+type: development
+group: group::progressive delivery
+default_enabled: false
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 1503e6547bf..84b9829dacf 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -17,6 +17,7 @@ namespace :admin do
put :activate
put :unlock
put :confirm
+ put :approve
post :impersonate
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 52ec38c8ef6..6129ff1f409 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -86,6 +86,10 @@
- 1
- - detect_repository_languages
- 1
+- - disallow_two_factor_for_group
+ - 1
+- - disallow_two_factor_for_subgroups
+ - 1
- - elastic_commit_indexer
- 1
- - elastic_delete_project
diff --git a/doc/api/search.md b/doc/api/search.md
index ae663f64755..bdf5bdd4924 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -24,6 +24,7 @@ GET /search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
+| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, users.
@@ -433,6 +434,7 @@ GET /groups/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
+| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
@@ -812,6 +814,7 @@ GET /projects/:id/search
| `search` | string | yes | The search query |
| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
+| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
index 0ea30fd8178..b86eb1c38b6 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -267,9 +267,14 @@ In this case, use [Firefox](https://www.mozilla.org/en-US/firefox/), [Google Chr
## Usage
-Once the integration is set up on GitLab and Jira you may refer any Jira issue by its ID in branch names, commit messages and merge request titles on GitLab's side,
-and you will be able to see the linked `branches`, `commits`, and `merge requests` when entering a Jira issue
-(inside the Jira issue, merge requests will be called "pull requests").
+After the integration is set up on GitLab and Jira, you can:
+
+- Refer to any Jira issue by its ID in GitLab branch names, commit messages, and merge request
+ titles.
+- See the linked branches, commits, and merge requests in Jira issues (merge requests are
+ called "pull requests" in Jira issues).
+
+Jira issue IDs must be formatted in uppercase for the integration to work.
![Branch, Commit and Pull Requests links on Jira issue](img/jira_dev_panel_jira_setup_3.png)
diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md
index 20cbc043d83..2eb50f07de3 100644
--- a/doc/user/group/iterations/index.md
+++ b/doc/user/group/iterations/index.md
@@ -64,6 +64,15 @@ To edit an iteration, click the three-dot menu (**{ellipsis_v}**) > **Edit itera
To learn how to add an issue to an iteration, see the steps in
[Managing issues](../../project/issues/managing_issues.md#add-an-issue-to-an-iteration).
+## View an iteration report
+
+> Viewing iteration reports in projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222763) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5.
+
+You can track the progress of an iteration by reviewing iteration reports.
+An iteration report displays a list of all the issues assigned to an iteration and their status.
+
+To view an iteration report, go to the iterations list page and click an iteration's title.
+
## Disable Iterations **(CORE ONLY)**
GitLab Iterations feature is deployed with a feature flag that is **enabled by default**.
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 2bfc929ba56..b4e02bbd5f3 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -122,6 +122,8 @@ By now you should have [configured Jira](#configuring-jira) and enabled the
you should be able to reference and close Jira issues by just mentioning their
ID in GitLab commits and merge requests.
+Jira issue IDs must be formatted in uppercase for the integration to work.
+
### Reference Jira issues
When GitLab project has Jira issue tracker configured and enabled, mentioning
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 185ab0e6298..4b4c930c7af 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -5,13 +5,16 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, concepts
---
-# Merge Request Approvals
+# Merge Request Approvals **(CORE)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/580) in GitLab Enterprise Edition 7.2. Available in GitLab Core and higher tiers.
+> - Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0.
Code review is an essential practice of every successful project, and giving your
approval once a merge request is in good shape is an important part of the review
process, as it clearly communicates the ability to merge the change.
-## Optional Approvals **(CORE ONLY)**
+## Optional Approvals
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27426) in GitLab 13.2.
@@ -335,26 +338,6 @@ of your security team when a vulnerability would be introduced by a merge reques
For more information, see
[Security approvals in merge requests](../../application_security/index.md#security-approvals-in-merge-requests).
-### Enabling the new approvals interface
-
-Since [GitLab v12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/10685), an updated approvals
-interface is available by default. In versions older than 12.0, the updated interface is not
-available unless the `approval_rules` feature flag is enabled, which can be done from
-the Rails console by instance administrators.
-
-Use these commands to start the Rails console:
-
-```shell
-# Omnibus GitLab
-gitlab-rails console
-
-# Installation from source
-cd /home/git/gitlab
-sudo -u git -H bin/rails console -e production
-```
-
-Then run `Feature.enable(:approval_rules)` to enable the updated interface.
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 0091b3d5d80..85f0a8e2e60 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -33,6 +33,7 @@ module API
scope: params[:scope],
search: params[:search],
state: params[:state],
+ confidential: params[:confidential],
snippets: snippets?,
page: params[:page],
per_page: params[:per_page]
@@ -75,6 +76,7 @@ module API
desc: 'The scope of the search',
values: Helpers::SearchHelpers.global_search_scopes
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
+ optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination
end
get do
@@ -96,6 +98,7 @@ module API
desc: 'The scope of the search',
values: Helpers::SearchHelpers.group_search_scopes
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
+ optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination
end
get ':id/(-/)search' do
@@ -118,6 +121,7 @@ module API
values: Helpers::SearchHelpers.project_search_scopes
optional :ref, type: String, desc: 'The name of a repository branch or tag. If not given, the default branch is used'
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
+ optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination
end
get ':id/(-/)search' do
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
index 4b957a8f771..e268b48d133 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
@@ -35,4 +35,3 @@ variables:
- if: $COVFUZZ_DISABLED
when: never
- if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bcoverage_fuzzing\b/
- - if: $CI_RUNNER_EXECUTABLE_ARCH == "linux"
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index fa68afd39f5..13cd6dcad3f 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -167,6 +167,21 @@ module Gitlab
end
end
+ # Ingresses resource is currently on the apis/extensions api group
+ # until Kubernetes 1.21. Kubernetest 1.22+ has ingresses resources in
+ # the networking.k8s.io/v1 api group.
+ #
+ # As we still support Kubernetes 1.12+, we will need to support both.
+ def get_ingresses(**args)
+ extensions_client.discover unless extensions_client.discovered
+
+ if extensions_client.respond_to?(:get_ingresses)
+ extensions_client.get_ingresses(**args)
+ else
+ networking_client.get_ingresses(**args)
+ end
+ end
+
def create_or_update_cluster_role_binding(resource)
update_cluster_role_binding(resource)
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 3d4920456e2..b81264c5d0c 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -219,8 +219,8 @@ module Gitlab
params[:state] = filters[:state] if filters.key?(:state)
- if Feature.enabled?(:search_filter_by_confidential) && filters.key?(:confidential) && %w(yes no).include?(filters[:confidential])
- params[:confidential] = filters[:confidential] == 'yes'
+ if [true, false].include?(filters[:confidential]) && Feature.enabled?(:search_filter_by_confidential)
+ params[:confidential] = filters[:confidential]
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2ac20d2efc8..c6f57c364dd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -630,9 +630,6 @@ msgstr ""
msgid "%{name_with_link} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
msgstr ""
-msgid "%{namespace_name} is now read-only. You cannot: %{base_message}"
-msgstr ""
-
msgid "%{name} contained %{resultsString}"
msgstr ""
@@ -2042,6 +2039,21 @@ msgstr ""
msgid "AdminStatistics|Snippets"
msgstr ""
+msgid "AdminUsers|(Admin)"
+msgstr ""
+
+msgid "AdminUsers|(Blocked)"
+msgstr ""
+
+msgid "AdminUsers|(Deactivated)"
+msgstr ""
+
+msgid "AdminUsers|(Internal)"
+msgstr ""
+
+msgid "AdminUsers|(Pending approval)"
+msgstr ""
+
msgid "AdminUsers|2FA Disabled"
msgstr ""
@@ -2051,6 +2063,12 @@ msgstr ""
msgid "AdminUsers|Access"
msgstr ""
+msgid "AdminUsers|Access Git repositories"
+msgstr ""
+
+msgid "AdminUsers|Access the API"
+msgstr ""
+
msgid "AdminUsers|Active"
msgstr ""
@@ -2063,12 +2081,30 @@ msgstr ""
msgid "AdminUsers|Admins"
msgstr ""
+msgid "AdminUsers|Approve"
+msgstr ""
+
+msgid "AdminUsers|Approve user"
+msgstr ""
+
+msgid "AdminUsers|Approved users can:"
+msgstr ""
+
+msgid "AdminUsers|Are you sure?"
+msgstr ""
+
msgid "AdminUsers|Automatically marked as default internal user"
msgstr ""
+msgid "AdminUsers|Be added to groups and projects"
+msgstr ""
+
msgid "AdminUsers|Block"
msgstr ""
+msgid "AdminUsers|Block this user"
+msgstr ""
+
msgid "AdminUsers|Block user"
msgstr ""
@@ -2123,6 +2159,9 @@ msgstr ""
msgid "AdminUsers|It's you!"
msgstr ""
+msgid "AdminUsers|Log in"
+msgstr ""
+
msgid "AdminUsers|New user"
msgstr ""
@@ -2132,6 +2171,9 @@ msgstr ""
msgid "AdminUsers|Owned groups will be left"
msgstr ""
+msgid "AdminUsers|Pending approval"
+msgstr ""
+
msgid "AdminUsers|Personal projects will be left"
msgstr ""
@@ -2177,6 +2219,9 @@ msgstr ""
msgid "AdminUsers|The user will not receive any notifications"
msgstr ""
+msgid "AdminUsers|This user has requested access"
+msgstr ""
+
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr ""
@@ -2201,6 +2246,9 @@ msgstr ""
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered."
msgstr ""
+msgid "AdminUsers|You can always unblock their account, their data will remain intact."
+msgstr ""
+
msgid "AdminUsers|You cannot remove your own admin rights."
msgstr ""
@@ -4638,6 +4686,9 @@ msgstr ""
msgid "Cannot enable shared runners because parent group does not allow it"
msgstr ""
+msgid "Cannot find user key."
+msgstr ""
+
msgid "Cannot have multiple Jira imports running at the same time"
msgstr ""
@@ -13502,9 +13553,6 @@ msgstr ""
msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
msgstr ""
-msgid "If you reach 100%% storage capacity, you will not be able to: %{base_message}"
-msgstr ""
-
msgid "If you recently signed in and recognize the IP address, you may disregard this email."
msgstr ""
@@ -13734,6 +13782,12 @@ msgstr ""
msgid "Incident Management Limits"
msgstr ""
+msgid "IncidentManagement|%{hours} hours, %{minutes} minutes remaining"
+msgstr ""
+
+msgid "IncidentManagement|%{minutes} minutes remaining"
+msgstr ""
+
msgid "IncidentManagement|All"
msgstr ""
@@ -13794,6 +13848,9 @@ msgstr ""
msgid "IncidentManagement|There was an error displaying the incidents."
msgstr ""
+msgid "IncidentManagement|Time to SLA"
+msgstr ""
+
msgid "IncidentManagement|Unassigned"
msgstr ""
@@ -17140,6 +17197,30 @@ msgstr ""
msgid "Namespace:"
msgstr ""
+msgid "NamespaceStorageSize|%{namespace_name} contains a locked project"
+msgstr ""
+
+msgid "NamespaceStorageSize|%{namespace_name} is now read-only. You cannot: %{base_message}"
+msgstr ""
+
+msgid "NamespaceStorageSize|If you reach 100%% storage capacity, you will not be able to: %{base_message}"
+msgstr ""
+
+msgid "NamespaceStorageSize|Please purchase additional storage to unlock your projects over the free 10GB project limit. You can't %{base_message}"
+msgstr ""
+
+msgid "NamespaceStorageSize|You have consumed all of your additional storage, please purchase more to unlock your projects over the free 10GB limit. You can't %{base_message}"
+msgstr ""
+
+msgid "NamespaceStorageSize|You have reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
+msgstr ""
+
+msgid "NamespaceStorageSize|You have reached the free storage limit of 10GB on %{locked_project_count} projects. To unlock them, please purchase additional storage"
+msgstr ""
+
+msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
+msgstr ""
+
msgid "Namespaces"
msgstr ""
@@ -24527,6 +24608,9 @@ msgstr ""
msgid "SortOptions|Access level, descending"
msgstr ""
+msgid "SortOptions|Blocking"
+msgstr ""
+
msgid "SortOptions|Created date"
msgstr ""
@@ -25250,6 +25334,9 @@ msgstr ""
msgid "Successfully activated"
msgstr ""
+msgid "Successfully approved"
+msgstr ""
+
msgid "Successfully blocked"
msgstr ""
@@ -26151,6 +26238,9 @@ msgstr ""
msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below."
msgstr ""
+msgid "The user you are trying to approve is not pending an approval"
+msgstr ""
+
msgid "The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"
msgstr ""
@@ -29701,6 +29791,9 @@ msgstr ""
msgid "You are going to turn on the confidentiality. This means that only team members with %{strongStart}at least Reporter access%{strongEnd} are able to see and leave comments on the %{issuableType}."
msgstr ""
+msgid "You are not allowed to approve a user"
+msgstr ""
+
msgid "You are not allowed to push into this branch. Create another branch or open a merge request."
msgstr ""
@@ -30046,9 +30139,6 @@ msgstr ""
msgid "You need to upload a Google Takeout archive."
msgstr ""
-msgid "You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
-msgstr ""
-
msgid "You successfully declined the invitation"
msgstr ""
@@ -31585,9 +31675,6 @@ msgstr ""
msgid "projects"
msgstr ""
-msgid "push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
-msgstr ""
-
msgid "quick actions"
msgstr ""
diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb
index d27e113832c..8da484003f4 100644
--- a/qa/qa/page/profile/ssh_keys.rb
+++ b/qa/qa/page/profile/ssh_keys.rb
@@ -11,7 +11,7 @@ module QA
element :add_key_button
end
- view 'app/helpers/profiles_helper.rb' do
+ view 'app/helpers/ssh_keys_helper.rb' do
element :delete_ssh_key_button
element :ssh_key_delete_modal
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 45707e156c5..64a262c7847 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -102,6 +102,58 @@ RSpec.describe Admin::UsersController do
end
end
+ describe 'PUT #approve' do
+ let(:user) { create(:user, :blocked_pending_approval) }
+
+ subject { put :approve, params: { id: user.username } }
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: false)
+ end
+
+ it 'responds with access denied' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: true)
+ end
+
+ context 'when successful' do
+ it 'activates the user' do
+ subject
+
+ user.reload
+
+ expect(user).to be_active
+ expect(flash[:notice]).to eq('Successfully approved')
+ end
+ end
+
+ context 'when unsuccessful' do
+ let(:user) { create(:user, :blocked) }
+
+ it 'displays the error' do
+ subject
+
+ expect(flash[:alert]).to eq('The user you are trying to approve is not pending an approval')
+ end
+
+ it 'does not activate the user' do
+ subject
+
+ user.reload
+ expect(user).not_to be_active
+ end
+ end
+ end
+ end
+
describe 'PUT #activate' do
shared_examples 'a request that activates the user' do
it 'activates the user' do
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index e08c92da87f..249e6322d1c 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -101,6 +101,19 @@ RSpec.describe ProfilesController, :request_store do
end
end
+ describe 'GET audit_log' do
+ it 'tracks search event', :snowplow do
+ sign_in(user)
+
+ get :audit_log
+
+ expect_snowplow_event(
+ category: 'ProfilesController',
+ action: 'search_audit_event'
+ )
+ end
+ end
+
describe 'PUT update_username' do
let(:namespace) { user.namespace }
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index af040f9ee8f..17db69e4699 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -23,7 +23,7 @@ FactoryBot.define do
end
trait :internal do
- visibility_level {Gitlab::VisibilityLevel::INTERNAL }
+ visibility_level { Gitlab::VisibilityLevel::INTERNAL }
end
trait :private do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 8b7750ab4ec..e06e2d14f3c 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -62,6 +62,43 @@ RSpec.describe "Admin::Users" do
end
end
+ describe 'tabs' do
+ it 'has multiple tabs to filter users' do
+ expect(page).to have_link('Active', href: admin_users_path)
+ expect(page).to have_link('Admins', href: admin_users_path(filter: 'admins'))
+ expect(page).to have_link('2FA Enabled', href: admin_users_path(filter: 'two_factor_enabled'))
+ expect(page).to have_link('2FA Disabled', href: admin_users_path(filter: 'two_factor_disabled'))
+ expect(page).to have_link('External', href: admin_users_path(filter: 'external'))
+ expect(page).to have_link('Blocked', href: admin_users_path(filter: 'blocked'))
+ expect(page).to have_link('Deactivated', href: admin_users_path(filter: 'deactivated'))
+ expect(page).to have_link('Without projects', href: admin_users_path(filter: 'wop'))
+ end
+
+ context '`Pending approval` tab' do
+ context 'feature is enabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: true)
+ visit admin_users_path
+ end
+
+ it 'shows the `Pending approval` tab' do
+ expect(page).to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
+ end
+ end
+
+ context 'feature is disabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: false)
+ visit admin_users_path
+ end
+
+ it 'does not show the `Pending approval` tab' do
+ expect(page).not_to have_link('Pending approval', href: admin_users_path(filter: 'blocked_pending_approval'))
+ end
+ end
+ end
+ end
+
describe 'search and sort' do
before do
create(:user, name: 'Foo Bar', last_activity_on: 3.days.ago)
@@ -160,6 +197,27 @@ RSpec.describe "Admin::Users" do
expect(page).to have_content(user.email)
end
end
+
+ describe 'Pending approval filter' do
+ it 'counts users who are pending approval' do
+ create_list(:user, 2, :blocked_pending_approval)
+
+ visit admin_users_path
+
+ page.within('.filter-blocked-pending-approval small') do
+ expect(page).to have_content('2')
+ end
+ end
+
+ it 'filters by users who are pending approval' do
+ user = create(:user, :blocked_pending_approval)
+
+ visit admin_users_path
+ click_link 'Pending approval'
+
+ expect(page).to have_content(user.email)
+ end
+ end
end
describe "GET /admin/users/new" do
@@ -301,6 +359,23 @@ RSpec.describe "Admin::Users" do
expect(page).to have_button('Delete user and contributions')
end
+ context 'user pending approval' do
+ it 'shows user info' do
+ user = create(:user, :blocked_pending_approval)
+
+ visit admin_users_path
+ click_link 'Pending approval'
+ click_link user.name
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('Pending approval')
+ expect(page).to have_link('Approve user')
+ expect(page).to have_button('Block user')
+ expect(page).to have_button('Delete user')
+ expect(page).to have_button('Delete user and contributions')
+ end
+ end
+
describe 'Impersonation' do
let(:another_user) { create(:user) }
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index caf8176a5d7..de746415205 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -13,319 +13,349 @@ RSpec.describe "Issues > User edits issue", :js do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:milestones) { create_list(:milestone, 25, project: project_with_milestones) }
- before do
- project.add_developer(user)
- project_with_milestones.add_developer(user)
- sign_in(user)
- end
-
- context "from edit page" do
+ context 'with authorized user' do
before do
- visit edit_project_issue_path(project, issue)
+ project.add_developer(user)
+ project_with_milestones.add_developer(user)
+ sign_in(user)
end
- it "previews content" do
- form = first(".gfm-form")
-
- page.within(form) do
- fill_in("Description", with: "Bug fixed :smile:")
- click_button("Preview")
+ context "from edit page" do
+ before do
+ visit edit_project_issue_path(project, issue)
end
- expect(form).to have_button("Write")
- end
+ it "previews content" do
+ form = first(".gfm-form")
- it 'allows user to select unassigned' do
- visit edit_project_issue_path(project, issue)
+ page.within(form) do
+ fill_in("Description", with: "Bug fixed :smile:")
+ click_button("Preview")
+ end
- expect(page).to have_content "Assignee #{user.name}"
+ expect(form).to have_button("Write")
+ end
- first('.js-user-search').click
- click_link 'Unassigned'
+ it 'allows user to select unassigned' do
+ visit edit_project_issue_path(project, issue)
- click_button 'Save changes'
+ expect(page).to have_content "Assignee #{user.name}"
- page.within('.assignee') do
- expect(page).to have_content 'None - assign yourself'
- end
- end
+ first('.js-user-search').click
+ click_link 'Unassigned'
- context 'with due date' do
- before do
- visit edit_project_issue_path(project, issue)
+ click_button 'Save changes'
+
+ page.within('.assignee') do
+ expect(page).to have_content 'None - assign yourself'
+ end
end
- it 'saves with due date' do
- date = Date.today.at_beginning_of_month.tomorrow
+ context 'with due date' do
+ before do
+ visit edit_project_issue_path(project, issue)
+ end
- fill_in 'issue_title', with: 'bug 345'
- fill_in 'issue_description', with: 'bug description'
- find('#issuable-due-date').click
+ it 'saves with due date' do
+ date = Date.today.at_beginning_of_month.tomorrow
- page.within '.pika-single' do
- click_button date.day
- end
+ fill_in 'issue_title', with: 'bug 345'
+ fill_in 'issue_description', with: 'bug description'
+ find('#issuable-due-date').click
- expect(find('#issuable-due-date').value).to eq date.to_s
+ page.within '.pika-single' do
+ click_button date.day
+ end
- click_button 'Save changes'
+ expect(find('#issuable-due-date').value).to eq date.to_s
- page.within '.issuable-sidebar' do
- expect(page).to have_content date.to_s(:medium)
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ expect(page).to have_content date.to_s(:medium)
+ end
end
- end
- it 'warns about version conflict' do
- issue.update(title: "New title")
+ it 'warns about version conflict' do
+ issue.update(title: "New title")
- fill_in 'issue_title', with: 'bug 345'
- fill_in 'issue_description', with: 'bug description'
+ fill_in 'issue_title', with: 'bug 345'
+ fill_in 'issue_description', with: 'bug description'
- click_button 'Save changes'
+ click_button 'Save changes'
- expect(page).to have_content 'Someone edited the issue the same time you did'
+ expect(page).to have_content 'Someone edited the issue the same time you did'
+ end
end
end
- end
- context "from issue#show" do
- before do
- visit project_issue_path(project, issue)
- end
+ context "from issue#show" do
+ before do
+ visit project_issue_path(project, issue)
+ end
- describe 'update labels' do
- it 'will not send ajax request when no data is changed' do
- page.within '.labels' do
- click_on 'Edit'
+ describe 'update labels' do
+ it 'will not send ajax request when no data is changed' do
+ page.within '.labels' do
+ click_on 'Edit'
- find('.dropdown-title button').click
+ find('.dropdown-title button').click
- expect(page).not_to have_selector('.block-loading')
- expect(page).not_to have_selector('.gl-spinner')
+ expect(page).not_to have_selector('.block-loading')
+ expect(page).not_to have_selector('.gl-spinner')
+ end
end
- end
- it 'can add label to issue' do
- page.within '.block.labels' do
- expect(page).to have_text('verisimilitude')
- expect(page).not_to have_text('syzygy')
+ it 'can add label to issue' do
+ page.within '.block.labels' do
+ expect(page).to have_text('verisimilitude')
+ expect(page).not_to have_text('syzygy')
- click_on 'Edit'
+ click_on 'Edit'
- wait_for_requests
+ wait_for_requests
- click_on 'syzygy'
- find('.dropdown-header-button').click
+ click_on 'syzygy'
+ find('.dropdown-header-button').click
- wait_for_requests
+ wait_for_requests
- expect(page).to have_text('verisimilitude')
- expect(page).to have_text('syzygy')
+ expect(page).to have_text('verisimilitude')
+ expect(page).to have_text('syzygy')
+ end
end
- end
- it 'can remove label from issue by clicking on the label `x` button' do
- page.within '.block.labels' do
- expect(page).to have_text('verisimilitude')
+ it 'can remove label from issue by clicking on the label `x` button' do
+ page.within '.block.labels' do
+ expect(page).to have_text('verisimilitude')
- within '.gl-label' do
- click_button
- end
+ within '.gl-label' do
+ click_button
+ end
- wait_for_requests
+ wait_for_requests
- expect(page).not_to have_text('verisimilitude')
+ expect(page).not_to have_text('verisimilitude')
+ end
end
end
- end
- describe 'update assignee' do
- context 'by authorized user' do
- def close_dropdown_menu_if_visible
- find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
- toggle.click if toggle.visible?
+ describe 'update assignee' do
+ context 'by authorized user' do
+ def close_dropdown_menu_if_visible
+ find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
+ toggle.click if toggle.visible?
+ end
end
- end
- it 'allows user to select unassigned' do
- visit project_issue_path(project, issue)
+ it 'allows user to select unassigned' do
+ visit project_issue_path(project, issue)
- page.within('.assignee') do
- expect(page).to have_content "#{user.name}"
+ page.within('.assignee') do
+ expect(page).to have_content "#{user.name}"
- click_link 'Edit'
- click_link 'Unassigned'
- first('.title').click
- expect(page).to have_content 'None - assign yourself'
+ click_link 'Edit'
+ click_link 'Unassigned'
+ first('.title').click
+ expect(page).to have_content 'None - assign yourself'
+ end
end
- end
- it 'allows user to select an assignee' do
- issue2 = create(:issue, project: project, author: user)
- visit project_issue_path(project, issue2)
+ it 'allows user to select an assignee' do
+ issue2 = create(:issue, project: project, author: user)
+ visit project_issue_path(project, issue2)
- page.within('.assignee') do
- expect(page).to have_content "None"
- end
+ page.within('.assignee') do
+ expect(page).to have_content "None"
+ end
- page.within '.assignee' do
- click_link 'Edit'
- end
+ page.within '.assignee' do
+ click_link 'Edit'
+ end
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
- page.within('.assignee') do
- expect(page).to have_content user.name
+ page.within('.assignee') do
+ expect(page).to have_content user.name
+ end
end
- end
- it 'allows user to unselect themselves' do
- issue2 = create(:issue, project: project, author: user, assignees: [user])
+ it 'allows user to unselect themselves' do
+ issue2 = create(:issue, project: project, author: user, assignees: [user])
- visit project_issue_path(project, issue2)
+ visit project_issue_path(project, issue2)
- page.within '.assignee' do
- expect(page).to have_content user.name
+ page.within '.assignee' do
+ expect(page).to have_content user.name
- click_link 'Edit'
- click_link user.name
+ click_link 'Edit'
+ click_link user.name
- close_dropdown_menu_if_visible
+ close_dropdown_menu_if_visible
- page.within '.value .assign-yourself' do
- expect(page).to have_content "None"
+ page.within '.value .assign-yourself' do
+ expect(page).to have_content "None"
+ end
end
end
end
- end
- context 'by unauthorized user' do
- let(:guest) { create(:user) }
+ context 'by unauthorized user' do
+ let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- end
+ before do
+ project.add_guest(guest)
+ end
- it 'shows assignee text' do
- sign_out(:user)
- sign_in(guest)
+ it 'shows assignee text' do
+ sign_out(:user)
+ sign_in(guest)
- visit project_issue_path(project, issue)
- expect(page).to have_content issue.assignees.first.name
+ visit project_issue_path(project, issue)
+ expect(page).to have_content issue.assignees.first.name
+ end
end
end
- end
- describe 'update milestone' do
- context 'by authorized user' do
- it 'allows user to select unassigned' do
- visit project_issue_path(project, issue)
+ describe 'update milestone' do
+ context 'by authorized user' do
+ it 'allows user to select unassigned' do
+ visit project_issue_path(project, issue)
- page.within('.milestone') do
- expect(page).to have_content "None"
- end
+ page.within('.milestone') do
+ expect(page).to have_content "None"
+ end
- find('.block.milestone .edit-link').click
- sleep 2 # wait for ajax stuff to complete
- first('.dropdown-content li').click
- sleep 2
- page.within('.milestone') do
- expect(page).to have_content 'None'
+ find('.block.milestone .edit-link').click
+ sleep 2 # wait for ajax stuff to complete
+ first('.dropdown-content li').click
+ sleep 2
+ page.within('.milestone') do
+ expect(page).to have_content 'None'
+ end
end
- end
- it 'allows user to de-select milestone' do
- visit project_issue_path(project, issue)
+ it 'allows user to de-select milestone' do
+ visit project_issue_path(project, issue)
- page.within('.milestone') do
- click_link 'Edit'
- click_link milestone.title
+ page.within('.milestone') do
+ click_link 'Edit'
+ click_link milestone.title
- page.within '.value' do
- expect(page).to have_content milestone.title
- end
+ page.within '.value' do
+ expect(page).to have_content milestone.title
+ end
- click_link 'Edit'
- click_link milestone.title
+ click_link 'Edit'
+ click_link milestone.title
- page.within '.value' do
- expect(page).to have_content 'None'
+ page.within '.value' do
+ expect(page).to have_content 'None'
+ end
end
end
- end
- it 'allows user to search milestone' do
- visit project_issue_path(project_with_milestones, issue_with_milestones)
+ it 'allows user to search milestone' do
+ visit project_issue_path(project_with_milestones, issue_with_milestones)
- page.within('.milestone') do
- click_link 'Edit'
- wait_for_requests
- # We need to enclose search string in quotes for exact match as all the milestone titles
- # within tests are prefixed with `My title`.
- find('.dropdown-input-field', visible: true).send_keys "\"#{milestones[0].title}\""
- wait_for_requests
+ page.within('.milestone') do
+ click_link 'Edit'
+ wait_for_requests
+ # We need to enclose search string in quotes for exact match as all the milestone titles
+ # within tests are prefixed with `My title`.
+ find('.dropdown-input-field', visible: true).send_keys "\"#{milestones[0].title}\""
+ wait_for_requests
- page.within '.dropdown-content' do
- expect(page).to have_content milestones[0].title
+ page.within '.dropdown-content' do
+ expect(page).to have_content milestones[0].title
+ end
end
end
end
- end
- context 'by unauthorized user' do
- let(:guest) { create(:user) }
+ context 'by unauthorized user' do
+ let(:guest) { create(:user) }
- before do
- project.add_guest(guest)
- issue.milestone = milestone
- issue.save
- end
+ before do
+ project.add_guest(guest)
+ issue.milestone = milestone
+ issue.save
+ end
- it 'shows milestone text' do
- sign_out(:user)
- sign_in(guest)
+ it 'shows milestone text' do
+ sign_out(:user)
+ sign_in(guest)
- visit project_issue_path(project, issue)
- expect(page).to have_content milestone.title
+ visit project_issue_path(project, issue)
+ expect(page).to have_content milestone.title
+ end
end
end
- end
- context 'update due date' do
- it 'adds due date to issue' do
- date = Date.today.at_beginning_of_month + 2.days
+ context 'update due date' do
+ it 'adds due date to issue' do
+ date = Date.today.at_beginning_of_month + 2.days
- page.within '.due_date' do
- click_link 'Edit'
+ page.within '.due_date' do
+ click_link 'Edit'
- page.within '.pika-single' do
- click_button date.day
- end
+ page.within '.pika-single' do
+ click_button date.day
+ end
- wait_for_requests
+ wait_for_requests
- expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
+ expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
+ end
end
- end
- it 'removes due date from issue' do
- date = Date.today.at_beginning_of_month + 2.days
+ it 'removes due date from issue' do
+ date = Date.today.at_beginning_of_month + 2.days
- page.within '.due_date' do
- click_link 'Edit'
+ page.within '.due_date' do
+ click_link 'Edit'
- page.within '.pika-single' do
- click_button date.day
+ page.within '.pika-single' do
+ click_button date.day
+ end
+
+ wait_for_requests
+
+ expect(page).to have_no_content 'None'
+
+ click_link 'remove due date'
+ expect(page).to have_content 'None'
end
+ end
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(user)
+ end
- wait_for_requests
+ context "from issue#show" do
+ before do
+ visit project_issue_path(project, issue)
+ end
- expect(page).to have_no_content 'None'
+ describe 'updating labels' do
+ it 'cannot edit labels' do
+ page.within '.block.labels' do
+ expect(page).not_to have_button('Edit')
+ end
+ end
- click_link 'remove due date'
- expect(page).to have_content 'None'
+ it 'cannot remove label with a click as it has no `x` button' do
+ page.within '.block.labels' do
+ within '.gl-label' do
+ expect(page).not_to have_button
+ end
+ end
end
end
end
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index 935e4ec8b8e..5b0437d9263 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -55,6 +55,7 @@ describe('Incidents List', () => {
const findLoader = () => wrapper.find(GlLoadingIcon);
const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip);
const findSearch = () => wrapper.find(FilteredSearchBar);
+ const findIncidentSlaHeader = () => wrapper.find('[data-testid="incident-management-sla"]');
const findAssignees = () => wrapper.findAll('[data-testid="incident-assignees"]');
const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
@@ -64,11 +65,16 @@ describe('Incidents List', () => {
const findStatusTabs = () => wrapper.find(GlTabs);
const findEmptyState = () => wrapper.find(GlEmptyState);
const findSeverity = () => wrapper.findAll(SeverityToken);
+ const findIncidentSla = () => wrapper.findAll("[data-testid='incident-sla']");
- function mountComponent({ data = { incidents: [], incidentsCount: {} }, loading = false }) {
+ function mountComponent({ data = {}, loading = false, provide = {} } = {}) {
wrapper = mount(IncidentsList, {
data() {
- return data;
+ return {
+ incidents: [],
+ incidentsCount: {},
+ ...data,
+ };
},
mocks: {
$apollo: {
@@ -90,11 +96,14 @@ describe('Incidents List', () => {
textQuery: '',
authorUsernamesQuery: '',
assigneeUsernamesQuery: '',
+ slaFeatureAvailable: true,
+ ...provide,
},
stubs: {
GlButton: true,
GlAvatar: true,
GlEmptyState: true,
+ ServiceLevelAgreementCell: true,
},
});
}
@@ -204,6 +213,35 @@ describe('Incidents List', () => {
joinPaths(`/project/issues/incident`, mockIncidents[0].iid),
);
});
+
+ describe('Incident SLA field', () => {
+ it('displays the column when the feature is available', () => {
+ mountComponent({
+ data: { incidents: { list: mockIncidents } },
+ provide: { slaFeatureAvailable: true },
+ });
+
+ expect(findIncidentSlaHeader().text()).toContain('Time to SLA');
+ });
+
+ it('does not display the column when the feature is not available', () => {
+ mountComponent({
+ data: { incidents: { list: mockIncidents } },
+ provide: { slaFeatureAvailable: false },
+ });
+
+ expect(findIncidentSlaHeader().exists()).toBe(false);
+ });
+
+ it('renders an SLA for each incident', () => {
+ mountComponent({
+ data: { incidents: { list: mockIncidents } },
+ provide: { slaFeatureAvailable: true },
+ });
+
+ expect(findIncidentSla().length).toBe(mockIncidents.length);
+ });
+ });
});
describe('Create Incident', () => {
diff --git a/spec/frontend/incidents/mocks/incidents.json b/spec/frontend/incidents/mocks/incidents.json
index 42b3d6d3eb6..07c87a5d43d 100644
--- a/spec/frontend/incidents/mocks/incidents.json
+++ b/spec/frontend/incidents/mocks/incidents.json
@@ -5,7 +5,8 @@
"createdAt": "2020-06-03T15:46:08Z",
"assignees": {},
"state": "opened",
- "severity": "CRITICAL"
+ "severity": "CRITICAL",
+ "slaDueAt": "2020-06-04T12:46:08Z"
},
{
"iid": "14",
@@ -22,7 +23,8 @@
]
},
"state": "opened",
- "severity": "HIGH"
+ "severity": "HIGH",
+ "slaDueAt": null
},
{
"iid": "13",
diff --git a/spec/helpers/projects/incidents_helper_spec.rb b/spec/helpers/projects/incidents_helper_spec.rb
index 68a5ce4eb91..5caf98f5693 100644
--- a/spec/helpers/projects/incidents_helper_spec.rb
+++ b/spec/helpers/projects/incidents_helper_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Projects::IncidentsHelper do
subject(:data) { helper.incidents_data(project, params) }
it 'returns frontend configuration' do
- expect(data).to match(
+ expect(data).to include(
'project-path' => project_path,
'new-issue-path' => new_issue_path,
'incident-template-name' => 'incident',
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b0ed5634680..6fe071521cd 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -448,4 +448,33 @@ RSpec.describe SearchHelper do
end
end
end
+
+ describe '#search_service' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { search_service }
+
+ before do
+ allow(self).to receive(:current_user).and_return(:the_current_user)
+ end
+
+ where(:confidential, :expected) do
+ '0' | false
+ '1' | true
+ 'yes' | true
+ 'no' | false
+ true | true
+ false | false
+ end
+
+ let(:params) {{ confidential: confidential }}
+
+ with_them do
+ it 'transforms confidentiality param' do
+ expect(::SearchService).to receive(:new).with(:the_current_user, { confidential: expected })
+
+ subject
+ end
+ end
+ end
end
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 8dfdb23c64b..c9dc3fcff3f 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -126,6 +126,16 @@ RSpec.describe UsersHelper do
end
end
+ context 'with a pending approval user' do
+ it 'returns the pending approval badge' do
+ blocked_pending_approval_user = create(:user, :blocked_pending_approval)
+
+ badges = helper.user_badges_in_admin_section(blocked_pending_approval_user)
+
+ expect(filter_ee_badges(badges)).to eq([text: 'Pending approval', variant: 'info'])
+ end
+ end
+
context 'with an admin user' do
it "returns the admin badge" do
admin_user = create(:admin)
@@ -179,6 +189,20 @@ RSpec.describe UsersHelper do
end
end
+ describe '#can_force_email_confirmation?' do
+ subject { helper.can_force_email_confirmation?(user) }
+
+ context 'for a user that is already confirmed' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'for a user that is not confirmed' do
+ let(:user) { create(:user, :unconfirmed) }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
describe '#work_information' do
subject { helper.work_information(user) }
diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
index 364be9cdc97..2dae4a65eeb 100644
--- a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
+++ b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
@@ -96,7 +96,6 @@ RSpec.describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMent
# this not does not have actual mentions
let!(:note4) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: 'note for an email@somesite.com and some other random @ ref' ) }
-
# this should have pointed to an innexisted commit record in a commits table
# but because commit is not an AR we'll just make it so that it does not have mentions
let!(:note5) { notes.create!(commit_id: 'abc', noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: 'note for an email@somesite.com and some other random @ ref') }
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index b161832c018..7b6d143dda9 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -347,6 +347,34 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
end
end
+ describe '#get_ingresses' do
+ let(:extensions_client) { client.extensions_client }
+ let(:networking_client) { client.networking_client }
+
+ include_examples 'redirection not allowed', 'get_ingresses'
+ include_examples 'dns rebinding not allowed', 'get_ingresses'
+
+ it 'delegates to the extensions client' do
+ expect(extensions_client).to receive(:get_ingresses)
+
+ client.get_ingresses
+ end
+
+ context 'extensions does not have deployments for Kubernetes 1.22+ clusters' do
+ before do
+ WebMock
+ .stub_request(:get, api_url + '/apis/extensions/v1beta1')
+ .to_return(kube_response(kube_1_22_extensions_v1beta1_discovery_body))
+ end
+
+ it 'delegates to the apps client' do
+ expect(networking_client).to receive(:get_ingresses)
+
+ client.get_ingresses
+ end
+ end
+ end
+
describe 'istio API group' do
let(:istio_client) { client.istio_client }
diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
index 833021a22ca..38a7d967c33 100644
--- a/spec/lib/gitlab/visibility_level_checker_spec.rb
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -5,16 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::VisibilityLevelChecker do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:visibility_level_checker) { }
let(:override_params) { {} }
- subject { described_class.new(user, project, project_params: override_params) }
-
describe '#level_restricted?' do
+ subject(:result) { described_class.new(user, project, project_params: override_params).level_restricted? }
+
context 'when visibility level is allowed' do
it 'returns false with nil for visibility level' do
- result = subject.level_restricted?
-
expect(result.restricted?).to eq(false)
expect(result.visibility_level).to be_nil
end
@@ -25,12 +22,26 @@ RSpec.describe Gitlab::VisibilityLevelChecker do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
- it 'returns true and visibility name' do
- project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- result = subject.level_restricted?
+ context 'for public project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ context 'for non-admin user' do
+ it 'returns true and visibility name' do
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context 'for admin user' do
+ let(:user) { create(:user, :admin) }
- expect(result.restricted?).to eq(true)
- expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ it 'returns false and a nil visibility level' do
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
end
context 'overridden visibility' do
@@ -50,8 +61,6 @@ RSpec.describe Gitlab::VisibilityLevelChecker do
let(:override_visibility) { 'public' }
it 'returns true and visibility name' do
- result = subject.level_restricted?
-
expect(result.restricted?).to eq(true)
expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
@@ -61,8 +70,6 @@ RSpec.describe Gitlab::VisibilityLevelChecker do
let(:override_visibility) { 'publik' }
it 'returns false with nil for visibility level' do
- result = subject.level_restricted?
-
expect(result.restricted?).to eq(false)
expect(result.visibility_level).to be_nil
end
@@ -72,8 +79,6 @@ RSpec.describe Gitlab::VisibilityLevelChecker do
let(:override_params) { {} }
it 'returns false with nil for visibility level' do
- result = subject.level_restricted?
-
expect(result.restricted?).to eq(false)
expect(result.visibility_level).to be_nil
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index c6a2b67a008..e877ba2ac96 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -412,7 +412,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
end
let(:namespace) { "project-namespace" }
- let(:environment) { instance_double(Environment, deployment_namespace: namespace) }
+ let(:environment) { instance_double(Environment, deployment_namespace: namespace, project: service.cluster.project) }
subject { service.calculate_reactive_cache_for(environment) }
@@ -428,6 +428,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
before do
stub_kubeclient_pods(namespace)
stub_kubeclient_deployments(namespace)
+ stub_kubeclient_ingresses(namespace)
end
it { is_expected.to include(pods: [expected_pod_cached_data]) }
@@ -437,6 +438,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
before do
stub_kubeclient_pods(namespace, status: 500)
stub_kubeclient_deployments(namespace, status: 500)
+ stub_kubeclient_ingresses(namespace, status: 500)
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
@@ -446,6 +448,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
before do
stub_kubeclient_pods(namespace, status: 404)
stub_kubeclient_deployments(namespace, status: 404)
+ stub_kubeclient_ingresses(namespace, status: 404)
end
it { is_expected.to include(pods: []) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 6c5c690a54f..a5262fabb08 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1506,6 +1506,28 @@ RSpec.describe Group do
end
end
+ describe "#default_branch_name" do
+ context "group.namespace_settings does not have a default branch name" do
+ it "returns nil" do
+ expect(group.default_branch_name).to be_nil
+ end
+ end
+
+ context "group.namespace_settings has a default branch name" do
+ let(:example_branch_name) { "example_branch_name" }
+
+ before do
+ expect(group.namespace_settings)
+ .to receive(:default_branch_name)
+ .and_return(example_branch_name)
+ end
+
+ it "returns the default branch name" do
+ expect(group.default_branch_name).to eq(example_branch_name)
+ end
+ end
+ end
+
describe '#default_owner' do
let(:group) { build(:group) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 062a4744b9f..17480584812 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4979,15 +4979,21 @@ RSpec.describe Project do
context "with an empty repository" do
let_it_be(:project) { create(:project_empty_repo) }
- context "Gitlab::CurrentSettings.default_branch_name is unavailable" do
+ context "group.default_branch_name is available" do
+ let(:project_group) { create(:group) }
+ let(:project) { create(:project, path: 'avatar', namespace: project_group) }
+
before do
expect(Gitlab::CurrentSettings)
+ .not_to receive(:default_branch_name)
+
+ expect(project.group)
.to receive(:default_branch_name)
- .and_return(nil)
+ .and_return('example_branch')
end
- it "returns that value" do
- expect(project.default_branch).to be_nil
+ it "returns the group default value" do
+ expect(project.default_branch).to eq("example_branch")
end
end
@@ -4995,11 +5001,23 @@ RSpec.describe Project do
before do
expect(Gitlab::CurrentSettings)
.to receive(:default_branch_name)
- .and_return('example_branch')
+ .and_return(example_branch_name)
end
- it "returns that value" do
- expect(project.default_branch).to eq("example_branch")
+ context "is missing or nil" do
+ let(:example_branch_name) { nil }
+
+ it "returns nil" do
+ expect(project.default_branch).to be_nil
+ end
+ end
+
+ context "is present" do
+ let(:example_branch_name) { "example_branch_name" }
+
+ it "returns the expected branch name" do
+ expect(project.default_branch).to eq(example_branch_name)
+ end
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 578bdc4e5b4..64bff5d00aa 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -705,22 +705,31 @@ RSpec.describe User do
end
describe "scopes" do
- describe '.blocked' do
- subject { described_class.blocked }
-
- it 'returns only blocked users' do
- active_user = create(:user)
- blocked_user = create(:user, :blocked)
- blocked_pending_approval_user = create(:user, :blocked_pending_approval)
- ldap_blocked_user = create(:omniauth_user, :ldap_blocked)
-
- expect(subject).to include(
- blocked_user,
- blocked_pending_approval_user,
- ldap_blocked_user
- )
+ context 'blocked users' do
+ let_it_be(:active_user) { create(:user) }
+ let_it_be(:blocked_user) { create(:user, :blocked) }
+ let_it_be(:ldap_blocked_user) { create(:omniauth_user, :ldap_blocked) }
+ let_it_be(:blocked_pending_approval_user) { create(:user, :blocked_pending_approval) }
+
+ describe '.blocked' do
+ subject { described_class.blocked }
+
+ it 'returns only blocked users' do
+ expect(subject).to include(
+ blocked_user,
+ ldap_blocked_user
+ )
+
+ expect(subject).not_to include(active_user, blocked_pending_approval_user)
+ end
+ end
- expect(subject).not_to include(active_user)
+ describe '.blocked_pending_approval' do
+ subject { described_class.blocked_pending_approval }
+
+ it 'returns only pending approval users' do
+ expect(subject).to contain_exactly(blocked_pending_approval_user)
+ end
end
end
@@ -1752,6 +1761,12 @@ RSpec.describe User do
expect(described_class.filter_items('blocked')).to include user
end
+ it 'filters by blocked pending approval' do
+ expect(described_class).to receive(:blocked_pending_approval).and_return([user])
+
+ expect(described_class.filter_items('blocked_pending_approval')).to include user
+ end
+
it 'filters by deactivated' do
expect(described_class).to receive(:deactivated).and_return([user])
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index e3fbd7116e8..2f9376f9b0a 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -130,6 +130,24 @@ RSpec.describe GlobalPolicy do
end
end
+ describe 'approving users' do
+ context 'regular user' do
+ it { is_expected.not_to be_allowed(:approve_user) }
+ end
+
+ context 'admin' do
+ let(:current_user) { create(:admin) }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:approve_user) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_disallowed(:approve_user) }
+ end
+ end
+ end
+
describe 'using project statistics filters' do
context 'regular user' do
it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 523f0f72f11..05cfad9cc62 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -58,6 +58,17 @@ RSpec.describe API::Search do
end
end
+ shared_examples 'filter by confidentiality' do |scope:, search:|
+ it 'respects confidentiality filtering' do
+ get api(endpoint, user), params: { scope: scope, search: search, confidential: confidential.to_s }
+
+ documents = Gitlab::Json.parse(response.body)
+
+ expect(documents.count).to eq(1)
+ expect(documents.first['confidential']).to eq(confidential)
+ end
+ end
+
describe 'GET /search' do
let(:endpoint) { '/search' }
@@ -137,6 +148,26 @@ RSpec.describe API::Search do
include_examples 'filter by state', scope: :issues, search: 'awesome'
end
end
+
+ context 'filter by confidentiality' do
+ before do
+ stub_feature_flags(search_filter_by_confidential: true)
+ create(:issue, project: project, author: user, title: 'awesome non-confidential issue')
+ create(:issue, :confidential, project: project, author: user, title: 'awesome confidential issue')
+ end
+
+ context 'confidential: true' do
+ let(:confidential) { true }
+
+ include_examples 'filter by confidentiality', scope: :issues, search: 'awesome'
+ end
+
+ context 'confidential: false' do
+ let(:confidential) { false }
+
+ include_examples 'filter by confidentiality', scope: :issues, search: 'awesome'
+ end
+ end
end
context 'for merge_requests scope' do
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 27673b905d3..588675f5232 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -30,6 +30,10 @@ RSpec.describe DeploymentEntity do
expect(subject[:ref][:name]).to eq 'master'
end
+ it 'exposes status' do
+ expect(subject).to include(:status)
+ end
+
it 'exposes creation date' do
expect(subject).to include(:created_at)
end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 6144b86a316..ae04eca3a9f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -567,6 +567,39 @@ RSpec.describe Groups::TransferService do
end
end
+ context 'when transferring a group with two factor authentication switched on' do
+ before do
+ TestEnv.clean_test_path
+ create(:group_member, :owner, group: new_parent_group, user: user)
+ create(:group, :private, parent: group, require_two_factor_authentication: true)
+ group.update!(require_two_factor_authentication: true)
+ end
+
+ it 'does not update group two factor authentication setting' do
+ transfer_service.execute(new_parent_group)
+
+ expect(group.require_two_factor_authentication).to eq(true)
+ end
+
+ context 'when new parent disallows two factor authentication switched on for descendants' do
+ before do
+ new_parent_group.namespace_settings.update!(allow_mfa_for_subgroups: false)
+ end
+
+ it 'updates group two factor authentication setting' do
+ transfer_service.execute(new_parent_group)
+
+ expect(group.require_two_factor_authentication).to eq(false)
+ end
+
+ it 'schedules update of group two factor authentication setting for descendants' do
+ expect(DisallowTwoFactorForSubgroupsWorker).to receive(:perform_async).with(group.id)
+
+ transfer_service.execute(new_parent_group)
+ end
+ end
+ end
+
context 'when updating the group goes wrong' do
let!(:subgroup1) { create(:group, :public, parent: group) }
let!(:subgroup2) { create(:group, :public, parent: group) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index a79cda86a86..bc7c066fa04 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -308,6 +308,25 @@ RSpec.describe Groups::UpdateService do
end
end
+ context 'changes allowing subgroups to establish own 2FA' do
+ let(:group) { create(:group) }
+ let(:params) { { allow_mfa_for_subgroups: false } }
+
+ subject { described_class.new(group, user, params).execute }
+
+ it 'changes settings' do
+ subject
+
+ expect(group.namespace_settings.reload.allow_mfa_for_subgroups).to eq(false)
+ end
+
+ it 'enqueues update subgroups and its members' do
+ expect(DisallowTwoFactorForSubgroupsWorker).to receive(:perform_async).with(group.id)
+
+ subject
+ end
+ end
+
def update_group(group, user, opts)
Groups::UpdateService.new(group, user, opts).execute
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 717358ef814..d959cc87901 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -14,15 +14,30 @@ RSpec.describe Projects::CreateService, '#execute' do
}
end
- it 'creates labels on Project creation if there are templates' do
- Label.create!(title: "bug", template: true)
- project = create_project(user, opts)
+ context 'with labels' do
+ subject(:project) { create_project(user, opts) }
+
+ before_all do
+ Label.create!(title: 'bug', template: true)
+ end
- created_label = project.reload.labels.last
+ it 'creates labels on project creation' do
+ created_label = project.labels.last
- expect(created_label.type).to eq('ProjectLabel')
- expect(created_label.project_id).to eq(project.id)
- expect(created_label.title).to eq('bug')
+ expect(created_label.type).to eq('ProjectLabel')
+ expect(created_label.project_id).to eq(project.id)
+ expect(created_label.title).to eq('bug')
+ end
+
+ context 'using gitlab project import' do
+ before do
+ opts[:import_type] = 'gitlab_project'
+ end
+
+ it 'does not creates labels on project creation' do
+ expect(project.labels.size).to eq(0)
+ end
+ end
end
context 'user namespace' do
@@ -59,10 +74,6 @@ RSpec.describe Projects::CreateService, '#execute' do
context "admin creates project with other user's namespace_id" do
it 'sets the correct permissions' do
admin = create(:admin)
- opts = {
- name: 'GitLab',
- namespace_id: user.namespace.id
- }
project = create_project(admin, opts)
expect(project).to be_persisted
diff --git a/spec/services/users/approve_service_spec.rb b/spec/services/users/approve_service_spec.rb
new file mode 100644
index 00000000000..50f2b6b0827
--- /dev/null
+++ b/spec/services/users/approve_service_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::ApproveService do
+ let_it_be(:current_user) { create(:admin) }
+ let(:user) { create(:user, :blocked_pending_approval) }
+
+ subject(:execute) { described_class.new(current_user).execute(user) }
+
+ describe '#execute' do
+ context 'failures' do
+ context 'when the executor user is not allowed to approve users' do
+ let(:current_user) { create(:user) }
+
+ it 'returns error result' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to match(/You are not allowed to approve a user/)
+ end
+ end
+
+ context 'when user is not in pending approval state' do
+ let(:user) { create(:user, state: 'active') }
+
+ it 'returns error result' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message])
+ .to match(/The user you are trying to approve is not pending an approval/)
+ end
+ end
+
+ context 'when user cannot be activated' do
+ let(:user) do
+ build(:user, state: 'blocked_pending_approval', email: 'invalid email')
+ end
+
+ it 'returns error result' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to match(/Email is invalid/)
+ end
+
+ it 'does not change the state of the user' do
+ expect { subject }.not_to change { user.state }
+ end
+ end
+ end
+
+ context 'success' do
+ it 'activates the user' do
+ expect(subject[:status]).to eq(:success)
+ expect(user.reload).to be_active
+ end
+
+ context 'email confirmation status' do
+ context 'user is unconfirmed' do
+ let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
+
+ it 'sends confirmation instructions' do
+ expect { subject }
+ .to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ end
+ end
+
+ context 'user is confirmed' do
+ it 'does not send a confirmation email' do
+ expect { subject }
+ .not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ end
+ end
+ end
+
+ context 'pending invitiations' do
+ let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
+ let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) }
+
+ context 'user is unconfirmed' do
+ let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
+
+ it 'does not accept pending invites of the user' do
+ expect(subject[:status]).to eq(:success)
+
+ group_member_invite.reload
+ project_member_invite.reload
+
+ expect(group_member_invite).to be_invite
+ expect(project_member_invite).to be_invite
+ end
+ end
+
+ context 'user is confirmed' do
+ it 'accepts pending invites of the user' do
+ expect(subject[:status]).to eq(:success)
+
+ group_member_invite.reload
+ project_member_invite.reload
+
+ expect(group_member_invite).not_to be_invite
+ expect(project_member_invite).not_to be_invite
+ expect(group_member_invite.user).to eq(user)
+ expect(project_member_invite.user).to eq(user)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 90ddab89943..113bb31e4be 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -33,6 +33,10 @@ module KubernetesHelpers
kube_response(kube_deployments_body)
end
+ def kube_ingresses_response
+ kube_response(kube_ingresses_body)
+ end
+
def stub_kubeclient_discover_base(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
WebMock
@@ -63,6 +67,9 @@ module KubernetesHelpers
WebMock
.stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
.to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
+ WebMock
+ .stub_request(:get, api_url + '/apis/networking.k8s.io/v1')
+ .to_return(kube_response(kube_v1_networking_discovery_body))
end
def stub_kubeclient_discover_knative_not_found(api_url)
@@ -148,6 +155,14 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
+ def stub_kubeclient_ingresses(namespace, status: nil)
+ stub_kubeclient_discover(service.api_url)
+ ingresses_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/ingresses"
+ response = { status: status } if status
+
+ WebMock.stub_request(:get, ingresses_url).to_return(response || kube_ingresses_response)
+ end
+
def stub_kubeclient_knative_services(options = {})
namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
@@ -304,6 +319,14 @@ module KubernetesHelpers
}
end
+ # From Kubernetes 1.22+ Ingresses are no longer served from apis/extensions
+ def kube_1_22_extensions_v1beta1_discovery_body
+ {
+ "kind" => "APIResourceList",
+ "resources" => []
+ }
+ end
+
def kube_knative_discovery_body
{
"kind" => "APIResourceList",
@@ -416,6 +439,17 @@ module KubernetesHelpers
}
end
+ def kube_v1_networking_discovery_body
+ {
+ "kind" => "APIResourceList",
+ "apiVersion" => "v1",
+ "groupVersion" => "networking.k8s.io/v1",
+ "resources" => [
+ { "name" => "ingresses", "namespaced" => true, "kind" => "Ingress" }
+ ]
+ }
+ end
+
def kube_istio_gateway_body(name, namespace)
{
"apiVersion" => "networking.istio.io/v1alpha3",
@@ -507,6 +541,13 @@ module KubernetesHelpers
}
end
+ def kube_ingresses_body
+ {
+ "kind" => "List",
+ "items" => [kube_ingress]
+ }
+ end
+
def kube_knative_pods_body(name, namespace)
{
"kind" => "PodList",
@@ -548,6 +589,38 @@ module KubernetesHelpers
}
end
+ def kube_ingress(track: :stable)
+ additional_annotations =
+ if track == :canary
+ {
+ "nginx.ingress.kubernetes.io/canary" => "true",
+ "nginx.ingress.kubernetes.io/canary-by-header" => "canary",
+ "nginx.ingress.kubernetes.io/canary-weight" => "50"
+ }
+ else
+ {}
+ end
+
+ {
+ "metadata" => {
+ "name" => "production-auto-deploy",
+ "labels" => {
+ "app" => "production",
+ "app.kubernetes.io/managed-by" => "Helm",
+ "chart" => "auto-deploy-app-2.0.0-beta.2",
+ "heritage" => "Helm",
+ "release" => "production"
+ },
+ "annotations" => {
+ "kubernetes.io/ingress.class" => "nginx",
+ "kubernetes.io/tls-acme" => "true",
+ "meta.helm.sh/release-name" => "production",
+ "meta.helm.sh/release-namespace" => "awesome-app-1-production"
+ }.merge(additional_annotations)
+ }
+ }
+ end
+
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_node
@@ -862,8 +935,8 @@ module KubernetesHelpers
end
end
- def kube_deployment_rollout_status
- ::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
+ def kube_deployment_rollout_status(ingresses: [])
+ ::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment, ingresses: ingresses)
end
def empty_deployment_rollout_status
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index a520b59a2f9..55bf981e0fb 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -44,7 +44,8 @@ RSpec.shared_context 'project navbar structure' do
_('Boards'),
_('Labels'),
_('Service Desk'),
- _('Milestones')
+ _('Milestones'),
+ _('Iterations')
]
},
{
diff --git a/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
index 6c8ab38413d..d0bef2ad730 100644
--- a/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb
@@ -24,7 +24,7 @@ RSpec.shared_examples 'search results filtered by confidential' do
end
context 'confidential filter' do
- let(:filters) { { confidential: 'yes' } }
+ let(:filters) { { confidential: true } }
context 'when Feature search_filter_by_confidential enabled' do
it 'returns only confidential results', :aggregate_failures do
@@ -46,7 +46,7 @@ RSpec.shared_examples 'search results filtered by confidential' do
end
context 'not confidential filter' do
- let(:filters) { { confidential: 'no' } }
+ let(:filters) { { confidential: false } }
context 'when Feature search_filter_by_confidential enabled' do
it 'returns not confidential results', :aggregate_failures do
@@ -66,26 +66,4 @@ RSpec.shared_examples 'search results filtered by confidential' do
end
end
end
-
- context 'unsupported filter' do
- let(:filters) { { confidential: 'goodbye' } }
-
- context 'when Feature search_filter_by_confidential enabled' do
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
- end
-
- context 'when Feature search_filter_by_confidential not enabled' do
- before do
- stub_feature_flags(search_filter_by_confidential: false)
- end
-
- it 'returns confidential and not confidential results', :aggregate_failures do
- expect(results.objects('issues')).to include confidential_result
- expect(results.objects('issues')).to include opened_result
- end
- end
- end
end
diff --git a/spec/workers/disallow_two_factor_for_group_worker_spec.rb b/spec/workers/disallow_two_factor_for_group_worker_spec.rb
new file mode 100644
index 00000000000..a69dd893f81
--- /dev/null
+++ b/spec/workers/disallow_two_factor_for_group_worker_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DisallowTwoFactorForGroupWorker do
+ let_it_be(:group) { create(:group, require_two_factor_authentication: true) }
+ let_it_be(:user) { create(:user, require_two_factor_authentication_from_group: true) }
+
+ it "updates group" do
+ described_class.new.perform(group.id)
+
+ expect(group.reload.require_two_factor_authentication).to eq(false)
+ end
+
+ it "updates group members" do
+ group.add_user(user, GroupMember::DEVELOPER)
+
+ described_class.new.perform(group.id)
+
+ expect(user.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+end
diff --git a/spec/workers/disallow_two_factor_for_subgroups_worker_spec.rb b/spec/workers/disallow_two_factor_for_subgroups_worker_spec.rb
new file mode 100644
index 00000000000..c3be8263171
--- /dev/null
+++ b/spec/workers/disallow_two_factor_for_subgroups_worker_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DisallowTwoFactorForSubgroupsWorker do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup_with_2fa) { create(:group, parent: group, require_two_factor_authentication: true) }
+ let_it_be(:subgroup_without_2fa) { create(:group, parent: group, require_two_factor_authentication: false) }
+ let_it_be(:subsubgroup_with_2fa) { create(:group, parent: subgroup_with_2fa, require_two_factor_authentication: true) }
+
+ it "schedules updating subgroups" do
+ expect(DisallowTwoFactorForGroupWorker).to receive(:perform_in).with(0, subgroup_with_2fa.id)
+ expect(DisallowTwoFactorForGroupWorker).to receive(:perform_in).with(2, subsubgroup_with_2fa.id)
+
+ described_class.new.perform(group.id)
+ end
+end