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
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-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
38 files changed, 360 insertions, 88 deletions
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