diff options
44 files changed, 428 insertions, 57 deletions
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 9b45be6db99..f16f752f85b 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::BaseController < ApplicationController + before_action :import_rate_limit, only: [:create] + private # rubocop: disable CodeReuse/ActiveRecord @@ -37,4 +39,18 @@ class Import::BaseController < ApplicationController def project_save_error(project) project.errors.full_messages.join(', ') end + + def import_rate_limit + key = "project_import".to_sym + + if rate_limiter.throttled?(key, scope: [current_user, key]) + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + + redirect_back_or_default(options: { alert: _('This endpoint has been requested too many times. Try again later.') }) + end + end + + def rate_limiter + ::Gitlab::ApplicationRateLimiter + end end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index a908ca28188..42b63fbef92 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.12.0' + VERSION = '0.13.0' self.table_name = 'clusters_applications_runners' diff --git a/app/models/event.rb b/app/models/event.rb index 35fb062311f..ba585937e1c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -76,6 +76,7 @@ class Event < ApplicationRecord # Scopes scope :recent, -> { reorder(id: :desc) } scope :code_push, -> { where(action: PUSHED) } + scope :merged, -> { where(action: MERGED) } scope :with_associations, -> do # We're using preload for "push_event_payload" as otherwise the association diff --git a/changelogs/unreleased/196777-add-account-creation-and-sign-in-ip-to-api.yml b/changelogs/unreleased/196777-add-account-creation-and-sign-in-ip-to-api.yml new file mode 100644 index 00000000000..96f30aa305a --- /dev/null +++ b/changelogs/unreleased/196777-add-account-creation-and-sign-in-ip-to-api.yml @@ -0,0 +1,5 @@ +--- +title: Expose current and last IPs to /users endpoint +merge_request: 19781 +author: +type: added diff --git a/changelogs/unreleased/fix-smime-scope.yml b/changelogs/unreleased/fix-smime-scope.yml new file mode 100644 index 00000000000..421fb75b977 --- /dev/null +++ b/changelogs/unreleased/fix-smime-scope.yml @@ -0,0 +1,5 @@ +--- +title: Remove the OpenSSL include within SMIME email signing +merge_request: 23642 +author: Roger Meier +type: fixed diff --git a/changelogs/unreleased/rate-limit-imports.yml b/changelogs/unreleased/rate-limit-imports.yml new file mode 100644 index 00000000000..7746840bdfe --- /dev/null +++ b/changelogs/unreleased/rate-limit-imports.yml @@ -0,0 +1,5 @@ +--- +title: Add rate limiter to Project Imports +merge_request: 22644 +author: +type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-13-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-13-0.yml new file mode 100644 index 00000000000..95e661f024c --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-13-0.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.13.0/12.7.0 +merge_request: 23308 +author: +type: other diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index dc064a76033..9135073eaea 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -35,6 +35,7 @@ unless Gitlab::Runtime.sidekiq? ::Gitlab::InstrumentationHelper.add_instrumentation_data(payload) payload[:response] = event.payload[:response] if event.payload[:response] + payload[:etag_route] = event.payload[:etag_route] if event.payload[:etag_route] payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id if cpu_s = Gitlab::Metrics::System.thread_cpu_duration(::Gitlab::RequestContext.instance.start_thread_cpu_time) diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md index 05ac42b912c..dd0e4658ee9 100644 --- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md +++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md @@ -8,7 +8,7 @@ Managing a large number of users in GitLab can become a burden for system admini In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems. -GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/blog/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](../how_to_configure_ldap_gitlab_ee/index.html#group-sync) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/blog/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab. +GitLab has supported LDAP integration since [version 2.2](https://about.gitlab.com/releases/2012/02/22/gitlab-version-2-2/). With GitLab LDAP [group syncing](../how_to_configure_ldap_gitlab_ee/index.md#group-sync) being added to GitLab Enterprise Edition in [version 6.0](https://about.gitlab.com/releases/2013/08/20/gitlab-6-dot-0-released/). LDAP integration has become one of the most popular features in GitLab. ## Getting started diff --git a/doc/api/users.md b/doc/api/users.md index 7694345d08b..3f9ccc24054 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -111,7 +111,9 @@ GET /users "can_create_project": true, "two_factor_enabled": true, "external": false, - "private_profile": false + "private_profile": false, + "current_sign_in_ip": "196.165.1.102", + "last_sign_in_ip": "172.127.2.22" }, { "id": 2, @@ -142,7 +144,9 @@ GET /users "can_create_project": true, "two_factor_enabled": true, "external": false, - "private_profile": false + "private_profile": false, + "current_sign_in_ip": "10.165.1.102", + "last_sign_in_ip": "172.127.2.22" } ] ``` @@ -294,7 +298,9 @@ Example Responses: "can_create_project": true, "two_factor_enabled": true, "external": false, - "private_profile": false + "private_profile": false, + "current_sign_in_ip": "196.165.1.102", + "last_sign_in_ip": "172.127.2.22" } ``` @@ -534,7 +540,9 @@ GET /user "can_create_project": true, "two_factor_enabled": true, "external": false, - "private_profile": false + "private_profile": false, + "current_sign_in_ip": "196.165.1.102", + "last_sign_in_ip": "172.127.2.22" } ``` diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index 17fb6f9a7c9..2b78dd60b8a 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -147,7 +147,7 @@ need to do for this: For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages) `confidence-check` that is executed _after_ the stage that deploys the review app. It uses the `node:latest` [Docker -image](../../docker/using_docker_images.html). However, WebdriverIO fires up actual browsers +image](../../docker/using_docker_images.md). However, WebdriverIO fires up actual browsers to interact with your application, so we need to install and run them. Furthermore, WebdriverIO uses Selenium as a common interface to control different browsers, so we need to install and run Selenium as well. Luckily, the Selenium project provides the Docker images @@ -187,7 +187,7 @@ option as an argument to `npm run confidence-check` on the command line. However, we still need to tell WebdriverIO which browser is available for it to use. [GitLab CI/CD makes -a number of variables available](../../variables/README.html#predefined-environment-variables) +a number of variables available](../../variables/README.md#predefined-environment-variables) with information about the current CI job. We can use this information to dynamically set up our WebdriverIO configuration according to the job that is running. More specifically, we can tell WebdriverIO what browser to execute the test on depending on the name of the currently running diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md index 92fc4de986c..edb585c0a21 100644 --- a/doc/ci/jenkins/index.md +++ b/doc/ci/jenkins/index.md @@ -62,7 +62,7 @@ rspec: Artifacts may work a bit differently than you've used them with Jenkins. In GitLab, any job can define a set of artifacts to be saved by using the `artifacts:` keyword. This can be configured to point to a file -or set of files that can then be persisted from job to job. Read more on our detailed [artifacts documentation](../../user/project/pipelines/job_artifacts.html) +or set of files that can then be persisted from job to job. Read more on our detailed [artifacts documentation](../../user/project/pipelines/job_artifacts.md) ```yaml pdf: @@ -129,7 +129,7 @@ stages: - test - deploy - after_pipeline -``` +``` Setting a step to be performed before and after any job can be done via the [`before_script` and `after_script` keywords](../yaml/README.md#before_script-and-after_script). diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index c6389ee5093..52b63de9e70 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -736,7 +736,7 @@ This means the `only:changes` policy is useful for pipelines where: - `$CI_PIPELINE_SOURCE == 'external_pull_request_event'` If there is no Git push event, such as for pipelines with -[sources other than the three above](../variables/predefined_variables.html#variables-reference), +[sources other than the three above](../variables/predefined_variables.md#variables-reference), `changes` cannot determine if a given file is new or old, and will always return true. diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index a9d8941488f..acba09986fb 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -196,13 +196,12 @@ Every GitLab instance includes the documentation, which is available at `/help` There are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this practice and instead link out from the GitLab application to <https://docs.gitlab.com> URLs. -The documentation available online on <https://docs.gitlab.com> is continuously -deployed every hour from the `master` branch of GitLab, Omnibus, and Runner. Therefore, -once a merge request gets merged, it will be available online on the same day. -However, they will be shipped (and available on `/help`) within the milestone assigned +The documentation available online on <https://docs.gitlab.com> is deployed every four hours from the `master` branch of GitLab, Omnibus, and Runner. Therefore, +after a merge request gets merged, it will be available online on the same day. +However, it will be shipped (and available on `/help`) within the milestone assigned to the MR. -For instance, let's say your merge request has a milestone set to 11.3, which +For example, let's say your merge request has a milestone set to 11.3, which will be released on 2018-09-22. If it gets merged on 2018-09-15, it will be available online on 2018-09-15, but, as the feature freeze date has passed, if the MR does not have a "pick into 11.3" label, the milestone has to be changed diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index 407633c24df..5dddb47a033 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -769,6 +769,74 @@ nicely on different mobile devices. - [Syntax highlighting for code blocks](https://github.com/rouge-ruby/rouge/wiki/List-of-supported-languages-and-lexers) is available for many languages. - For a complete reference on code blocks, check the [Kramdown guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/#code-blocks). +## GitLab SVG icons + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/issues/384) in GitLab 12.7. + +You can use icons from the [GitLab SVG library](https://gitlab-org.gitlab.io/gitlab-svgs/) directly +in the documentation. + +This way, you can achieve a consistent look when writing about interacting with GitLab UI elements. + +Usage examples: + +- Icon with default size (16px): `**{icon-name}**` + + Example: `**{tanuki}**` renders as: **{tanuki}**. +- Icon with custom size: `**{icon-name, size}**` + + Available sizes (in px): 8, 10, 12, 14, 16, 18, 24, 32, 48, and 72 + + Example: `**{tanuki, 24}**` renders as: **{tanuki, 24}**. +- Icon with custom size and class: `**{icon-name, size, class-name}**`. + + You can access any class available to this element in GitLab docs CSS. + + Example with `float-right`, a + [Bootstrap utility class](https://getbootstrap.com/docs/4.4/utilities/float/): + `**{tanuki, 32, float-right}**` renders as: **{tanuki, 32, float-right}** + +### Using GitLab SVGs to describe UI elements + +When using GitLab SVGs to describe screen elements, also include the name or tooltip of the element as text. + +For example, for references to the Admin Area: + +- Correct: `**{admin}** **Admin Area > Settings**` (**{admin}** **Admin Area > Settings**) +- Incorrect: `**{admin}** **> Settings**` (**{admin}** **> Settings**) + +This will ensure that the source Markdown remains readable and should help with accessibility. + +The following are examples of source Markdown for menu items with their published output: + +```md +1. Go to **{home}** **Project overview > Details** +1. Go to **{doc-text}** **Repository > Branches** +1. Go to **{issues}** **Issues > List** +1. Go to **{merge-request}** **Merge Requests** +1. Go to **{rocket}** **CI/CD > Pipelines** +1. Go to **{shield}** **Security & Compliance > Configuration** +1. Go to **{cloud-gear}** **Operations > Metrics** +1. Go to **{package}** **Packages > Container Registry** +1. Go to **{chart}** **Project Analytics > Code Review** +1. Go to **{book}** **Wiki** +1. Go to **{snippet}** **Snippets** +1. Go to **{users}** **Members** +``` + +1. Go to **{home}** **Project overview > Details** +1. Go to **{doc-text}** **Repository > Branches** +1. Go to **{issues}** **Issues > List** +1. Go to **{merge-request}** **Merge Requests** +1. Go to **{rocket}** **CI/CD > Pipelines** +1. Go to **{shield}** **Security & Compliance > Configuration** +1. Go to **{cloud-gear}** **Operations > Metrics** +1. Go to **{package}** **Packages > Container Registry** +1. Go to **{chart}** **Project Analytics > Code Review** +1. Go to **{book}** **Wiki** +1. Go to **{snippet}** **Snippets** +1. Go to **{users}** **Members** + ## Alert boxes Whenever you need to call special attention to particular sentences, diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index 386baf825b8..488d7ae6762 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -77,6 +77,24 @@ Or: hello = _("Hello world!") ``` +Be careful when translating strings at the class or module level since these would only be +evaluated once at class load time. + +For example: + +```ruby +validates :group_id, uniqueness: { scope: [:project_id], message: _("already shared with this group") } +``` + +This would be translated when the class is loaded and result in the error message +always being in the default locale. + +Active Record's `:message` option accepts a `Proc`, so we can do this instead: + +```ruby +validates :group_id, uniqueness: { scope: [:project_id], message: -> (object, data) { _("already shared with this group") } } +``` + NOTE: **Note:** Messages in the API (`lib/api/` or `app/graphql`) do not need to be externalised. diff --git a/doc/integration/github.md b/doc/integration/github.md index f2eab0cdf98..269f3aa0f19 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -59,7 +59,7 @@ Follow these steps to incorporate the GitHub OAuth 2 app in your GitLab server: **Replace `https://github.example.com/` with your GitHub URL.** -1. Save the file and [reconfigure](../administration/restart_gitlab.html#omnibus-gitlab-reconfigure) GitLab for the changes to take effect. +1. Save the file and [reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab for the changes to take effect. --- @@ -86,7 +86,7 @@ Follow these steps to incorporate the GitHub OAuth 2 app in your GitLab server: **Replace `https://github.example.com/` with your GitHub URL.** -1. Save the file and [restart](../administration/restart_gitlab.html#installations-from-source) GitLab for the changes to take effect. +1. Save the file and [restart](../administration/restart_gitlab.md#installations-from-source) GitLab for the changes to take effect. --- diff --git a/doc/integration/saml.md b/doc/integration/saml.md index aa8e29637cc..261fe6e8c18 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -549,7 +549,7 @@ GitLab will sign the request with the provided private key. GitLab will include If you need to troubleshoot, [a complete GitLab+SAML testing environment using Docker compose](https://gitlab.com/gitlab-com/support/toolbox/replication/tree/master/compose_files) is available. -If you only need a SAML provider for testing, a [quick start guide to start a Docker container](../administration/troubleshooting/test_environments.html#saml) with a plug and play SAML 2.0 Identity Provider (IdP) is available. +If you only need a SAML provider for testing, a [quick start guide to start a Docker container](../administration/troubleshooting/test_environments.md#saml) with a plug and play SAML 2.0 Identity Provider (IdP) is available. ### 500 error after login diff --git a/doc/user/admin_area/activating_deactivating_users.md b/doc/user/admin_area/activating_deactivating_users.md index 78a07f4a04e..5836129b222 100644 --- a/doc/user/admin_area/activating_deactivating_users.md +++ b/doc/user/admin_area/activating_deactivating_users.md @@ -38,7 +38,7 @@ Please note that for the deactivation option to be visible to an admin, the user - Must be currently active. - Should not have any activity in the last 180 days. -Users can also be deactivated using the [GitLab API](../../api/users.html#deactivate-user). +Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user). NOTE: **Note:** A deactivated user does not consume a [seat](../../subscriptions/index.md#managing-subscriptions). @@ -56,7 +56,7 @@ To do this: 1. Select a user. 1. Under the **Account** tab, click **Activate user**. -Users can also be activated using the [GitLab API](../../api/users.html#activate-user). +Users can also be activated using the [GitLab API](../../api/users.md#activate-user). NOTE: **Note:** Activating a user will change the user's state to active and it consumes a diff --git a/doc/user/admin_area/blocking_unblocking_users.md b/doc/user/admin_area/blocking_unblocking_users.md index 8868170169e..cb86e28ff1e 100644 --- a/doc/user/admin_area/blocking_unblocking_users.md +++ b/doc/user/admin_area/blocking_unblocking_users.md @@ -27,7 +27,7 @@ A blocked user: Personal projects, and group and user history of the blocked user will be left intact. -Users can also be blocked using the [GitLab API](../../api/users.html#block-user). +Users can also be blocked using the [GitLab API](../../api/users.md#block-user). NOTE: **Note:** A blocked user does not consume a [seat](../../subscriptions/index.md#managing-subscriptions). @@ -41,7 +41,7 @@ A blocked user can be unblocked from the Admin Area. To do this: 1. Select a user. 1. Under the **Account** tab, click **Unblock user**. -Users can also be unblocked using the [GitLab API](../../api/users.html#unblock-user). +Users can also be unblocked using the [GitLab API](../../api/users.md#unblock-user). NOTE: **Note:** Unblocking a user will change the user's state to active and it consumes a diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 5fe2d0da5c8..e0028e7124d 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -6,7 +6,9 @@ type: reference, howto > Introduced in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.0. -SAML on GitLab.com allows users to be automatically added to a group, and then allows those users to sign into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time. +SAML on GitLab.com allows users to be added to a group. Those users can then sign in to GitLab.com. If such users don't already have an account on the GitLab instance, they can create one when signing in for the first time. + +If you follow our guidance to automate user provisioning using [SCIM](scim_setup.md) or [group managed accounts](#group-managed-accounts), you do not need to create such accounts manually. User synchronization for GitLab.com is partially supported using [SCIM](scim_setup.md). @@ -91,7 +93,7 @@ assertions to be able to create a user. | First Name | `first_name`, `firstname`, `firstName` | | Last Name | `last_name`, `lastname`, `lastName` | -## Metadata configuration +### Metadata configuration GitLab provides metadata XML that can be used to configure your Identity Provider. @@ -111,6 +113,37 @@ Once you've set up your identity provider to work with GitLab, you'll need to co ![Group SAML Settings for GitLab.com](img/group_saml_settings.png) +## User access and management + +Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup). + +When a user tries to sign in with Group SSO, they'll need an account that's configured with one of the following: + +- [SCIM](scim_setup.md). +- [Group-managed accounts](#group-managed-accounts). +- A GitLab.com account. + +1. Click on the GitLab app in the identity provider's dashboard, or visit the Group's GitLab SSO URL. +1. Sign in to GitLab.com. The next time you connect on the same browser, you won't have to sign in again provided the active session has not expired. +1. Click on the **Authorize** button. + +On subsequent visits, users can access the group through the identify provider's dashboard or by visiting links directly. With the **enforce SSO** option turned on, users will be redirected to log in through the identity provider as required. + +### Role + +Upon first sign in, a new user is added to the parent group with the Guest role. Existing members with an appropriate role will have to elevate users to a higher role where relevant. + +If a user is already a member of the group, linking the SAML identity does not change their role. + +### Blocking access + +To rescind access to the group: + +1. Remove the user from the identity provider or users list for the specific app. +1. Remove the user from the GitLab.com group. + +Even when **enforce SSO** is active, we recommend removing the user from the group. Otherwise, the user can sign in through the identity provider if they do not have an active session. + ## Providers NOTE: **Note:** GitLab is unable to provide support for IdPs that are not listed here. diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index a117364a355..2c8848b79df 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -117,6 +117,28 @@ bottom of the **Provisioning** screen, together with a link to the audit logs. CAUTION: **Warning:** Once synchronized, changing the field mapped to `id` and `externalId` will likely cause provisioning errors, duplicate users, and prevent existing users from accessing the GitLab group. +## User access and linking setup + +As long as [Group SAML](index.md) has been configured, prior to turning on sync, existing GitLab.com users can link to their accounts in one of the following ways, before synchronization is active: + +- By updating their *primary* email address in their GitLab.com user account to match their identity provider's user profile email address. +- By following these steps: + + 1. Sign in to GitLab.com if needed. + 1. Click on the GitLab app in the identity provider's dashboard or visit the **GitLab single sign on URL**. + 1. Click on the **Authorize** button. + +New users and existing users on subsequent visits can access the group through the identify provider's dashboard or by visiting links directly. + +For role information, please see the [Group SAML page](index.md#user-access-and-management) + +### Blocking access + +To rescind access to the group, we recommend removing the user from the identity +provider or users list for the specific app. + +Upon the next sync, the user will be deprovisioned, which means that the user will be removed from the group. The user account will not be deleted unless using [group managed accounts](index.md#group-managed-accounts). + ## Troubleshooting This section contains possible solutions for problems you might encounter. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 57873494a48..9a32f0adfaa 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -425,7 +425,7 @@ for details about the pipelines security model. ## LDAP users permissions Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. -Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more. +Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.md) to learn more. ## Project aliases diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index f5cfa256f5d..2c90a17f37e 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -123,3 +123,13 @@ NOTE: **Note:** If use of the `Internal` visibility level [is restricted](../../../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects), all imported projects are given the visibility of `Private`. + +## Rate limits + +To help avoid abuse, users are rate limited to: + +| Request Type | Limit | +| ---------------- | --------------------------- | +| Export | 1 project per 5 minutes | +| Download export | 10 projects per 10 minutes | +| Import | 30 projects per 10 minutes | diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e6bc4ed69a1..6aebeb1a597 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -115,6 +115,8 @@ module API class UserDetailsWithAdmin < UserWithAdmin expose :highest_role + expose :current_sign_in_ip + expose :last_sign_in_ip end class UserStatus < Grape::Entity diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index 629632b744b..49e1f1edfb9 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -22,6 +22,7 @@ module Gitlab project_export: { threshold: 1, interval: 5.minutes }, project_download_export: { threshold: 10, interval: 10.minutes }, project_generate_new_export: { threshold: 1, interval: 5.minutes }, + project_import: { threshold: 30, interval: 10.minutes }, play_pipeline_schedule: { threshold: 1, interval: 1.minute }, show_raw_controller: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.raw_blob_request_limit }, interval: 1.minute } }.freeze diff --git a/lib/gitlab/email/smime/certificate.rb b/lib/gitlab/email/smime/certificate.rb index b331c4ca19c..59d7b0c3c5b 100644 --- a/lib/gitlab/email/smime/certificate.rb +++ b/lib/gitlab/email/smime/certificate.rb @@ -4,8 +4,6 @@ module Gitlab module Email module Smime class Certificate - include OpenSSL - attr_reader :key, :cert def key_string @@ -17,8 +15,8 @@ module Gitlab end def self.from_strings(key_string, cert_string) - key = PKey::RSA.new(key_string) - cert = X509::Certificate.new(cert_string) + key = OpenSSL::PKey::RSA.new(key_string) + cert = OpenSSL::X509::Certificate.new(cert_string) new(key, cert) end diff --git a/lib/gitlab/email/smime/signer.rb b/lib/gitlab/email/smime/signer.rb index 2fa83014003..db03e383ecf 100644 --- a/lib/gitlab/email/smime/signer.rb +++ b/lib/gitlab/email/smime/signer.rb @@ -7,20 +7,18 @@ module Gitlab module Smime # Tooling for signing and verifying data with SMIME class Signer - include OpenSSL - def self.sign(cert:, key:, data:) - signed_data = PKCS7.sign(cert, key, data, nil, PKCS7::DETACHED) - PKCS7.write_smime(signed_data) + signed_data = OpenSSL::PKCS7.sign(cert, key, data, nil, OpenSSL::PKCS7::DETACHED) + OpenSSL::PKCS7.write_smime(signed_data) end # return nil if data cannot be verified, otherwise the signed content data def self.verify_signature(cert:, ca_cert: nil, signed_data:) - store = X509::Store.new + store = OpenSSL::X509::Store.new store.set_default_paths store.add_cert(ca_cert) if ca_cert - signed_smime = PKCS7.read_smime(signed_data) + signed_smime = OpenSSL::PKCS7.read_smime(signed_data) signed_smime if signed_smime.verify([cert], store) end end diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index a11d6b66409..303e1a23e6b 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -18,7 +18,7 @@ module Gitlab if_none_match = env['HTTP_IF_NONE_MATCH'] if if_none_match == etag - handle_cache_hit(etag, route) + handle_cache_hit(etag, route, request) else track_cache_miss(if_none_match, cached_value_present, route) @@ -47,11 +47,13 @@ module Gitlab %Q{W/"#{value}"} end - def handle_cache_hit(etag, route) + def handle_cache_hit(etag, route, request) track_event(:etag_caching_cache_hit, route) status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429 + add_instrument_for_cache_hit(status_code, route, request) + [status_code, { 'ETag' => etag, 'X-Gitlab-From-Cache' => 'true' }, []] end @@ -68,6 +70,21 @@ module Gitlab def track_event(name, route) Gitlab::Metrics.add_event(name, endpoint: route.name) end + + def add_instrument_for_cache_hit(status, route, request) + payload = { + etag_route: route.name, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.filtered_path, + status: status + } + + ActiveSupport::Notifications.instrument( + "process_action.action_controller", payload) + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0562b7560bd..9a840aa11bc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2098,6 +2098,9 @@ msgstr "" msgid "Approve the current merge request." msgstr "" +msgid "Approved by: " +msgstr "" + msgid "Approved the current merge request." msgstr "" @@ -4897,6 +4900,12 @@ msgstr "" msgid "Complete" msgstr "" +msgid "Compliance" +msgstr "" + +msgid "Compliance Dashboard" +msgstr "" + msgid "Confidence: %{confidence}" msgstr "" @@ -22490,6 +22499,9 @@ msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "merged %{time_ago}" +msgstr "" + msgid "milestone should belong either to a project or a group." msgstr "" diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index d013bd6d427..74ffcc3aeef 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -136,6 +136,8 @@ describe Import::BitbucketController do expect(response).to have_gitlab_http_status(422) end + it_behaves_like 'project import rate limiter' + context "when the repository owner is the Bitbucket user" do context "when the Bitbucket user and GitLab user's usernames match" do it "takes the current user's namespace" do diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb index f30eace7d30..a84f8368198 100644 --- a/spec/controllers/import/bitbucket_server_controller_spec.rb +++ b/spec/controllers/import/bitbucket_server_controller_spec.rb @@ -102,6 +102,8 @@ describe Import::BitbucketServerController do expect(response).to have_gitlab_http_status(422) end + + it_behaves_like 'project import rate limiter' end describe 'POST configure' do diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index f7c813576aa..9a647b8caae 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -75,4 +75,8 @@ describe Import::FogbugzController do expect(assigns(:repos)).to eq([]) end end + + describe 'POST create' do + it_behaves_like 'project import rate limiter' + end end diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb index b7bdfcc3dc6..730e3f98c98 100644 --- a/spec/controllers/import/gitea_controller_spec.rb +++ b/spec/controllers/import/gitea_controller_spec.rb @@ -41,6 +41,8 @@ describe Import::GiteaController do assign_host_url end end + + it_behaves_like 'project import rate limiter' end describe "GET realtime_changes" do diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 5675798ac33..54fbe624cb7 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -71,6 +71,8 @@ describe Import::GithubController do describe "POST create" do it_behaves_like 'a GitHub-ish import controller: POST create' + + it_behaves_like 'project import rate limiter' end describe "GET realtime_changes" do diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 6a3713a1212..495ea62456c 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -282,6 +282,8 @@ describe Import::GitlabController do expect(response).to have_gitlab_http_status(422) end end + + it_behaves_like 'project import rate limiter' end end end diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb index a3f6d8dcea2..285291c53fa 100644 --- a/spec/controllers/import/gitlab_projects_controller_spec.rb +++ b/spec/controllers/import/gitlab_projects_controller_spec.rb @@ -36,5 +36,7 @@ describe Import::GitlabProjectsController do expect(response).to have_gitlab_http_status(302) end end + + it_behaves_like 'project import rate limiter' end end diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb index 17be91c0bbb..3773f691ed0 100644 --- a/spec/controllers/import/google_code_controller_spec.rb +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -58,4 +58,8 @@ describe Import::GoogleCodeController do expect(assigns(:incompatible_repos)).to eq([@repo]) end end + + describe "POST create" do + it_behaves_like 'project import rate limiter' + end end diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb index a127e3cda3a..62a719cfb5b 100644 --- a/spec/controllers/import/phabricator_controller_spec.rb +++ b/spec/controllers/import/phabricator_controller_spec.rb @@ -88,5 +88,7 @@ describe Import::PhabricatorController do expect { post_create }.not_to change { current_user.namespace.projects.reload.size } end end + + it_behaves_like 'project import rate limiter' end end diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb index 65652468d93..7c61d034ac9 100644 --- a/spec/initializers/lograge_spec.rb +++ b/spec/initializers/lograge_spec.rb @@ -94,6 +94,11 @@ describe 'lograge', type: :request do let(:logger) do Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } } end + let(:log_data) { JSON.parse(log_output.string) } + + before do + Lograge.logger = logger + end describe 'with an exception' do let(:exception) { RuntimeError.new('bad request') } @@ -102,18 +107,29 @@ describe 'lograge', type: :request do before do allow(exception).to receive(:backtrace).and_return(backtrace) event.payload[:exception_object] = exception - Lograge.logger = logger end it 'adds exception data to log' do subscriber.process_action(event) - log_data = JSON.parse(log_output.string) - expect(log_data['exception.class']).to eq('RuntimeError') expect(log_data['exception.message']).to eq('bad request') expect(log_data['exception.backtrace']).to eq(Gitlab::BacktraceCleaner.clean_backtrace(backtrace)) end end + + describe 'with etag_route' do + let(:etag_route) { 'etag route' } + + before do + event.payload[:etag_route] = etag_route + end + + it 'adds etag_route to log' do + subscriber.process_action(event) + + expect(log_data['etag_route']).to eq(etag_route) + end + end end end diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index 24df67b3058..5e9df555241 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -8,6 +8,7 @@ describe Gitlab::EtagCaching::Middleware do let(:app_status_code) { 200 } let(:if_none_match) { nil } let(:enabled_path) { '/gitlab-org/gitlab-foss/noteable/issue/1/notes' } + let(:endpoint) { 'issue_notes' } context 'when ETag caching is not enabled for current route' do let(:path) { '/gitlab-org/gitlab-foss/tree/master/noteable/issue/1/notes' } @@ -50,9 +51,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_key_not_found" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used, endpoint: 'issue_notes') + .with(:etag_caching_middleware_used, endpoint: endpoint) expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_key_not_found, endpoint: 'issue_notes') + .with(:etag_caching_key_not_found, endpoint: endpoint) middleware.call(build_request(path, if_none_match)) end @@ -74,6 +75,37 @@ describe Gitlab::EtagCaching::Middleware do end end + shared_examples 'sends a process_action.action_controller notification' do |status_code| + let(:expected_items) do + { + etag_route: endpoint, + params: {}, + format: :html, + method: 'GET', + path: enabled_path, + status: status_code + } + end + + it 'sends the expected payload' do + payload = payload_for('process_action.action_controller') do + middleware.call(build_request(path, if_none_match)) + end + + expect(payload).to include(expected_items) + + expect(payload[:headers].env['HTTP_IF_NONE_MATCH']).to eq('W/"123"') + end + + it 'log subscriber processes action' do + expect_any_instance_of(ActionController::LogSubscriber).to receive(:process_action) + .with(instance_of(ActiveSupport::Notifications::Event)) + .and_call_original + + middleware.call(build_request(path, if_none_match)) + end + end + context 'when If-None-Match header matches ETag in store' do let(:path) { enabled_path } let(:if_none_match) { 'W/"123"' } @@ -94,6 +126,8 @@ describe Gitlab::EtagCaching::Middleware do expect(status).to eq 304 end + it_behaves_like 'sends a process_action.action_controller notification', 304 + it 'returns empty body' do _, _, body = middleware.call(build_request(path, if_none_match)) @@ -102,9 +136,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_cache_hit" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used, endpoint: 'issue_notes') + .with(:etag_caching_middleware_used, endpoint: endpoint) expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_cache_hit, endpoint: 'issue_notes') + .with(:etag_caching_cache_hit, endpoint: endpoint) middleware.call(build_request(path, if_none_match)) end @@ -120,6 +154,8 @@ describe Gitlab::EtagCaching::Middleware do expect(status).to eq 429 end + + it_behaves_like 'sends a process_action.action_controller notification', 429 end end @@ -141,9 +177,9 @@ describe Gitlab::EtagCaching::Middleware do mock_app_response expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used, endpoint: 'issue_notes') + .with(:etag_caching_middleware_used, endpoint: endpoint) expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_resource_changed, endpoint: 'issue_notes') + .with(:etag_caching_resource_changed, endpoint: endpoint) middleware.call(build_request(path, if_none_match)) end @@ -159,9 +195,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_header_missing" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used, endpoint: 'issue_notes') + .with(:etag_caching_middleware_used, endpoint: endpoint) expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_header_missing, endpoint: 'issue_notes') + .with(:etag_caching_header_missing, endpoint: endpoint) middleware.call(build_request(path, if_none_match)) end @@ -197,6 +233,21 @@ describe Gitlab::EtagCaching::Middleware do end def build_request(path, if_none_match) - { 'PATH_INFO' => path, 'HTTP_IF_NONE_MATCH' => if_none_match } + { 'PATH_INFO' => path, + 'HTTP_IF_NONE_MATCH' => if_none_match, + 'rack.input' => '', + 'REQUEST_METHOD' => 'GET' } + end + + def payload_for(event) + payload = nil + subscription = ActiveSupport::Notifications.subscribe event do |_, _, _, _, extra_payload| + payload = extra_payload + end + + yield + + ActiveSupport::Notifications.unsubscribe(subscription) + payload end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 4438d3aab82..84e1f95828a 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -77,6 +77,14 @@ describe API::Users do expect(json_response.first.keys).not_to include 'highest_role' end + it "does not return the current or last sign-in ip addresses" do + get api("/users"), params: { username: user.username } + + expect(response).to match_response_schema('public_api/v4/user/basics') + expect(json_response.first.keys).not_to include 'current_sign_in_ip' + expect(json_response.first.keys).not_to include 'last_sign_in_ip' + end + context "when public level is restricted" do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @@ -314,6 +322,14 @@ describe API::Users do expect(json_response.keys).not_to include 'highest_role' end + it "does not return the user's sign in IPs" do + get api("/users/#{user.id}", user) + + expect(response).to match_response_schema('public_api/v4/user/basic') + expect(json_response.keys).not_to include 'current_sign_in_ip' + expect(json_response.keys).not_to include 'last_sign_in_ip' + end + context 'when authenticated as admin' do it 'includes the `is_admin` field' do get api("/users/#{user.id}", admin) @@ -328,12 +344,34 @@ describe API::Users do expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response.keys).to include 'created_at' end + it 'includes the `highest_role` field' do get api("/users/#{user.id}", admin) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['highest_role']).to be(0) end + + context 'when user has not logged in' do + it 'does not include the sign in IPs' do + get api("/users/#{user.id}", admin) + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(json_response).to include('current_sign_in_ip' => nil, 'last_sign_in_ip' => nil) + end + end + + context 'when user has logged in' do + let_it_be(:signed_in_user) { create(:user, :with_sign_ins) } + + it 'includes the sign in IPs' do + get api("/users/#{signed_in_user.id}", admin) + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(json_response['current_sign_in_ip']).to eq('127.0.0.1') + expect(json_response['last_sign_in_ip']).to eq('127.0.0.1') + end + end end context 'for an anonymous user' do diff --git a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb new file mode 100644 index 00000000000..336f801f923 --- /dev/null +++ b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +shared_examples 'project import rate limiter' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when limit exceeds' do + before do + allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) + end + + it 'notifies and redirects user' do + post :create, params: {} + + expect(flash[:alert]).to eq('This endpoint has been requested too many times. Try again later.') + expect(response).to have_gitlab_http_status(302) + end + end +end diff --git a/spec/support/helpers/smime_helper.rb b/spec/support/helpers/smime_helper.rb index 3ad19cd3da0..96da3d81708 100644 --- a/spec/support/helpers/smime_helper.rb +++ b/spec/support/helpers/smime_helper.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true module SmimeHelper - include OpenSSL - INFINITE_EXPIRY = 1000.years SHORT_EXPIRY = 30.minutes @@ -20,12 +18,12 @@ module SmimeHelper public_key = key.public_key subject = if certificate_authority - X509::Name.parse("/CN=EU") + OpenSSL::X509::Name.parse("/CN=EU") else - X509::Name.parse("/CN=#{email_address}") + OpenSSL::X509::Name.parse("/CN=#{email_address}") end - cert = X509::Certificate.new + cert = OpenSSL::X509::Certificate.new cert.subject = subject cert.issuer = signed_by&.fetch(:cert, nil)&.subject || subject @@ -36,7 +34,7 @@ module SmimeHelper cert.serial = 0x0 cert.version = 2 - extension_factory = X509::ExtensionFactory.new + extension_factory = OpenSSL::X509::ExtensionFactory.new if certificate_authority extension_factory.subject_certificate = cert extension_factory.issuer_certificate = cert @@ -50,7 +48,7 @@ module SmimeHelper cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'clientAuth,emailProtection', false)) end - cert.sign(signed_by&.fetch(:key, nil) || key, Digest::SHA256.new) + cert.sign(signed_by&.fetch(:key, nil) || key, OpenSSL::Digest::SHA256.new) { key: key, cert: cert } end |