Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-13 00:09:49 +00:00
parent 458209640c
commit cf58163b56
52 changed files with 828 additions and 69 deletions

View file

@ -1,15 +1,17 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { GlButton, GlIcon, GlModal, GlModalDirective } from '@gitlab/ui';
import RequestWarning from './request_warning.vue';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
export default {
components: {
RequestWarning,
GlModal: DeprecatedModal2,
GlButton,
GlModal,
GlIcon,
},
directives: {
'gl-modal': GlModalDirective,
},
props: {
currentRequest: {
type: Object,
@ -35,7 +37,15 @@ export default {
required: true,
},
},
data() {
return {
openedBacktraces: [],
};
},
computed: {
modalId() {
return `modal-peek-${this.metric}-details`;
},
metricDetails() {
return this.currentRequest.details[this.metric];
},
@ -58,29 +68,35 @@ export default {
return '';
},
},
methods: {
toggleBacktrace(toggledIndex) {
const toggledOpenedIndex = this.openedBacktraces.indexOf(toggledIndex);
if (toggledOpenedIndex === -1) {
this.openedBacktraces = [...this.openedBacktraces, toggledIndex];
} else {
this.openedBacktraces = this.openedBacktraces.filter(
openedIndex => openedIndex !== toggledIndex,
);
}
},
itemHasOpenedBacktrace(toggledIndex) {
return this.openedBacktraces.find(openedIndex => openedIndex === toggledIndex) >= 0;
},
},
};
</script>
<template>
<div
v-if="currentRequest.details && metricDetails"
:id="`peek-view-${metric}`"
class="view"
class="gl-display-flex gl-align-items-center view"
data-qa-selector="detailed_metric_content"
>
<button
:data-target="`#modal-peek-${metric}-details`"
class="btn-blank btn-link bold"
type="button"
data-toggle="modal"
>
<gl-button v-gl-modal="modalId" class="gl-mr-2" type="button" variant="link">
{{ metricDetailsLabel }}
</button>
<gl-modal
:id="`modal-peek-${metric}-details`"
:header-title-text="header"
modal-size="xl"
class="performance-bar-modal"
>
</gl-button>
<gl-modal :modal-id="modalId" :title="header" size="lg" modal-class="gl-mt-7" scrollable>
<table class="table">
<template v-if="detailsList.length">
<tr v-for="(item, index) in detailsList" :key="index">
@ -90,7 +106,7 @@ export default {
}}</span>
</td>
<td>
<div class="js-toggle-container">
<div>
<div
v-for="(key, keyIndex) in keys"
:key="key"
@ -98,16 +114,18 @@ export default {
:class="{ 'mb-3 bold': keyIndex == 0 }"
>
{{ item[key] }}
<button
<gl-button
v-if="keyIndex == 0 && item.backtrace"
class="text-expander js-toggle-button"
class="gl-ml-3"
data-testid="backtrace-expand-btn"
type="button"
:aria-label="__('Toggle backtrace')"
@click="toggleBacktrace(index)"
>
<gl-icon :size="12" name="ellipsis_h" />
</button>
</gl-button>
</div>
<pre v-if="item.backtrace" class="backtrace-row js-toggle-content mt-2">{{
<pre v-if="itemHasOpenedBacktrace(index)" class="backtrace-row mt-2">{{
item.backtrace
}}</pre>
</div>

View file

@ -6,7 +6,7 @@
left: 0;
top: 0;
width: 100%;
z-index: #{$zindex-modal-backdrop + 1};
z-index: #{$zindex-modal-backdrop - 1};
height: $performance-bar-height;
background: $black;

View file

@ -28,6 +28,11 @@ class RegistrationsController < Devise::RegistrationsController
super do |new_user|
persist_accepted_terms_if_required(new_user)
set_role_required(new_user)
if pending_approval?
NotificationService.new.new_instance_access_request(new_user)
end
yield new_user if block_given?
end
@ -131,6 +136,12 @@ class RegistrationsController < Devise::RegistrationsController
render action: 'new'
end
def pending_approval?
return false unless Gitlab::CurrentSettings.require_admin_approval_after_user_signup
resource.persisted? && resource.blocked_pending_approval?
end
def sign_up_params
params.require(:user).permit(:username, :email, :name, :first_name, :last_name, :password)
end

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Mutations
module PackageEventable
extend ActiveSupport::Concern
private
def track_event(event, scope)
::Packages::CreateEventService.new(nil, current_user, event_name: event, scope: scope).execute
::Gitlab::Tracking.event(event.to_s, scope.to_s)
end
end
end

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Mutations
module ContainerRepositories
class Destroy < Mutations::BaseMutation
include ::Mutations::PackageEventable
graphql_name 'DestroyContainerRepository'
authorize :destroy_container_image
argument :id,
::Types::GlobalIDType[::ContainerRepository],
required: true,
description: 'ID of the container repository.'
field :container_repository,
Types::ContainerRepositoryType,
null: false,
description: 'The container repository policy after scheduling the deletion.'
def resolve(id:)
container_repository = authorized_find!(id: id)
container_repository.delete_scheduled!
DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id)
track_event(:delete_repository, :container)
{
container_repository: container_repository,
errors: []
}
end
private
def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
end
end
end

View file

@ -2,6 +2,8 @@
module Resolvers
class ContainerRepositoriesResolver < BaseResolver
include ::Mutations::PackageEventable
type Types::ContainerRepositoryType, null: true
argument :name, GraphQL::STRING_TYPE,
@ -13,12 +15,5 @@ module Resolvers
.execute
.tap { track_event(:list_repositories, :container) }
end
private
def track_event(event, scope)
::Packages::CreateEventService.new(nil, current_user, event_name: event, scope: scope).execute
::Gitlab::Tracking.event(event.to_s, scope.to_s)
end
end
end

View file

@ -82,6 +82,7 @@ module Types
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
mount_mutation Mutations::DesignManagement::Move
mount_mutation Mutations::ContainerExpirationPolicies::Update
mount_mutation Mutations::ContainerRepositories::Destroy
mount_mutation Mutations::Ci::PipelineCancel
mount_mutation Mutations::Ci::PipelineDestroy
mount_mutation Mutations::Ci::PipelineRetry

View file

@ -214,6 +214,24 @@ module EmailsHelper
end
end
def instance_access_request_text(user, format: nil)
gitlab_host = Gitlab.config.gitlab.host
_('%{username} has asked for a GitLab account on your instance %{host}:') % { username: sanitize_name(user.name), host: gitlab_host }
end
def instance_access_request_link(user, format: nil)
url = admin_user_url(user)
case format
when :html
user_page = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: url }
_("Click %{link_start}here%{link_end} to view the request.").html_safe % { link_start: user_page, link_end: '</a>'.html_safe }
else
_('Click %{link_to} to view the request.') % { link_to: url }
end
end
def contact_your_administrator_text
_('Please contact your administrator with any questions.')
end

View file

@ -9,6 +9,15 @@ module Emails
mail(to: @user.notification_email, subject: subject("Account was created for you"))
end
def instance_access_request_email(user, recipient)
@user = user
@recipient = recipient
profile_email_with_layout(
to: recipient.notification_email,
subject: subject(_("GitLab Account Request")))
end
# rubocop: disable CodeReuse/ActiveRecord
def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id)
@ -63,13 +72,9 @@ module Emails
@target_url = edit_profile_password_url
Gitlab::I18n.with_locale(@user.preferred_language) do
mail(
profile_email_with_layout(
to: @user.notification_email,
subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host })
) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
subject: subject(_("%{host} sign-in from new location") % { host: Gitlab.config.gitlab.host }))
end
end
@ -82,6 +87,15 @@ module Emails
mail(to: @user.notification_email, subject: subject(_("Two-factor authentication disabled")))
end
end
private
def profile_email_with_layout(to:, subject:, layout: 'mailer')
mail(to: to, subject: subject) do |format|
format.html { render layout: layout }
format.text { render layout: layout }
end
end
end
end

View file

@ -109,6 +109,11 @@ module HasRepository
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :http)
end
# Is overridden in EE::Project for Geo support
def lfs_http_url_to_repo(_operation = nil)
http_url_to_repo
end
def web_url(only_path: nil)
Gitlab::UrlBuilder.build(self, only_path: only_path)
end

View file

@ -1469,11 +1469,6 @@ class Project < ApplicationRecord
services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
end
# Is overridden in EE
def lfs_http_url_to_repo(_)
http_url_to_repo
end
def feature_usage
super.presence || build_feature_usage
end

View file

@ -28,6 +28,8 @@ class User < ApplicationRecord
DEFAULT_NOTIFICATION_LEVEL = :participating
INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
add_authentication_token_field :feed_token
add_authentication_token_field :static_object_token
@ -341,6 +343,7 @@ class User < ApplicationRecord
# Scopes
scope :admins, -> { where(admin: true) }
scope :instance_access_request_approvers_to_be_notified, -> { admins.active.order_recent_sign_in.limit(INSTANCE_ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) }
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
scope :external, -> { where(external: true) }

View file

@ -370,6 +370,16 @@ class NotificationService
end
end
def new_instance_access_request(user)
recipients = User.instance_access_request_approvers_to_be_notified # https://gitlab.com/gitlab-org/gitlab/-/issues/277016 will change this
return true if recipients.empty?
recipients.each do |recipient|
mailer.instance_access_request_email(user, recipient).deliver_later
end
end
# Members
def new_access_request(member)
return true unless member.notifiable?(:subscription)

View file

@ -0,0 +1,10 @@
#content
= email_default_heading(say_hello(@recipient))
%p
= instance_access_request_text(@user, format: :html)
%p
= _("Username: %{username}") % { username: @user.username }
%p
= _("Email: %{email}") % { email: @user.email }
%p
= instance_access_request_link(@user, format: :html)

View file

@ -0,0 +1,8 @@
<%= say_hello(@recipient) %>
<%= instance_access_request_text(@user) %>
<%= _("Username: %{username}") % { username: @user.username } %>
<%= _("Email: %{email}") % { email: @user.email } %>
<%= instance_access_request_link(@user) %>

View file

@ -0,0 +1,5 @@
---
title: Add container repository destroy GraphQL mutation
merge_request: 47175
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Update detailed_metric.vue modal to match Pajamas guidelines
merge_request: 46183
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Send email notifications to admins about users pending approval
merge_request: 46895
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Use dedicated signing key for CI_JOB_JWT by default
merge_request: 47336
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix internal lfs_authenticate API for non-project repositories
merge_request: 47404
author:
type: fixed

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258546
milestone: '13.6'
type: development
group: group::release management
default_enabled: false
default_enabled: true

View file

@ -69,10 +69,10 @@ options = {
# :filter is optional
# 'cn' looks for all "cn"s under :base
# '*' is the search string - here, it's a wildcard
filter: Net::Ldap::Filter.eq('cn', '*'),
filter: Net::LDAP::Filter.eq('cn', '*'),
# :attributes is optional
# the attributes we want to get returned
# the attributes we want to get returnedk
attributes: %w(dn cn memberuid member submember uniquemember memberof)
}
adapter.ldap_search(options)

View file

@ -397,6 +397,20 @@ To configure the `s3` storage driver in Omnibus:
}
```
To avoid using static credentials, use an
[IAM role](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
and omit `accesskey` and `secretkey`. Make sure that your IAM profile follows
[the permissions documented by Docker](https://docs.docker.com/registry/storage-drivers/s3/#s3-permission-scopes).
```ruby
registry['storage'] = {
's3' => {
'bucket' => 'your-s3-bucket',
'region' => 'your-s3-region'
}
}
```
- `regionendpoint` is only required when configuring an S3 compatible service such as MinIO. It takes a URL such as `http://127.0.0.1:9000`.
- `your-s3-bucket` should be the name of a bucket that exists, and can't include subdirectories.
@ -412,8 +426,8 @@ when you [deployed your Docker registry](https://docs.docker.com/registry/deploy
```yaml
storage:
s3:
accesskey: 's3-access-key'
secretkey: 's3-secret-key-for-access-key'
accesskey: 's3-access-key' # Not needed if IAM role used
secretkey: 's3-secret-key-for-access-key' # Not needed if IAM role used
bucket: 'your-s3-bucket'
region: 'your-s3-region'
regionendpoint: 'your-s3-regionendpoint'

View file

@ -6093,6 +6093,41 @@ type DestroyBoardPayload {
errors: [String!]!
}
"""
Autogenerated input type of DestroyContainerRepository
"""
input DestroyContainerRepositoryInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
ID of the container repository.
"""
id: ContainerRepositoryID!
}
"""
Autogenerated return type of DestroyContainerRepository
"""
type DestroyContainerRepositoryPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The container repository policy after scheduling the deletion.
"""
containerRepository: ContainerRepository!
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Autogenerated input type of DestroyNote
"""
@ -13470,6 +13505,7 @@ type Mutation {
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
destroyBoard(input: DestroyBoardInput!): DestroyBoardPayload
destroyBoardList(input: DestroyBoardListInput!): DestroyBoardListPayload
destroyContainerRepository(input: DestroyContainerRepositoryInput!): DestroyContainerRepositoryPayload
destroyNote(input: DestroyNoteInput!): DestroyNotePayload
destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload

View file

@ -16765,6 +16765,112 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DestroyContainerRepositoryInput",
"description": "Autogenerated input type of DestroyContainerRepository",
"fields": null,
"inputFields": [
{
"name": "id",
"description": "ID of the container repository.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ContainerRepositoryID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DestroyContainerRepositoryPayload",
"description": "Autogenerated return type of DestroyContainerRepository",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "containerRepository",
"description": "The container repository policy after scheduling the deletion.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ContainerRepository",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DestroyNoteInput",
@ -38177,6 +38283,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "destroyContainerRepository",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "DestroyContainerRepositoryInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DestroyContainerRepositoryPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "destroyNote",
"description": null,

View file

@ -1002,6 +1002,16 @@ Autogenerated return type of DestroyBoard.
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### DestroyContainerRepositoryPayload
Autogenerated return type of DestroyContainerRepository.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `containerRepository` | ContainerRepository! | The container repository policy after scheduling the deletion. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
### DestroyNotePayload
Autogenerated return type of DestroyNote.

View file

@ -56,7 +56,7 @@ The JWT's payload looks like this:
}
```
The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID Connect private key. The expire time for the token will be set to job's timeout, if specified, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
The JWT is encoded by using RS256 and signed with a dedicated private key. The expire time for the token will be set to job's timeout, if specified, or 5 minutes if it is not. The key used to sign this token may change without any notice. In such case retrying the job will generate new JWT using the current signing key.
You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication.

View file

@ -50,7 +50,7 @@ with the [GitLab Container Registry](../../user/packages/container_registry/inde
This way of triggering can only be used when invoked inside `.gitlab-ci.yml`,
and it creates a dependent pipeline relation visible on the
[pipeline graph](../multi_project_pipelines.md#overview). For example:
[pipeline graph](../multi_project_pipelines.md). For example:
```yaml
build_docs:

View file

@ -3837,8 +3837,8 @@ For more information, see [Deployments Safety](../environments/deployment_safety
These methods are supported:
- [`tag_name`](#releasetag_name)
- [`description`](#releasedescription)
- [`name`](#releasename) (optional)
- [`description`](#releasedescription) (optional)
- [`ref`](#releaseref) (optional)
- [`milestones`](#releasemilestones) (optional)
- [`released_at`](#releasereleased_at) (optional)

View file

@ -899,6 +899,32 @@ it.each([
);
```
**Note**: only use template literal block if pretty print is **not** needed for spec output. For example, empty strings, nested objects etc.
For example, when testing the difference between an empty search string and a non-empty search string, the use of the array block syntax with the pretty print option would be preferred. That way the differences between an empty string e.g. `''` and a non-empty string e.g. `'search string'` would be visible in the spec output. Whereas with a template literal block, the empty string would be shown as a space, which could lead to a confusing developer experience
```javascript
// bad
it.each`
searchTerm | expected
${''} | ${{ issue: { users: { nodes: [] } } }}
${'search term'} | ${{ issue: { other: { nested: [] } } }}
`('when search term is $searchTerm, it returns $expected', ({ searchTerm, expected }) => {
expect(search(searchTerm)).toEqual(expected)
});
// good
it.each([
['', { issue: { users: { nodes: [] } } }],
['search term', { issue: { other: { nested: [] } } }],
])('when search term is %p, expect to return %p',
(searchTerm, expected) => {
expect(search(searchTerm)).toEqual(expected)
}
);
```
```javascript
// test suite with tagged template literal block
describe.each`

View file

@ -166,7 +166,7 @@ a base domain of `example.com`, you'd need a DNS entry like:
```
In this case, the deployed applications are served from `example.com`, and `1.2.3.4`
is the IP address of your load balancer; generally NGINX ([see requirements](#requirements)).
is the IP address of your load balancer; generally NGINX ([see requirements](requirements.md)).
Setting up the DNS record is beyond the scope of this document; check with your
DNS provider for information.
@ -183,7 +183,7 @@ See [Auto DevOps requirements for Amazon ECS](requirements.md#auto-devops-requir
## Enabling/Disabling Auto DevOps
When first using Auto DevOps, review the [requirements](#requirements) to ensure
When first using Auto DevOps, review the [requirements](requirements.md) to ensure
all the necessary components to make full use of Auto DevOps are available. First-time
users should follow the [quick start guide](quick_start_guide.md).

View file

@ -65,11 +65,16 @@ To add a broadcast message:
1. Navigate to the **Admin Area > Messages** page.
1. Add the text for the message to the **Message** field. Markdown and emoji are supported.
1. If required, click the **Customize colors** link to edit the background color and font color of the message.
1. Select one of the suggested background colors, or add the hex code of a different color. The default color is orange.
1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `/users/*/issues`.
1. Select a date for the message to start and end.
1. Click the **Add broadcast message** button.
NOTE: **Note:**
The **Background color** field expects the value to be a hexadecimal code because
the form uses the [color_field](https://api.rubyonrails.org/v6.0.3.4/classes/ActionView/Helpers/FormHelper.html#method-i-color_field)
helper method, which generates the proper HTML to render.
NOTE: **Note:**
Once a broadcast message has expired, it is no longer displayed in the UI but is still listed in the
list of broadcast messages. User can also dismiss a broadcast message if the option **Dismissable** is set.

View file

@ -46,6 +46,22 @@ To enforce confirmation of the email address used for new sign ups:
1. Go to **Admin Area > Settings > General** and expand **Sign-up restrictions**.
1. Select the **Enable email restrictions for sign ups** checkbox, then select **Save changes**.
## Soft email confirmation
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2.
> - It's [deployed behind a feature flag](../../..//user/feature_flags.md), disabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-soft-email-confirmation).
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
The soft email confirmation improves the signup experience for new users by allowing
them to sign in without an immediate confirmation when an email confirmation is required.
GitLab shows the user a reminder to confirm their email address, and the user can't
create or update pipelines until their email address is confirmed.
## Minimum password length limit
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20661) in GitLab 12.6
@ -90,6 +106,25 @@ semicolon, comma, or a new line.
![Domain Denylist](img/domain_denylist.png)
### Enable or disable soft email confirmation
Soft email confirmation is under development but ready for production use.
It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:soft_email_confirmation)
```
To disable it:
```ruby
Feature.disable(:soft_email_confirmation)
```
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View file

@ -299,7 +299,7 @@ To recover a default stage that was previously hidden:
A default value stream is readily available for each group. You can create additional value streams based on the different areas of work that you would like to measure.
Once created, a new value stream includes the [seven stages](#overview) that follow
Once created, a new value stream includes the [seven stages](#default-stages) that follow
[GitLab workflow](../../topics/gitlab_flow.md)
best practices. You can customize this flow by adding, hiding or re-ordering stages.

View file

@ -71,11 +71,11 @@ The following languages and dependency managers are supported:
Plans are underway for supporting the following languages, dependency managers, and dependency files. For details, see the issue link for each.
| Package Managers | Languages | Supported files | Scan tools |
| ------------------- | --------- | --------------- | ------------ |
| Package Managers | Languages | Supported files | Scan tools | Issue |
| ------------------- | --------- | --------------- | ---------- | ----- |
| [Pipenv](https://pipenv.pypa.io/en/latest/) | Python | `Pipfile.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#11756](https://gitlab.com/gitlab-org/gitlab/-/issues/11756) |
| [Poetry](https://python-poetry.org/) | Python | `poetry.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#7006](https://gitlab.com/gitlab-org/gitlab/-/issues/7006) |
| [sbt](https://www.scala-sbt.org/) 1.3+ ([Coursier](https://get-coursier.io/))| Scala | `build.sbt` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#249526](https://gitlab.com/gitlab-org/gitlab/-/issues/249526) |
| [sbt](https://www.scala-sbt.org/) 1.3+ ([Coursier](https://get-coursier.io/))| Scala | `build.sbt` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | [GitLab#271345](https://gitlab.com/gitlab-org/gitlab/-/issues/271345) |
## Contribute your scanner

View file

@ -609,7 +609,7 @@ Instructions are available in the [legacy template project](https://gitlab.com/g
This is the current default behavior, because the job's status indicates success or failure of the analyzer itself.
Analyzer results are displayed in the [job logs](../../ci/pipelines/#expand-and-collapse-job-log-sections),
[Merge Request widget](sast/index.md#overview)
[Merge Request widget](sast/index.md)
or [Security Dashboard](security_dashboard/index.md).
There is [an open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235772) in which changes to this behavior are being discussed.

View file

@ -107,7 +107,7 @@ as shown in the following table:
| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** |
| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities) | **{dotted-circle}** | **{check-circle}** |
| [Access to Security Dashboard](#security-dashboard) | **{dotted-circle}** | **{check-circle}** |
| [Configure SAST in the UI](#configure-sast-in-the-ui) | **{dotted-circle}** | **{check-circle}** |

View file

@ -83,7 +83,7 @@ as shown in the following table:
| [Configure Secret Detection Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
| [Customize Secret Detection Settings](#customizing-settings) | **{check-circle}** | **{check-circle}** |
| View [JSON Report](../sast/index.md#reports-json-format) | **{check-circle}** | **{check-circle}** |
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
| Presentation of JSON Report in Merge Request | **{dotted-circle}** | **{check-circle}** |
| [Interaction with Vulnerabilities](../vulnerabilities/index.md) | **{dotted-circle}** | **{check-circle}** |
| [Access to Security Dashboard](../security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |

View file

@ -41,6 +41,9 @@ The Dependency Proxy is not available for projects.
## Use the Dependency Proxy for Docker images
CAUTION: **Important:**
In some specific storage configurations, an issue occurs and container images are not pulled correctly from the cache. The problem occurs when an image is located in object storage. The proxy looks for it locally and fails to find it. View [issue #208080](https://gitlab.com/gitlab-org/gitlab/-/issues/208080) for details.
You can use GitLab as a source for your Docker images.
Prerequisites:

View file

@ -11,7 +11,7 @@ GitLab Pages offers.
To familiarize yourself with GitLab Pages first:
- Read an [introduction to GitLab Pages](index.md#overview).
- Read an [introduction to GitLab Pages](index.md).
- Learn [how to get started with Pages](index.md#getting-started).
- Learn how to enable GitLab Pages
across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md).

View file

@ -34,10 +34,10 @@ module API
{ status: success, message: message }.merge(extra_options).compact
end
def lfs_authentication_url(project)
def lfs_authentication_url(container)
# This is a separate method so that EE can alter its behaviour more
# easily.
project.http_url_to_repo
container.lfs_http_url_to_repo
end
def check_allowed(params)
@ -135,6 +135,8 @@ module API
end
post "/lfs_authenticate", feature_category: :source_code_management do
not_found! unless container&.lfs_enabled?
status 200
unless actor.key_or_user
@ -145,7 +147,7 @@ module API
Gitlab::LfsToken
.new(actor.key_or_user)
.authentication_payload(lfs_authentication_url(project))
.authentication_payload(lfs_authentication_url(container))
end
#

View file

@ -63,7 +63,7 @@ module Gitlab
def key
@key ||= begin
key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project)
key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project, default_enabled: true)
Gitlab::CurrentSettings.ci_jwt_signing_key
else
Rails.application.secrets.openid_connect_signing_key

View file

@ -879,6 +879,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
msgid "%{username} has asked for a GitLab account on your instance %{host}:"
msgstr ""
msgid "%{username}'s avatar"
msgstr ""
@ -5551,6 +5554,12 @@ msgstr ""
msgid "Clears weight."
msgstr ""
msgid "Click %{link_start}here%{link_end} to view the request."
msgstr ""
msgid "Click %{link_to} to view the request."
msgstr ""
msgid "Click the %{strong_open}Download%{strong_close} button and wait for downloading to complete."
msgstr ""
@ -9993,6 +10002,9 @@ msgstr ""
msgid "Email updates (optional)"
msgstr ""
msgid "Email: %{email}"
msgstr ""
msgid "EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
msgstr ""
@ -12580,6 +12592,9 @@ msgstr ""
msgid "GitLab API"
msgstr ""
msgid "GitLab Account Request"
msgstr ""
msgid "GitLab Billing Team."
msgstr ""
@ -29564,6 +29579,9 @@ msgstr ""
msgid "Username or email"
msgstr ""
msgid "Username: %{username}"
msgstr ""
msgid "Users"
msgstr ""

View file

@ -53,6 +53,14 @@ RSpec.describe RegistrationsController do
.to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.')
end
it 'emails the access request to approvers' do
expect_next_instance_of(NotificationService) do |notification|
allow(notification).to receive(:new_instance_access_request).with(User.find_by(email: 'new@user.com'))
end
subject
end
context 'email confirmation' do
context 'when `send_user_confirmation_email` is true' do
before do
@ -86,6 +94,12 @@ RSpec.describe RegistrationsController do
expect(flash[:notice]).to be_nil
end
it 'does not email the approvers' do
expect(NotificationService).not_to receive(:new)
subject
end
context 'email confirmation' do
context 'when `send_user_confirmation_email` is true' do
before do

View file

@ -66,7 +66,7 @@ FactoryBot.define do
trait :with_sign_ins do
sign_in_count { 3 }
current_sign_in_at { Time.now }
current_sign_in_at { FFaker::Time.between(10.days.ago, 1.day.ago) }
last_sign_in_at { FFaker::Time.between(10.days.ago, 1.day.ago) }
current_sign_in_ip { '127.0.0.1' }
last_sign_in_ip { '127.0.0.1' }

View file

@ -0,0 +1,36 @@
{
"type": "object",
"required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete"],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"location": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"expirationPolicyStartedAt": {
"type": ["string", "null"]
},
"status": {
"type": ["string", "null"]
},
"tagsCount": {
"type": "integer"
},
"canDelete": {
"type": "boolean"
}
}
}

View file

@ -1,3 +1,4 @@
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import DetailedMetric from '~/performance_bar/components/detailed_metric.vue';
@ -14,6 +15,11 @@ describe('detailedMetric', () => {
});
};
const findAllTraceBlocks = () => wrapper.findAll('pre');
const findTraceBlockAtIndex = index => findAllTraceBlocks().at(index);
const findExpandBacktraceBtns = () => wrapper.findAll('[data-testid="backtrace-expand-btn"]');
const findExpandedBacktraceBtnAtIndex = index => findExpandBacktraceBtns().at(index);
afterEach(() => {
wrapper.destroy();
});
@ -37,7 +43,12 @@ describe('detailedMetric', () => {
describe('when the current request has details', () => {
const requestDetails = [
{ duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
{ duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
{
duration: '23',
feature: 'rebase_in_progress',
request: '',
backtrace: ['other', 'example'],
},
];
describe('with a default metric name', () => {
@ -82,7 +93,7 @@ describe('detailedMetric', () => {
expect(request.text()).toContain(requestDetails[index].request);
});
expect(wrapper.find('.text-expander.js-toggle-button')).not.toBeNull();
expect(wrapper.find('.js-toggle-button')).not.toBeNull();
wrapper.findAll('.performance-bar-modal td:nth-child(2)').wrappers.forEach(request => {
expect(request.text()).toContain('world');
@ -96,6 +107,30 @@ describe('detailedMetric', () => {
it('displays request warnings', () => {
expect(wrapper.find(RequestWarning).exists()).toBe(true);
});
it('can open and close traces', async () => {
expect(findAllTraceBlocks()).toHaveLength(0);
// Each block click on a new trace and assert that the correct
// count is open and that the content is what we expect to ensure
// we opened or closed the right one
const secondExpandButton = findExpandedBacktraceBtnAtIndex(1);
findExpandedBacktraceBtnAtIndex(0).vm.$emit('click');
await nextTick();
expect(findAllTraceBlocks()).toHaveLength(1);
expect(findTraceBlockAtIndex(0).text()).toContain(requestDetails[0].backtrace[0]);
secondExpandButton.vm.$emit('click');
await nextTick();
expect(findAllTraceBlocks()).toHaveLength(2);
expect(findTraceBlockAtIndex(1).text()).toContain(requestDetails[1].backtrace[0]);
secondExpandButton.vm.$emit('click');
await nextTick();
expect(findAllTraceBlocks()).toHaveLength(1);
expect(findTraceBlockAtIndex(0).text()).toContain(requestDetails[0].backtrace[0]);
});
});
describe('when using a custom metric title', () => {
@ -140,7 +175,11 @@ describe('detailedMetric', () => {
});
});
it('renders only the number of calls', () => {
it('renders only the number of calls', async () => {
expect(trimText(wrapper.text())).toEqual('456 notification bullet');
findExpandedBacktraceBtnAtIndex(0).vm.$emit('click');
await nextTick();
expect(trimText(wrapper.text())).toEqual('456 notification backtrace bullet');
});
});

View file

@ -0,0 +1,67 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::ContainerRepositories::Destroy do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:container_repository) { create(:container_repository) }
let_it_be(:user) { create(:user) }
let(:project) { container_repository.project }
let(:id) { container_repository.to_global_id.to_s }
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
describe '#resolve' do
subject do
described_class.new(object: nil, context: { current_user: user }, field: nil)
.resolve(id: id)
end
shared_examples 'destroying the container repository' do
it 'destroys the container repistory' do
expect(::Packages::CreateEventService)
.to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
expect(DeleteContainerRepositoryWorker)
.to receive(:perform_async).with(user.id, container_repository.id)
expect { subject }.to change { ::Packages::Event.count }.by(1)
expect(container_repository.reload.delete_scheduled?).to be true
end
end
shared_examples 'denying access to container respository' do
it 'raises an error' do
expect(DeleteContainerRepositoryWorker)
.not_to receive(:perform_async).with(user.id, container_repository.id)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'with valid id' do
where(:user_role, :shared_examples_name) do
:maintainer | 'destroying the container repository'
:developer | 'destroying the container repository'
:reporter | 'denying access to container respository'
:guest | 'denying access to container respository'
:anonymous | 'denying access to container respository'
end
with_them do
before do
project.send("add_#{user_role}", user) unless user_role == :anonymous
end
it_behaves_like params[:shared_examples_name]
end
end
context 'with invalid id' do
let(:id) { 'gid://gitlab/ContainerRepository/5555' }
it_behaves_like 'denying access to container respository'
end
end
end

View file

@ -1740,6 +1740,16 @@ RSpec.describe User do
end
end
describe '.instance_access_request_approvers_to_be_notified' do
let_it_be(:admin_list) { create_list(:user, 12, :admin, :with_sign_ins) }
it 'returns up to the ten most recently active instance admins' do
active_admins_in_recent_sign_in_desc_order = User.admins.active.order_recent_sign_in.limit(10)
expect(User.instance_access_request_approvers_to_be_notified).to eq(active_admins_in_recent_sign_in_desc_order)
end
end
describe '.filter_items' do
let(:user) { double }

View file

@ -0,0 +1,91 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Destroying a container repository' do
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be_with_reload(:container_repository) { create(:container_repository) }
let_it_be(:user) { create(:user) }
let(:project) { container_repository.project }
let(:id) { container_repository.to_global_id.to_s }
let(:query) do
<<~GQL
containerRepository {
#{all_graphql_fields_for('ContainerRepository')}
}
errors
GQL
end
let(:params) { { id: container_repository.to_global_id.to_s } }
let(:mutation) { graphql_mutation(:destroy_container_repository, params, query) }
let(:mutation_response) { graphql_mutation_response(:destroyContainerRepository) }
let(:container_repository_mutation_response) { mutation_response['containerRepository'] }
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(tags: %w[a b c])
end
shared_examples 'destroying the container repository' do
it 'destroy the container repository' do
expect(::Packages::CreateEventService)
.to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original
expect(DeleteContainerRepositoryWorker)
.to receive(:perform_async).with(user.id, container_repository.id)
expect { subject }.to change { ::Packages::Event.count }.by(1)
expect(container_repository_mutation_response).to match_schema('graphql/container_repository')
expect(container_repository_mutation_response['status']).to eq('DELETE_SCHEDULED')
end
it_behaves_like 'returning response status', :success
end
shared_examples 'denying the mutation request' do
it 'does not destroy the container repository' do
expect(DeleteContainerRepositoryWorker)
.not_to receive(:perform_async).with(user.id, container_repository.id)
expect { subject }.not_to change { ::Packages::Event.count }
expect(mutation_response).to be_nil
end
it_behaves_like 'returning response status', :success
end
describe 'post graphql mutation' do
subject { post_graphql_mutation(mutation, current_user: user) }
context 'with valid id' do
where(:user_role, :shared_examples_name) do
:maintainer | 'destroying the container repository'
:developer | 'destroying the container repository'
:reporter | 'denying the mutation request'
:guest | 'denying the mutation request'
:anonymous | 'denying the mutation request'
end
with_them do
before do
project.send("add_#{user_role}", user) unless user_role == :anonymous
end
it_behaves_like params[:shared_examples_name]
end
end
context 'with invalid id' do
let(:params) { { id: 'gid://gitlab/ContainerRepository/5555' } }
it_behaves_like 'denying the mutation request'
end
end
end

View file

@ -253,6 +253,7 @@ RSpec.describe API::Internal::Base do
describe "POST /internal/lfs_authenticate" do
before do
stub_lfs_setting(enabled: true)
project.add_developer(user)
end
@ -293,6 +294,33 @@ RSpec.describe API::Internal::Base do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns a 404 when LFS is disabled on the project' do
project.update!(lfs_enabled: false)
lfs_auth_user(user.id, project)
expect(response).to have_gitlab_http_status(:not_found)
end
context 'other repository types' do
it 'returns the correct information for a project wiki' do
wiki = create(:project_wiki, project: project)
lfs_auth_user(user.id, wiki)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['username']).to eq(user.username)
expect(json_response['repository_http_path']).to eq(wiki.http_url_to_repo)
expect(json_response['expires_in']).to eq(Gitlab::LfsToken::DEFAULT_EXPIRE_TIME)
expect(Gitlab::LfsToken.new(user).token_valid?(json_response['lfs_token'])).to be_truthy
end
it 'returns a 404 when the container does not support LFS' do
snippet = create(:project_snippet)
lfs_auth_user(user.id, snippet)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'deploy key' do

View file

@ -2305,6 +2305,26 @@ RSpec.describe NotificationService, :mailer do
end
end
describe '#new_instance_access_request', :deliver_mails_inline do
let_it_be(:user) { create(:user, :blocked_pending_approval) }
let_it_be(:admins) { create_list(:admin, 12, :with_sign_ins) }
subject { notification.new_instance_access_request(user) }
before do
reset_delivered_emails!
stub_application_setting(require_admin_approval_after_user_signup: true)
end
it 'sends notification only to a maximum of ten most recently active instance admins' do
ten_most_recently_active_instance_admins = User.admins.active.sort_by(&:current_sign_in_at).last(10)
subject
should_only_email(*ten_most_recently_active_instance_admins)
end
end
describe 'GroupMember', :deliver_mails_inline do
let(:added_user) { create(:user) }