diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index d29aedff63f..a0df088b088 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -800,7 +800,6 @@ RSpec/ContextWording:
- 'ee/spec/services/projects/operations/update_service_spec.rb'
- 'ee/spec/services/projects/protect_default_branch_service_spec.rb'
- 'ee/spec/services/projects/restore_service_spec.rb'
- - 'ee/spec/services/projects/slack_application_install_service_spec.rb'
- 'ee/spec/services/projects/transfer_service_spec.rb'
- 'ee/spec/services/projects/update_mirror_service_spec.rb'
- 'ee/spec/services/projects/update_service_spec.rb'
diff --git a/.rubocop_todo/rspec/expect_in_hook.yml b/.rubocop_todo/rspec/expect_in_hook.yml
index ceeffd1f098..4b588f265c9 100644
--- a/.rubocop_todo/rspec/expect_in_hook.yml
+++ b/.rubocop_todo/rspec/expect_in_hook.yml
@@ -4,7 +4,6 @@ RSpec/ExpectInHook:
- 'ee/spec/controllers/ee/projects/merge_requests/content_controller_spec.rb'
- 'ee/spec/controllers/groups/analytics/productivity_analytics_controller_spec.rb'
- 'ee/spec/controllers/groups/seat_usage_controller_spec.rb'
- - 'ee/spec/controllers/projects/settings/slacks_controller_spec.rb'
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
- 'ee/spec/elastic/migrate/20220118150500_delete_orphaned_commits_spec.rb'
- 'ee/spec/elastic/migrate/20220119120500_populate_commit_permissions_in_main_index_spec.rb'
diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml
index 4257adbdc6f..81b154f906b 100644
--- a/.rubocop_todo/style/class_and_module_children.yml
+++ b/.rubocop_todo/style/class_and_module_children.yml
@@ -412,7 +412,6 @@ Style/ClassAndModuleChildren:
- 'ee/app/controllers/groups/wikis_controller.rb'
- 'ee/app/controllers/oauth/geo_auth_controller.rb'
- 'ee/app/controllers/profiles/billings_controller.rb'
- - 'ee/app/controllers/profiles/slacks_controller.rb'
- 'ee/app/controllers/projects/analytics/issues_analytics_controller.rb'
- 'ee/app/controllers/projects/analytics/merge_request_analytics_controller.rb'
- 'ee/app/controllers/projects/approver_groups_controller.rb'
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index adf3e8ee9b2..184e0d926d5 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -94,7 +94,6 @@ Style/EmptyMethod:
- 'ee/app/controllers/projects/security/dast_scanner_profiles_controller.rb'
- 'ee/app/controllers/projects/security/dast_site_profiles_controller.rb'
- 'ee/app/controllers/projects/security/sast_configuration_controller.rb'
- - 'ee/app/controllers/projects/settings/slacks_controller.rb'
- 'ee/app/controllers/subscriptions/groups_controller.rb'
- 'ee/app/models/ee/epic.rb'
- 'ee/app/services/feature_flag_issues/destroy_service.rb'
diff --git a/.rubocop_todo/style/guard_clause.yml b/.rubocop_todo/style/guard_clause.yml
index 10d4a22876a..4fcefa6ef85 100644
--- a/.rubocop_todo/style/guard_clause.yml
+++ b/.rubocop_todo/style/guard_clause.yml
@@ -274,7 +274,6 @@ Style/GuardClause:
- 'ee/app/controllers/profiles/billings_controller.rb'
- 'ee/app/controllers/projects/path_locks_controller.rb'
- 'ee/app/controllers/projects/security/policies_controller.rb'
- - 'ee/app/controllers/projects/settings/slacks_controller.rb'
- 'ee/app/controllers/smartcard_controller.rb'
- 'ee/app/finders/ee/template_finder.rb'
- 'ee/app/finders/iterations_finder.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index 78e27951786..9fdfb6c960d 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -411,7 +411,6 @@ Style/IfUnlessModifier:
- 'ee/app/controllers/projects/integrations/zentao/issues_controller.rb'
- 'ee/app/controllers/projects/path_locks_controller.rb'
- 'ee/app/controllers/projects/push_rules_controller.rb'
- - 'ee/app/controllers/projects/settings/slacks_controller.rb'
- 'ee/app/finders/security/pipeline_vulnerabilities_finder.rb'
- 'ee/app/finders/security/vulnerabilities_finder.rb'
- 'ee/app/graphql/mutations/audit_events/external_audit_event_destinations/create.rb'
diff --git a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
index 89d7a2c958c..c53007b68cf 100644
--- a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue
@@ -133,8 +133,3 @@ export default {
-
diff --git a/app/assets/javascripts/content_editor/content_editor.stories.js b/app/assets/javascripts/content_editor/content_editor.stories.js
index b98050b61ea..1aa6568848f 100644
--- a/app/assets/javascripts/content_editor/content_editor.stories.js
+++ b/app/assets/javascripts/content_editor/content_editor.stories.js
@@ -1,9 +1,11 @@
+import { withGitLabAPIAccess } from 'storybook_addons/gitlab_api_access';
import Api from '~/api';
import { ContentEditor } from './index';
export default {
component: ContentEditor,
title: 'ce/content_editor/content_editor',
+ decorators: [withGitLabAPIAccess],
};
const Template = (_, { argTypes }) => ({
diff --git a/app/controllers/profiles/slacks_controller.rb b/app/controllers/profiles/slacks_controller.rb
new file mode 100644
index 00000000000..7c78c01416a
--- /dev/null
+++ b/app/controllers/profiles/slacks_controller.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Profiles
+ class SlacksController < Profiles::ApplicationController
+ include IntegrationsHelper
+
+ skip_before_action :authenticate_user!
+
+ layout 'application'
+
+ feature_category :integrations
+
+ def edit
+ @projects = disabled_projects.inc_routes if current_user
+ end
+
+ def slack_link
+ project = disabled_projects.find(params[:project_id])
+ link = add_to_slack_link(project, Gitlab::CurrentSettings.slack_app_id)
+
+ render json: { add_to_slack_link: link }
+ end
+
+ private
+
+ def disabled_projects
+ @disabled_projects ||= current_user
+ .authorized_projects(Gitlab::Access::MAINTAINER)
+ .with_slack_application_disabled
+ end
+ end
+end
diff --git a/app/controllers/projects/settings/slacks_controller.rb b/app/controllers/projects/settings/slacks_controller.rb
new file mode 100644
index 00000000000..4e55103cb4c
--- /dev/null
+++ b/app/controllers/projects/settings/slacks_controller.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Projects
+ module Settings
+ class SlacksController < Projects::ApplicationController
+ before_action :handle_oauth_error, only: :slack_auth
+ before_action :check_oauth_state, only: :slack_auth
+ before_action :authorize_admin_project!
+ before_action :slack_integration, only: [:edit, :update]
+ before_action :service, only: [:destroy, :edit, :update]
+
+ layout 'project_settings'
+
+ feature_category :integrations
+
+ def slack_auth
+ result = Projects::SlackApplicationInstallService.new(project, current_user, params).execute
+
+ flash[:alert] = result[:message] if result[:status] == :error
+
+ session[:slack_install_success] = true
+ redirect_to_service_page
+ end
+
+ def destroy
+ slack_integration.destroy
+
+ redirect_to_service_page
+ end
+
+ def edit; end
+
+ def update
+ if slack_integration.update(slack_integration_params)
+ flash[:notice] = 'The project alias was updated successfully'
+
+ redirect_to_service_page
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def redirect_to_service_page
+ redirect_to edit_project_settings_integration_path(
+ project,
+ project.gitlab_slack_application_integration || project.build_gitlab_slack_application_integration
+ )
+ end
+
+ def check_oauth_state
+ render_403 unless valid_authenticity_token?(session, params[:state])
+
+ true
+ end
+
+ def handle_oauth_error
+ return unless params[:error] == 'access_denied'
+
+ flash[:alert] = 'Access denied'
+ redirect_to_service_page
+ end
+
+ def slack_integration
+ @slack_integration ||= project.gitlab_slack_application_integration.slack_integration
+ end
+
+ def service
+ @service = project.gitlab_slack_application_integration
+ end
+
+ def slack_integration_params
+ params.require(:slack_integration).permit(:alias)
+ end
+ end
+ end
+end
diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb
index 5471109e6d5..ffea23bf55d 100644
--- a/app/helpers/integrations_helper.rb
+++ b/app/helpers/integrations_helper.rb
@@ -136,6 +136,11 @@ module IntegrationsHelper
form_data[:jira_issue_transition_id] = integration.jira_issue_transition_id
end
+ if integration.is_a?(::Integrations::GitlabSlackApplication)
+ form_data[:upgrade_slack_url] = add_to_slack_link(project, slack_app_id)
+ form_data[:should_upgrade_slack] = integration.upgrade_needed?.to_s
+ end
+
form_data
end
@@ -212,6 +217,28 @@ module IntegrationsHelper
event_i18n_map[event] || event.to_s.humanize
end
+ def add_to_slack_link(project, slack_app_id)
+ query = {
+ scope: SlackIntegration::SCOPES.join(','),
+ client_id: slack_app_id,
+ redirect_uri: slack_auth_project_settings_slack_url(project),
+ state: form_authenticity_token
+ }
+
+ "#{::Projects::SlackApplicationInstallService::SLACK_AUTHORIZE_URL}?#{query.to_query}"
+ end
+
+ def gitlab_slack_application_data(projects)
+ {
+ projects: (projects || []).to_json(only: [:id, :name], methods: [:avatar_url, :name_with_namespace]),
+ sign_in_path: new_session_path(:user, redirect_to_referer: 'yes'),
+ is_signed_in: current_user.present?.to_s,
+ slack_link_path: slack_link_profile_slack_path,
+ gitlab_logo_path: image_path('illustrations/gitlab_logo.svg'),
+ slack_logo_path: image_path('illustrations/slack_logo.svg')
+ }
+ end
+
extend self
private
diff --git a/app/models/user.rb b/app/models/user.rb
index 3016532fc1c..c5c0353e6f4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2279,6 +2279,14 @@ class User < ApplicationRecord
abuse_trust_scores.telesign.order(created_at: :desc).first&.score || 0.0
end
+ def arkose_global_score
+ abuse_trust_scores.arkose_global_score.order(created_at: :desc).first&.score || 0.0
+ end
+
+ def arkose_custom_score
+ abuse_trust_scores.arkose_custom_score.order(created_at: :desc).first&.score || 0.0
+ end
+
def trust_scores_for_source(source)
abuse_trust_scores.where(source: source)
end
diff --git a/app/services/projects/slack_application_install_service.rb b/app/services/projects/slack_application_install_service.rb
new file mode 100644
index 00000000000..812b8b0a082
--- /dev/null
+++ b/app/services/projects/slack_application_install_service.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Projects
+ class SlackApplicationInstallService < BaseService
+ include Gitlab::Routing
+
+ # Endpoint to initiate the OAuth flow, redirects to Slack's authorization screen
+ # https://api.slack.com/authentication/oauth-v2#asking
+ SLACK_AUTHORIZE_URL = 'https://slack.com/oauth/v2/authorize'
+
+ # Endpoint to exchange the temporary authorization code for an access token
+ # https://api.slack.com/authentication/oauth-v2#exchanging
+ SLACK_EXCHANGE_TOKEN_URL = 'https://slack.com/api/oauth.v2.access'
+
+ def execute
+ slack_data = exchange_slack_token
+
+ return error("Slack: #{slack_data['error']}") unless slack_data['ok']
+
+ integration = project.gitlab_slack_application_integration \
+ || project.create_gitlab_slack_application_integration!
+
+ installation = integration.slack_integration || integration.build_slack_integration
+
+ installation.update!(
+ bot_user_id: slack_data['bot_user_id'],
+ bot_access_token: slack_data['access_token'],
+ team_id: slack_data.dig('team', 'id'),
+ team_name: slack_data.dig('team', 'name'),
+ alias: project.full_path,
+ user_id: slack_data.dig('authed_user', 'id'),
+ authorized_scope_names: slack_data['scope']
+ )
+
+ update_legacy_installations!(installation)
+
+ success
+ end
+
+ private
+
+ def exchange_slack_token
+ query = {
+ client_id: Gitlab::CurrentSettings.slack_app_id,
+ client_secret: Gitlab::CurrentSettings.slack_app_secret,
+ code: params[:code],
+ # NOTE: Needs to match the `redirect_uri` passed to the authorization endpoint,
+ # otherwise we get a `bad_redirect_uri` error.
+ redirect_uri: slack_auth_project_settings_slack_url(project)
+ }
+
+ Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
+ end
+
+ # Update any legacy SlackIntegration records for the Slack Workspace. Legacy SlackIntegration records
+ # are any created before our Slack App was upgraded to use Granular Bot Permissions and issue a
+ # bot_access_token. Any SlackIntegration records for the Slack Workspace will already have the same
+ # bot_access_token.
+ def update_legacy_installations!(installation)
+ updatable_attributes = installation.attributes.slice(
+ 'user_id',
+ 'bot_user_id',
+ 'encrypted_bot_access_token',
+ 'encrypted_bot_access_token_iv',
+ 'updated_at'
+ )
+
+ SlackIntegration.by_team(installation.team_id).id_not_in(installation.id).each_batch do |batch|
+ batch_ids = batch.pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
+ batch.update_all(updatable_attributes)
+
+ ::Integrations::SlackWorkspace::IntegrationApiScope.update_scopes(batch_ids, installation.slack_api_scopes)
+ end
+ end
+ end
+end
diff --git a/app/views/profiles/slacks/edit.html.haml b/app/views/profiles/slacks/edit.html.haml
new file mode 100644
index 00000000000..20274735650
--- /dev/null
+++ b/app/views/profiles/slacks/edit.html.haml
@@ -0,0 +1,6 @@
+- add_to_breadcrumbs _('Profile'), profile_path
+- @hide_top_links = true
+- @content_class = 'limit-container-width'
+- page_title s_('SlackIntegration|GitLab for Slack')
+
+.js-gitlab-slack-application{ data: gitlab_slack_application_data(@projects) }
diff --git a/app/views/projects/settings/slacks/edit.html.haml b/app/views/projects/settings/slacks/edit.html.haml
new file mode 100644
index 00000000000..867b90655e3
--- /dev/null
+++ b/app/views/projects/settings/slacks/edit.html.haml
@@ -0,0 +1,20 @@
+- page_title _('Edit Slack integration')
+
+.row.gl-mt-3.gl-mb-3
+ .col-lg-3
+ %h4.gl-mt-0
+ = s_('Integrations|Edit project alias')
+
+ %p= s_('Integrations|You can use this alias in your Slack commands')
+ .col-lg-9
+ = form_errors(@slack_integration)
+ = form_for(@slack_integration, url: project_settings_slack_path(@project), method: :put, html: { class: 'gl-show-field-errors js-integration-settings-form'}) do |form|
+ .form-group.row
+ = form.label :alias, s_('Integrations|Enter your alias'), class: 'col-form-label'
+ .col-sm-10
+ = form.text_field :alias, class: 'form-control', placeholder: @slack_integration.alias, required: true
+
+ .footer-block.row-content-block
+ = form.submit _('Save changes'), pajamas_button: true
+
+ = link_to _('Cancel'), edit_project_settings_integration_path(@project, @service), class: 'btn gl-button btn-cancel'
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index f42f6a9037d..73c8d63b8ec 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -39,6 +39,13 @@ resource :profile, only: [:show, :update] do
put :reset
end
end
+
+ resource :slack, only: [:edit] do
+ member do
+ get :slack_link
+ end
+ end
+
resource :preferences, only: [:show, :update]
resources :comment_templates, only: [:index, :show], action: :index
diff --git a/config/routes/project.rb b/config/routes/project.rb
index f296143dca8..bf73f461629 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -144,6 +144,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ resource :slack, only: [:destroy, :edit, :update] do
+ get :slack_auth
+ end
+
resource :repository, only: [:show, :update], controller: :repository do
# TODO: Removed this "create_deploy_token" route after change was made in app/helpers/ci_variables_helper.rb:14
# See MR comment for more detail: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27059#note_311585356
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 4b90fe5e108..39052d29287 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -224,6 +224,7 @@ const alias = {
test_fixtures_static: path.join(ROOT_PATH, 'spec/frontend/fixtures/static'),
test_helpers: path.join(ROOT_PATH, 'spec/frontend_integration/test_helpers'),
public: path.join(ROOT_PATH, 'public'),
+ storybook_addons: path.resolve(ROOT_PATH, 'storybook/config/addons'),
};
if (IS_EE) {
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 758d75ee294..41e71cbac6a 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -1747,7 +1747,7 @@ If the above steps are **not successful**, proceed through the next steps:
If different operating systems or different operating system versions are deployed across Geo sites, you should perform a locale data compatibility check before setting up Geo.
-Geo uses PostgreSQL and Streaming Replication to replicate data across Geo sites. PostgreSQL uses locale data provided by the operating system's C library for sorting text. If the locale data in the C library is incompatible across Geo sites, erroneous query results that lead to [incorrect behavior on secondary sites](https://gitlab.com/gitlab-org/gitlab/-/issues/360723).
+Geo uses PostgreSQL and Streaming Replication to replicate data across Geo sites. PostgreSQL uses locale data provided by the operating system's C library for sorting text. If the locale data in the C library is incompatible across Geo sites, it can cause erroneous query results that lead to [incorrect behavior on secondary sites](https://gitlab.com/gitlab-org/gitlab/-/issues/360723).
For example, Ubuntu 18.04 (and earlier) and RHEL/Centos7 (and earlier) are incompatible with their later releases.
See the [PostgreSQL wiki for more details](https://wiki.postgresql.org/wiki/Locale_data_changes).
diff --git a/doc/administration/silent_mode/index.md b/doc/administration/silent_mode/index.md
index d51a06045a5..e6bfe5b58ca 100644
--- a/doc/administration/silent_mode/index.md
+++ b/doc/administration/silent_mode/index.md
@@ -65,9 +65,9 @@ This section documents the current behavior of GitLab when Silent Mode is enable
Incoming emails still raise issues, but the users who sent the emails to [Service Desk](../../user/project/service_desk.md) are not notified of issue creation or comments on their issues.
-### Project and group webhooks
+### Webhooks
-Project and group webhooks are suppressed. The relevant Sidekiq jobs fail 4 times and then disappear, while Silent Mode is enabled. [Issue 393639](https://gitlab.com/gitlab-org/gitlab/-/issues/393639) discusses preventing the Sidekiq jobs from running in the first place.
+[Project and group webhooks](../../user/project/integrations/webhooks.md), and [system hooks](../system_hooks.md) are suppressed. The relevant Sidekiq jobs fail 4 times and then disappear, while Silent Mode is enabled. [Issue 393639](https://gitlab.com/gitlab-org/gitlab/-/issues/393639) discusses preventing the Sidekiq jobs from running in the first place.
Triggering webhook tests via the UI results in HTTP status 500 responses.
diff --git a/doc/api/users.md b/doc/api/users.md
index a69bae1c2cf..a148f780f84 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -2219,13 +2219,14 @@ Returns:
- `403 Forbidden` if not authenticated as an administrator.
- `404 User Not Found` if user cannot be found.
-## Create a runner **(FREE SELF)**
+## Create a runner **(FREE)**
Creates a runner linked to the current user.
Prerequisites:
- You must be an administrator or have the Owner role of the target namespace or project.
+- For `instance_type`, you must be an administrator of the GitLab instance.
Be sure to copy or save the `token` in the response, the value cannot be retrieved again.
diff --git a/doc/development/fe_guide/storybook.md b/doc/development/fe_guide/storybook.md
index 44169f110c7..eaa8f8b4068 100644
--- a/doc/development/fe_guide/storybook.md
+++ b/doc/development/fe_guide/storybook.md
@@ -78,12 +78,29 @@ a starting point.
You can also use the GitLab API Access panel in the Storybook UI to set the GitLab instance URL and access token.
-### Using REST API
+### Set up API access in your stories
+
+You should apply the `withGitLabAPIAccess` decorator to the stories that will consume GitLab’s APIs. This decorator
+will display a badge indicating that the story won't work without providing the API access parameters:
+
+```javascript
+import { withGitLabAPIAccess } from 'storybook_addons/gitlab_api_access';
+import Api from '~/api';
+import { ContentEditor } from './index';
+
+export default {
+ component: ContentEditor,
+ title: 'ce/content_editor/content_editor',
+ decorators: [withGitLabAPIAccess],
+};
+```
+
+#### Using REST API
The Storybook sets up `~/lib/utils/axios_utils` in `storybook/config/preview.js`. Components that use the REST API
should work out of the box as long as you provide a valid GitLab instance URL and access token.
-### Using GraphQL
+#### Using GraphQL
To write a story for a component that uses the GraphQL API, use the `createVueApollo` method provided in
the Story context.
@@ -91,6 +108,7 @@ the Story context.
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { withGitLabAPIAccess } from 'storybook_addons/gitlab_api_access';
import WorkspacesList from './list.vue';
Vue.use(VueApollo);
@@ -99,6 +117,9 @@ const Template = (_, { argTypes, createVueApollo }) => {
return {
components: { WorkspacesList },
apolloProvider: createVueApollo(),
+ provide: {
+ emptyStateSvgPath: '',
+ },
props: Object.keys(argTypes),
template: '