diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 00:10:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 00:10:11 +0000 |
commit | b5452c76b5b35884482214dbf6fe9971e0276d3b (patch) | |
tree | 45eaee5a27650a3c63a742398054cb666838aae3 | |
parent | af969ba2792f5d872f446398abcf9895573bd292 (diff) | |
download | gitlab-ce-b5452c76b5b35884482214dbf6fe9971e0276d3b.tar.gz |
Add latest changes from gitlab-org/gitlab@master
32 files changed, 349 insertions, 65 deletions
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 05e0b9e7089..990c8faf253 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -12,6 +12,7 @@ import { getProjectSlug, spriteIcon, } from './lib/utils/common_utils'; +import Tracking from '~/tracking'; /** * Search input in top navigation bar. @@ -355,6 +356,15 @@ export class SearchAutocomplete { if (!this.dropdown.hasClass('show')) { this.loadingSuggestions = false; this.dropdownToggle.dropdown('toggle'); + + const trackEvent = 'click_search_bar'; + const trackCategory = undefined; // will be default set in event method + + Tracking.event(trackCategory, trackEvent, { + label: 'main_navigation', + property: 'navigation', + }); + return this.searchInput.removeClass('js-autocomplete-disabled'); } } diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index d932588bee1..3b6b0a91e97 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -213,6 +213,7 @@ export default { class="d-inline-block" > <gl-deprecated-dropdown + ref="dropdown" :text="timeWindowText" v-bind="$attrs" class="date-time-picker w-100" @@ -250,7 +251,9 @@ export default { /> </div> <gl-form-group> - <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button> + <gl-button data-testid="cancelButton" @click="closeDropdown">{{ + __('Cancel') + }}</gl-button> <gl-button variant="success" category="primary" diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 738150dbd2e..9d67b175294 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -132,10 +132,10 @@ ul.content-list { a { color: $gl-text-color; - } - .member-group-link { - color: $blue-600; + &.inline-link { + color: $blue-600; + } } .description { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 54bca80194f..2d9a9f3029f 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -180,10 +180,6 @@ word-break: break-all; } - .member-group-link { - display: inline-block; - } - .form-control { width: inherit; } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index f99efa743c0..99a13cc4e44 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -14,12 +14,6 @@ #{'.text-#{$variant}-#{$suffix}'} { color: $color; } - - #{'.hover-text-#{$variant}-#{$suffix}'} { - &:hover { - color: $color; - } - } } } diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 4c87920ad89..0c6932e59a9 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,5 +1,5 @@ .search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } } - = form_tag search_path, method: :get, class: 'form-inline' do |f| + = form_tag search_path, method: :get, class: 'form-inline' do |_f| .search-input-container .search-input-wrap .dropdown{ data: { url: search_autocomplete_path } } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index ea5a663a654..56b70c463d0 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -7,7 +7,7 @@ .title-container %h1.title %span.gl-sr-only GitLab - = link_to root_path, title: _('Dashboard'), id: 'logo' do + = link_to root_path, title: _('Dashboard'), id: 'logo', **tracking_attrs('main_navigation', 'click_gitlab_logo_link', 'navigation') do = brand_header_logo - logo_text = brand_header_logo_type - if logo_text.present? @@ -35,25 +35,40 @@ = sprite_icon('search') - if header_link?(:issues) = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do - = link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') }, data: { qa_selector: 'issues_shortcut_button', toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') }, + data: { qa_selector: 'issues_shortcut_button', toggle: 'tooltip', placement: 'bottom', + track_label: 'main_navigation', + track_event: 'click_issues_link', + track_property: 'navigation', + container: 'body' } do = sprite_icon('issues') - issues_count = assigned_issuables_count(:issues) %span.badge.badge-pill.issues-count.green-badge{ class: ('hidden' if issues_count == 0) } = number_with_delimiter(issues_count) - if header_link?(:merge_requests) = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do - = link_to assigned_mrs_dashboard_path, title: _('Merge requests'), class: 'dashboard-shortcuts-merge_requests', aria: { label: _('Merge requests') }, data: { qa_selector: 'merge_requests_shortcut_button', toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to assigned_mrs_dashboard_path, title: _('Merge requests'), class: 'dashboard-shortcuts-merge_requests', aria: { label: _('Merge requests') }, + data: { qa_selector: 'merge_requests_shortcut_button', toggle: 'tooltip', placement: 'bottom', + track_label: 'main_navigation', + track_event: 'click_merge_link', + track_property: 'navigation', + container: 'body' } do = sprite_icon('git-merge') - merge_requests_count = assigned_issuables_count(:merge_requests) %span.badge.badge-pill.merge-requests-count{ class: ('hidden' if merge_requests_count == 0) } = number_with_delimiter(merge_requests_count) - if header_link?(:todos) = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do - = link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos', data: { qa_selector: 'todos_shortcut_button', toggle: 'tooltip', placement: 'bottom', container: 'body' } do + = link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos', + data: { qa_selector: 'todos_shortcut_button', toggle: 'tooltip', placement: 'bottom', + track_label: 'main_navigation', + track_event: 'click_to_do_link', + track_property: 'navigation', + container: 'body' } do = sprite_icon('todo-done') %span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count == 0) } = todos_count_format(todos_pending_count) - %li.nav-item.header-help.dropdown.d-none.d-md-block + %li.nav-item.header-help.dropdown.d-none.d-md-block{ **tracking_attrs('main_navigation', 'click_question_mark_link', 'navigation') } = link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do %span.gl-sr-only = s_('Nav|Help') diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index e6cfd7d56bb..29cacbe4aff 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -18,7 +18,7 @@ = render "layouts/nav/groups_dropdown/show" - if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets]) - %li.header-more.dropdown + %li.header-more.dropdown{ **tracking_attrs('main_navigation', 'click_more_link', 'navigation') } %a{ href: "#", data: { toggle: "dropdown", qa_selector: 'more_dropdown' } } = _('More') = sprite_icon('angle-down', css_class: 'caret-down') diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 05e6b065dff..47dad21edd7 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,7 +1,7 @@ - issues_count = group_issues_count(state: 'opened') - merge_requests_count = group_merge_requests_count(state: 'opened') -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation') } .nav-sidebar-inner-scroll .context-header = link_to group_path(@group), title: @group.name do diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 21b1387f620..dadab554c02 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('user_side_navigation', 'render', 'user_side_navigation') } .nav-sidebar-inner-scroll .context-header = link_to profile_path, title: _('Profile Settings') do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index bc9983b710e..054311214ab 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -1,6 +1,5 @@ -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } +.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('projects_side_navigation', 'render', 'projects_side_navigation') } .nav-sidebar-inner-scroll - - can_edit = can?(current_user, :admin_project, @project) .context-header = link_to project_path(@project), title: @project.name do .avatar-container.rect-avatar.s40.project-avatar diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 96d9870514c..ba9ab50cb3a 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,7 +6,7 @@ = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected-issuable" .issuable-info-container .issuable-main-info - .issue-title.title.d-flex.align-items-center + .issue-title.title %span.issue-title-text.js-onboarding-issue-item{ dir: "auto" } - if issue.confidential? %span.has-tooltip{ title: _('Confidential') } diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 20473b47484..fa71f4dc9b9 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -33,7 +33,7 @@ - if source.instance_of?(Group) && source != @group · - = link_to source.full_name, source, class: "member-group-link" + = link_to source.full_name, source, class: "gl-display-inline-block inline-link" .cgray - if member.request? diff --git a/changelogs/unreleased/223927-write-schema-to-instrument-the-navigation-on-gitlab-com.yml b/changelogs/unreleased/223927-write-schema-to-instrument-the-navigation-on-gitlab-com.yml new file mode 100644 index 00000000000..9d37820ff7a --- /dev/null +++ b/changelogs/unreleased/223927-write-schema-to-instrument-the-navigation-on-gitlab-com.yml @@ -0,0 +1,5 @@ +--- +title: Setup basic level telemetry for navigation +merge_request: 39638 +author: +type: added diff --git a/changelogs/unreleased/24327-add-overarching-auto_link_user-omniauth-configuration.yml b/changelogs/unreleased/24327-add-overarching-auto_link_user-omniauth-configuration.yml new file mode 100644 index 00000000000..87492b1c7ad --- /dev/null +++ b/changelogs/unreleased/24327-add-overarching-auto_link_user-omniauth-configuration.yml @@ -0,0 +1,5 @@ +--- +title: Add auto_link_user OmniAuth setting +merge_request: 36664 +author: +type: added diff --git a/changelogs/unreleased/33767-search-users-in-LDAP-by-their-e-mail-address.yml b/changelogs/unreleased/33767-search-users-in-LDAP-by-their-e-mail-address.yml new file mode 100644 index 00000000000..7104233838b --- /dev/null +++ b/changelogs/unreleased/33767-search-users-in-LDAP-by-their-e-mail-address.yml @@ -0,0 +1,5 @@ +--- +title: "Allow OAuth to auto link LDAP users via email address" +merge_request: 33767 +author: Niko Wenselowski +type: changed diff --git a/changelogs/unreleased/fix-failing-datetimepicker-hide-button.yml b/changelogs/unreleased/fix-failing-datetimepicker-hide-button.yml new file mode 100644 index 00000000000..8268005b55b --- /dev/null +++ b/changelogs/unreleased/fix-failing-datetimepicker-hide-button.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken date time picker hide button +merge_request: 39755 +author: +type: fixed diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e16a254e256..5d217332634 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -888,6 +888,11 @@ production: &base # (default: false) auto_link_saml_user: false + # Allow users with existing accounts to sign in and auto link their account via OmniAuth + # login, without having to do a manual login first and manually add OmniAuth. Links on email. + # (default: false) + auto_link_user: false + # Set different Omniauth providers as external so that all users creating accounts # via these providers will not be able to have access to internal projects. You # will need to use the full name of the provider, like `google_oauth2` for Google. diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 9b08f1e5be4..628d9c65ce0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -83,6 +83,7 @@ Settings.omniauth['external_providers'] = [] if Settings.omniauth['external_prov Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil? Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil? +Settings.omniauth['auto_link_user'] = false if Settings.omniauth['auto_link_user'].nil? Settings.omniauth['sync_profile_from_provider'] = false if Settings.omniauth['sync_profile_from_provider'].nil? Settings.omniauth['sync_profile_attributes'] = ['email'] if Settings.omniauth['sync_profile_attributes'].nil? diff --git a/config/webpack.config.js b/config/webpack.config.js index 49e865d668d..a5b5d0f987d 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -267,6 +267,8 @@ module.exports = { runtimeChunk: 'single', splitChunks: { maxInitialRequests: 20, + // In order to prevent firewalls tripping up: https://gitlab.com/gitlab-org/gitlab/-/issues/22648 + automaticNameDelimiter: '-', cacheGroups: { default: false, common: () => ({ diff --git a/doc/api/groups.md b/doc/api/groups.md index 79ac10d4ac6..07b2738f2d3 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1164,3 +1164,46 @@ DELETE /groups/:id/share/:group_id | --------- | -------------- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | | `group_id` | integer | yes | The ID of the group to share with | + +## Push Rules **(STARTER)** + +### Get group push rules + +Get the [push rules](../user/group/index.md#group-push-rules-starter) of a group. + +```plaintext +GET /groups/:id/push_rule +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the group or [URL-encoded path of the group](README.md#namespaced-path-encoding) | + +```json +{ + "id": 2, + "created_at": "2020-08-17T19:09:19.580Z", + "commit_message_regex": "[a-zA-Z]", + "commit_message_negative_regex": "[x+]", + "branch_name_regex": "[a-z]", + "deny_delete_tag": true, + "member_check": true, + "prevent_secrets": true, + "author_email_regex": "^[A-Za-z0-9.]+@gitlab.com$", + "file_name_regex": "(exe)$", + "max_file_size": 100 +} +``` + +Users on GitLab [Premium, Silver, or higher](https://about.gitlab.com/pricing/) will also see +the `commit_committer_check` and `reject_unsigned_commits` parameters: + +```json +{ + "id": 2, + "created_at": "2020-08-17T19:09:19.580Z", + "commit_committer_check": true, + "reject_unsigned_commits": false, + ... +} +``` diff --git a/doc/api/projects.md b/doc/api/projects.md index 3640008f16d..ee9779b54e0 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2124,7 +2124,7 @@ POST /projects/:id/housekeeping ### Get project push rules -Get the push rules of a project. +Get the [push rules](../push_rules/push_rules.md#enabling-push-rules) of a project. ```plaintext GET /projects/:id/push_rule diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md index 808c7426365..dc19d42ee2e 100644 --- a/doc/integration/jira_development_panel.md +++ b/doc/integration/jira_development_panel.md @@ -32,6 +32,9 @@ This differs from the [Jira integration](../user/project/integrations/jira.md), ## Configuration +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For an overview, see [Agile Management - GitLab-Jira Development Panel Integration](https://www.youtube.com/watch?v=VjVTOmMl85M&feature=youtu.be). + - If you're using GitLab.com and Jira Cloud, the recommended method to enable this integration is to install the [GitLab for Jira app](#gitlab-for-jira-app) from the Atlassian Marketplace, which offers a real-time sync between GitLab and Jira. - If you're using self-managed GitLab, self-managed Jira, or both, configure the integration using [Jira's DVCS Connector](#jira-dvcs-configuration), which syncs data hourly. diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 8ad3f47ce61..9dd7f2cd9e1 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -140,6 +140,23 @@ OmniAuth provider for an existing user. The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on. +## Automatically Link Existing Users to OmniAuth Users + +You can automatically link OmniAuth users with existing GitLab users if their email addresses match by adding the following setting: + +**For Omnibus installations** + +```ruby +gitlab_rails['omniauth_auto_link_user'] = true +``` + +**For installations from source** + +```yaml +omniauth: + auto_link_user: true +``` + ## Configure OmniAuth Providers as External > Introduced in GitLab 8.7. diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md index 7fa0e34d90d..1672e9fbb25 100644 --- a/doc/user/application_security/coverage_fuzzing/index.md +++ b/doc/user/application_security/coverage_fuzzing/index.md @@ -14,12 +14,14 @@ behavior, such as a crash. Such behavior indicates a bug that you should address We recommend that you use fuzz testing in addition to the other security scanners in [GitLab Secure](../index.md) and your own test processes. If you're using [GitLab CI/CD](../../../ci/README.md), -you can run your coverage guided fuzz tests as part your CI/CD workflow. You can take advantage of -Coverage Guided Fuzzing by including the CI job in your existing `.gitlab-ci.yml` file. +you can run your coverage-guided fuzz tests as part your CI/CD workflow. You can take advantage of +coverage-guided fuzzing by including the CI job in your existing `.gitlab-ci.yml` file. ## Supported fuzzing engines and languages -GitLab supports these languages through the fuzzing engine listed for each. We currently provide a Docker image for apps written in Go, but you can test the other languages below by providing a Docker image with the fuzz engine to run your app. +GitLab supports these languages through the fuzzing engine listed for each. We currently provide a +Docker image for apps written in Go, but you can test the other languages below by providing a +Docker image with the fuzz engine to run your app. | Language | Fuzzing Engine | Example | |----------|----------------|---------| @@ -65,8 +67,8 @@ The `my_fuzz_target` job (the separate job for your fuzz target) does the follow The `gitlab-cov-fuzz` is a command-line tool that runs the instrumented application. It parses and analyzes the exception information that the fuzzer outputs. It also downloads the [corpus](#glossary) -and crash events from previous pipelines automatically. This helps your fuzz targets build on the progress of -previous fuzzing jobs. The parsed crash events and data are written to +and crash events from previous pipelines automatically. This helps your fuzz targets build on the +progress of previous fuzzing jobs. The parsed crash events and data are written to `gl-coverage-fuzzing-report.json`. ### Artifacts @@ -125,7 +127,7 @@ The `gitlab-cov-fuzz` tool emits a JSON report file. For more information, see t You can download the JSON report file from the CI pipelines page. For more information, see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md#downloading-artifacts). -Here's an example Coverage Fuzzing report: +Here's an example coverage fuzzing report: ```json-doc { diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 17c1011d3b7..f0d0fbff158 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -18,6 +18,8 @@ If you follow our guidance to automate user provisioning using [SCIM](scim_setup User synchronization of SAML SSO groups is supported through [SCIM](scim_setup.md). SCIM supports adding and removing users from the GitLab group. For example, if you remove a user from the SCIM app, SCIM removes that same user from the GitLab group. +SAML SSO is not supported at the subgroup level, + ## Configuring your Identity Provider 1. Navigate to the group and click **Settings > SAML SSO**. diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index 3d23b230322..f11cd4d9539 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -31,6 +31,9 @@ See the [feature comparison](jira_integrations.md#feature-comparison) for more d ## Configuration +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For an overview, see [Agile Management - GitLab-Jira Basic Integration](https://www.youtube.com/watch?v=fWvwkx5_00E&feature=youtu.be). + Each GitLab project can be configured to connect to an entire Jira instance. That means one GitLab project can interact with _all_ Jira projects in that instance, once configured. Therefore, you will not have to explicitly associate diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb index bdfef723da8..086f4a2e91c 100644 --- a/lib/gitlab/auth/o_auth/user.rb +++ b/lib/gitlab/auth/o_auth/user.rb @@ -62,6 +62,7 @@ module Gitlab def find_user user = find_by_uid_and_provider + user ||= find_by_email if auto_link_user? user ||= find_or_build_ldap_user if auto_link_ldap_user? user ||= build_new_user if signup_enabled? @@ -150,6 +151,7 @@ module Gitlab def find_ldap_person(auth_hash, adapter) Gitlab::Auth::Ldap::Person.find_by_uid(auth_hash.uid, adapter) || Gitlab::Auth::Ldap::Person.find_by_email(auth_hash.uid, adapter) || + Gitlab::Auth::Ldap::Person.find_by_email(auth_hash.email, adapter) || Gitlab::Auth::Ldap::Person.find_by_dn(auth_hash.uid, adapter) rescue Gitlab::Auth::Ldap::LdapConnectionError nil @@ -269,6 +271,10 @@ module Gitlab .disabled_oauth_sign_in_sources .include?(auth_hash.provider) end + + def auto_link_user? + Gitlab.config.omniauth.auto_link_user + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a72573e151d..497757c72d0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12593,6 +12593,9 @@ msgstr "" msgid "How many users will be evaluating the trial?" msgstr "" +msgid "How to upgrade" +msgstr "" + msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation." msgstr "" diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js index d0ee43332e8..ee46dc015af 100644 --- a/spec/frontend/search_autocomplete_spec.js +++ b/spec/frontend/search_autocomplete_spec.js @@ -3,6 +3,7 @@ import $ from 'jquery'; import '~/gl_dropdown'; import AxiosMockAdapter from 'axios-mock-adapter'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import initSearchAutocomplete from '~/search_autocomplete'; import '~/lib/utils/common_utils'; import axios from '~/lib/utils/axios_utils'; @@ -274,11 +275,32 @@ describe('Search autocomplete dropdown', () => { }); describe('enableAutocomplete', () => { + let toggleSpy; + let trackingSpy; + + beforeEach(() => { + toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown'); + trackingSpy = mockTracking('_category_', undefined, jest.spyOn); + document.body.dataset.page = 'some:page'; // default tracking for category + }); + + afterEach(() => { + unmockTracking(); + }); + it('should open the Dropdown', () => { - const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown'); widget.enableAutocomplete(); expect(toggleSpy).toHaveBeenCalledWith('toggle'); }); + + it('should track the opening', () => { + widget.enableAutocomplete(); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_search_bar', { + label: 'main_navigation', + property: 'navigation', + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js index ceea8d2fa92..223e22d650b 100644 --- a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js +++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js @@ -13,9 +13,9 @@ describe('DateTimePicker', () => { const dropdownToggle = () => wrapper.find('.dropdown-toggle'); const dropdownMenu = () => wrapper.find('.dropdown-menu'); + const cancelButton = () => wrapper.find('[data-testid="cancelButton"]'); const applyButtonElement = () => wrapper.find('button.btn-success').element; const findQuickRangeItems = () => wrapper.findAll('.dropdown-item'); - const cancelButtonElement = () => wrapper.find('button.btn-secondary').element; const createComponent = props => { wrapper = mount(DateTimePicker, { @@ -260,7 +260,7 @@ describe('DateTimePicker', () => { dropdownToggle().trigger('click'); return wrapper.vm.$nextTick(() => { - cancelButtonElement().click(); + cancelButton().trigger('click'); return wrapper.vm.$nextTick(() => { expect(dropdownMenu().classes('show')).toBe(false); diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index cc9e3b957e3..12e774ec1f8 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -194,6 +194,43 @@ RSpec.describe Gitlab::Auth::OAuth::User do end end + context "with auto_link_user disabled (default)" do + before do + stub_omniauth_config(auto_link_user: false) + end + + include_examples "to verify compliance with allow_single_sign_on" + end + + context "with auto_link_user enabled" do + before do + stub_omniauth_config(auto_link_user: true) + end + + context "and a current GitLab user with a matching email" do + let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') } + + it "adds the OmniAuth identity to the GitLab user account" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 1 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ + { provider: 'twitter', extern_uid: uid } + ] + ) + end + end + + context "and no current GitLab user with a matching email" do + include_examples "to verify compliance with allow_single_sign_on" + end + end + context "with auto_link_ldap_user disabled (default)" do before do stub_omniauth_config(auto_link_ldap_user: false) @@ -230,39 +267,56 @@ RSpec.describe Gitlab::Auth::OAuth::User do end context "and no account for the LDAP user" do - before do - allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) + context 'when the LDAP user is found by UID' do + before do + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) - oauth_user.save - end + oauth_user.save + end - it "creates a user with dual LDAP and omniauth identities" do - expect(gl_user).to be_valid - expect(gl_user.username).to eql uid - expect(gl_user.name).to eql 'John Doe' - expect(gl_user.email).to eql 'johndoe@example.com' - expect(gl_user.identities.length).to be 2 - identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash).to match_array( - [ - { provider: 'ldapmain', extern_uid: dn }, - { provider: 'twitter', extern_uid: uid } - ] - ) - end + it "creates a user with dual LDAP and omniauth identities" do + expect(gl_user).to be_valid + expect(gl_user.username).to eql uid + expect(gl_user.name).to eql 'John Doe' + expect(gl_user.email).to eql 'johndoe@example.com' + expect(gl_user.identities.length).to be 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end - it "has name and email set as synced" do - expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy - expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy - end + it "has name and email set as synced" do + expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end - it "has name and email set as read-only" do - expect(gl_user.read_only_attribute?(:name)).to be_truthy - expect(gl_user.read_only_attribute?(:email)).to be_truthy + it "has name and email set as read-only" do + expect(gl_user.read_only_attribute?(:name)).to be_truthy + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + end end - it "has synced attributes provider set to ldapmain" do - expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + context 'when the LDAP user is found by email address' do + before do + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil) + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(uid, any_args).and_return(nil) + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(info_hash[:email], any_args).and_return(ldap_user) + + oauth_user.save + end + + it 'creates the LDAP identity' do + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to include({ provider: 'ldapmain', extern_uid: dn }) + end end end @@ -364,6 +418,90 @@ RSpec.describe Gitlab::Auth::OAuth::User do end end end + + context "with both auto_link_user and auto_link_ldap_user enabled" do + before do + stub_omniauth_config(auto_link_user: true, auto_link_ldap_user: true) + end + + context "and at least one LDAP provider is defined" do + before do + stub_ldap_config(providers: %w(ldapmain)) + end + + context "and a corresponding LDAP person" do + before do + allow(ldap_user).to receive_messages( + uid: uid, + username: uid, + name: 'John Doe', + email: ['john@mail.com'], + dn: dn + ) + end + + context "and no account for the LDAP user" do + before do + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) + + oauth_user.save + end + + it "creates a user with dual LDAP and omniauth identities" do + expect(gl_user).to be_valid + expect(gl_user.username).to eql uid + expect(gl_user.name).to eql 'John Doe' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end + + it "has name and email set as synced" do + expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy + expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy + end + + it "has name and email set as read-only" do + expect(gl_user.read_only_attribute?(:name)).to be_truthy + expect(gl_user.read_only_attribute?(:email)).to be_truthy + end + + it "has synced attributes provider set to ldapmain" do + expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain' + end + end + + context "and LDAP user has an account already" do + let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@mail.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } + + it "adds the omniauth identity to the LDAP account" do + allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user) + + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.name).to eql 'John Doe' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'twitter', extern_uid: uid } + ] + ) + end + end + end + end + end end describe 'blocking' do @@ -791,7 +929,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do end end - describe '.find_by_uid_and_provider' do + describe '._uid_and_provider' do let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } it 'normalizes extern_uid' do |