Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-08 18:07:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-08 18:07:19 +0300
commita34d7fd9a723d6cc9c7348be2afe522bdc2be67f (patch)
tree5971e13ca0832ae06c599b3d5eec2e2fe71d884f
parent5f89187f0433fc84d8387de25220185235d61ed1 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js28
-rw-r--r--app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue45
-rw-r--r--app/assets/javascripts/api/bulk_imports_api.js15
-rw-r--r--app/assets/javascripts/import/constants.js12
-rw-r--r--app/assets/javascripts/import/details/components/bulk_import_details_app.vue31
-rw-r--r--app/assets/javascripts/import/details/components/import_details_app.vue32
-rw-r--r--app/assets/javascripts/import/details/components/import_details_table.vue96
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/details/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/product_analytics/graphs/index.js3
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue48
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js10
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/finders/organizations/user_organizations_finder.rb26
-rw-r--r--app/graphql/resolvers/users/frecent_groups_resolver.rb23
-rw-r--r--app/graphql/resolvers/users/frecent_projects_resolver.rb21
-rw-r--r--app/graphql/resolvers/users/organizations_resolver.rb18
-rw-r--r--app/graphql/types/query_type.rb8
-rw-r--r--app/graphql/types/user_interface.rb5
-rw-r--r--app/helpers/sorting_helper.rb15
-rw-r--r--app/models/concerns/enums/package_metadata.rb3
-rw-r--r--app/models/concerns/enums/sbom.rb3
-rw-r--r--app/models/concerns/users/visitable.rb39
-rw-r--r--app/models/deploy_token.rb3
-rw-r--r--app/models/users/group_visit.rb7
-rw-r--r--app/models/users/project_visit.rb7
-rw-r--r--app/policies/group_policy.rb26
-rw-r--r--app/policies/user_policy.rb1
-rw-r--r--app/services/auth/dependency_proxy_authentication_service.rb26
-rw-r--r--app/services/service_desk/custom_emails/create_service.rb4
-rw-r--r--app/views/import/bulk_imports/details.html.haml2
-rw-r--r--config/feature_flags/development/frecent_namespaces_suggestions.yml (renamed from config/feature_flags/development/claude_description_generation.yml)8
-rw-r--r--db/migrate/20231030205639_update_default_package_metadata_purl_types.rb15
-rw-r--r--db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types.rb31
-rw-r--r--db/schema_migrations/202310302056391
-rw-r--r--db/schema_migrations/202311031628251
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/audit_event_streaming/audit_event_types.md1
-rw-r--r--doc/api/graphql/reference/index.md51
-rw-r--r--doc/development/permissions/custom_roles.md4
-rw-r--r--doc/integration/oauth2_generic.md3
-rw-r--r--doc/user/packages/dependency_proxy/index.md2
-rw-r--r--generator_templates/gitlab_internal_events/metric_definition.yml4
-rw-r--r--jest.config.contract.js2
-rw-r--r--jest.config.js6
-rw-r--r--jest.config.scripts.js2
-rw-r--r--locale/gitlab.pot27
-rw-r--r--package.json2
-rw-r--r--spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb68
-rw-r--r--spec/finders/organizations/user_organizations_finder_spec.rb50
-rw-r--r--spec/frontend/analytics/product_analytics/components/activity_chart_spec.js34
-rw-r--r--spec/frontend/import/details/components/import_details_table_spec.js66
-rw-r--r--spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js34
-rw-r--r--spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb7
-rw-r--r--spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb7
-rw-r--r--spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb35
-rw-r--r--spec/models/concerns/enums/sbom_spec.rb1
-rw-r--r--spec/models/users/group_visit_spec.rb25
-rw-r--r--spec/models/users/project_visit_spec.rb25
-rw-r--r--spec/policies/group_policy_spec.rb107
-rw-r--r--spec/policies/user_policy_spec.rb26
-rw-r--r--spec/requests/api/graphql/user_spec.rb32
-rw-r--r--spec/requests/jwt_controller_spec.rb12
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb77
-rw-r--r--spec/services/service_desk/custom_emails/create_service_spec.rb29
-rw-r--r--spec/support/shared_contexts/graphql/types/query_type_shared_context.rb2
-rw-r--r--spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/models/users/pages_visits_shared_examples.rb104
-rw-r--r--yarn.lock78
72 files changed, 1290 insertions, 308 deletions
diff --git a/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js b/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js
deleted file mode 100644
index 91cb48e181b..00000000000
--- a/app/assets/javascripts/analytics/product_analytics/activity_charts_bundle.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue';
-import ActivityChart from './components/activity_chart.vue';
-
-export default () => {
- const containers = document.querySelectorAll('.js-project-analytics-chart');
-
- if (!containers) {
- return false;
- }
-
- return containers.forEach((container) => {
- const { chartData } = container.dataset;
- const formattedData = JSON.parse(chartData);
-
- return new Vue({
- el: container,
- components: {
- ActivityChart,
- },
- provide: {
- formattedData,
- },
- render(createElement) {
- return createElement('activity-chart');
- },
- });
- });
-};
diff --git a/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue b/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue
deleted file mode 100644
index 2be9ebda87a..00000000000
--- a/app/assets/javascripts/analytics/product_analytics/components/activity_chart.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script>
-import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { s__ } from '~/locale';
-
-export default {
- i18n: {
- noDataMsg: s__(
- 'ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already.',
- ),
- },
- components: {
- GlColumnChart,
- },
- inject: {
- formattedData: {
- default: {},
- },
- },
- computed: {
- barSeriesData() {
- return [
- {
- name: 'full',
- data: this.formattedData.keys.map((val, idx) => [val, this.formattedData.values[idx]]),
- },
- ];
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-xs-w-full">
- <gl-column-chart
- v-if="formattedData.keys"
- :bars="barSeriesData"
- :x-axis-title="__('Value')"
- :y-axis-title="__('Number of events')"
- :x-axis-type="'category'"
- />
- <p v-else data-testid="noActivityChartData">
- {{ $options.i18n.noDataMsg }}
- </p>
- </div>
-</template>
diff --git a/app/assets/javascripts/api/bulk_imports_api.js b/app/assets/javascripts/api/bulk_imports_api.js
index d636cfdff0b..248f5601705 100644
--- a/app/assets/javascripts/api/bulk_imports_api.js
+++ b/app/assets/javascripts/api/bulk_imports_api.js
@@ -2,6 +2,21 @@ import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils';
const BULK_IMPORT_ENTITIES_PATH = '/api/:version/bulk_imports/entities';
+const BULK_IMPORT_ENTITIES_FAILURES_PATH =
+ '/api/:version/bulk_imports/:id/entities/:entity_id/failures';
export const getBulkImportsHistory = (params) =>
axios.get(buildApiUrl(BULK_IMPORT_ENTITIES_PATH), { params });
+
+export const getBulkImportFailures = (id, entityId, { page, perPage }) => {
+ const failuresPath = buildApiUrl(BULK_IMPORT_ENTITIES_FAILURES_PATH)
+ .replace(':id', encodeURIComponent(id))
+ .replace(':entity_id', encodeURIComponent(entityId));
+
+ return axios.get(failuresPath, {
+ params: {
+ page,
+ per_page: perPage,
+ },
+ });
+};
diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index ddf69a8fcdf..b02eb3c4307 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -1,6 +1,18 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __, s__ } from '~/locale';
+export const BULK_IMPORT_STATIC_ITEMS = {
+ badges: __('Badge'),
+ boards: s__('IssueBoards|Board'),
+ epics: __('Epic'),
+ issues: __('Issue'),
+ labels: __('Label'),
+ members: __('Member'),
+ merge_requests: __('Merge request'),
+ milestones: __('Milestone'),
+ project: __('Project'),
+};
+
const STATISTIC_ITEMS = {
diff_note: __('Diff notes'),
issue: __('Issues'),
diff --git a/app/assets/javascripts/import/details/components/bulk_import_details_app.vue b/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
index d6c16075482..5da16454032 100644
--- a/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/bulk_import_details_app.vue
@@ -1,15 +1,44 @@
<script>
+import { __ } from '~/locale';
import ImportDetailsTable from '~/import/details/components/import_details_table.vue';
export default {
name: 'BulkImportDetailsApp',
components: { ImportDetailsTable },
+
+ fields: [
+ {
+ key: 'relation',
+ label: __('Type'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'source_title',
+ label: __('Title'),
+ tdClass: 'gl-md-w-30 gl-word-break-word',
+ },
+ {
+ key: 'error',
+ label: __('Error'),
+ },
+ {
+ key: 'correlation_id_value',
+ label: __('Correlation ID'),
+ },
+ ],
+
+ LOCAL_STORAGE_KEY: 'gl-bulk-import-details-page-size',
};
</script>
<template>
<div>
<h1>{{ s__('Import|GitLab Migration details') }}</h1>
- <import-details-table />
+
+ <import-details-table
+ bulk-import
+ :fields="$options.fields"
+ :local-storage-key="$options.LOCAL_STORAGE_KEY"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/import/details/components/import_details_app.vue b/app/assets/javascripts/import/details/components/import_details_app.vue
index 3aa60c00ff8..f654dc61e07 100644
--- a/app/assets/javascripts/import/details/components/import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/import_details_app.vue
@@ -1,14 +1,44 @@
<script>
+import { __ } from '~/locale';
import ImportDetailsTable from './import_details_table.vue';
export default {
+ name: 'ImportDetailsApp',
components: { ImportDetailsTable },
+
+ fields: [
+ {
+ key: 'type',
+ label: __('Type'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'title',
+ label: __('Title'),
+ tdClass: 'gl-md-w-30 gl-word-break-word',
+ },
+ {
+ key: 'provider_url',
+ label: __('URL'),
+ tdClass: 'gl-white-space-nowrap',
+ },
+ {
+ key: 'details',
+ label: __('Details'),
+ },
+ ],
+
+ LOCAL_STORAGE_KEY: 'gl-import-details-page-size',
};
</script>
<template>
<div>
<h1>{{ s__('Import|GitHub import details') }}</h1>
- <import-details-table />
+
+ <import-details-table
+ :fields="$options.fields"
+ :local-storage-key="$options.LOCAL_STORAGE_KEY"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue
index 813dc1f2645..535ccb525ac 100644
--- a/app/assets/javascripts/import/details/components/import_details_table.vue
+++ b/app/assets/javascripts/import/details/components/import_details_table.vue
@@ -1,12 +1,13 @@
<script>
import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
-import { __, s__ } from '~/locale';
+import { s__ } from '~/locale';
import { createAlert } from '~/alert';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
-import { STATISTIC_ITEMS } from '../../constants';
+import { getBulkImportFailures } from '~/rest_api';
+import { BULK_IMPORT_STATIC_ITEMS, STATISTIC_ITEMS } from '../../constants';
import { fetchImportFailures } from '../api';
const DEFAULT_PAGE_SIZE = 20;
@@ -21,28 +22,6 @@ export default {
PaginationBar,
},
STATISTIC_ITEMS,
- LOCAL_STORAGE_KEY: 'gl-import-details-page-size',
- fields: [
- {
- key: 'type',
- label: __('Type'),
- tdClass: 'gl-white-space-nowrap',
- },
- {
- key: 'title',
- label: __('Title'),
- tdClass: 'gl-md-w-30 gl-word-break-word',
- },
- {
- key: 'provider_url',
- label: __('URL'),
- tdClass: 'gl-white-space-nowrap',
- },
- {
- key: 'details',
- label: __('Details'),
- },
- ],
i18n: {
fetchErrorMessage: s__('Import|An error occurred while fetching import details.'),
@@ -55,6 +34,25 @@ export default {
},
},
+ props: {
+ bulkImport: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ fields: {
+ type: Array,
+ required: true,
+ },
+
+ localStorageKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
data() {
return {
items: [],
@@ -97,18 +95,28 @@ export default {
this.loadImportFailures();
},
+ fetchFn(params) {
+ return this.bulkImport
+ ? getBulkImportFailures(
+ getParameterValues('id')[0],
+ getParameterValues('entity_id')[0],
+ params,
+ )
+ : fetchImportFailures(this.failuresPath, {
+ projectId: getParameterValues('project_id')[0],
+ ...params,
+ });
+ },
+
async loadImportFailures() {
- if (!this.failuresPath) {
+ if (!this.bulkImport && !this.failuresPath) {
return;
}
this.loading = true;
+
try {
- const response = await fetchImportFailures(this.failuresPath, {
- projectId: getParameterValues('project_id')[0],
- page: this.page,
- perPage: this.perPage,
- });
+ const response = await this.fetchFn({ page: this.page, perPage: this.perPage });
const { page, perPage, totalPages, total } = parseIntPagination(
normalizeHeaders(response.headers),
@@ -123,13 +131,17 @@ export default {
}
this.loading = false;
},
+
+ itemTypeText(type) {
+ return (this.bulkImport ? BULK_IMPORT_STATIC_ITEMS[type] : STATISTIC_ITEMS[type]) || type;
+ },
},
};
</script>
<template>
<div>
- <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
+ <gl-table :fields="fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
<template #table-busy>
<gl-loading-icon size="lg" class="gl-my-5" />
</template>
@@ -139,7 +151,7 @@ export default {
</template>
<template #cell(type)="{ item: { type } }">
- {{ $options.STATISTIC_ITEMS[type] }}
+ {{ itemTypeText(type) }}
</template>
<template #cell(provider_url)="{ item: { provider_url } }">
<gl-link v-if="provider_url" :href="provider_url" target="_blank">
@@ -147,12 +159,30 @@ export default {
<gl-icon name="external-link" />
</gl-link>
</template>
+
+ <template #cell(relation)="{ item: { relation } }">
+ {{ itemTypeText(relation) }}
+ </template>
+ <template #cell(source_title)="{ item: { source_title, source_url } }">
+ <gl-link v-if="source_url" :href="source_url" target="_blank">
+ {{ source_title }}
+ <gl-icon name="external-link" />
+ </gl-link>
+ <span v-else>
+ {{ source_title }}
+ </span>
+ </template>
+ <template #cell(error)="{ item: { exception_class, exception_message } }">
+ <strong>{{ exception_class }}</strong>
+ <p>{{ exception_message }}</p>
+ </template>
</gl-table>
+
<pagination-bar
v-if="hasItems"
:page-info="pageInfo"
class="gl-mt-5"
- :storage-key="$options.LOCAL_STORAGE_KEY"
+ :storage-key="localStorageKey"
@set-page="setPage"
@set-page-size="setPageSize"
/>
diff --git a/app/assets/javascripts/pages/import/bulk_imports/details/index.js b/app/assets/javascripts/pages/import/bulk_imports/details/index.js
index ca5de576536..5c2571af60f 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/details/index.js
+++ b/app/assets/javascripts/pages/import/bulk_imports/details/index.js
@@ -8,14 +8,9 @@ export const initBulkImportDetails = () => {
return null;
}
- const { failuresPath } = el.dataset;
-
return new Vue({
el,
name: 'BulkImportDetailsRoot',
- provide: {
- failuresPath,
- },
render(createElement) {
return createElement(BulkImportDetailsApp);
},
diff --git a/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js b/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js
deleted file mode 100644
index ba03fccdb03..00000000000
--- a/app/assets/javascripts/pages/projects/product_analytics/graphs/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initActivityCharts from '~/analytics/product_analytics/activity_charts_bundle';
-
-initActivityCharts();
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
index 8edf2cfb4aa..6f22af4bd26 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/custom_email_form.vue
@@ -6,6 +6,7 @@ import {
GlFormInputGroup,
GlFormInput,
GlLink,
+ GlFormSelect,
GlSprintf,
} from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
@@ -28,6 +29,11 @@ import {
I18N_FORM_SMTP_USERNAME_LABEL,
I18N_FORM_SMTP_PASSWORD_LABEL,
I18N_FORM_SMTP_PASSWORD_DESCRIPTION,
+ I18N_FORM_SMTP_AUTHENTICATION_LABEL,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
I18N_FORM_SUBMIT_LABEL,
I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL,
I18N_FORM_INVALID_FEEDBACK_SMTP_ADDRESS,
@@ -47,6 +53,7 @@ export default {
GlFormGroup,
GlFormInputGroup,
GlFormInput,
+ GlFormSelect,
GlLink,
GlSprintf,
},
@@ -61,6 +68,11 @@ export default {
I18N_FORM_SMTP_USERNAME_LABEL,
I18N_FORM_SMTP_PASSWORD_LABEL,
I18N_FORM_SMTP_PASSWORD_DESCRIPTION,
+ I18N_FORM_SMTP_AUTHENTICATION_LABEL,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
I18N_FORM_SUBMIT_LABEL,
I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL,
I18N_FORM_INVALID_FEEDBACK_SMTP_ADDRESS,
@@ -87,6 +99,7 @@ export default {
smtpPort: '587',
smtpUsername: '',
smtpPassword: '',
+ smtpAuthentication: null,
validationState: {
customEmail: null,
smtpAddress: null,
@@ -118,6 +131,7 @@ export default {
smtp_port: this.smtpPort,
smtp_username: this.smtpUsername,
smtp_password: this.smtpPassword,
+ smtp_authentication: this.smtpAuthentication,
};
},
onCustomEmailChange() {
@@ -150,6 +164,26 @@ export default {
this.validateSmtpUsername();
this.validateSmtpPassword();
},
+ getSmtpAuthenticationOptions() {
+ return [
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ value: null,
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ value: 'plain',
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ value: 'login',
+ },
+ {
+ text: this.$options.I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+ value: 'cram_md5',
+ },
+ ];
+ },
},
};
</script>
@@ -303,6 +337,20 @@ export default {
/>
</gl-form-group>
+ <gl-form-group
+ :label="$options.I18N_FORM_SMTP_AUTHENTICATION_LABEL"
+ label-for="custom-email-form-smtp-password"
+ class="gl-mt-3"
+ >
+ <gl-form-select
+ id="custom-email-form-smtp-authentication"
+ v-model.trim="smtpAuthentication"
+ :options="getSmtpAuthenticationOptions()"
+ :aria-label="$options.I18N_FORM_SMTP_AUTHENTICATION_LABEL"
+ :disabled="isSubmitting"
+ />
+ </gl-form-group>
+
<gl-button
type="submit"
variant="confirm"
diff --git a/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js b/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
index aafd77bd25e..8ac186e292c 100644
--- a/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
+++ b/app/assets/javascripts/projects/settings_service_desk/custom_email_constants.js
@@ -37,6 +37,13 @@ export const I18N_FORM_SMTP_PORT_DESCRIPTION = s__(
export const I18N_FORM_SMTP_USERNAME_LABEL = s__('ServiceDesk|SMTP username');
export const I18N_FORM_SMTP_PASSWORD_LABEL = s__('ServiceDesk|SMTP password');
export const I18N_FORM_SMTP_PASSWORD_DESCRIPTION = s__('ServiceDesk|Minimum 8 characters long.');
+export const I18N_FORM_SMTP_AUTHENTICATION_LABEL = s__('ServiceDesk|SMTP authentication method');
+export const I18N_FORM_SMTP_AUTHENTICATION_NONE = s__(
+ 'ServiceDesk|Let GitLab select a server-supported method (recommended)',
+);
+export const I18N_FORM_SMTP_AUTHENTICATION_PLAIN = s__('ServiceDesk|Plain');
+export const I18N_FORM_SMTP_AUTHENTICATION_LOGIN = s__('ServiceDesk|Login');
+export const I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5 = s__('ServiceDesk|CRAM-MD5');
export const I18N_FORM_SUBMIT_LABEL = s__('ServiceDesk|Save and test connection');
export const I18N_FORM_INVALID_FEEDBACK_CUSTOM_EMAIL = s__(
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index a3a5864240c..a29393d9f93 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -236,7 +236,7 @@ export default {
};
</script>
<template>
- <div class="js-mr-approvals mr-section-container mr-widget-workflow">
+ <div v-if="approvals" class="js-mr-approvals mr-section-container mr-widget-workflow">
<state-container
:is-loading="$apollo.queries.approvals.loading"
:mr="mr"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 24ec740c910..bc3d8ad7824 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -63,6 +63,10 @@ export default {
},
manual: true,
result({ data }) {
+ if (!data.project) {
+ return;
+ }
+
if (Object.keys(this.state).length === 0) {
this.removeSourceBranch =
data.project.mergeRequest.shouldRemoveSourceBranch ||
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
index 564e9321d54..8bb2f2898eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
@@ -18,8 +18,16 @@ export default {
iid: `${this.mr.iid}`,
};
},
- update: (data) => data.project.mergeRequest,
+ update: (data) => data.project?.mergeRequest,
result({ data }) {
+ // This case can occur when backend returns an empty project due to expired session.
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/413627 for more information.
+ if (!data.project) {
+ // Needed to suppress several errors.
+ this.mr.setApprovals({});
+ return;
+ }
+
const { mergeRequest } = data.project;
this.disableCommittersApproval = data.project.mergeRequestsDisableCommittersApproval;
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 83409c7e096..4163ff8727c 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -34,6 +34,7 @@ class JwtController < ApplicationController
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, request: request)
+ @raw_token = password
if @authentication_result.failed?
log_authentication_failed(login, @authentication_result)
@@ -80,6 +81,7 @@ class JwtController < ApplicationController
def additional_params
{
scopes: scopes_param,
+ raw_token: @raw_token,
deploy_token: @authentication_result.deploy_token,
auth_type: @authentication_result.type
}.compact
diff --git a/app/finders/organizations/user_organizations_finder.rb b/app/finders/organizations/user_organizations_finder.rb
new file mode 100644
index 00000000000..739940c44ca
--- /dev/null
+++ b/app/finders/organizations/user_organizations_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Organizations
+ class UserOrganizationsFinder
+ def initialize(current_user, target_user, params = {})
+ @current_user = current_user
+ @target_user = target_user
+ @params = params
+ end
+
+ def execute
+ return Organizations::Organization.none unless can_read_user_organizations?
+ return Organizations::Organization.none if target_user.blank?
+
+ target_user.organizations
+ end
+
+ private
+
+ attr_reader :current_user, :target_user, :params
+
+ def can_read_user_organizations?
+ current_user&.can?(:read_user_organizations, target_user)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/frecent_groups_resolver.rb b/app/graphql/resolvers/users/frecent_groups_resolver.rb
new file mode 100644
index 00000000000..2fc757e31ab
--- /dev/null
+++ b/app/graphql/resolvers/users/frecent_groups_resolver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class FrecentGroupsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type [Types::GroupType], null: true
+
+ def resolve
+ return unless current_user.present?
+
+ if Feature.disabled?(:frecent_namespaces_suggestions, current_user)
+ raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled")
+ end
+
+ return unless Feature.enabled?(:frecent_namespaces_suggestions, current_user)
+
+ ::Users::GroupVisit.frecent_groups(user_id: current_user.id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/frecent_projects_resolver.rb b/app/graphql/resolvers/users/frecent_projects_resolver.rb
new file mode 100644
index 00000000000..397d4ca0cfd
--- /dev/null
+++ b/app/graphql/resolvers/users/frecent_projects_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class FrecentProjectsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type [Types::ProjectType], null: true
+
+ def resolve
+ return unless current_user.present?
+
+ if Feature.disabled?(:frecent_namespaces_suggestions, current_user)
+ raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled")
+ end
+
+ ::Users::ProjectVisit.frecent_projects(user_id: current_user.id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/organizations_resolver.rb b/app/graphql/resolvers/users/organizations_resolver.rb
new file mode 100644
index 00000000000..ffc1a141eb6
--- /dev/null
+++ b/app/graphql/resolvers/users/organizations_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class OrganizationsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::Organizations::OrganizationType.connection_type, null: true
+
+ authorize :read_user_organizations
+ authorizes_object!
+
+ def resolve(**args)
+ ::Organizations::UserOrganizationsFinder.new(current_user, object, args).execute
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index ce458af4e60..173e877d86c 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -55,6 +55,14 @@ module Types
null: false,
description: 'Fields related to design management.'
field :echo, resolver: Resolvers::EchoResolver
+ field :frecent_groups, [Types::GroupType],
+ resolver: Resolvers::Users::FrecentGroupsResolver,
+ description: "A user's frecently visited groups. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.",
+ alpha: { milestone: '16.6' }
+ field :frecent_projects, [Types::ProjectType],
+ resolver: Resolvers::Users::FrecentProjectsResolver,
+ description: "A user's frecently visited projects. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.",
+ alpha: { milestone: '16.6' }
field :gitpod_enabled, GraphQL::Types::Boolean,
null: true,
description: "Whether Gitpod is enabled in application settings."
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index ca1b6eaa900..7a43d5891d7 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -71,6 +71,11 @@ module Types
type: GraphQL::Types::String,
null: false,
description: 'Web path of the user.'
+ field :organizations,
+ resolver: Resolvers::Users::OrganizationsResolver,
+ null: true,
+ alpha: { milestone: '16.6' },
+ description: 'Organizations where the user has access.'
field :group_memberships,
type: Types::GroupMemberType.connection_type,
null: true,
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index a1afb0493d5..8b5c0707d08 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -263,21 +263,6 @@ module SortingHelper
sort_direction_button(url, reverse_sort, sort_value)
end
- def packages_reverse_sort_order_hash
- {
- sort_value_recently_created => sort_value_oldest_created,
- sort_value_oldest_created => sort_value_recently_created,
- sort_value_name => sort_value_name_desc,
- sort_value_name_desc => sort_value_name,
- sort_value_version_desc => sort_value_version_asc,
- sort_value_version_asc => sort_value_version_desc,
- sort_value_type_desc => sort_value_type_asc,
- sort_value_type_asc => sort_value_type_desc,
- sort_value_project_name_desc => sort_value_project_name_asc,
- sort_value_project_name_asc => sort_value_project_name_desc
- }
- end
-
def forks_sort_direction_button(sort_value, without = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id])
reverse_sort = forks_reverse_sort_options_hash[sort_value]
url = page_filter_path(sort: reverse_sort, without: without)
diff --git a/app/models/concerns/enums/package_metadata.rb b/app/models/concerns/enums/package_metadata.rb
index 3f107987ef6..cba6cd2db2e 100644
--- a/app/models/concerns/enums/package_metadata.rb
+++ b/app/models/concerns/enums/package_metadata.rb
@@ -14,7 +14,8 @@ module Enums
apk: 9,
rpm: 10,
deb: 11,
- cbl_mariner: 12
+ cbl_mariner: 12,
+ wolfi: 13
}.with_indifferent_access.freeze
ADVISORY_SOURCES = {
diff --git a/app/models/concerns/enums/sbom.rb b/app/models/concerns/enums/sbom.rb
index 59aafc32d94..64e0a7653d6 100644
--- a/app/models/concerns/enums/sbom.rb
+++ b/app/models/concerns/enums/sbom.rb
@@ -18,7 +18,8 @@ module Enums
apk: 9,
rpm: 10,
deb: 11,
- cbl_mariner: 12
+ cbl_mariner: 12,
+ wolfi: 13
}.with_indifferent_access.freeze
def self.component_types
diff --git a/app/models/concerns/users/visitable.rb b/app/models/concerns/users/visitable.rb
index cb8e5fdc682..029d60d61ee 100644
--- a/app/models/concerns/users/visitable.rb
+++ b/app/models/concerns/users/visitable.rb
@@ -13,6 +13,45 @@ module Users
time = time.to_datetime
where(entity_id: entity_id, user_id: user_id, visited_at: (time - 15.minutes)..(time + 15.minutes))
end
+
+ scope :for_user, ->(user_id) { where(user_id: user_id) }
+
+ scope :recently_visited, -> do
+ where('visited_at > ?', 3.months.ago)
+ .where('visited_at <= ?', Time.current)
+ end
+
+ def self.grouped_by_week_start_and_entity_for_user(user_id:)
+ recently_visited
+ .for_user(user_id)
+ .group(:week_start, :entity_id)
+ .select(
+ :entity_id,
+ "COUNT(entity_id) AS week_count",
+ "DATE_TRUNC('week', visited_at)::date AS week_start",
+ "DENSE_RANK() OVER (ORDER BY DATE_TRUNC('week', visited_at)::date)"
+ )
+ end
+
+ def self.frecent_visits_scores(user_id:, limit:)
+ ranked_entity_visits_query = grouped_by_week_start_and_entity_for_user(user_id: user_id).to_sql
+ sql = <<~SQL
+ SELECT
+ entity_id,
+ SUM(week_count * dense_rank) AS score
+ FROM
+ (#{ranked_entity_visits_query}) as ranked_entity_visits
+ GROUP BY
+ entity_id
+ ORDER BY
+ score DESC
+ LIMIT #{limit}
+ SQL
+
+ ::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do
+ connection.execute(sql).to_a
+ end
+ end
end
end
end
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index 920321a1699..2405ff3d252 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -11,7 +11,6 @@ class DeployToken < ApplicationRecord
AVAILABLE_SCOPES = %i[read_repository read_registry write_registry
read_package_registry write_package_registry].freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
- REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze
attribute :expires_at, default: -> { Forever.date }
@@ -57,7 +56,7 @@ class DeployToken < ApplicationRecord
def valid_for_dependency_proxy?
group_type? &&
active? &&
- REQUIRED_DEPENDENCY_PROXY_SCOPES.all? { |scope| scope.in?(scopes) }
+ (Gitlab::Auth::REGISTRY_SCOPES & scopes).size == Gitlab::Auth::REGISTRY_SCOPES.size
end
def revoke!
diff --git a/app/models/users/group_visit.rb b/app/models/users/group_visit.rb
index 0bcfda049fc..d7c76e2ee2c 100644
--- a/app/models/users/group_visit.rb
+++ b/app/models/users/group_visit.rb
@@ -13,5 +13,12 @@ module Users
validates :entity_id, presence: true
validates :user_id, presence: true
validates :visited_at, presence: true
+
+ MAX_FRECENT_ITEMS = 3
+
+ def self.frecent_groups(user_id:)
+ ids = frecent_visits_scores(user_id: user_id, limit: MAX_FRECENT_ITEMS).pluck("entity_id")
+ Group.find(ids)
+ end
end
end
diff --git a/app/models/users/project_visit.rb b/app/models/users/project_visit.rb
index 1d076e0be56..9ff3d8d2c91 100644
--- a/app/models/users/project_visit.rb
+++ b/app/models/users/project_visit.rb
@@ -13,5 +13,12 @@ module Users
validates :entity_id, presence: true
validates :user_id, presence: true
validates :visited_at, presence: true
+
+ MAX_FRECENT_ITEMS = 5
+
+ def self.frecent_projects(user_id:)
+ ids = frecent_visits_scores(user_id: user_id, limit: MAX_FRECENT_ITEMS).pluck("entity_id")
+ Project.find(ids)
+ end
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index ca170133105..f927d976f0d 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -69,7 +69,9 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
end
condition(:dependency_proxy_access_allowed) do
- access_level(for_any_session: true) >= GroupMember::GUEST || valid_dependency_proxy_deploy_token
+ valid_dependency_proxy_human_token ||
+ valid_dependency_proxy_group_access_token ||
+ valid_dependency_proxy_deploy_token
end
desc "Deploy token with read_package_registry scope"
@@ -386,6 +388,18 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
user.is_a?(User)
end
+ def user_is_human?
+ user_is_user? && user.human?
+ end
+
+ def user_is_project_bot?
+ user_is_user? && user.project_bot?
+ end
+
+ def user_is_deploy_token?
+ user.is_a?(DeployToken)
+ end
+
def group
@subject
end
@@ -406,8 +420,16 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
resource_access_token_create_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end
+ def valid_dependency_proxy_human_token
+ user_is_human? && access_level(for_any_session: true) >= GroupMember::GUEST
+ end
+
+ def valid_dependency_proxy_group_access_token
+ user_is_project_bot? && access_level(for_any_session: true) >= GroupMember::GUEST
+ end
+
def valid_dependency_proxy_deploy_token
- @user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
+ user_is_deploy_token? && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 2fd198b8cf4..04fbc8467c9 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -29,6 +29,7 @@ class UserPolicy < BasePolicy
enable :read_user_personal_access_tokens
enable :read_group_count
enable :read_user_groups
+ enable :read_user_organizations
enable :read_saved_replies
enable :read_user_email_address
enable :admin_user_email_address
diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb
index 164594d6f6c..9033baf8c15 100644
--- a/app/services/auth/dependency_proxy_authentication_service.rb
+++ b/app/services/auth/dependency_proxy_authentication_service.rb
@@ -5,10 +5,11 @@ module Auth
AUDIENCE = 'dependency_proxy'
HMAC_KEY = 'gitlab-dependency-proxy'
DEFAULT_EXPIRE_TIME = 1.minute
+ REQUIRED_ABILITIES = %i[read_container_image create_container_image].freeze
def execute(authentication_abilities:)
return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled
- return error('access forbidden', 403) unless valid_user_actor?
+ return error('access forbidden', 403) unless valid_user_actor?(authentication_abilities)
{ token: authorized_token.encoded }
end
@@ -33,8 +34,17 @@ module Auth
private
- def valid_user_actor?
- current_user || valid_deploy_token?
+ def valid_user_actor?(authentication_abilities)
+ valid_human_user? || valid_group_access_token?(authentication_abilities) || valid_deploy_token?
+ end
+
+ def valid_human_user?
+ current_user.is_a?(User) && current_user.human?
+ end
+
+ def valid_group_access_token?(authentication_abilities)
+ current_user&.project_bot? && group_access_token&.active? &&
+ (REQUIRED_ABILITIES & authentication_abilities).size == REQUIRED_ABILITIES.size
end
def valid_deploy_token?
@@ -49,8 +59,18 @@ module Auth
end
end
+ def group_access_token
+ return unless current_user&.project_bot?
+
+ PersonalAccessTokensFinder.new(state: 'active').find_by_token(raw_token)
+ end
+
def deploy_token
params[:deploy_token]
end
+
+ def raw_token
+ params[:raw_token]
+ end
end
end
diff --git a/app/services/service_desk/custom_emails/create_service.rb b/app/services/service_desk/custom_emails/create_service.rb
index 305f5b3fa11..c06c836f0fa 100644
--- a/app/services/service_desk/custom_emails/create_service.rb
+++ b/app/services/service_desk/custom_emails/create_service.rb
@@ -42,6 +42,8 @@ module ServiceDesk
def create_credential
credential = ::ServiceDesk::CustomEmailCredential.new(create_credential_params.merge(project: project))
credential.save
+ rescue ArgumentError
+ false
end
def create_verification
@@ -53,7 +55,7 @@ module ServiceDesk
end
def create_credential_params
- ensure_params.permit(:smtp_address, :smtp_port, :smtp_username, :smtp_password)
+ ensure_params.permit(:smtp_address, :smtp_port, :smtp_username, :smtp_password, :smtp_authentication)
end
def ensure_params
diff --git a/app/views/import/bulk_imports/details.html.haml b/app/views/import/bulk_imports/details.html.haml
index 0efe71362a6..511bf2c38a1 100644
--- a/app/views/import/bulk_imports/details.html.haml
+++ b/app/views/import/bulk_imports/details.html.haml
@@ -2,4 +2,4 @@
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- page_title s_('Import|GitLab Migration details')
-.js-bulk-import-details{ data: { failures_path: '' } }
+.js-bulk-import-details
diff --git a/config/feature_flags/development/claude_description_generation.yml b/config/feature_flags/development/frecent_namespaces_suggestions.yml
index f22ec7a7be3..1fe0f0694e8 100644
--- a/config/feature_flags/development/claude_description_generation.yml
+++ b/config/feature_flags/development/frecent_namespaces_suggestions.yml
@@ -1,8 +1,8 @@
---
-name: claude_description_generation
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135706
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430077
+name: frecent_namespaces_suggestions
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132128
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/428362
milestone: '16.6'
type: development
-group: group::project management
+group: group::foundations
default_enabled: false
diff --git a/db/migrate/20231030205639_update_default_package_metadata_purl_types.rb b/db/migrate/20231030205639_update_default_package_metadata_purl_types.rb
new file mode 100644
index 00000000000..1e2f1ccb578
--- /dev/null
+++ b/db/migrate/20231030205639_update_default_package_metadata_purl_types.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class UpdateDefaultPackageMetadataPurlTypes < Gitlab::Database::Migration[2.2]
+ milestone '16.6'
+
+ disable_ddl_transaction!
+
+ PARTIALLY_ENABLED_SYNC = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].freeze
+ FULLY_ENABLED_SYNC = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13].freeze
+
+ def change
+ change_column_default :application_settings, :package_metadata_purl_types,
+ from: PARTIALLY_ENABLED_SYNC, to: FULLY_ENABLED_SYNC
+ end
+end
diff --git a/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types.rb b/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types.rb
new file mode 100644
index 00000000000..bdbe8aa3a63
--- /dev/null
+++ b/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddWolfiPurlTypeToPackageMetadataPurlTypes < Gitlab::Database::Migration[2.2]
+ milestone '16.6'
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ class ApplicationSetting < MigrationRecord
+ end
+
+ WOLFI_PURL_TYPE = 13
+
+ def up
+ application_setting = ApplicationSetting.last
+ return unless application_setting
+
+ application_setting.package_metadata_purl_types |= [WOLFI_PURL_TYPE]
+ application_setting.save
+ end
+
+ def down
+ application_setting = ApplicationSetting.last
+ return unless application_setting
+
+ application_setting.package_metadata_purl_types.delete(WOLFI_PURL_TYPE)
+ application_setting.save
+ end
+end
diff --git a/db/schema_migrations/20231030205639 b/db/schema_migrations/20231030205639
new file mode 100644
index 00000000000..4abedebbd44
--- /dev/null
+++ b/db/schema_migrations/20231030205639
@@ -0,0 +1 @@
+873fab24af680c9e33bedfe574f20a5a2242732b922bb4bd2f01d13180601de3 \ No newline at end of file
diff --git a/db/schema_migrations/20231103162825 b/db/schema_migrations/20231103162825
new file mode 100644
index 00000000000..6bb33354de4
--- /dev/null
+++ b/db/schema_migrations/20231103162825
@@ -0,0 +1 @@
+a6b5c59b0035f536185b94157950a2900754e07bcc2c6ea980cd9213f35b899c \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 8582b819feb..cde8c368f00 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12075,7 +12075,7 @@ CREATE TABLE application_settings (
encrypted_product_analytics_configurator_connection_string bytea,
encrypted_product_analytics_configurator_connection_string_iv bytea,
silent_mode_enabled boolean DEFAULT false NOT NULL,
- package_metadata_purl_types smallint[] DEFAULT '{1,2,3,4,5,6,7,8,9,10,11,12}'::smallint[],
+ package_metadata_purl_types smallint[] DEFAULT '{1,2,3,4,5,6,7,8,9,10,11,12,13}'::smallint[],
ci_max_includes integer DEFAULT 150 NOT NULL,
remember_me_enabled boolean DEFAULT true NOT NULL,
encrypted_anthropic_api_key bytea,
diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md
index ac630c18717..34d19327c18 100644
--- a/doc/administration/audit_event_streaming/audit_event_types.md
+++ b/doc/administration/audit_event_streaming/audit_event_types.md
@@ -310,7 +310,6 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Streamed | Introduced in |
|:-----|:------------|:------------------|:---------|:--------------|
| [`experiment_features_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) | Event triggered on toggling setting for enabling experiment AI features| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.0](https://gitlab.com/gitlab-org/gitlab/-/issues/404856/) |
-| [`third_party_ai_features_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) | Event triggered on toggling setting for enabling third-party AI features| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.0](https://gitlab.com/gitlab-org/gitlab/-/issues/404856/) |
### Portfolio management
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1289cc4a329..65aa3bdd5e7 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -326,6 +326,26 @@ Returns [`ExplainVulnerabilityPrompt`](#explainvulnerabilityprompt).
| ---- | ---- | ----------- |
| <a id="queryexplainvulnerabilitypromptvulnerabilityid"></a>`vulnerabilityId` | [`VulnerabilityID!`](#vulnerabilityid) | Vulnerability to generate a prompt for. |
+### `Query.frecentGroups`
+
+A user's frecently visited groups. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.
+
+WARNING:
+**Introduced** in 16.6.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`[Group!]`](#group).
+
+### `Query.frecentProjects`
+
+A user's frecently visited projects. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.
+
+WARNING:
+**Introduced** in 16.6.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`[Project!]`](#project).
+
### `Query.geoNode`
Find a Geo node.
@@ -11437,6 +11457,29 @@ The edge type for [`OncallParticipantType`](#oncallparticipanttype).
| <a id="oncallparticipanttypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="oncallparticipanttypeedgenode"></a>`node` | [`OncallParticipantType`](#oncallparticipanttype) | The item at the end of the edge. |
+#### `OrganizationConnection`
+
+The connection type for [`Organization`](#organization).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="organizationconnectionedges"></a>`edges` | [`[OrganizationEdge]`](#organizationedge) | A list of edges. |
+| <a id="organizationconnectionnodes"></a>`nodes` | [`[Organization]`](#organization) | A list of nodes. |
+| <a id="organizationconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `OrganizationEdge`
+
+The edge type for [`Organization`](#organization).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="organizationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="organizationedgenode"></a>`node` | [`Organization`](#organization) | The item at the end of the edge. |
+
#### `OrganizationUserConnection`
The connection type for [`OrganizationUser`](#organizationuser).
@@ -13414,6 +13457,7 @@ A user with add-on data.
| <a id="addonusernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="addonusernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="addonuserorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="addonuserorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="addonuserpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="addonuserprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="addonuserprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -14072,6 +14116,7 @@ Core representation of a GitLab user.
| <a id="autocompletedusernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="autocompletedusernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="autocompleteduserorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="autocompleteduserorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="autocompleteduserpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="autocompleteduserprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="autocompleteduserprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -20510,6 +20555,7 @@ A user assigned to a merge request.
| <a id="mergerequestassigneenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestassigneenamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestassigneeorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="mergerequestassigneeorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="mergerequestassigneepreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestassigneeprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestassigneeprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -20791,6 +20837,7 @@ The author of the merge request.
| <a id="mergerequestauthornamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestauthornamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestauthororganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="mergerequestauthororganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="mergerequestauthorpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestauthorprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestauthorprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -21135,6 +21182,7 @@ A user participating in a merge request.
| <a id="mergerequestparticipantnamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestparticipantnamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestparticipantorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="mergerequestparticipantorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="mergerequestparticipantpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestparticipantprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestparticipantprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -21452,6 +21500,7 @@ A user assigned to a merge request as a reviewer.
| <a id="mergerequestreviewernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestreviewernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestreviewerorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="mergerequestreviewerorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="mergerequestreviewerpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestreviewerprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestreviewerprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -26388,6 +26437,7 @@ Core representation of a GitLab user.
| <a id="usercorenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usercorenamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="usercoreorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="usercoreorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="usercorepreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="usercoreprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="usercoreprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@@ -31912,6 +31962,7 @@ Implementations:
| <a id="usernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="userorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
+| <a id="userorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="userpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="userprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="userprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
diff --git a/doc/development/permissions/custom_roles.md b/doc/development/permissions/custom_roles.md
index a060d7a740b..1630ea7b9ab 100644
--- a/doc/development/permissions/custom_roles.md
+++ b/doc/development/permissions/custom_roles.md
@@ -200,6 +200,10 @@ Examples of merge requests adding new abilities to custom roles:
You should make sure a new custom roles ability is under a feature flag.
+### Privilege escalation consideration
+
+A base role typically has permissions that allow creation or management of artifacts corresponding to the base role when interacting with that artifact. For example, when a `Developer` creates an access token for a project, it is created with `Developer` access encoded into that credential. It is important to keep in mind that as new custom permissions are created, there might be a risk of elevated privileges when interacting with GitLab artifacts, and appropriate safeguards or base role checks should be added.
+
### Consuming seats
If a new user with a role `Guest` is added to a member role that includes enablement of an ability that is **not** in the `CUSTOMIZABLE_PERMISSIONS_EXEMPT_FROM_CONSUMING_SEAT` array, a seat is consumed. We simply want to make sure we are charging Ultimate customers for guest users, who have "elevated" abilities. This only applies to billable users on SaaS (billable users that are counted towards namespace subscription). More details about this topic can be found in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390269).
diff --git a/doc/integration/oauth2_generic.md b/doc/integration/oauth2_generic.md
index fa65020a4dc..6bcecffaeda 100644
--- a/doc/integration/oauth2_generic.md
+++ b/doc/integration/oauth2_generic.md
@@ -6,6 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Use Generic OAuth2 gem as an OAuth 2.0 authentication provider **(FREE SELF)**
+NOTE:
+If your provider supports the OpenID specification, you should use [`omniauth-openid-connect`](../administration/auth/oidc.md) as your authentication provider.
+
The [`omniauth-oauth2-generic` gem](https://gitlab.com/satorix/omniauth-oauth2-generic) allows single sign-on (SSO) between GitLab
and your OAuth 2.0 provider, or any OAuth 2.0 provider compatible with this gem.
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index 02810bcb922..7bd5a09d8e3 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -88,7 +88,7 @@ You can authenticate using:
- Your GitLab username and password.
- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `read_registry` and `write_registry`.
- A [group deploy token](../../../user/project/deploy_tokens/index.md) with the scope set to `read_registry` and `write_registry`.
-- A [group access token](../../../user/group/settings/group_access_tokens.md) for the group, with the scope set to `read_registry` and `write_registry`.
+- A [group access token](../../../user/group/settings/group_access_tokens.md) for the group with the scope set to `read_registry` and `write_registry`.
Users accessing the Dependency Proxy with a personal access token or username and password must
have at least the Guest role for the group they pull images from.
diff --git a/generator_templates/gitlab_internal_events/metric_definition.yml b/generator_templates/gitlab_internal_events/metric_definition.yml
index 3b09544207c..c31f7d54df0 100644
--- a/generator_templates/gitlab_internal_events/metric_definition.yml
+++ b/generator_templates/gitlab_internal_events/metric_definition.yml
@@ -13,8 +13,8 @@ time_frame: <%= args.third %>
data_source: internal_events
data_category: optional
instrumentation_class: <%= class_name(args.third) %>
-distribution: <%= distributions %>
-tier: <%= tiers %>
+distribution:<%= distributions %>
+tier:<%= tiers %>
options:
events:
- <%= event %>
diff --git a/jest.config.contract.js b/jest.config.contract.js
index 224d50f87d6..059a3207088 100644
--- a/jest.config.contract.js
+++ b/jest.config.contract.js
@@ -1,6 +1,6 @@
module.exports = () => {
return {
modulePaths: ['<rootDir>/spec/contracts/consumer/node_modules/'],
- roots: ['spec/contracts/consumer', 'ee/spec/contracts/consumer'],
+ roots: ['spec/contracts/consumer/', 'ee/spec/contracts/consumer/'],
};
};
diff --git a/jest.config.js b/jest.config.js
index 3f3e1abbf0c..acfaee14fbf 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -10,9 +10,9 @@ if (IS_JH && fs.existsSync('./jh/jest.config.js')) {
} else {
module.exports = {
...baseConfig('spec/frontend', {
- roots: ['<rootDir>/spec/frontend'],
- rootsEE: ['<rootDir>/ee/spec/frontend'],
- rootsJH: ['<rootDir>/jh/spec/frontend'],
+ roots: ['<rootDir>/spec/frontend/'],
+ rootsEE: ['<rootDir>/ee/spec/frontend/'],
+ rootsJH: ['<rootDir>/jh/spec/frontend/'],
}),
};
}
diff --git a/jest.config.scripts.js b/jest.config.scripts.js
index dd824bd01bf..2f4ef3906a1 100644
--- a/jest.config.scripts.js
+++ b/jest.config.scripts.js
@@ -2,7 +2,7 @@ const baseConfig = require('./jest.config.base');
module.exports = {
...baseConfig('spec/frontend', {
- roots: ['<rootDir>/scripts/lib', '<rootDir>/spec/frontend'],
+ roots: ['<rootDir>/scripts/lib/', '<rootDir>/spec/frontend/'],
}),
testMatch: [],
};
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3a97cd45f24..09570609ff4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13927,6 +13927,9 @@ msgstr ""
msgid "CorpusManagement|Total Size: %{totalSize}"
msgstr ""
+msgid "Correlation ID"
+msgstr ""
+
msgid "Cost Factor Settings"
msgstr ""
@@ -29398,6 +29401,9 @@ msgstr ""
msgid "Medium vulnerabilities present"
msgstr ""
+msgid "Member"
+msgstr ""
+
msgid "Member since"
msgstr ""
@@ -32756,9 +32762,6 @@ msgstr ""
msgid "Number of employees"
msgstr ""
-msgid "Number of events"
-msgstr ""
-
msgid "Number of files touched"
msgstr ""
@@ -36464,9 +36467,6 @@ msgstr ""
msgid "ProductAnalytics|The sender of tracking events"
msgstr ""
-msgid "ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already."
-msgstr ""
-
msgid "ProductAnalytics|This might take a while, feel free to navigate away from this page and come back later."
msgstr ""
@@ -44237,6 +44237,9 @@ msgstr ""
msgid "ServiceDesk|Add external participants from the %{codeStart}Cc%{codeEnd} header"
msgstr ""
+msgid "ServiceDesk|CRAM-MD5"
+msgstr ""
+
msgid "ServiceDesk|Cannot create custom email"
msgstr ""
@@ -44312,9 +44315,15 @@ msgstr ""
msgid "ServiceDesk|Keep custom email"
msgstr ""
+msgid "ServiceDesk|Let GitLab select a server-supported method (recommended)"
+msgstr ""
+
msgid "ServiceDesk|Like the author, external participants receive Service Desk emails and can participate in the discussion."
msgstr ""
+msgid "ServiceDesk|Login"
+msgstr ""
+
msgid "ServiceDesk|Minimum 8 characters long."
msgstr ""
@@ -44324,6 +44333,9 @@ msgstr ""
msgid "ServiceDesk|Parameters missing"
msgstr ""
+msgid "ServiceDesk|Plain"
+msgstr ""
+
msgid "ServiceDesk|Please share your feedback on this feature in the %{linkStart}feedback issue%{linkEnd}"
msgstr ""
@@ -44342,6 +44354,9 @@ msgstr ""
msgid "ServiceDesk|SMTP address is required and must be resolvable."
msgstr ""
+msgid "ServiceDesk|SMTP authentication method"
+msgstr ""
+
msgid "ServiceDesk|SMTP host"
msgstr ""
diff --git a/package.json b/package.json
index e697cc0e95c..ff2cfdb2620 100644
--- a/package.json
+++ b/package.json
@@ -190,7 +190,7 @@
"remark-rehype": "^10.1.0",
"scrollparent": "^2.0.1",
"semver": "^7.3.4",
- "sentrybrowser": "npm:@sentry/browser@7.77.0",
+ "sentrybrowser": "npm:@sentry/browser@7.78.0",
"sentrybrowser5": "npm:@sentry/browser@5.30.0",
"sortablejs": "^1.10.2",
"string-hash": "1.1.3",
diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
index 6bb791d2fd4..810c773de00 100644
--- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
+++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
@@ -60,6 +60,42 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
it { is_expected.to have_gitlab_http_status(:not_found) }
end
+ context 'with invalid group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+
+ context 'not under the group' do
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'with sufficient scopes, but not active' do
+ context 'expired' do
+ let_it_be(:pat) do
+ create(:personal_access_token, :expired, user: user).tap do |pat|
+ pat.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'revoked' do
+ let_it_be(:pat) do
+ create(:personal_access_token, :revoked, user: user).tap do |pat|
+ pat.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+ end
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
+ context 'with insufficient scopes' do
+ let_it_be(:pat) { create(:personal_access_token, user: user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
context 'with deploy token from a different group,' do
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
@@ -119,11 +155,7 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
end
shared_examples 'authorize action with permission' do
- context 'with a valid user' do
- before do
- group.add_guest(user)
- end
-
+ shared_examples 'sends Workhorse instructions' do
it 'sends Workhorse local file instructions', :aggregate_failures do
subject
@@ -144,6 +176,32 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
expect(json_response['MaximumSize']).to eq(maximum_size)
end
end
+
+ before do
+ group.add_guest(user)
+ end
+
+ context 'with a valid user' do
+ it_behaves_like 'sends Workhorse instructions'
+ end
+
+ context 'with a valid group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+ let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
+
+ before do
+ token.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
+ end
+
+ it_behaves_like 'sends Workhorse instructions'
+ end
+
+ context 'with a deploy token' do
+ let_it_be(:user) { create(:deploy_token, :dependency_proxy_scopes, :group) }
+ let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
+
+ it_behaves_like 'sends Workhorse instructions'
+ end
end
shared_examples 'namespace statistics refresh' do
diff --git a/spec/finders/organizations/user_organizations_finder_spec.rb b/spec/finders/organizations/user_organizations_finder_spec.rb
new file mode 100644
index 00000000000..71c9b861831
--- /dev/null
+++ b/spec/finders/organizations/user_organizations_finder_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Organizations::UserOrganizationsFinder, '#execute', feature_category: :cell do
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:organization_user) { create(:organization_user) }
+ let_it_be(:organization) { organization_user.organization }
+ let_it_be(:another_organization) { create(:organization) }
+ let_it_be(:another_user) { create(:user) }
+
+ let(:current_user) { organization_user.user }
+ let(:target_user) { organization_user.user }
+
+ subject(:finder) { described_class.new(current_user, target_user).execute }
+
+ context 'when the current user has access to the organization' do
+ it { is_expected.to contain_exactly(organization) }
+ end
+
+ context 'when the current user is an admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to contain_exactly(organization) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_empty }
+ end
+ end
+
+ context 'when the current user does not access to the organization' do
+ let(:current_user) { another_user }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the current user is nil' do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when the target user is nil' do
+ let(:target_user) { nil }
+
+ it { is_expected.to be_empty }
+ end
+end
diff --git a/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js b/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js
deleted file mode 100644
index 4f8126aaacf..00000000000
--- a/spec/frontend/analytics/product_analytics/components/activity_chart_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { shallowMount } from '@vue/test-utils';
-import ActivityChart from '~/analytics/product_analytics/components/activity_chart.vue';
-
-describe('Activity Chart Bundle', () => {
- let wrapper;
- function mountComponent({ provide }) {
- wrapper = shallowMount(ActivityChart, {
- provide: {
- formattedData: {},
- ...provide,
- },
- });
- }
-
- const findChart = () => wrapper.findComponent(GlColumnChart);
- const findNoData = () => wrapper.find('[data-testid="noActivityChartData"]');
-
- describe('Activity Chart', () => {
- it('renders an warning message with no data', () => {
- mountComponent({ provide: { formattedData: {} } });
- expect(findNoData().exists()).toBe(true);
- });
-
- it('renders a chart with data', () => {
- mountComponent({
- provide: { formattedData: { keys: ['key1', 'key2'], values: [5038, 2241] } },
- });
-
- expect(findNoData().exists()).toBe(false);
- expect(findChart().exists()).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/import/details/components/import_details_table_spec.js b/spec/frontend/import/details/components/import_details_table_spec.js
index aee8573eb02..e2ba0ddad17 100644
--- a/spec/frontend/import/details/components/import_details_table_spec.js
+++ b/spec/frontend/import/details/components/import_details_table_spec.js
@@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
+import { getParameterValues } from '~/lib/utils/url_utility';
import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
import { createAlert } from '~/alert';
import waitForPromises from 'helpers/wait_for_promises';
@@ -11,13 +12,32 @@ import ImportDetailsTable from '~/import/details/components/import_details_table
import { mockImportFailures, mockHeaders } from '../mock_data';
jest.mock('~/alert');
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ getParameterValues: jest.fn().mockReturnValue([]),
+}));
describe('Import details table', () => {
let wrapper;
let mock;
- const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => {
+ const mockFields = [
+ {
+ key: 'type',
+ label: 'Type',
+ },
+ {
+ key: 'title',
+ label: 'Title',
+ },
+ ];
+
+ const createComponent = ({ mountFn = shallowMount, props = {}, provide = {} } = {}) => {
wrapper = mountFn(ImportDetailsTable, {
+ propsData: {
+ fields: mockFields,
+ ...props,
+ },
provide,
});
};
@@ -109,5 +129,49 @@ describe('Import details table', () => {
});
});
});
+
+ describe('when bulk_import is true', () => {
+ const mockId = 144;
+ const mockEntityId = 68;
+
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ getParameterValues.mockReturnValueOnce([mockId]);
+ getParameterValues.mockReturnValueOnce([mockEntityId]);
+
+ mock
+ .onGet(`/api/v4/bulk_imports/${mockId}/entities/${mockEntityId}/failures`)
+ .reply(HTTP_STATUS_OK, mockImportFailures, mockHeaders);
+
+ createComponent({
+ mountFn: mount,
+ props: {
+ bulkImport: true,
+ },
+ });
+ });
+
+ it('renders loading icon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render loading icon after fetch', async () => {
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sets items and pagination info', async () => {
+ await waitForPromises();
+
+ expect(findGlTableRows().length).toBe(mockImportFailures.length);
+ expect(findPaginationBar().props('pageInfo')).toMatchObject({
+ page: mockHeaders['x-page'],
+ perPage: mockHeaders['x-per-page'],
+ total: mockHeaders['x-total'],
+ totalPages: mockHeaders['x-total-pages'],
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
index 9b012995ea4..efefcdb20df 100644
--- a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js
@@ -1,11 +1,17 @@
import { mount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlFormSelect, GlLink } from '@gitlab/ui';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
import CustomEmailForm from '~/projects/settings_service_desk/components/custom_email_form.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import { I18N_FORM_FORWARDING_CLIPBOARD_BUTTON_TITLE } from '~/projects/settings_service_desk/custom_email_constants';
+import {
+ I18N_FORM_FORWARDING_CLIPBOARD_BUTTON_TITLE,
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+} from '~/projects/settings_service_desk/custom_email_constants';
describe('CustomEmailForm', () => {
let wrapper;
@@ -24,6 +30,7 @@ describe('CustomEmailForm', () => {
const findSmtpPortInput = () => findInputByTestId('form-smtp-port');
const findSmtpUsernameInput = () => findInputByTestId('form-smtp-username');
const findSmtpPasswordInput = () => findInputByTestId('form-smtp-password');
+ const findSmtpAuthenticationSelect = () => wrapper.findComponent(GlFormSelect).find('select');
const findSubmit = () => wrapper.findByTestId('form-submit');
const clickButtonAndExpectNoSubmitEvent = async () => {
@@ -60,6 +67,23 @@ describe('CustomEmailForm', () => {
);
});
+ it('renders correct translations for options for SMTP authentication', () => {
+ createWrapper();
+
+ const translationStrings = [
+ I18N_FORM_SMTP_AUTHENTICATION_NONE,
+ I18N_FORM_SMTP_AUTHENTICATION_PLAIN,
+ I18N_FORM_SMTP_AUTHENTICATION_LOGIN,
+ I18N_FORM_SMTP_AUTHENTICATION_CRAM_MD5,
+ ];
+
+ findSmtpAuthenticationSelect()
+ .findAll('option')
+ .wrappers.forEach((item, index) => {
+ expect(item.text()).toEqual(translationStrings[index]);
+ });
+ });
+
it('form inputs are disabled when submitting', () => {
createWrapper({ isSubmitting: true });
@@ -68,6 +92,7 @@ describe('CustomEmailForm', () => {
expect(findSmtpPortInput().attributes('disabled')).toBeDefined();
expect(findSmtpUsernameInput().attributes('disabled')).toBeDefined();
expect(findSmtpPasswordInput().attributes('disabled')).toBeDefined();
+ expect(findSmtpAuthenticationSelect().attributes('disabled')).toBeDefined();
expect(findSubmit().props('loading')).toBe(true);
});
@@ -99,6 +124,8 @@ describe('CustomEmailForm', () => {
findSmtpPasswordInput().setValue('supersecret');
findSmtpPasswordInput().trigger('change');
+
+ findSmtpAuthenticationSelect().setValue('login');
});
it('is invalid when malformed email provided', async () => {
@@ -200,9 +227,10 @@ describe('CustomEmailForm', () => {
{
custom_email: 'user@example.com',
smtp_address: 'smtp.example.com',
+ smtp_username: 'user@example.com',
smtp_password: 'supersecret',
smtp_port: '587',
- smtp_username: 'user@example.com',
+ smtp_authentication: 'login',
},
],
]);
diff --git a/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb b/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb
new file mode 100644
index 00000000000..8836ba73110
--- /dev/null
+++ b/spec/graphql/resolvers/users/frecent_groups_resolver_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::FrecentGroupsResolver, feature_category: :navigation do
+ it_behaves_like 'namespace visits resolver'
+end
diff --git a/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb b/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb
new file mode 100644
index 00000000000..30a0a7d93b3
--- /dev/null
+++ b/spec/graphql/resolvers/users/frecent_projects_resolver_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Users::FrecentProjectsResolver, feature_category: :navigation do
+ it_behaves_like 'namespace visits resolver'
+end
diff --git a/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb b/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb
new file mode 100644
index 00000000000..43ce53fffcb
--- /dev/null
+++ b/spec/migrations/db/migrate/20231103162825_add_wolfi_purl_type_to_package_metadata_purl_types_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddWolfiPurlTypeToPackageMetadataPurlTypes, feature_category: :software_composition_analysis do
+ let(:settings) { table(:application_settings) }
+
+ describe "#up" do
+ it 'updates setting' do
+ settings.create!(package_metadata_purl_types: [1, 2, 4, 5, 9, 10])
+
+ disable_migrations_output do
+ migrate!
+ end
+
+ expect(ApplicationSetting.last.package_metadata_purl_types).to eq([1, 2, 4, 5, 9, 10, 13])
+ end
+ end
+
+ describe "#down" do
+ context 'with default value' do
+ it 'updates setting' do
+ settings.create!(package_metadata_purl_types: [1, 2, 4, 5, 9, 10, 13])
+
+ disable_migrations_output do
+ migrate!
+ schema_migrate_down!
+ end
+
+ expect(ApplicationSetting.last.package_metadata_purl_types).to eq([1, 2, 4, 5, 9, 10])
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/enums/sbom_spec.rb b/spec/models/concerns/enums/sbom_spec.rb
index 41670880630..4cfd50b6c8a 100644
--- a/spec/models/concerns/enums/sbom_spec.rb
+++ b/spec/models/concerns/enums/sbom_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe Enums::Sbom, feature_category: :dependency_management do
:rpm | 10
:deb | 11
:cbl_mariner | 12
+ :wolfi | 13
'unknown-pkg-manager' | 0
'Python (unknown)' | 0
end
diff --git a/spec/models/users/group_visit_spec.rb b/spec/models/users/group_visit_spec.rb
index 63c4631ad7d..241cb537fad 100644
--- a/spec/models/users/group_visit_spec.rb
+++ b/spec/models/users/group_visit_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe Users::GroupVisit, feature_category: :navigation do
let_it_be(:user) { create(:user) }
let_it_be(:base_time) { DateTime.now }
- before do
- described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
- end
-
it_behaves_like 'namespace visits model'
it_behaves_like 'cleanup by a loose foreign key' do
@@ -22,4 +18,25 @@ RSpec.describe Users::GroupVisit, feature_category: :navigation do
let!(:model) { create(:group_visit, entity_id: entity.id, user_id: user.id, visited_at: base_time) }
let!(:parent) { user }
end
+
+ describe '#frecent_groups' do
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+
+ before do
+ [
+ [group1.id, 1.day.ago],
+ [group2.id, 2.days.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it "returns the associated frecently visited groups" do
+ expect(described_class.frecent_groups(user_id: user.id)).to eq([
+ group1,
+ group2
+ ])
+ end
+ end
end
diff --git a/spec/models/users/project_visit_spec.rb b/spec/models/users/project_visit_spec.rb
index 38747bd6462..50e9ef02fd8 100644
--- a/spec/models/users/project_visit_spec.rb
+++ b/spec/models/users/project_visit_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe Users::ProjectVisit, feature_category: :navigation do
let_it_be(:user) { create(:user) }
let_it_be(:base_time) { DateTime.now }
- before do
- described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
- end
-
it_behaves_like 'namespace visits model'
it_behaves_like 'cleanup by a loose foreign key' do
@@ -22,4 +18,25 @@ RSpec.describe Users::ProjectVisit, feature_category: :navigation do
let!(:model) { create(:project_visit, entity_id: entity.id, user_id: user.id, visited_at: base_time) }
let!(:parent) { user }
end
+
+ describe '#frecent_projects' do
+ let_it_be(:project1) { create(:project) }
+ let_it_be(:project2) { create(:project) }
+
+ before do
+ [
+ [project1.id, 1.day.ago],
+ [project2.id, 2.days.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it "returns the associated frecently visited projects" do
+ expect(described_class.frecent_projects(user_id: user.id)).to eq([
+ project1,
+ project2
+ ])
+ end
+ end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index cb7884b141e..042dbb09436 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1110,53 +1110,103 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
it { is_expected.to be_allowed(:admin_dependency_proxy) }
end
+ shared_examples 'disallows all dependency proxy access' do
+ it { is_expected.to be_disallowed(:read_dependency_proxy) }
+ it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ end
+
+ shared_examples 'allows dependency proxy read access but not admin' do
+ it { is_expected.to be_allowed(:read_dependency_proxy) }
+ it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ end
+
context 'feature disabled' do
let(:current_user) { owner }
- it { is_expected.to be_disallowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ before do
+ stub_config(dependency_proxy: { enabled: false })
+ end
+
+ it_behaves_like 'disallows all dependency proxy access'
end
context 'feature enabled' do
before do
- stub_config(dependency_proxy: { enabled: true })
+ stub_config(dependency_proxy: { enabled: true }, registry: { enabled: true })
end
- context 'reporter' do
- let(:current_user) { reporter }
+ context 'human user' do
+ context 'reporter' do
+ let(:current_user) { reporter }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
- end
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
- context 'developer' do
- let(:current_user) { developer }
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ it_behaves_like 'disabling admin_package feature flag'
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ it { is_expected.to be_allowed(:read_dependency_proxy) }
+ it { is_expected.to be_allowed(:admin_dependency_proxy) }
+
+ it_behaves_like 'disabling admin_package feature flag'
+ end
end
- context 'maintainer' do
- let(:current_user) { maintainer }
+ context 'deploy token user' do
+ let!(:group_deploy_token) do
+ create(:group_deploy_token, group: group, deploy_token: deploy_token)
+ end
+
+ subject { described_class.new(deploy_token, group) }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
+ context 'with insufficient scopes' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group) }
- it_behaves_like 'disabling admin_package feature flag'
+ it_behaves_like 'disallows all dependency proxy access'
+ end
+
+ context 'with sufficient scopes' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
end
- context 'owner' do
- let(:current_user) { owner }
+ context 'group access token user' do
+ let_it_be(:bot_user) { create(:user, :project_bot) }
+ let_it_be(:token) { create(:personal_access_token, user: bot_user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
+
+ subject { described_class.new(bot_user, group) }
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_allowed(:admin_dependency_proxy) }
+ context 'not a member of the group' do
+ it_behaves_like 'disallows all dependency proxy access'
+ end
+
+ context 'a member of the group' do
+ before do
+ group.add_guest(bot_user)
+ end
- it_behaves_like 'disabling admin_package feature flag'
+ it_behaves_like 'allows dependency proxy read access but not admin'
+ end
end
end
end
- context 'deploy token access' do
+ context 'deploy token user' do
let!(:group_deploy_token) do
create(:group_deploy_token, group: group, deploy_token: deploy_token)
end
@@ -1179,17 +1229,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
it { is_expected.to be_allowed(:read_group) }
it { is_expected.to be_disallowed(:destroy_package) }
end
-
- context 'a deploy token with dependency proxy scopes' do
- let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
-
- before do
- stub_config(dependency_proxy: { enabled: true })
- end
-
- it { is_expected.to be_allowed(:read_dependency_proxy) }
- it { is_expected.to be_disallowed(:admin_dependency_proxy) }
- end
end
it_behaves_like 'Self-managed Core resource access tokens'
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 9a2caeb7435..bbfd231ed38 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -247,6 +247,32 @@ RSpec.describe UserPolicy do
end
end
+ describe ':read_user_organizations' do
+ context 'when user is admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:read_user_organizations) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.not_to be_allowed(:read_user_organizations) }
+ end
+ end
+
+ context 'when user is not an admin' do
+ context 'requesting their own organizations' do
+ subject { described_class.new(current_user, current_user) }
+
+ it { is_expected.to be_allowed(:read_user_organizations) }
+ end
+
+ context "requesting a different user's orgnanizations" do
+ it { is_expected.not_to be_allowed(:read_user_organizations) }
+ end
+ end
+ end
+
describe ':read_user_email_address' do
context 'when user is admin' do
let(:current_user) { admin }
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 41ee233dfc5..22ebc1be964 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -113,4 +113,36 @@ RSpec.describe 'User', feature_category: :user_profile do
end
end
end
+
+ describe 'organizations field' do
+ let_it_be(:organization_user) { create(:organization_user, user: current_user) }
+ let_it_be(:organization) { organization_user.organization }
+ let_it_be(:another_organization) { create(:organization) }
+ let_it_be(:another_user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(
+ :user,
+ { username: current_user.username.to_s.upcase },
+ 'organizations { nodes { path } }'
+ )
+ end
+
+ context 'with permission' do
+ it 'returns the relevant organization details' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes').pluck('path'))
+ .to match_array(organization.path)
+ end
+ end
+
+ context 'without permission' do
+ it 'does not return organization details' do
+ post_graphql(query, current_user: another_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes')).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 966cc2d6d4e..0ac059b5ed3 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe JwtController, feature_category: :system_access do
context 'project with enabled CI' do
subject! { get '/jwt/auth', params: parameters, headers: headers }
- it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build)).permit!) }
+ it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build, raw_token: build.token)).permit!) }
it_behaves_like 'user logging'
end
@@ -119,7 +119,7 @@ RSpec.describe JwtController, feature_category: :system_access do
.with(
nil,
nil,
- ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token)).permit!
+ ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token, raw_token: deploy_token.token)).permit!
)
end
@@ -144,7 +144,7 @@ RSpec.describe JwtController, feature_category: :system_access do
.with(
nil,
user,
- ActionController::Parameters.new(parameters.merge(auth_type: :personal_access_token)).permit!
+ ActionController::Parameters.new(parameters.merge(auth_type: :personal_access_token, raw_token: pat.token)).permit!
)
end
@@ -160,7 +160,7 @@ RSpec.describe JwtController, feature_category: :system_access do
subject! { get '/jwt/auth', params: parameters, headers: headers }
- it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap)).permit!) }
+ it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)).permit!) }
it_behaves_like 'rejecting a blocked user'
@@ -180,7 +180,7 @@ RSpec.describe JwtController, feature_category: :system_access do
ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
end
- it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
it_behaves_like 'user logging'
end
@@ -197,7 +197,7 @@ RSpec.describe JwtController, feature_category: :system_access do
ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
end
- it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
end
context 'when user has 2FA enabled' do
diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
index 3ef9c8fc96e..04f7e46daa6 100644
--- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb
+++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
@@ -4,15 +4,17 @@ require 'spec_helper'
RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :dependency_proxy do
let_it_be(:user) { create(:user) }
+ let_it_be(:params) { {} }
+ let_it_be(:authentication_abilities) { nil }
- let(:service) { described_class.new(nil, user) }
+ let(:service) { described_class.new(nil, user, params) }
before do
- stub_config(dependency_proxy: { enabled: true })
+ stub_config(dependency_proxy: { enabled: true }, registry: { enabled: true })
end
describe '#execute' do
- subject { service.execute(authentication_abilities: nil) }
+ subject { service.execute(authentication_abilities: authentication_abilities) }
shared_examples 'returning' do |status:, message:|
it "returns #{message}", :aggregate_failures do
@@ -21,9 +23,23 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
end
end
- shared_examples 'returning a token' do
- it 'returns a token' do
- expect(subject[:token]).not_to be_nil
+ shared_examples 'returning a token with encoded user_id' do
+ it 'returns a token with encoded user_id' do
+ token = subject[:token]
+ expect(token).not_to be_nil
+
+ decoded_token = decode(token)
+ expect(decoded_token['user_id']).not_to be_nil
+ end
+ end
+
+ shared_examples 'returning a token with encoded deploy_token' do
+ it 'returns a token with encoded deploy_token' do
+ token = subject[:token]
+ expect(token).not_to be_nil
+
+ decoded_token = decode(token)
+ expect(decoded_token['deploy_token']).not_to be_nil
end
end
@@ -41,14 +57,53 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
it_behaves_like 'returning', status: 403, message: 'access forbidden'
end
- context 'with a deploy token as user' do
- let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+ context 'with a deploy token' do
+ let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
+ let_it_be(:params) { { deploy_token: deploy_token } }
+
+ it_behaves_like 'returning a token with encoded deploy_token'
+ end
+
+ context 'with a human user' do
+ it_behaves_like 'returning a token with encoded user_id'
+ end
+
+ context 'with a group access token' do
+ let_it_be(:user) { create(:user, :project_bot) }
+ let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
- it_behaves_like 'returning a token'
+ context 'with insufficient authentication abilities' do
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+
+ context 'with sufficient authentication abilities' do
+ let_it_be(:authentication_abilities) { Auth::DependencyProxyAuthenticationService::REQUIRED_ABILITIES }
+ let_it_be(:params) { { raw_token: token.token } }
+
+ subject { service.execute(authentication_abilities: authentication_abilities) }
+
+ it_behaves_like 'returning a token with encoded user_id'
+
+ context 'revoked' do
+ before do
+ token.revoke!
+ end
+
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+
+ context 'expired' do
+ before do
+ token.update_column(:expires_at, 1.day.ago)
+ end
+
+ it_behaves_like 'returning', status: 403, message: 'access forbidden'
+ end
+ end
end
- context 'with a user' do
- it_behaves_like 'returning a token'
+ def decode(token)
+ DependencyProxy::AuthTokenService.new(token).execute
end
end
end
diff --git a/spec/services/service_desk/custom_emails/create_service_spec.rb b/spec/services/service_desk/custom_emails/create_service_spec.rb
index 2029c9a0c3f..e165131bcf9 100644
--- a/spec/services/service_desk/custom_emails/create_service_spec.rb
+++ b/spec/services/service_desk/custom_emails/create_service_spec.rb
@@ -156,7 +156,7 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
}
end
- it 'creates all records returns a successful response' do
+ it 'creates all records and returns a successful response' do
# Because we also log in ServiceDesk::CustomEmailVerifications::CreateService
expect(Gitlab::AppLogger).to receive(:info).with({ category: 'custom_email_verification' }).once
expect(Gitlab::AppLogger).to receive(:info).with(logger_params).once
@@ -174,7 +174,8 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
smtp_address: params[:smtp_address],
smtp_port: params[:smtp_port].to_i,
smtp_username: params[:smtp_username],
- smtp_password: params[:smtp_password]
+ smtp_password: params[:smtp_password],
+ smtp_authentication: nil
)
expect(project.service_desk_custom_email_verification).to have_attributes(
state: 'started',
@@ -183,6 +184,30 @@ RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :serv
)
end
+ context 'with optional smtp_authentication parameter' do
+ before do
+ params[:smtp_authentication] = 'login'
+ end
+
+ it 'sets authentication and returns a successful response' do
+ response = service.execute
+ project.reset
+
+ expect(response).to be_success
+ expect(project.service_desk_custom_email_credential.smtp_authentication).to eq 'login'
+ end
+
+ context 'with unsupported value' do
+ let(:expected_error_message) { error_cannot_create_custom_email }
+
+ before do
+ params[:smtp_authentication] = 'unsupported'
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+ end
+
context 'when custom email aready exists' do
let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'user@example.com') }
let!(:credential) { create(:service_desk_custom_email_credential, project: project) }
diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
index 434592ccd38..257ccc553fe 100644
--- a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
+++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb
@@ -13,6 +13,8 @@ RSpec.shared_context 'with FOSS query type fields' do
:current_user,
:design_management,
:echo,
+ :frecent_groups,
+ :frecent_projects,
:gitpod_enabled,
:group,
:groups,
diff --git a/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb
new file mode 100644
index 00000000000..0dca28a4e74
--- /dev/null
+++ b/spec/support/shared_examples/graphql/resolvers/users/pages_visits_resolvers_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'namespace visits resolver' do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ context 'when user is not logged in' do
+ let_it_be(:current_user) { nil }
+
+ it 'returns nil' do
+ expect(resolve_items).to eq(nil)
+ end
+ end
+
+ context 'when user is logged in' do
+ let_it_be(:current_user) { create(:user) }
+
+ context 'when the frecent_namespaces_suggestions feature flag is disabled' do
+ before do
+ stub_feature_flags(frecent_namespaces_suggestions: false)
+ end
+
+ it 'raises a "Resource not available" exception' do
+ expect(resolve_items).to be_a(::Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ it 'returns frecent groups' do
+ expect(resolve_items).to be_an_instance_of(Array)
+ end
+ end
+ end
+
+ private
+
+ def resolve_items
+ sync(resolve(described_class, ctx: { current_user: current_user }))
+ end
+end
diff --git a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
index b653b4e265a..7a3b3d6924c 100644
--- a/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/merge_request_interactions_type_shared_examples.rb
@@ -28,6 +28,7 @@ RSpec.shared_examples "a user type with merge request interaction type" do
authoredMergeRequests
assignedMergeRequests
reviewRequestedMergeRequests
+ organizations
groupMemberships
groupCount
projectMemberships
diff --git a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
index 0b3e8516d25..ec7dc1fb8b9 100644
--- a/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
+++ b/spec/support/shared_examples/models/users/pages_visits_shared_examples.rb
@@ -6,6 +6,10 @@ RSpec.shared_examples 'namespace visits model' do
it { is_expected.to validate_presence_of(:visited_at) }
describe '#visited_around?' do
+ before do
+ described_class.create!(entity_id: entity.id, user_id: user.id, visited_at: base_time)
+ end
+
context 'when the checked time matches a recent visit' do
[-15.minutes, 15.minutes].each do |time_diff|
it 'returns true' do
@@ -24,4 +28,104 @@ RSpec.shared_examples 'namespace visits model' do
end
end
end
+
+ describe '#frecent_visits_scores' do
+ def frecent_visits_scores_to_array(visits)
+ visits.map { |visit| [visit["entity_id"], visit["score"]] }
+ end
+
+ context 'when there is lots of data' do
+ before do
+ create_visit_records
+ end
+
+ it 'returns the frecent items, sorted by their frecency score' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 10))).to eq([[2, 31], [1, 30], [3, 28], [6, 6], [7, 6], [8, 6], [4, 6], [5, 6]])
+ end
+
+ it 'limits the amount of returned entries' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 2))).to eq([
+ [2, 31], [1, 30]
+ ])
+ end
+ end
+
+ context 'when there is few data' do
+ before do
+ [
+ # Multiplier: 4
+ [1, Time.current],
+
+ # Multiplier: 3
+ [2, 2.weeks.ago],
+ [3, 2.weeks.ago],
+
+ # Multiplier: 2
+ [1, 3.weeks.ago],
+ [1, 3.weeks.ago],
+
+ # Multiplier: 1
+ [2, 5.weeks.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+
+ it 'returns the frecent items, sorted by their frecency score' do
+ expect(frecent_visits_scores_to_array(described_class.frecent_visits_scores(user_id: user.id,
+ limit: 5))).to eq([
+ [1, 8], # Entity 1 gets a score of (1 * 4) + (2 * 2) = 8
+ [2, 4], # Entity 2 gets a score of (1 * 3) + (1 * 1) = 4
+ [3, 3] # Entity 3 gets a score of 1 * 3 = 3
+ ])
+ end
+ end
+ end
+
+ private
+
+ # rubocop: disable Metrics/AbcSize -- Despite being long, this method is quite straightforward. Splitting it in smaller chunks would likely harm readability more than anything.
+ def create_visit_records
+ [
+ [1, Time.current],
+
+ [2, 1.week.ago],
+ [2, 1.week.ago],
+
+ [2, 2.weeks.ago],
+ [3, 2.weeks.ago],
+ [3, 2.weeks.ago],
+ [4, 2.weeks.ago],
+ [5, 2.weeks.ago],
+ [6, 2.weeks.ago],
+ [7, 2.weeks.ago],
+ [8, 2.weeks.ago],
+
+ [1, 3.weeks.ago],
+ [1, 3.weeks.ago],
+ [3, 3.weeks.ago],
+ [3, 3.weeks.ago],
+
+ [1, 4.weeks.ago],
+ [2, 4.weeks.ago],
+ [2, 4.weeks.ago],
+
+ [3, 7.weeks.ago],
+ [3, 7.weeks.ago],
+
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+ [1, 8.weeks.ago],
+
+ [2, 9.weeks.ago],
+ [2, 9.weeks.ago],
+ [2, 9.weeks.ago]
+ ].each do |id, datetime|
+ described_class.create!(entity_id: id, user_id: user.id, visited_at: datetime)
+ end
+ end
+ # rubocop: enable Metrics/AbcSize
end
diff --git a/yarn.lock b/yarn.lock
index 6c025818d9a..0c6973456bb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1913,14 +1913,14 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
-"@sentry-internal/tracing@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.77.0.tgz#f3d82486f8934a955b3dd2aa54c8d29586e42a37"
- integrity sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==
+"@sentry-internal/tracing@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.78.0.tgz#254d29a9434ee1de41201620b172a8cc56387e72"
+ integrity sha512-53wf+HWyjiansZ0U+q7Q7U2kEjXfL/C8+XOwE4ntLVPGFyvxuYaRUb8OEYFNTsODf20ihnxOLZ+GsXXPlqPfFA==
dependencies:
- "@sentry/core" "7.77.0"
- "@sentry/types" "7.77.0"
- "@sentry/utils" "7.77.0"
+ "@sentry/core" "7.78.0"
+ "@sentry/types" "7.78.0"
+ "@sentry/utils" "7.78.0"
"@sentry/core@5.30.0":
version "5.30.0"
@@ -1933,13 +1933,13 @@
"@sentry/utils" "5.30.0"
tslib "^1.9.3"
-"@sentry/core@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.77.0.tgz#21100843132beeeff42296c8370cdcc7aa1d8510"
- integrity sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==
+"@sentry/core@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.78.0.tgz#0c306f258e12637508b14e0633a4a5506628e914"
+ integrity sha512-cf8U+1cNkIK14KC5ySXtzu4RW5Veto3lqkS95Rbl4ExUsrVsfansFNH63j6Bvu7UbJlBRK67uf6Ug2GBbUBUIg==
dependencies:
- "@sentry/types" "7.77.0"
- "@sentry/utils" "7.77.0"
+ "@sentry/types" "7.78.0"
+ "@sentry/utils" "7.78.0"
"@sentry/hub@5.30.0":
version "5.30.0"
@@ -1959,25 +1959,25 @@
"@sentry/types" "5.30.0"
tslib "^1.9.3"
-"@sentry/replay@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.77.0.tgz#21d242c9cd70a7235237216174873fd140b6eb80"
- integrity sha512-M9Ik2J5ekl+C1Och3wzLRZVaRGK33BlnBwfwf3qKjgLDwfKW+1YkwDfTHbc2b74RowkJbOVNcp4m8ptlehlSaQ==
+"@sentry/replay@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.78.0.tgz#b657ae24448dc12268afe6cdc37b95e7ae04a8cc"
+ integrity sha512-BYBEJg8rdya8nN5H39ZVsklvFja/TX0Z5evkihCHYOAsUN74SPqmwsFYzziQhswFPJWEp0qpKweC7iuCKCSqyw==
dependencies:
- "@sentry-internal/tracing" "7.77.0"
- "@sentry/core" "7.77.0"
- "@sentry/types" "7.77.0"
- "@sentry/utils" "7.77.0"
+ "@sentry-internal/tracing" "7.78.0"
+ "@sentry/core" "7.78.0"
+ "@sentry/types" "7.78.0"
+ "@sentry/utils" "7.78.0"
"@sentry/types@5.30.0":
version "5.30.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402"
integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==
-"@sentry/types@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.77.0.tgz#c5d00fe547b89ccde59cdea59143bf145cee3144"
- integrity sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==
+"@sentry/types@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.78.0.tgz#1ed40b43dbf7b92d4e7117d66be312a927cb48e4"
+ integrity sha512-XNyu6EFTrXmKlVgKHOxGdBJ6Aw7BnMBWtptr5TPOQJ4kh+rP+4DB3I6nafcSSUbIsO+hBVgBpj0J8R3Ps86CMQ==
"@sentry/utils@5.30.0":
version "5.30.0"
@@ -1987,12 +1987,12 @@
"@sentry/types" "5.30.0"
tslib "^1.9.3"
-"@sentry/utils@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.77.0.tgz#1f88501f0b8777de31b371cf859d13c82ebe1379"
- integrity sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==
+"@sentry/utils@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.78.0.tgz#a3dd459f2347e20af81bfe92b6f75bae95918fb8"
+ integrity sha512-vxPZaMTthMgEgKvlkuqD9rQuQ4Q8fsWAuOuzeuwUbrVCBIeM/WpmyjUUx4ozy6axNGXSXGE4CzrEQUNv3+t9kQ==
dependencies:
- "@sentry/types" "7.77.0"
+ "@sentry/types" "7.78.0"
"@sinclair/typebox@^0.24.1":
version "0.24.40"
@@ -11775,16 +11775,16 @@ send@0.17.2:
"@sentry/utils" "5.30.0"
tslib "^1.9.3"
-"sentrybrowser@npm:@sentry/browser@7.77.0":
- version "7.77.0"
- resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.77.0.tgz#155440f1a0d3a1bbd5d564c28d6b0c9853a51d72"
- integrity sha512-nJ2KDZD90H8jcPx9BysQLiQW+w7k7kISCWeRjrEMJzjtge32dmHA8G4stlUTRIQugy5F+73cOayWShceFP7QJQ==
+"sentrybrowser@npm:@sentry/browser@7.78.0":
+ version "7.78.0"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.78.0.tgz#02a8a142814cb914c6199cabd965e951fd0c5deb"
+ integrity sha512-KVeC71y7hnqYN3HN6KZH33y0yBAE8CmiApQ08KFIF3+6Ty1V3PC1mbQAmNGttka57jNvyBXTCpN8Fw8MPK9V/A==
dependencies:
- "@sentry-internal/tracing" "7.77.0"
- "@sentry/core" "7.77.0"
- "@sentry/replay" "7.77.0"
- "@sentry/types" "7.77.0"
- "@sentry/utils" "7.77.0"
+ "@sentry-internal/tracing" "7.78.0"
+ "@sentry/core" "7.78.0"
+ "@sentry/replay" "7.78.0"
+ "@sentry/types" "7.78.0"
+ "@sentry/utils" "7.78.0"
serialize-javascript@^2.1.2:
version "2.1.2"