Commit a9b3bf08 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 5084c6e9 f0e9b443
...@@ -6,8 +6,8 @@ import { ...@@ -6,8 +6,8 @@ import {
GlModal, GlModal,
GlAlert, GlAlert,
GlLoadingIcon, GlLoadingIcon,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
GlButton, GlButton,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -28,8 +28,8 @@ export default { ...@@ -28,8 +28,8 @@ export default {
GlModal, GlModal,
GlAlert, GlAlert,
GlLoadingIcon, GlLoadingIcon,
GlDeprecatedDropdown, GlDropdown,
GlDeprecatedDropdownItem, GlDropdownItem,
TimeAgoTooltip, TimeAgoTooltip,
GlButton, GlButton,
}, },
...@@ -231,17 +231,17 @@ export default { ...@@ -231,17 +231,17 @@ export default {
</template> </template>
</div> </div>
<div class="d-block d-sm-none dropdown"> <div class="d-block d-sm-none dropdown">
<gl-deprecated-dropdown :text="__('Options')" class="w-100" toggle-class="text-center"> <gl-dropdown :text="__('Options')" block>
<gl-deprecated-dropdown-item <gl-dropdown-item
v-for="(action, index) in personalSnippetActions" v-for="(action, index) in personalSnippetActions"
:key="index" :key="index"
:disabled="action.disabled" :disabled="action.disabled"
:title="action.title" :title="action.title"
:href="action.href" :href="action.href"
@click="action.click ? action.click() : undefined" @click="action.click ? action.click() : undefined"
>{{ action.text }}</gl-deprecated-dropdown-item >{{ action.text }}</gl-dropdown-item
> >
</gl-deprecated-dropdown> </gl-dropdown>
</div> </div>
</div> </div>
......
...@@ -119,7 +119,7 @@ class Namespace < ApplicationRecord ...@@ -119,7 +119,7 @@ class Namespace < ApplicationRecord
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query, include_parents: false) def search(query, include_parents: false)
if include_parents if include_parents
where(id: Route.fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id)) where(id: Route.for_routable_type(Namespace.name).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]]).select(:source_id))
else else
fuzzy_search(query, [:path, :name]) fuzzy_search(query, [:path, :name])
end end
......
...@@ -20,6 +20,7 @@ class Route < ApplicationRecord ...@@ -20,6 +20,7 @@ class Route < ApplicationRecord
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") } scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
scope :for_routable, -> (routable) { where(source: routable) } scope :for_routable, -> (routable) { where(source: routable) }
scope :for_routable_type, -> (routable_type) { where(source_type: routable_type) }
scope :sort_by_path_length, -> { order('LENGTH(routes.path)', :path) } scope :sort_by_path_length, -> { order('LENGTH(routes.path)', :path) }
def rename_descendants def rename_descendants
......
---
title: Fix problems with Groups API search query parameter
merge_request: 46394
author:
type: fixed
---
title: Add minimal access users to group members api endpoints
merge_request: 46238
author:
type: changed
---
title: Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/snippets/components/snippet_header.vue
merge_request: 41428
author: nuwe1
type: other
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
- authentication_and_authorization - authentication_and_authorization
- auto_devops - auto_devops
- backup_restore - backup_restore
- behavior_analytics
- boards - boards
- chatops - chatops
- cloud_native_installation - cloud_native_installation
...@@ -56,7 +55,6 @@ ...@@ -56,7 +55,6 @@
- gitaly - gitaly
- gitlab_docs - gitlab_docs
- gitlab_handbook - gitlab_handbook
- gitter
- global_search - global_search
- helm_chart_registry - helm_chart_registry
- importers - importers
...@@ -101,6 +99,8 @@ ...@@ -101,6 +99,8 @@
- secret_detection - secret_detection
- secrets_management - secrets_management
- security_benchmarking - security_benchmarking
- security_orchestration
- self_monitoring
- serverless - serverless
- service_desk - service_desk
- snippets - snippets
...@@ -108,12 +108,13 @@ ...@@ -108,12 +108,13 @@
- static_application_security_testing - static_application_security_testing
- static_site_editor - static_site_editor
- subgroups - subgroups
- synthetic_monitoring
- templates - templates
- time_tracking - time_tracking
- tracing - tracing
- usability_testing - usability_testing
- users - users
- value_stream_management - value_stream_analytics
- vulnerability_database - vulnerability_database
- vulnerability_management - vulnerability_management
- web_firewall - web_firewall
......
...@@ -171,7 +171,7 @@ Example response: ...@@ -171,7 +171,7 @@ Example response:
} }
``` ```
## Create an issue board **(STARTER)** ## Create an issue board
Creates a project issue board. Creates a project issue board.
...@@ -248,7 +248,7 @@ Example response: ...@@ -248,7 +248,7 @@ Example response:
} }
``` ```
## Update an issue board **(STARTER)** ## Update an issue board
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5954) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.1. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5954) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.1.
...@@ -259,14 +259,14 @@ PUT /projects/:id/boards/:board_id ...@@ -259,14 +259,14 @@ PUT /projects/:id/boards/:board_id
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- | | ---------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board | | `board_id` | integer | yes | The ID of a board |
| `name` | string | no | The new name of the board | | `name` | string | no | The new name of the board |
| `assignee_id` | integer | no | The assignee the board should be scoped to | | `assignee_id` **(STARTER)** | integer | no | The assignee the board should be scoped to |
| `milestone_id` | integer | no | The milestone the board should be scoped to | | `milestone_id` **(STARTER)** | integer | no | The milestone the board should be scoped to |
| `labels` | string | no | Comma-separated list of label names which the board should be scoped to | | `labels` **(STARTER)** | string | no | Comma-separated list of label names which the board should be scoped to |
| `weight` | integer | no | The weight range from 0 to 9, to which the board should be scoped to | | `weight` **(STARTER)** | integer | no | The weight range from 0 to 9, to which the board should be scoped to |
```shell ```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/boards/1?name=new_name&milestone_id=43&assignee_id=1&labels=Doing&weight=4" curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/boards/1?name=new_name&milestone_id=43&assignee_id=1&labels=Doing&weight=4"
...@@ -329,7 +329,7 @@ Example response: ...@@ -329,7 +329,7 @@ Example response:
} }
``` ```
## Delete an issue board **(STARTER)** ## Delete an issue board
Deletes a project issue board. Deletes a project issue board.
......
...@@ -53,6 +53,7 @@ export default { ...@@ -53,6 +53,7 @@ export default {
variables() { variables() {
return { return {
fullPath: this.namespacePath, fullPath: this.namespacePath,
searchTerm: this.searchTerm,
withExcessStorageData: this.isAdditionalStorageFlagEnabled, withExcessStorageData: this.isAdditionalStorageFlagEnabled,
}; };
}, },
...@@ -62,6 +63,7 @@ export default { ...@@ -62,6 +63,7 @@ export default {
data() { data() {
return { return {
namespace: {}, namespace: {},
searchTerm: '',
}; };
}, },
computed: { computed: {
...@@ -94,6 +96,14 @@ export default { ...@@ -94,6 +96,14 @@ export default {
return this.isAdditionalStorageFlagEnabled && !this.$apollo.queries.namespace.loading; return this.isAdditionalStorageFlagEnabled && !this.$apollo.queries.namespace.loading;
}, },
}, },
methods: {
handleSearch(input) {
// if length === 0 clear the search, if length > 2 update the search term
if (input.length === 0 || input.length > 2) {
this.searchTerm = input;
}
},
},
modalId: 'temporary-increase-storage-modal', modalId: 'temporary-increase-storage-modal',
}; };
...@@ -172,6 +182,7 @@ export default { ...@@ -172,6 +182,7 @@ export default {
<projects-table <projects-table
:projects="namespaceProjects" :projects="namespaceProjects"
:additional-purchased-storage-size="namespace.additionalPurchasedStorageSize || 0" :additional-purchased-storage-size="namespace.additionalPurchasedStorageSize || 0"
@search="handleSearch"
/> />
<temporary-storage-increase-modal <temporary-storage-increase-modal
v-if="isStorageIncreaseModalVisible" v-if="isStorageIncreaseModalVisible"
......
...@@ -116,7 +116,7 @@ export default { ...@@ -116,7 +116,7 @@ export default {
data-testid="projectTableRow" data-testid="projectTableRow"
> >
<div <div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-50 gl-text-truncate" class="table-section gl-white-space-normal! gl-flex-sm-wrap section-50 gl-text-truncate gl-pr-5"
role="gridcell" role="gridcell"
> >
<div class="table-mobile-header gl-font-weight-bold" role="rowheader"> <div class="table-mobile-header gl-font-weight-bold" role="rowheader">
...@@ -146,7 +146,7 @@ export default { ...@@ -146,7 +146,7 @@ export default {
</div> </div>
</div> </div>
<div <div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-25 gl-text-truncate" class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
role="gridcell" role="gridcell"
> >
<div class="table-mobile-header gl-font-weight-bold" role="rowheader"> <div class="table-mobile-header gl-font-weight-bold" role="rowheader">
...@@ -155,7 +155,7 @@ export default { ...@@ -155,7 +155,7 @@ export default {
<div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div> <div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</div>
</div> </div>
<div <div
class="table-section gl-white-space-normal! gl-flex-sm-wrap section-25 gl-text-truncate" class="table-section gl-white-space-normal! gl-flex-sm-wrap section-15 gl-text-truncate"
role="gridcell" role="gridcell"
> >
<div class="table-mobile-header gl-font-weight-bold" role="rowheader"> <div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
<script> <script>
import { GlSearchBoxByType } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Project from './project.vue'; import Project from './project.vue';
import ProjectWithExcessStorage from './project_with_excess_storage.vue'; import ProjectWithExcessStorage from './project_with_excess_storage.vue';
import { SEARCH_DEBOUNCE_MS } from '~/ref/constants';
export default { export default {
components: { components: {
Project, Project,
ProjectWithExcessStorage, ProjectWithExcessStorage,
GlSearchBoxByType,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
props: { props: {
...@@ -30,25 +33,33 @@ export default { ...@@ -30,25 +33,33 @@ export default {
return Project; return Project;
}, },
}, },
searchDebounceValue: SEARCH_DEBOUNCE_MS,
}; };
</script> </script>
<template> <template>
<div> <div>
<div <div
class="gl-responsive-table-row table-row-header gl-pl-5 gl-border-t-solid gl-border-t-1 gl-border-gray-100 gl-mt-5 gl-line-height-normal gl-text-black-normal gl-font-base" class="gl-responsive-table-row table-row-header gl-border-t-solid gl-border-t-1 gl-border-gray-100 gl-mt-5 gl-line-height-normal gl-text-black-normal gl-font-base"
role="row" role="row"
> >
<template v-if="isAdditionalStorageFlagEnabled"> <template v-if="isAdditionalStorageFlagEnabled">
<div class="table-section section-50 gl-font-weight-bold" role="columnheader"> <div class="table-section section-50 gl-font-weight-bold gl-pl-5" role="columnheader">
{{ __('Project') }} {{ __('Project') }}
</div> </div>
<div class="table-section section-25 gl-font-weight-bold" role="columnheader"> <div class="table-section section-15 gl-font-weight-bold" role="columnheader">
{{ __('Usage') }} {{ __('Usage') }}
</div> </div>
<div class="table-section section-25 gl-font-weight-bold" role="columnheader"> <div class="table-section section-15 gl-font-weight-bold" role="columnheader">
{{ __('Excess storage') }} {{ __('Excess storage') }}
</div> </div>
<div class="table-section section-20 gl-font-weight-bold gl-pl-6" role="columnheader">
<gl-search-box-by-type
:placeholder="__('Search by name')"
:debounce="$options.searchDebounceValue"
@input="input => this.$emit('search', input)"
/>
</div>
</template> </template>
<template v-else> <template v-else>
<div class="table-section section-70 gl-font-weight-bold" role="columnheader"> <div class="table-section section-70 gl-font-weight-bold" role="columnheader">
......
query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false) { query getStorageCounter(
$fullPath: ID!
$searchTerm: String = ""
$withExcessStorageData: Boolean = false
) {
namespace(fullPath: $fullPath) { namespace(fullPath: $fullPath) {
id id
name name
...@@ -19,7 +23,7 @@ query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false) ...@@ -19,7 +23,7 @@ query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false)
wikiSize wikiSize
snippetsSize snippetsSize
} }
projects(includeSubgroups: true, sort: STORAGE) { projects(includeSubgroups: true, sort: STORAGE, search: $searchTerm) {
edges { edges {
node { node {
id id
......
---
title: Resolve "SAST_DEFAULT_ANALYZERS is written with default value by SAST Config UI"
merge_request: 46388
author:
type: fixed
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
class CodeReviewAnalytics < ::API::Base class CodeReviewAnalytics < ::API::Base
include PaginationParams include PaginationParams
feature_category :planning_analytics
helpers do helpers do
def project def project
@project ||= find_project!(params[:project_id]) @project ||= find_project!(params[:project_id])
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
'This feature is gated by the `:group_activity_analytics`'\ 'This feature is gated by the `:group_activity_analytics`'\
' feature flag, introduced in GitLab 12.9.' ' feature flag, introduced in GitLab 12.9.'
feature_category :planning_analytics
before do before do
authenticate! authenticate!
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class AuditEvents < ::API::Base class AuditEvents < ::API::Base
include ::API::PaginationParams include ::API::PaginationParams
feature_category :audit_events
before do before do
authenticated_as_admin! authenticated_as_admin!
forbidden! unless ::License.feature_available?(:admin_audit_log) forbidden! unless ::License.feature_available?(:admin_audit_log)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class Dependencies < ::API::Base class Dependencies < ::API::Base
feature_category :dependency_scanning
helpers do helpers do
def dependencies_by(params) def dependencies_by(params)
pipeline = ::Security::ReportFetchService.new(user_project, ::Ci::JobArtifact.dependency_list_reports).pipeline pipeline = ::Security::ReportFetchService.new(user_project, ::Ci::JobArtifact.dependency_list_reports).pipeline
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class DependencyProxy < ::API::Base class DependencyProxy < ::API::Base
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
feature_category :dependency_proxy
helpers do helpers do
def obtain_new_purge_cache_lease def obtain_new_purge_cache_lease
Gitlab::ExclusiveLease Gitlab::ExclusiveLease
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class ElasticsearchIndexedNamespaces < ::API::Base class ElasticsearchIndexedNamespaces < ::API::Base
before { authenticated_as_admin! } before { authenticated_as_admin! }
feature_category :global_search
resource :elasticsearch_indexed_namespaces do resource :elasticsearch_indexed_namespaces do
desc 'Rollout namespaces to be indexed up to n%' do desc 'Rollout namespaces to be indexed up to n%' do
detail <<~END detail <<~END
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class EpicIssues < ::API::Base class EpicIssues < ::API::Base
feature_category :epics
before do before do
authenticate! authenticate!
authorize_epics_feature! authorize_epics_feature!
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class EpicLinks < ::API::Base class EpicLinks < ::API::Base
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :epics
before do before do
authenticate! authenticate!
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class Epics < ::API::Base class Epics < ::API::Base
include PaginationParams include PaginationParams
feature_category :epics
before do before do
authenticate_non_get! authenticate_non_get!
authorize_epics_feature! authorize_epics_feature!
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class Experiments < ::API::Base class Experiments < ::API::Base
before { authorize_read_experiments! } before { authorize_read_experiments! }
feature_category :product_analytics
resource :experiments do resource :experiments do
desc 'Get a list of all experiments' do desc 'Get a list of all experiments' do
success EE::API::Entities::Experiment success EE::API::Entities::Experiment
......
...@@ -4,6 +4,7 @@ require 'base64' ...@@ -4,6 +4,7 @@ require 'base64'
module API module API
class Geo < ::API::Base class Geo < ::API::Base
feature_category :geo_replication
resource :geo do resource :geo do
helpers do helpers do
def sanitized_node_status_params def sanitized_node_status_params
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
include APIGuard include APIGuard
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :geo_replication
before do before do
authenticate_admin_or_geo_node! authenticate_admin_or_geo_node!
end end
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
include APIGuard include APIGuard
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :geo_replication
before do before do
authenticated_as_admin! authenticated_as_admin!
not_found!('Geo node not found') unless Gitlab::Geo.current_node not_found!('Geo node not found') unless Gitlab::Geo.current_node
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class GroupHooks < ::API::Base class GroupHooks < ::API::Base
include ::API::PaginationParams include ::API::PaginationParams
feature_category :integrations
before { authenticate! } before { authenticate! }
before { authorize! :admin_group, user_group } before { authorize! :admin_group, user_group }
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class GroupPushRule < ::API::Base class GroupPushRule < ::API::Base
feature_category :source_code_management
before { authenticate! } before { authenticate! }
before { check_group_push_rule_access! } before { check_group_push_rule_access! }
before { authorize_change_param(user_group, :commit_committer_check, :reject_unsigned_commits) } before { authorize_change_param(user_group, :commit_committer_check, :reject_unsigned_commits) }
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class Iterations < ::API::Base class Iterations < ::API::Base
include PaginationParams include PaginationParams
feature_category :issue_tracking
helpers do helpers do
params :list_params do params :list_params do
optional :state, type: String, values: %w[opened upcoming started closed all], default: 'all', optional :state, type: String, values: %w[opened upcoming started closed all], default: 'all',
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
# group. # group.
before { authenticated_with_ldap_admin_access! } before { authenticated_with_ldap_admin_access! }
feature_category :authentication_and_authorization
resource :ldap do resource :ldap do
helpers do helpers do
def get_group_list(provider, search) def get_group_list(provider, search)
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class LdapGroupLinks < ::API::Base class LdapGroupLinks < ::API::Base
before { authenticate! } before { authenticate! }
feature_category :authentication_and_authorization
params do params do
requires :id, type: String, desc: 'The ID of a group' requires :id, type: String, desc: 'The ID of a group'
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class License < ::API::Base class License < ::API::Base
before { authenticated_as_admin! } before { authenticated_as_admin! }
feature_category :provision
resource :license do resource :license do
desc 'Get information on the currently active license' do desc 'Get information on the currently active license' do
success EE::API::Entities::GitlabLicenseWithActiveUsers success EE::API::Entities::GitlabLicenseWithActiveUsers
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class ManagedLicenses < ::API::Base class ManagedLicenses < ::API::Base
include PaginationParams include PaginationParams
feature_category :license_compliance
before { authenticate! unless route.settings[:skip_authentication] } before { authenticate! unless route.settings[:skip_authentication] }
helpers do helpers do
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class MergeRequestApprovalRules < ::API::Base class MergeRequestApprovalRules < ::API::Base
before { authenticate_non_get! } before { authenticate_non_get! }
feature_category :code_review
helpers do helpers do
def find_merge_request_approval_rule(merge_request, id) def find_merge_request_approval_rule(merge_request, id)
merge_request.approval_rules.find_by_id!(id) merge_request.approval_rules.find_by_id!(id)
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class MergeTrains < ::API::Base class MergeTrains < ::API::Base
include PaginationParams include PaginationParams
feature_category :continuous_integration
before do before do
authorize_read_merge_trains! authorize_read_merge_trains!
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class ProjectAliases < ::API::Base class ProjectAliases < ::API::Base
include PaginationParams include PaginationParams
feature_category :source_code_management
before { check_feature_availability } before { check_feature_availability }
before { authenticated_as_admin! } before { authenticated_as_admin! }
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::ProjectApprovalRulesHelpers helpers ::API::Helpers::ProjectApprovalRulesHelpers
feature_category :code_review
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::ProjectApprovalRulesHelpers helpers ::API::Helpers::ProjectApprovalRulesHelpers
feature_category :code_review
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class ProjectApprovals < ::API::Base class ProjectApprovals < ::API::Base
feature_category :code_review
before { authenticate! } before { authenticate! }
before { authorize! :update_approvers, user_project } before { authorize! :update_approvers, user_project }
......
...@@ -4,6 +4,8 @@ require_dependency 'declarative_policy' ...@@ -4,6 +4,8 @@ require_dependency 'declarative_policy'
module API module API
class ProjectMirror < ::API::Base class ProjectMirror < ::API::Base
feature_category :continuous_integration
helpers do helpers do
def github_webhook_signature def github_webhook_signature
@github_webhook_signature ||= headers['X-Hub-Signature'] @github_webhook_signature ||= headers['X-Hub-Signature']
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module API module API
class ProjectPushRule < ::API::Base class ProjectPushRule < ::API::Base
feature_category :source_code_management
before { authenticate! } before { authenticate! }
before { authorize_admin_project } before { authorize_admin_project }
before { check_project_feature_available!(:push_rules) } before { check_project_feature_available!(:push_rules) }
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
before { authorize_admin_project } before { authorize_admin_project }
feature_category :continuous_delivery
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -7,7 +7,7 @@ module API ...@@ -7,7 +7,7 @@ module API
before { authenticate! } before { authenticate! }
[Issue].each do |eventable_type| { Issue => :issue_tracking }.each do |eventable_type, feature_category|
parent_type = eventable_type.parent_class.to_s.underscore parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize eventables_str = eventable_type.to_s.underscore.pluralize
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
use :pagination use :pagination
end end
get ":id/#{eventables_str}/:eventable_id/resource_iteration_events" do get ":id/#{eventables_str}/:eventable_id/resource_iteration_events", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
events = eventable.resource_iteration_events.with_api_entity_associations events = eventable.resource_iteration_events.with_api_entity_associations
...@@ -39,7 +39,7 @@ module API ...@@ -39,7 +39,7 @@ module API
requires :event_id, type: String, desc: 'The ID of a resource iteration event' requires :event_id, type: String, desc: 'The ID of a resource iteration event'
requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
end end
get ":id/#{eventables_str}/:eventable_id/resource_iteration_events/:event_id" do get ":id/#{eventables_str}/:eventable_id/resource_iteration_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
event = eventable.resource_iteration_events.find(params[:event_id]) event = eventable.resource_iteration_events.find(params[:event_id])
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
before { authenticate! } before { authenticate! }
feature_category :issue_tracking
params do params do
requires :id, type: String, desc: "The ID of a project" requires :id, type: String, desc: "The ID of a project"
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class Scim < ::API::Base class Scim < ::API::Base
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :authentication_and_authorization
prefix 'api/scim' prefix 'api/scim'
version 'v2' version 'v2'
content_type :json, 'application/scim+json' content_type :json, 'application/scim+json'
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::NotesHelpers helpers ::API::Helpers::NotesHelpers
helpers ::RendersNotes helpers ::RendersNotes
feature_category :code_review
params do params do
requires :id, type: String, desc: "The ID of a Project" requires :id, type: String, desc: "The ID of a Project"
end end
......
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
include ::API::Helpers::VulnerabilitiesHooks include ::API::Helpers::VulnerabilitiesHooks
include PaginationParams include PaginationParams
feature_category :vulnerability_management
helpers ::API::Helpers::VulnerabilitiesHelpers helpers ::API::Helpers::VulnerabilitiesHelpers
helpers do helpers do
......
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
include ::API::Helpers::VulnerabilitiesHooks include ::API::Helpers::VulnerabilitiesHooks
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :vulnerability_management
helpers do helpers do
def vulnerability_export def vulnerability_export
strong_memoize(:vulnerability_export) do strong_memoize(:vulnerability_export) do
......
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
include PaginationParams include PaginationParams
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :vulnerability_management
helpers do helpers do
def pipeline def pipeline
strong_memoize(:pipeline) do strong_memoize(:pipeline) do
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class VulnerabilityIssueLinks < ::API::Base class VulnerabilityIssueLinks < ::API::Base
include ::API::Helpers::VulnerabilitiesHooks include ::API::Helpers::VulnerabilitiesHooks
feature_category :vulnerability_management
helpers ::API::Helpers::VulnerabilitiesHelpers helpers ::API::Helpers::VulnerabilitiesHelpers
helpers do helpers do
......
...@@ -8,6 +8,8 @@ module EE ...@@ -8,6 +8,8 @@ module EE
prepend EE::API::BoardsResponses # rubocop: disable Cop/InjectEnterpriseEditionModule prepend EE::API::BoardsResponses # rubocop: disable Cop/InjectEnterpriseEditionModule
feature_category :boards
before do before do
authenticate! authenticate!
end end
......
...@@ -28,6 +28,14 @@ module EE ...@@ -28,6 +28,14 @@ module EE
members members
end end
override :source_members
def source_members(source)
return super if source.is_a?(Project)
return super unless source.minimal_access_role_allowed?
source.all_group_members
end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def can_view_group_identity?(members_source) def can_view_group_identity?(members_source)
......
...@@ -7,19 +7,19 @@ module EE ...@@ -7,19 +7,19 @@ module EE
module Controller module Controller
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
WHITELISTED_GEO_ROUTES = { ALLOWLISTED_GEO_ROUTES = {
'admin/geo/nodes' => %w{update} 'admin/geo/nodes' => %w{update}
}.freeze }.freeze
WHITELISTED_GEO_ROUTES_TRACKING_DB = { ALLOWLISTED_GEO_ROUTES_TRACKING_DB = {
'admin/geo/projects' => %w{destroy resync reverify force_redownload resync_all reverify_all}, 'admin/geo/projects' => %w{destroy resync reverify force_redownload resync_all reverify_all},
'admin/geo/uploads' => %w{destroy} 'admin/geo/uploads' => %w{destroy}
}.freeze }.freeze
private private
override :whitelisted_routes override :allowlisted_routes
def whitelisted_routes def allowlisted_routes
super || geo_node_update_route? || geo_proxy_git_ssh_route? || geo_api_route? super || geo_node_update_route? || geo_proxy_git_ssh_route? || geo_api_route?
end end
...@@ -30,10 +30,10 @@ module EE ...@@ -30,10 +30,10 @@ module EE
controller = route_hash[:controller] controller = route_hash[:controller]
action = route_hash[:action] action = route_hash[:action]
if WHITELISTED_GEO_ROUTES[controller]&.include?(action) if ALLOWLISTED_GEO_ROUTES[controller]&.include?(action)
::Gitlab::Database.db_read_write? ::Gitlab::Database.db_read_write?
else else
WHITELISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action) ALLOWLISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Security module Security
module CiConfiguration module CiConfiguration
class SastBuildActions class SastBuildActions
SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec' SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs'
def initialize(auto_devops_enabled, params, existing_gitlab_ci_content) def initialize(auto_devops_enabled, params, existing_gitlab_ci_content)
@auto_devops_enabled = auto_devops_enabled @auto_devops_enabled = auto_devops_enabled
...@@ -55,6 +55,7 @@ module Security ...@@ -55,6 +55,7 @@ module Security
config['analyzers'] config['analyzers']
&.select {|a| a['enabled'] } &.select {|a| a['enabled'] }
&.collect {|a| a['name'] } &.collect {|a| a['name'] }
&.sort
&.join(', ') &.join(', ')
else else
SAST_DEFAULT_ANALYZERS SAST_DEFAULT_ANALYZERS
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import StorageApp from 'ee/storage_counter/components/app.vue'; import StorageApp from 'ee/storage_counter/components/app.vue';
import Project from 'ee/storage_counter/components/project.vue'; import Project from 'ee/storage_counter/components/project.vue';
import ProjectsTable from 'ee/storage_counter/components/projects_table.vue';
import StorageInlineAlert from 'ee/storage_counter/components/storage_inline_alert.vue'; import StorageInlineAlert from 'ee/storage_counter/components/storage_inline_alert.vue';
import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
import UsageGraph from 'ee/storage_counter/components/usage_graph.vue'; import UsageGraph from 'ee/storage_counter/components/usage_graph.vue';
import UsageStatistics from 'ee/storage_counter/components/usage_statistics.vue'; import UsageStatistics from 'ee/storage_counter/components/usage_statistics.vue';
import TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
import { formatUsageSize } from 'ee/storage_counter/utils'; import { formatUsageSize } from 'ee/storage_counter/utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { namespaceData, withRootStorageStatistics } from '../mock_data'; import { namespaceData, withRootStorageStatistics } from '../mock_data';
...@@ -21,6 +22,7 @@ describe('Storage counter app', () => { ...@@ -21,6 +22,7 @@ describe('Storage counter app', () => {
const findUsageGraph = () => wrapper.find(UsageGraph); const findUsageGraph = () => wrapper.find(UsageGraph);
const findUsageStatistics = () => wrapper.find(UsageStatistics); const findUsageStatistics = () => wrapper.find(UsageStatistics);
const findStorageInlineAlert = () => wrapper.find(StorageInlineAlert); const findStorageInlineAlert = () => wrapper.find(StorageInlineAlert);
const findProjectsTable = () => wrapper.find(ProjectsTable);
const createComponent = ({ const createComponent = ({
props = {}, props = {},
...@@ -213,4 +215,46 @@ describe('Storage counter app', () => { ...@@ -213,4 +215,46 @@ describe('Storage counter app', () => {
}); });
}); });
}); });
describe('filtering projects', () => {
beforeEach(() => {
createComponent({
additionalRepoStorageByNamespace: true,
namespace: withRootStorageStatistics,
});
});
const sampleSearchTerm = 'GitLab';
const sampleShortSearchTerm = '12';
it('triggers search if user enters search input', () => {
expect(wrapper.vm.searchTerm).toBe('');
findProjectsTable().vm.$emit('search', sampleSearchTerm);
expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm);
});
it('triggers search if user clears the entered search input', () => {
const projectsTable = findProjectsTable();
expect(wrapper.vm.searchTerm).toBe('');
projectsTable.vm.$emit('search', sampleSearchTerm);
expect(wrapper.vm.searchTerm).toBe(sampleSearchTerm);
projectsTable.vm.$emit('search', '');
expect(wrapper.vm.searchTerm).toBe('');
});
it('does not trigger search if user enters short search input', () => {
expect(wrapper.vm.searchTerm).toBe('');
findProjectsTable().vm.$emit('search', sampleShortSearchTerm);
expect(wrapper.vm.searchTerm).toBe('');
});
});
}); });
...@@ -64,11 +64,11 @@ RSpec.describe Security::CiConfiguration::SastBuildActions do ...@@ -64,11 +64,11 @@ RSpec.describe Security::CiConfiguration::SastBuildActions do
params.merge( { 'analyzers' => params.merge( { 'analyzers' =>
[ [
{ {
'name' => "brakeman", 'name' => "flawfinder",
'enabled' => true 'enabled' => true
}, },
{ {
'name' => "flawfinder", 'name' => "brakeman",
'enabled' => true 'enabled' => true
} }
] } ] }
...@@ -305,6 +305,20 @@ RSpec.describe Security::CiConfiguration::SastBuildActions do ...@@ -305,6 +305,20 @@ RSpec.describe Security::CiConfiguration::SastBuildActions do
end end
end end
describe 'Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS' do
subject(:variable) {Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS}
it 'is sorted alphabetically' do
sorted_variable = Security::CiConfiguration::SastBuildActions::SAST_DEFAULT_ANALYZERS
.split(',')
.map(&:strip)
.sort
.join(', ')
expect(variable).to eq(sorted_variable)
end
end
# stubbing this method allows this spec file to use fast_spec_helper # stubbing this method allows this spec file to use fast_spec_helper
def fast_auto_devops_stages def fast_auto_devops_stages
auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') ) auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') )
......
...@@ -3,6 +3,117 @@ ...@@ -3,6 +3,117 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Members do RSpec.describe API::Members do
context 'group members endpoints for group with minimal access feature' do
let_it_be(:group) { create(:group) }
let_it_be(:minimal_access_member) { create(:group_member, :minimal_access, source: group) }
let_it_be(:owner) { create(:user) }
before do
group.add_owner(owner)
end
describe "GET /groups/:id/members" do
subject do
get api("/groups/#{group.id}/members", owner)
json_response
end
it 'returns user with minimal access when feature is available' do
stub_licensed_features(minimal_access_role: true)
expect(subject.map { |u| u['id'] }).to match_array [owner.id, minimal_access_member.user_id]
end
it 'does not return user with minimal access when feature is unavailable' do
stub_licensed_features(minimal_access_role: false)
expect(subject.map { |u| u['id'] }).not_to include(minimal_access_member.user_id)
end
end
describe 'POST /groups/:id/members' do
let(:stranger) { create(:user) }
subject do
post api("/groups/#{group.id}/members", owner),
params: { user_id: stranger.id, access_level: Member::MINIMAL_ACCESS }
end
context 'when minimal access role is not available' do
it 'does not create a member' do
expect do
subject
end.not_to change { group.all_group_members.count }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq({ 'access_level' => ['is not included in the list'] })
end
end
context 'when minimal access role is available' do
it 'creates a member' do
stub_licensed_features(minimal_access_role: true)
expect do
subject
end.to change { group.all_group_members.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(stranger.id)
end
end
end
describe 'PUT /groups/:id/members/:user_id' do
let(:expires_at) { 2.days.from_now.to_date }
context 'when minimal access role is available' do
it 'updates the member' do
stub_licensed_features(minimal_access_role: true)
put api("/groups/#{group.id}/members/#{minimal_access_member.user_id}", owner),
params: { expires_at: expires_at, access_level: Member::MINIMAL_ACCESS }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(minimal_access_member.user_id)
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
end
context 'when minimal access role is not available' do
it 'does not update the member' do
put api("/groups/#{group.id}/members/#{minimal_access_member.user_id}", owner),
params: { expires_at: expires_at, access_level: Member::MINIMAL_ACCESS }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'DELETE /groups/:id/members/:user_id' do
context 'when minimal access role is available' do
it 'deletes the member' do
stub_licensed_features(minimal_access_role: true)
expect do
delete api("/groups/#{group.id}/members/#{minimal_access_member.user_id}", owner)
end.to change { group.all_group_members.count }.by(-1)
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when minimal access role is not available' do
it 'does not delete the member' do
expect do
delete api("/groups/#{group.id}/members/#{minimal_access_member.id}", owner)
expect(response).to have_gitlab_http_status(:not_found)
end.not_to change { group.all_group_members.count }
end
end
end
end
context 'group members endpoint for group managed accounts' do context 'group members endpoint for group managed accounts' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:owner) { create(:user) } let(:owner) { create(:user) }
......
...@@ -282,7 +282,7 @@ module API ...@@ -282,7 +282,7 @@ module API
end end
end end
route :any, '*path' do route :any, '*path', feature_category: :not_owned do
error!('404 Not Found', 404) error!('404 Not Found', 404)
end end
end end
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
before { authenticate! } before { authenticate! }
feature_category :container_registry
namespace 'registry' do namespace 'registry' do
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
......
...@@ -20,12 +20,16 @@ module API ...@@ -20,12 +20,16 @@ module API
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def retrieve_members(source, params:, deep: false) def retrieve_members(source, params:, deep: false)
members = deep ? find_all_members(source) : source.members.where.not(user_id: nil) members = deep ? find_all_members(source) : source_members(source).where.not(user_id: nil)
members = members.includes(:user) members = members.includes(:user)
members = members.references(:user).merge(User.search(params[:query])) if params[:query].present? members = members.references(:user).merge(User.search(params[:query])) if params[:query].present?
members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members = members.where(user_id: params[:user_ids]) if params[:user_ids].present?
members members
end end
def source_members(source)
source.members
end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def find_all_members(source) def find_all_members(source)
......
...@@ -128,13 +128,13 @@ module API ...@@ -128,13 +128,13 @@ module API
# changes - changes as "oldrev newrev ref", see Gitlab::ChangesList # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
# check_ip - optional, only in EE version, may limit access to # check_ip - optional, only in EE version, may limit access to
# group resources based on its IP restrictions # group resources based on its IP restrictions
post "/allowed" do post "/allowed", feature_category: :source_code_management do
# It was moved to a separate method so that EE can alter its behaviour more # It was moved to a separate method so that EE can alter its behaviour more
# easily. # easily.
check_allowed(params) check_allowed(params)
end end
post "/lfs_authenticate" do post "/lfs_authenticate", feature_category: :source_code_management do
status 200 status 200
unless actor.key_or_user unless actor.key_or_user
...@@ -152,7 +152,7 @@ module API ...@@ -152,7 +152,7 @@ module API
# Get a ssh key using the fingerprint # Get a ssh key using the fingerprint
# #
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get '/authorized_keys' do get '/authorized_keys', feature_category: :source_code_management do
fingerprint = params.fetch(:fingerprint) do fingerprint = params.fetch(:fingerprint) do
Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
end end
...@@ -165,11 +165,11 @@ module API ...@@ -165,11 +165,11 @@ module API
# #
# Discover user by ssh key, user id or username # Discover user by ssh key, user id or username
# #
get '/discover' do get '/discover', feature_category: :authentication_and_authorization do
present actor.user, with: Entities::UserSafe present actor.user, with: Entities::UserSafe
end end
get '/check' do get '/check', feature_category: :not_owned do
{ {
api_version: API.version, api_version: API.version,
gitlab_version: Gitlab::VERSION, gitlab_version: Gitlab::VERSION,
...@@ -178,7 +178,7 @@ module API ...@@ -178,7 +178,7 @@ module API
} }
end end
post '/two_factor_recovery_codes' do post '/two_factor_recovery_codes', feature_category: :authentication_and_authorization do
status 200 status 200
actor.update_last_used_at! actor.update_last_used_at!
...@@ -207,7 +207,7 @@ module API ...@@ -207,7 +207,7 @@ module API
{ success: true, recovery_codes: codes } { success: true, recovery_codes: codes }
end end
post '/personal_access_token' do post '/personal_access_token', feature_category: :authentication_and_authorization do
status 200 status 200
actor.update_last_used_at! actor.update_last_used_at!
...@@ -257,7 +257,7 @@ module API ...@@ -257,7 +257,7 @@ module API
{ success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at } { success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at }
end end
post '/pre_receive' do post '/pre_receive', feature_category: :source_code_management do
status 200 status 200
reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
...@@ -265,7 +265,7 @@ module API ...@@ -265,7 +265,7 @@ module API
{ reference_counter_increased: reference_counter_increased } { reference_counter_increased: reference_counter_increased }
end end
post '/post_receive' do post '/post_receive', feature_category: :source_code_management do
status 200 status 200
response = PostReceiveService.new(actor.user, repository, project, params).execute response = PostReceiveService.new(actor.user, repository, project, params).execute
...@@ -273,7 +273,7 @@ module API ...@@ -273,7 +273,7 @@ module API
present response, with: Entities::InternalPostReceive::Response present response, with: Entities::InternalPostReceive::Response
end end
post '/two_factor_config' do post '/two_factor_config', feature_category: :authentication_and_authorization do
status 200 status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli) break { success: false } unless Feature.enabled?(:two_factor_for_cli)
...@@ -295,7 +295,7 @@ module API ...@@ -295,7 +295,7 @@ module API
end end
end end
post '/two_factor_otp_check' do post '/two_factor_otp_check', feature_category: :authentication_and_authorization do
status 200 status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli) break { success: false } unless Feature.enabled?(:two_factor_for_cli)
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
# Kubernetes Internal API # Kubernetes Internal API
module Internal module Internal
class Kubernetes < ::API::Base class Kubernetes < ::API::Base
feature_category :kubernetes_management
before do before do
check_feature_enabled check_feature_enabled
authenticate_gitlab_kas_request! authenticate_gitlab_kas_request!
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
before { authenticate_by_gitlab_shell_token! } before { authenticate_by_gitlab_shell_token! }
feature_category :source_code_management
helpers do helpers do
def find_lfs_object(lfs_oid) def find_lfs_object(lfs_oid)
LfsObject.find_by_oid(lfs_oid) LfsObject.find_by_oid(lfs_oid)
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
# Pages Internal API # Pages Internal API
module Internal module Internal
class Pages < ::API::Base class Pages < ::API::Base
feature_category :pages
before do before do
authenticate_gitlab_pages_request! authenticate_gitlab_pages_request!
end end
......
...@@ -136,7 +136,7 @@ module API ...@@ -136,7 +136,7 @@ module API
source = find_source(source_type, params.delete(:id)) source = find_source(source_type, params.delete(:id))
authorize_admin_source!(source_type, source) authorize_admin_source!(source_type, source)
member = source.members.find_by!(user_id: params[:user_id]) member = source_members(source).find_by!(user_id: params[:user_id])
updated_member = updated_member =
::Members::UpdateService ::Members::UpdateService
.new(current_user, declared_params(include_missing: false)) .new(current_user, declared_params(include_missing: false))
...@@ -159,7 +159,7 @@ module API ...@@ -159,7 +159,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ":id/members/:user_id" do delete ":id/members/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
member = source.members.find_by!(user_id: params[:user_id]) member = source_members(source).find_by!(user_id: params[:user_id])
destroy_conditionally!(member) do destroy_conditionally!(member) do
::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables]) ::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables])
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class PersonalAccessTokens < ::API::Base class PersonalAccessTokens < ::API::Base
include ::API::PaginationParams include ::API::PaginationParams
feature_category :authentication_and_authorization
desc 'Get all Personal Access Tokens' do desc 'Get all Personal Access Tokens' do
detail 'This feature was added in GitLab 13.3' detail 'This feature was added in GitLab 13.3'
success Entities::PersonalAccessToken success Entities::PersonalAccessToken
......
...@@ -22,6 +22,8 @@ module API ...@@ -22,6 +22,8 @@ module API
include PaginationParams include PaginationParams
feature_category :integrations
before do before do
authorize_jira_user_agent!(request) authorize_jira_user_agent!(request)
authenticate! authenticate!
......
...@@ -9,20 +9,20 @@ module Gitlab ...@@ -9,20 +9,20 @@ module Gitlab
APPLICATION_JSON_TYPES = %W{#{APPLICATION_JSON} application/vnd.git-lfs+json}.freeze APPLICATION_JSON_TYPES = %W{#{APPLICATION_JSON} application/vnd.git-lfs+json}.freeze
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
WHITELISTED_GIT_ROUTES = { ALLOWLISTED_GIT_ROUTES = {
'repositories/git_http' => %w{git_upload_pack git_receive_pack} 'repositories/git_http' => %w{git_upload_pack git_receive_pack}
}.freeze }.freeze
WHITELISTED_GIT_LFS_ROUTES = { ALLOWLISTED_GIT_LFS_ROUTES = {
'repositories/lfs_api' => %w{batch}, 'repositories/lfs_api' => %w{batch},
'repositories/lfs_locks_api' => %w{verify create unlock} 'repositories/lfs_locks_api' => %w{verify create unlock}
}.freeze }.freeze
WHITELISTED_GIT_REVISION_ROUTES = { ALLOWLISTED_GIT_REVISION_ROUTES = {
'projects/compare' => %w{create} 'projects/compare' => %w{create}
}.freeze }.freeze
WHITELISTED_SESSION_ROUTES = { ALLOWLISTED_SESSION_ROUTES = {
'sessions' => %w{destroy}, 'sessions' => %w{destroy},
'admin/sessions' => %w{create destroy} 'admin/sessions' => %w{create destroy}
}.freeze }.freeze
...@@ -55,7 +55,7 @@ module Gitlab ...@@ -55,7 +55,7 @@ module Gitlab
def disallowed_request? def disallowed_request?
DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) &&
!whitelisted_routes !allowlisted_routes
end end
def json_request? def json_request?
...@@ -87,7 +87,7 @@ module Gitlab ...@@ -87,7 +87,7 @@ module Gitlab
end end
# Overridden in EE module # Overridden in EE module
def whitelisted_routes def allowlisted_routes
workhorse_passthrough_route? || internal_route? || lfs_route? || compare_git_revisions_route? || sidekiq_route? || session_route? || graphql_query? workhorse_passthrough_route? || internal_route? || lfs_route? || compare_git_revisions_route? || sidekiq_route? || session_route? || graphql_query?
end end
...@@ -98,7 +98,7 @@ module Gitlab ...@@ -98,7 +98,7 @@ module Gitlab
return false unless request.post? && return false unless request.post? &&
request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack') request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack')
WHITELISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def internal_route? def internal_route?
...@@ -109,7 +109,7 @@ module Gitlab ...@@ -109,7 +109,7 @@ module Gitlab
# Calling route_hash may be expensive. Only do it if we think there's a possible match # Calling route_hash may be expensive. Only do it if we think there's a possible match
return false unless request.post? && request.path.end_with?('compare') return false unless request.post? && request.path.end_with?('compare')
WHITELISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def lfs_route? def lfs_route?
...@@ -120,7 +120,7 @@ module Gitlab ...@@ -120,7 +120,7 @@ module Gitlab
return false return false
end end
WHITELISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def session_route? def session_route?
...@@ -128,7 +128,7 @@ module Gitlab ...@@ -128,7 +128,7 @@ module Gitlab
return false unless request.post? && request.path.end_with?('/users/sign_out', return false unless request.post? && request.path.end_with?('/users/sign_out',
'/admin/session', '/admin/session/destroy') '/admin/session', '/admin/session/destroy')
WHITELISTED_SESSION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_SESSION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def sidekiq_route? def sidekiq_route?
......
...@@ -90,7 +90,7 @@ function rspec_simple_job() { ...@@ -90,7 +90,7 @@ function rspec_simple_job() {
export NO_KNAPSACK="1" export NO_KNAPSACK="1"
bin/rspec --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts} bin/rspec -Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}
} }
function rspec_paralellized_job() { function rspec_paralellized_job() {
...@@ -143,7 +143,7 @@ function rspec_paralellized_job() { ...@@ -143,7 +143,7 @@ function rspec_paralellized_job() {
export MEMORY_TEST_PATH="tmp/memory_test/${report_name}_memory.csv" export MEMORY_TEST_PATH="tmp/memory_test/${report_name}_memory.csv"
knapsack rspec "-Ispec --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}" knapsack rspec "-Ispec -rspec_helper --color --format documentation --format RspecJunitFormatter --out junit_rspec.xml ${rspec_opts}"
date date
} }
......
# frozen_string_literal: true # frozen_string_literal: true
# When running in CI environment, we need to load a full `spec_helper` # $" is $LOADED_FEATURES, but RuboCop didn't like it
if ENV['CI'] if $".include?(File.expand_path('spec_helper.rb', __dir__))
require_relative 'spec_helper' # There's no need to load anything here if spec_helper is already loaded
# because spec_helper is more extensive than fast_spec_helper
return return
end end
......
...@@ -18,46 +18,6 @@ RSpec.describe 'Every API endpoint' do ...@@ -18,46 +18,6 @@ RSpec.describe 'Every API endpoint' do
api_endpoints.map do |(klass, path)| api_endpoints.map do |(klass, path)|
next if klass.try(:feature_category_for_action, path) next if klass.try(:feature_category_for_action, path)
# We'll add the rest in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/463
completed_classes = [
::API::Users, ::API::Issues, ::API::AccessRequests, ::API::Admin::Ci::Variables,
::API::Admin::InstanceClusters, ::API::Admin::Sidekiq, ::API::Appearance,
::API::Applications, ::API::Avatar, ::API::AwardEmoji, API::Badges,
::API::Boards, ::API::Branches, ::API::BroadcastMessages, ::API::Ci::Pipelines,
::API::Ci::PipelineSchedules, ::API::Ci::Runners, ::API::Ci::Runner,
::API::Commits, ::API::CommitStatuses, ::API::ContainerRegistryEvent,
::API::DeployKeys, ::API::DeployTokens, ::API::Deployments, ::API::Environments,
::API::ErrorTracking, ::API::Events, ::API::FeatureFlags, ::API::FeatureFlagScopes,
::API::FeatureFlagsUserLists, ::API::Features, ::API::Files, ::API::FreezePeriods,
::API::GroupBoards, ::API::GroupClusters, ::API::GroupExport, ::API::GroupImport,
::API::GroupLabels, ::API::GroupMilestones, ::API::Groups,
::API::GroupContainerRepositories, ::API::GroupVariables,
::API::ImportBitbucketServer, ::API::ImportGithub, ::API::IssueLinks,
::API::Issues, ::API::JobArtifacts, ::API::Jobs, ::API::Keys, ::API::Labels,
::API::Lint, ::API::Markdown, ::API::Members, ::API::MergeRequestDiffs,
::API::MergeRequests, ::API::MergeRequestApprovals, ::API::Metrics::Dashboard::Annotations,
::API::Metrics::UserStarredDashboards, ::API::Namespaces, ::API::Notes,
::API::Discussions, ::API::ResourceLabelEvents, ::API::ResourceMilestoneEvents,
::API::ResourceStateEvents, ::API::NotificationSettings, ::API::ProjectPackages,
::API::GroupPackages, ::API::PackageFiles, ::API::NugetPackages, ::API::PypiPackages,
::API::ComposerPackages, ::API::ConanProjectPackages, ::API::ConanInstancePackages,
::API::DebianGroupPackages, ::API::DebianProjectPackages, ::API::MavenPackages,
::API::NpmPackages, ::API::GenericPackages, ::API::GoProxy, ::API::Pages,
::API::PagesDomains, ::API::ProjectClusters, ::API::ProjectContainerRepositories,
::API::ProjectEvents, ::API::ProjectExport, ::API::ProjectImport, ::API::ProjectHooks,
::API::ProjectMilestones, ::API::ProjectRepositoryStorageMoves, ::API::Projects,
::API::ProjectSnapshots, ::API::ProjectSnippets, ::API::ProjectStatistics,
::API::ProjectTemplates, ::API::Terraform::State, ::API::Terraform::StateVersion,
::API::ProtectedBranches, ::API::ProtectedTags, ::API::Releases, ::API::Release::Links,
::API::RemoteMirrors, ::API::Repositories, ::API::Search, ::API::Services,
::API::Settings, ::API::SidekiqMetrics, ::API::Snippets, ::API::Statistics,
::API::Submodules, ::API::Subscriptions, ::API::Suggestions, ::API::SystemHooks,
::API::Tags, ::API::Templates, ::API::Todos, ::API::Triggers, ::API::Unleash,
::API::UsageData, ::API::UserCounts, ::API::Variables, ::API::Version,
::API::Wikis
]
next unless completed_classes.include?(klass)
"#{klass}##{path}" "#{klass}##{path}"
end.compact.uniq end.compact.uniq
end end
......
...@@ -147,42 +147,45 @@ RSpec.describe Namespace do ...@@ -147,42 +147,45 @@ RSpec.describe Namespace do
end end
describe '.search' do describe '.search' do
let_it_be(:namespace) { create(:namespace) } let_it_be(:first_namespace) { build(:namespace, name: 'my first namespace', path: 'old-path').tap(&:save!) }
let_it_be(:parent_namespace) { build(:namespace, name: 'my parent namespace', path: 'parent-path').tap(&:save!) }
let_it_be(:second_namespace) { build(:namespace, name: 'my second namespace', path: 'new-path', parent: parent_namespace).tap(&:save!) }
let_it_be(:project_with_same_path) { create(:project, id: second_namespace.id, path: first_namespace.path) }
it 'returns namespaces with a matching name' do it 'returns namespaces with a matching name' do
expect(described_class.search(namespace.name)).to eq([namespace]) expect(described_class.search('my first namespace')).to eq([first_namespace])
end end
it 'returns namespaces with a partially matching name' do it 'returns namespaces with a partially matching name' do
expect(described_class.search(namespace.name[0..2])).to eq([namespace]) expect(described_class.search('first')).to eq([first_namespace])
end end
it 'returns namespaces with a matching name regardless of the casing' do it 'returns namespaces with a matching name regardless of the casing' do
expect(described_class.search(namespace.name.upcase)).to eq([namespace]) expect(described_class.search('MY FIRST NAMESPACE')).to eq([first_namespace])
end end
it 'returns namespaces with a matching path' do it 'returns namespaces with a matching path' do
expect(described_class.search(namespace.path)).to eq([namespace]) expect(described_class.search('old-path')).to eq([first_namespace])
end end
it 'returns namespaces with a partially matching path' do it 'returns namespaces with a partially matching path' do
expect(described_class.search(namespace.path[0..2])).to eq([namespace]) expect(described_class.search('old')).to eq([first_namespace])
end end
it 'returns namespaces with a matching path regardless of the casing' do it 'returns namespaces with a matching path regardless of the casing' do
expect(described_class.search(namespace.path.upcase)).to eq([namespace]) expect(described_class.search('OLD-PATH')).to eq([first_namespace])
end end
it 'returns namespaces with a matching route path' do it 'returns namespaces with a matching route path' do
expect(described_class.search(namespace.route.path, include_parents: true)).to eq([namespace]) expect(described_class.search('parent-path/new-path', include_parents: true)).to eq([second_namespace])
end end
it 'returns namespaces with a partially matching route path' do it 'returns namespaces with a partially matching route path' do
expect(described_class.search(namespace.route.path[0..2], include_parents: true)).to eq([namespace]) expect(described_class.search('parent-path/new', include_parents: true)).to eq([second_namespace])
end end
it 'returns namespaces with a matching route path regardless of the casing' do it 'returns namespaces with a matching route path regardless of the casing' do
expect(described_class.search(namespace.route.path.upcase, include_parents: true)).to eq([namespace]) expect(described_class.search('PARENT-PATH/NEW-PATH', include_parents: true)).to eq([second_namespace])
end end
end end
......
...@@ -62,6 +62,15 @@ RSpec.describe Route do ...@@ -62,6 +62,15 @@ RSpec.describe Route do
end end
end end
describe '.for_routable_type' do
let!(:nested_group) { create(:group, path: 'foo', name: 'foo', parent: group) }
let!(:project) { create(:project, path: 'other-project') }
it 'returns correct routes' do
expect(described_class.for_routable_type(Project.name)).to match_array([project.route])
end
end
describe '#rename_descendants' do describe '#rename_descendants' do
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) } let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) } let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
......
...@@ -7,6 +7,7 @@ RSpec.describe API::Members do ...@@ -7,6 +7,7 @@ RSpec.describe API::Members do
let(:developer) { create(:user) } let(:developer) { create(:user) }
let(:access_requester) { create(:user) } let(:access_requester) { create(:user) }
let(:stranger) { create(:user) } let(:stranger) { create(:user) }
let(:user_with_minimal_access) { create(:user) }
let(:project) do let(:project) do
create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project| create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project|
...@@ -20,6 +21,7 @@ RSpec.describe API::Members do ...@@ -20,6 +21,7 @@ RSpec.describe API::Members do
create(:group, :public) do |group| create(:group, :public) do |group|
group.add_developer(developer) group.add_developer(developer)
group.add_owner(maintainer) group.add_owner(maintainer)
create(:group_member, :minimal_access, source: group, user: user_with_minimal_access)
group.request_access(access_requester) group.request_access(access_requester)
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# $" is $LOADED_FEATURES, but RuboCop didn't like it
if $".include?(File.expand_path('fast_spec_helper.rb', __dir__))
warn 'Detected fast_spec_helper is loaded first than spec_helper.'
warn 'If running test files using both spec_helper and fast_spec_helper,'
warn 'make sure test file with spec_helper is loaded first.'
abort 'Aborting...'
end
require './spec/simplecov_env' require './spec/simplecov_env'
SimpleCovEnv.start! SimpleCovEnv.start!
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment