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 {
GlModal,
GlAlert,
GlLoadingIcon,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownItem,
GlButton,
GlTooltipDirective,
} from '@gitlab/ui';
......@@ -28,8 +28,8 @@ export default {
GlModal,
GlAlert,
GlLoadingIcon,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownItem,
TimeAgoTooltip,
GlButton,
},
......@@ -231,17 +231,17 @@ export default {
</template>
</div>
<div class="d-block d-sm-none dropdown">
<gl-deprecated-dropdown :text="__('Options')" class="w-100" toggle-class="text-center">
<gl-deprecated-dropdown-item
<gl-dropdown :text="__('Options')" block>
<gl-dropdown-item
v-for="(action, index) in personalSnippetActions"
:key="index"
:disabled="action.disabled"
:title="action.title"
:href="action.href"
@click="action.click ? action.click() : undefined"
>{{ action.text }}</gl-deprecated-dropdown-item
>{{ action.text }}</gl-dropdown-item
>
</gl-deprecated-dropdown>
</gl-dropdown>
</div>
</div>
......
......@@ -119,7 +119,7 @@ class Namespace < ApplicationRecord
# Returns an ActiveRecord::Relation.
def search(query, include_parents: false)
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
fuzzy_search(query, [:path, :name])
end
......
......@@ -20,6 +20,7 @@ class Route < ApplicationRecord
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
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) }
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 @@
- authentication_and_authorization
- auto_devops
- backup_restore
- behavior_analytics
- boards
- chatops
- cloud_native_installation
......@@ -56,7 +55,6 @@
- gitaly
- gitlab_docs
- gitlab_handbook
- gitter
- global_search
- helm_chart_registry
- importers
......@@ -101,6 +99,8 @@
- secret_detection
- secrets_management
- security_benchmarking
- security_orchestration
- self_monitoring
- serverless
- service_desk
- snippets
......@@ -108,12 +108,13 @@
- static_application_security_testing
- static_site_editor
- subgroups
- synthetic_monitoring
- templates
- time_tracking
- tracing
- usability_testing
- users
- value_stream_management
- value_stream_analytics
- vulnerability_database
- vulnerability_management
- web_firewall
......
......@@ -171,7 +171,7 @@ Example response:
}
```
## Create an issue board **(STARTER)**
## Create an issue board
Creates a project issue board.
......@@ -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.
......@@ -258,15 +258,15 @@ Updates a project issue board.
PUT /projects/:id/boards/:board_id
```
| 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 |
| `board_id` | integer | yes | The ID of a board |
| `name` | string | no | The new name of the board |
| `assignee_id` | integer | no | The assignee the board should be scoped to |
| `milestone_id` | 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 |
| `weight` | integer | no | The weight range from 0 to 9, to which the board should be scoped to |
| 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 |
| `board_id` | integer | yes | The ID of a board |
| `name` | string | no | The new name of the board |
| `assignee_id` **(STARTER)** | integer | no | The assignee the board should be scoped to |
| `milestone_id` **(STARTER)** | integer | no | The milestone the board should be scoped to |
| `labels` **(STARTER)** | string | no | Comma-separated list of label names 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
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:
}
```
## Delete an issue board **(STARTER)**
## Delete an issue board
Deletes a project issue board.
......
......@@ -53,6 +53,7 @@ export default {
variables() {
return {
fullPath: this.namespacePath,
searchTerm: this.searchTerm,
withExcessStorageData: this.isAdditionalStorageFlagEnabled,
};
},
......@@ -62,6 +63,7 @@ export default {
data() {
return {
namespace: {},
searchTerm: '',
};
},
computed: {
......@@ -94,6 +96,14 @@ export default {
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',
};
......@@ -172,6 +182,7 @@ export default {
<projects-table
:projects="namespaceProjects"
:additional-purchased-storage-size="namespace.additionalPurchasedStorageSize || 0"
@search="handleSearch"
/>
<temporary-storage-increase-modal
v-if="isStorageIncreaseModalVisible"
......
......@@ -116,7 +116,7 @@ export default {
data-testid="projectTableRow"
>
<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"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -146,7 +146,7 @@ export default {
</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"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......@@ -155,7 +155,7 @@ export default {
<div class="table-mobile-content gl-text-gray-900">{{ storageSize }}</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"
>
<div class="table-mobile-header gl-font-weight-bold" role="rowheader">
......
<script>
import { GlSearchBoxByType } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Project from './project.vue';
import ProjectWithExcessStorage from './project_with_excess_storage.vue';
import { SEARCH_DEBOUNCE_MS } from '~/ref/constants';
export default {
components: {
Project,
ProjectWithExcessStorage,
GlSearchBoxByType,
},
mixins: [glFeatureFlagsMixin()],
props: {
......@@ -30,25 +33,33 @@ export default {
return Project;
},
},
searchDebounceValue: SEARCH_DEBOUNCE_MS,
};
</script>
<template>
<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"
>
<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') }}
</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') }}
</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') }}
</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 v-else>
<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) {
id
name
......@@ -19,7 +23,7 @@ query getStorageCounter($fullPath: ID!, $withExcessStorageData: Boolean = false)
wikiSize
snippetsSize
}
projects(includeSubgroups: true, sort: STORAGE) {
projects(includeSubgroups: true, sort: STORAGE, search: $searchTerm) {
edges {
node {
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
class CodeReviewAnalytics < ::API::Base
include PaginationParams
feature_category :planning_analytics
helpers do
def project
@project ||= find_project!(params[:project_id])
......
......@@ -7,6 +7,8 @@ module API
'This feature is gated by the `:group_activity_analytics`'\
' feature flag, introduced in GitLab 12.9.'
feature_category :planning_analytics
before do
authenticate!
end
......
......@@ -4,6 +4,8 @@ module API
class AuditEvents < ::API::Base
include ::API::PaginationParams
feature_category :audit_events
before do
authenticated_as_admin!
forbidden! unless ::License.feature_available?(:admin_audit_log)
......
......@@ -2,6 +2,8 @@
module API
class Dependencies < ::API::Base
feature_category :dependency_scanning
helpers do
def dependencies_by(params)
pipeline = ::Security::ReportFetchService.new(user_project, ::Ci::JobArtifact.dependency_list_reports).pipeline
......
......@@ -4,6 +4,8 @@ module API
class DependencyProxy < ::API::Base
helpers ::API::Helpers::PackagesHelpers
feature_category :dependency_proxy
helpers do
def obtain_new_purge_cache_lease
Gitlab::ExclusiveLease
......
......@@ -4,6 +4,8 @@ module API
class ElasticsearchIndexedNamespaces < ::API::Base
before { authenticated_as_admin! }
feature_category :global_search
resource :elasticsearch_indexed_namespaces do
desc 'Rollout namespaces to be indexed up to n%' do
detail <<~END
......
......@@ -2,6 +2,8 @@
module API
class EpicIssues < ::API::Base
feature_category :epics
before do
authenticate!
authorize_epics_feature!
......
......@@ -4,6 +4,8 @@ module API
class EpicLinks < ::API::Base
include ::Gitlab::Utils::StrongMemoize
feature_category :epics
before do
authenticate!
end
......
......@@ -4,6 +4,8 @@ module API
class Epics < ::API::Base
include PaginationParams
feature_category :epics
before do
authenticate_non_get!
authorize_epics_feature!
......
......@@ -4,6 +4,8 @@ module API
class Experiments < ::API::Base
before { authorize_read_experiments! }
feature_category :product_analytics
resource :experiments do
desc 'Get a list of all experiments' do
success EE::API::Entities::Experiment
......
......@@ -4,6 +4,7 @@ require 'base64'
module API
class Geo < ::API::Base
feature_category :geo_replication
resource :geo do
helpers do
def sanitized_node_status_params
......
......@@ -6,6 +6,8 @@ module API
include APIGuard
include ::Gitlab::Utils::StrongMemoize
feature_category :geo_replication
before do
authenticate_admin_or_geo_node!
end
......
......@@ -6,6 +6,8 @@ module API
include APIGuard
include ::Gitlab::Utils::StrongMemoize
feature_category :geo_replication
before do
authenticated_as_admin!
not_found!('Geo node not found') unless Gitlab::Geo.current_node
......
......@@ -4,6 +4,8 @@ module API
class GroupHooks < ::API::Base
include ::API::PaginationParams
feature_category :integrations
before { authenticate! }
before { authorize! :admin_group, user_group }
......
......@@ -2,6 +2,8 @@
module API
class GroupPushRule < ::API::Base
feature_category :source_code_management
before { authenticate! }
before { check_group_push_rule_access! }
before { authorize_change_param(user_group, :commit_committer_check, :reject_unsigned_commits) }
......
......@@ -4,6 +4,8 @@ module API
class Iterations < ::API::Base
include PaginationParams
feature_category :issue_tracking
helpers do
params :list_params do
optional :state, type: String, values: %w[opened upcoming started closed all], default: 'all',
......
......@@ -8,6 +8,8 @@ module API
# group.
before { authenticated_with_ldap_admin_access! }
feature_category :authentication_and_authorization
resource :ldap do
helpers do
def get_group_list(provider, search)
......
......@@ -4,6 +4,8 @@ module API
class LdapGroupLinks < ::API::Base
before { authenticate! }
feature_category :authentication_and_authorization
params do
requires :id, type: String, desc: 'The ID of a group'
end
......
......@@ -4,6 +4,8 @@ module API
class License < ::API::Base
before { authenticated_as_admin! }
feature_category :provision
resource :license do
desc 'Get information on the currently active license' do
success EE::API::Entities::GitlabLicenseWithActiveUsers
......
......@@ -4,6 +4,8 @@ module API
class ManagedLicenses < ::API::Base
include PaginationParams
feature_category :license_compliance
before { authenticate! unless route.settings[:skip_authentication] }
helpers do
......
......@@ -4,6 +4,8 @@ module API
class MergeRequestApprovalRules < ::API::Base
before { authenticate_non_get! }
feature_category :code_review
helpers do
def find_merge_request_approval_rule(merge_request, id)
merge_request.approval_rules.find_by_id!(id)
......
......@@ -4,6 +4,8 @@ module API
class MergeTrains < ::API::Base
include PaginationParams
feature_category :continuous_integration
before do
authorize_read_merge_trains!
end
......
......@@ -4,6 +4,8 @@ module API
class ProjectAliases < ::API::Base
include PaginationParams
feature_category :source_code_management
before { check_feature_availability }
before { authenticated_as_admin! }
......
......@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::ProjectApprovalRulesHelpers
feature_category :code_review
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::ProjectApprovalRulesHelpers
feature_category :code_review
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -2,6 +2,8 @@
module API
class ProjectApprovals < ::API::Base
feature_category :code_review
before { authenticate! }
before { authorize! :update_approvers, user_project }
......
......@@ -4,6 +4,8 @@ require_dependency 'declarative_policy'
module API
class ProjectMirror < ::API::Base
feature_category :continuous_integration
helpers do
def github_webhook_signature
@github_webhook_signature ||= headers['X-Hub-Signature']
......
......@@ -2,6 +2,7 @@
module API
class ProjectPushRule < ::API::Base
feature_category :source_code_management
before { authenticate! }
before { authorize_admin_project }
before { check_project_feature_available!(:push_rules) }
......
......@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
feature_category :continuous_delivery
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -7,7 +7,7 @@ module API
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
eventables_str = eventable_type.to_s.underscore.pluralize
......@@ -24,7 +24,7 @@ module API
use :pagination
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])
events = eventable.resource_iteration_events.with_api_entity_associations
......@@ -39,7 +39,7 @@ module API
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'
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])
event = eventable.resource_iteration_events.find(params[:event_id])
......
......@@ -7,6 +7,8 @@ module API
before { authenticate! }
feature_category :issue_tracking
params do
requires :id, type: String, desc: "The ID of a project"
end
......
......@@ -4,6 +4,8 @@ module API
class Scim < ::API::Base
include ::Gitlab::Utils::StrongMemoize
feature_category :authentication_and_authorization
prefix 'api/scim'
version 'v2'
content_type :json, 'application/scim+json'
......
......@@ -6,6 +6,8 @@ module API
helpers ::API::Helpers::NotesHelpers
helpers ::RendersNotes
feature_category :code_review
params do
requires :id, type: String, desc: "The ID of a Project"
end
......
......@@ -5,6 +5,8 @@ module API
include ::API::Helpers::VulnerabilitiesHooks
include PaginationParams
feature_category :vulnerability_management
helpers ::API::Helpers::VulnerabilitiesHelpers
helpers do
......
......@@ -5,6 +5,8 @@ module API
include ::API::Helpers::VulnerabilitiesHooks
include ::Gitlab::Utils::StrongMemoize
feature_category :vulnerability_management
helpers do
def vulnerability_export
strong_memoize(:vulnerability_export) do
......
......@@ -5,6 +5,8 @@ module API
include PaginationParams
include ::Gitlab::Utils::StrongMemoize
feature_category :vulnerability_management
helpers do
def pipeline
strong_memoize(:pipeline) do
......
......@@ -4,6 +4,8 @@ module API
class VulnerabilityIssueLinks < ::API::Base
include ::API::Helpers::VulnerabilitiesHooks
feature_category :vulnerability_management
helpers ::API::Helpers::VulnerabilitiesHelpers
helpers do
......
......@@ -8,6 +8,8 @@ module EE
prepend EE::API::BoardsResponses # rubocop: disable Cop/InjectEnterpriseEditionModule
feature_category :boards
before do
authenticate!
end
......
......@@ -28,6 +28,14 @@ module EE
members
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
def can_view_group_identity?(members_source)
......
......@@ -7,19 +7,19 @@ module EE
module Controller
extend ::Gitlab::Utils::Override
WHITELISTED_GEO_ROUTES = {
ALLOWLISTED_GEO_ROUTES = {
'admin/geo/nodes' => %w{update}
}.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/uploads' => %w{destroy}
}.freeze
private
override :whitelisted_routes
def whitelisted_routes
override :allowlisted_routes
def allowlisted_routes
super || geo_node_update_route? || geo_proxy_git_ssh_route? || geo_api_route?
end
......@@ -30,10 +30,10 @@ module EE
controller = route_hash[:controller]
action = route_hash[:action]
if WHITELISTED_GEO_ROUTES[controller]&.include?(action)
if ALLOWLISTED_GEO_ROUTES[controller]&.include?(action)
::Gitlab::Database.db_read_write?
else
WHITELISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
ALLOWLISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
end
end
......
......@@ -3,7 +3,7 @@
module Security
module CiConfiguration
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)
@auto_devops_enabled = auto_devops_enabled
......@@ -55,6 +55,7 @@ module Security
config['analyzers']
&.select {|a| a['enabled'] }
&.collect {|a| a['name'] }
&.sort
&.join(', ')
else
SAST_DEFAULT_ANALYZERS
......
import { mount } from '@vue/test-utils';
import StorageApp from 'ee/storage_counter/components/app.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 TemporaryStorageIncreaseModal from 'ee/storage_counter/components/temporary_storage_increase_modal.vue';
import UsageGraph from 'ee/storage_counter/components/usage_graph.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 { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { namespaceData, withRootStorageStatistics } from '../mock_data';
......@@ -21,6 +22,7 @@ describe('Storage counter app', () => {
const findUsageGraph = () => wrapper.find(UsageGraph);
const findUsageStatistics = () => wrapper.find(UsageStatistics);
const findStorageInlineAlert = () => wrapper.find(StorageInlineAlert);
const findProjectsTable = () => wrapper.find(ProjectsTable);
const createComponent = ({
props = {},
......@@ -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
params.merge( { 'analyzers' =>
[
{
'name' => "brakeman",
'name' => "flawfinder",
'enabled' => true
},
{
'name' => "flawfinder",
'name' => "brakeman",
'enabled' => true
}
] }
......@@ -305,6 +305,20 @@ RSpec.describe Security::CiConfiguration::SastBuildActions do
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
def fast_auto_devops_stages
auto_devops_template = YAML.safe_load( File.read('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') )
......
......@@ -3,6 +3,117 @@
require 'spec_helper'
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
let(:group) { create(:group) }
let(:owner) { create(:user) }
......
......@@ -282,7 +282,7 @@ module API
end
end
route :any, '*path' do
route :any, '*path', feature_category: :not_owned do
error!('404 Not Found', 404)
end
end
......
......@@ -7,6 +7,8 @@ module API
before { authenticate! }
feature_category :container_registry
namespace 'registry' do
params do
requires :id, type: String, desc: 'The ID of a project'
......
......@@ -20,12 +20,16 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
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.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
end
def source_members(source)
source.members
end
# rubocop: enable CodeReuse/ActiveRecord
def find_all_members(source)
......
......@@ -128,13 +128,13 @@ module API
# changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
# check_ip - optional, only in EE version, may limit access to
# 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
# easily.
check_allowed(params)
end
post "/lfs_authenticate" do
post "/lfs_authenticate", feature_category: :source_code_management do
status 200
unless actor.key_or_user
......@@ -152,7 +152,7 @@ module API
# Get a ssh key using the fingerprint
#
# rubocop: disable CodeReuse/ActiveRecord
get '/authorized_keys' do
get '/authorized_keys', feature_category: :source_code_management do
fingerprint = params.fetch(:fingerprint) do
Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
end
......@@ -165,11 +165,11 @@ module API
#
# 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
end
get '/check' do
get '/check', feature_category: :not_owned do
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,
......@@ -178,7 +178,7 @@ module API
}
end
post '/two_factor_recovery_codes' do
post '/two_factor_recovery_codes', feature_category: :authentication_and_authorization do
status 200
actor.update_last_used_at!
......@@ -207,7 +207,7 @@ module API
{ success: true, recovery_codes: codes }
end
post '/personal_access_token' do
post '/personal_access_token', feature_category: :authentication_and_authorization do
status 200
actor.update_last_used_at!
......@@ -257,7 +257,7 @@ module API
{ success: true, token: access_token.token, scopes: access_token.scopes, expires_at: access_token.expires_at }
end
post '/pre_receive' do
post '/pre_receive', feature_category: :source_code_management do
status 200
reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
......@@ -265,7 +265,7 @@ module API
{ reference_counter_increased: reference_counter_increased }
end
post '/post_receive' do
post '/post_receive', feature_category: :source_code_management do
status 200
response = PostReceiveService.new(actor.user, repository, project, params).execute
......@@ -273,7 +273,7 @@ module API
present response, with: Entities::InternalPostReceive::Response
end
post '/two_factor_config' do
post '/two_factor_config', feature_category: :authentication_and_authorization do
status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli)
......@@ -295,7 +295,7 @@ module API
end
end
post '/two_factor_otp_check' do
post '/two_factor_otp_check', feature_category: :authentication_and_authorization do
status 200
break { success: false } unless Feature.enabled?(:two_factor_for_cli)
......
......@@ -4,6 +4,8 @@ module API
# Kubernetes Internal API
module Internal
class Kubernetes < ::API::Base
feature_category :kubernetes_management
before do
check_feature_enabled
authenticate_gitlab_kas_request!
......
......@@ -7,6 +7,8 @@ module API
before { authenticate_by_gitlab_shell_token! }
feature_category :source_code_management
helpers do
def find_lfs_object(lfs_oid)
LfsObject.find_by_oid(lfs_oid)
......
......@@ -4,6 +4,8 @@ module API
# Pages Internal API
module Internal
class Pages < ::API::Base
feature_category :pages
before do
authenticate_gitlab_pages_request!
end
......
......@@ -136,7 +136,7 @@ module API
source = find_source(source_type, params.delete(:id))
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 =
::Members::UpdateService
.new(current_user, declared_params(include_missing: false))
......@@ -159,7 +159,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
delete ":id/members/:user_id" do
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
::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables])
......
......@@ -4,6 +4,8 @@ module API
class PersonalAccessTokens < ::API::Base
include ::API::PaginationParams
feature_category :authentication_and_authorization
desc 'Get all Personal Access Tokens' do
detail 'This feature was added in GitLab 13.3'
success Entities::PersonalAccessToken
......
......@@ -22,6 +22,8 @@ module API
include PaginationParams
feature_category :integrations
before do
authorize_jira_user_agent!(request)
authenticate!
......
......@@ -9,20 +9,20 @@ module Gitlab
APPLICATION_JSON_TYPES = %W{#{APPLICATION_JSON} application/vnd.git-lfs+json}.freeze
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}
}.freeze
WHITELISTED_GIT_LFS_ROUTES = {
ALLOWLISTED_GIT_LFS_ROUTES = {
'repositories/lfs_api' => %w{batch},
'repositories/lfs_locks_api' => %w{verify create unlock}
}.freeze
WHITELISTED_GIT_REVISION_ROUTES = {
ALLOWLISTED_GIT_REVISION_ROUTES = {
'projects/compare' => %w{create}
}.freeze
WHITELISTED_SESSION_ROUTES = {
ALLOWLISTED_SESSION_ROUTES = {
'sessions' => %w{destroy},
'admin/sessions' => %w{create destroy}
}.freeze
......@@ -55,7 +55,7 @@ module Gitlab
def disallowed_request?
DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) &&
!whitelisted_routes
!allowlisted_routes
end
def json_request?
......@@ -87,7 +87,7 @@ module Gitlab
end
# 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?
end
......@@ -98,7 +98,7 @@ module Gitlab
return false unless request.post? &&
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
def internal_route?
......@@ -109,7 +109,7 @@ module Gitlab
# 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')
WHITELISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
ALLOWLISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
def lfs_route?
......@@ -120,7 +120,7 @@ module Gitlab
return false
end
WHITELISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
ALLOWLISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
def session_route?
......@@ -128,7 +128,7 @@ module Gitlab
return false unless request.post? && request.path.end_with?('/users/sign_out',
'/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
def sidekiq_route?
......
......@@ -90,7 +90,7 @@ function rspec_simple_job() {
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() {
......@@ -143,7 +143,7 @@ function rspec_paralellized_job() {
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
}
......
# frozen_string_literal: true
# When running in CI environment, we need to load a full `spec_helper`
if ENV['CI']
require_relative 'spec_helper'
# $" is $LOADED_FEATURES, but RuboCop didn't like it
if $".include?(File.expand_path('spec_helper.rb', __dir__))
# 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
end
......
......@@ -18,46 +18,6 @@ RSpec.describe 'Every API endpoint' do
api_endpoints.map do |(klass, 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}"
end.compact.uniq
end
......
......@@ -147,42 +147,45 @@ RSpec.describe Namespace do
end
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
expect(described_class.search(namespace.name)).to eq([namespace])
expect(described_class.search('my first namespace')).to eq([first_namespace])
end
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
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
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
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
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
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
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
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
......
......@@ -62,6 +62,15 @@ RSpec.describe Route do
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
let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) }
let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) }
......
......@@ -7,6 +7,7 @@ RSpec.describe API::Members do
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
let(:stranger) { create(:user) }
let(:user_with_minimal_access) { create(:user) }
let(:project) do
create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project|
......@@ -20,6 +21,7 @@ RSpec.describe API::Members do
create(:group, :public) do |group|
group.add_developer(developer)
group.add_owner(maintainer)
create(:group_member, :minimal_access, source: group, user: user_with_minimal_access)
group.request_access(access_requester)
end
end
......
# 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'
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