Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6e12437923
commit
46f49bc8e6
67
CHANGELOG.md
67
CHANGELOG.md
|
|
@ -2,6 +2,31 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 15.10.1 (2023-03-30)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Sync security policy rule schedules that may have been deleted by bug](gitlab-org/security/gitlab@5ac094761b5cfac26c44d63988359fbae263a415)
|
||||
- [Fix issue dashboard returning issues from archived projects](gitlab-org/security/gitlab@6127799167081845824e8759f358aac8f702adb8)
|
||||
|
||||
### Security (15 changes)
|
||||
|
||||
- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@c10a48134447128486e2254fc54d0af0d8e6fee0) ([merge request](gitlab-org/security/gitlab!3155))
|
||||
- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@4aa387fec0c995607f03e8c057d2c2a11168aca9) ([merge request](gitlab-org/security/gitlab!3158))
|
||||
- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@e9cf398f8c205ae1b8cafddbb2cfbcb214a84d51) ([merge request](gitlab-org/security/gitlab!3162))
|
||||
- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@fb55096b37ab82f49f2a0205f7ab8bdda14b0010) ([merge request](gitlab-org/security/gitlab!3153))
|
||||
- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@433996f41e89db3e2073314c0644a6f95ab67062) ([merge request](gitlab-org/security/gitlab!3146))
|
||||
- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@c9942785d9a26cf7bb96a81ccd14e5c6e5582bbe) ([merge request](gitlab-org/security/gitlab!3156))
|
||||
- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@a42d166e743edb966b0a581bf1325ffb7c96041b) ([merge request](gitlab-org/security/gitlab!3148))
|
||||
- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@58a823e09c27948d15432c344248a8436587f9af) ([merge request](gitlab-org/security/gitlab!3165))
|
||||
- [Check access to target project before looking for branch](gitlab-org/security/gitlab@804d9da677451889e0a7a0880f2c2f4c3c04faed) ([merge request](gitlab-org/security/gitlab!3151))
|
||||
- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@e21dbf4373a4c4e5179b073f5cba4318ee174918) ([merge request](gitlab-org/security/gitlab!3154))
|
||||
- [Use UntrustedRegexp to limit scan of HTML comments](gitlab-org/security/gitlab@874edf184764fa801866fbd4e89b9f7e87c570fd) ([merge request](gitlab-org/security/gitlab!3143))
|
||||
- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e88f78f19dc5ed01a74e6c0d4bb5c22f3a69b65b) ([merge request](gitlab-org/security/gitlab!3114))
|
||||
- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@94e4e543762998a9bbff75c5ffb5cd5da6bd2d88) ([merge request](gitlab-org/security/gitlab!3147))
|
||||
- [Fix security report authorization](gitlab-org/security/gitlab@10f33b260212ebf811acecf4b05af1311b44fb64) ([merge request](gitlab-org/security/gitlab!3145))
|
||||
- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@4ce175e4096c973a2d16b93fff6b60bc0144eee0) ([merge request](gitlab-org/security/gitlab!3132))
|
||||
|
||||
## 15.10.0 (2023-03-21)
|
||||
|
||||
### Added (155 changes)
|
||||
|
|
@ -726,6 +751,27 @@ entry.
|
|||
|
||||
- [Update submit buttons to use Pajamas component](gitlab-org/gitlab@4ffb92755e6be3268c78f02e471f5c2a21f437be) ([merge request](gitlab-org/gitlab!114246))
|
||||
|
||||
## 15.9.4 (2023-03-30)
|
||||
|
||||
### Security (16 changes)
|
||||
|
||||
- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@98b1bd243f454bd28c262131be616ee2060c3a78) ([merge request](gitlab-org/security/gitlab!3104))
|
||||
- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@0f0c0f21dffe300a56abf1e07a2fefb17160faeb) ([merge request](gitlab-org/security/gitlab!3133))
|
||||
- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@12498f791f9c5fe833f5202b06cc818d4dcf965b) ([merge request](gitlab-org/security/gitlab!3124))
|
||||
- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@440a7989ff46ca333f86a38aefa47f74301e66fc) ([merge request](gitlab-org/security/gitlab!3163))
|
||||
- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@9dd0dff69d3941e827c461c67b9af10da07d69f8) ([merge request](gitlab-org/security/gitlab!3084))
|
||||
- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@cd20b44dd5b075827203330802e331b896448265) ([merge request](gitlab-org/security/gitlab!3140))
|
||||
- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@76975082c41870265e1285fa8f4e053eb6ff11ae) ([merge request](gitlab-org/security/gitlab!3136))
|
||||
- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@7fcc4a0d010d3a428e803f95ef47904c4c7178a8) ([merge request](gitlab-org/security/gitlab!3149))
|
||||
- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@4e7313536e4cdb3ecef37100b5a73720eabfbc79) ([merge request](gitlab-org/security/gitlab!3108))
|
||||
- [Check access to target project before looking for branch](gitlab-org/security/gitlab@f55edf39e52af9eecb19caf8ed5d4cb8524ef64d) ([merge request](gitlab-org/security/gitlab!3040))
|
||||
- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@be73600e8c43c22cda1ace5910eb2052b2741972) ([merge request](gitlab-org/security/gitlab!3120))
|
||||
- [Use UntrustedRegexp to limit scan of HTML comments](gitlab-org/security/gitlab@d5e65583debcae71787e171643275bc9b9d4393e) ([merge request](gitlab-org/security/gitlab!3142))
|
||||
- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@54045b508a9ba9ae18f5992b77970240774b28a7) ([merge request](gitlab-org/security/gitlab!3111))
|
||||
- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@bc033cd3a98c9a1468545811a8180604f7f8aee3) ([merge request](gitlab-org/security/gitlab!3101))
|
||||
- [Fix security report authorization](gitlab-org/security/gitlab@a01cf9d8383ffc4c0e29514f71d49bf345e1f7c2) ([merge request](gitlab-org/security/gitlab!3106))
|
||||
- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@3cefb16a5e369ee99f4c3ccbaa02cead6faf1a99) ([merge request](gitlab-org/security/gitlab!3130))
|
||||
|
||||
## 15.9.3 (2023-03-09)
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
|
@ -1482,6 +1528,27 @@ entry.
|
|||
- [Remove Gitlab::Redis::DuplicateJobs](gitlab-org/gitlab@73d863b0a49175cce7649c0936b2e16157f61665) ([merge request](gitlab-org/gitlab!109122))
|
||||
- [Clean-up feature flag `hash_based_cache_for_protected_branches`](gitlab-org/gitlab@96e8a07564bac07a100556e00ce4af3f21dca293) ([merge request](gitlab-org/gitlab!108724))
|
||||
|
||||
## 15.8.5 (2023-03-30)
|
||||
|
||||
### Security (16 changes)
|
||||
|
||||
- [Fix rubocop offenses in lib/gitlab/url_sanitizer.rb](gitlab-org/security/gitlab@ddc04cf7059e411e20033b95e1297381d64d4b22) ([merge request](gitlab-org/security/gitlab!3175))
|
||||
- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@a22ce3851128eb900dbabe9e38c07889967a2915) ([merge request](gitlab-org/security/gitlab!3032))
|
||||
- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@fad24ae9d8fa0e7bd9eff0c9e6914c8267451b4d) ([merge request](gitlab-org/security/gitlab!3134))
|
||||
- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@85be0fbfc98cdb774d68070479e35be22f6ba40a) ([merge request](gitlab-org/security/gitlab!3125))
|
||||
- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@2df2fa2dc4b9015d044d0ddc5d26e17e9e5f85c0) ([merge request](gitlab-org/security/gitlab!3164))
|
||||
- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@53f7f06843eea4d666d361f5a1d349bd1e3f4312) ([merge request](gitlab-org/security/gitlab!3085))
|
||||
- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@9fa9dbff463f6015ffaf8d082db3d41ae623763e) ([merge request](gitlab-org/security/gitlab!3141))
|
||||
- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@20d77d4d680d13f916fb69de0d79802753421c8f) ([merge request](gitlab-org/security/gitlab!3137))
|
||||
- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@0fed113756b27a3a078f87f29711b225e1ed4cce) ([merge request](gitlab-org/security/gitlab!3150))
|
||||
- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@2285088f37aca877b1dcd59c728cdf33171b30cb) ([merge request](gitlab-org/security/gitlab!3109))
|
||||
- [Check access to target project before looking for branch](gitlab-org/security/gitlab@37b8d855d87c88170322e6a6d4c285fee6c6cb64) ([merge request](gitlab-org/security/gitlab!3038))
|
||||
- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@66f8cc2eb13509397b980d53a4b67ca03d8903f7) ([merge request](gitlab-org/security/gitlab!3121))
|
||||
- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e1859de393b4794e1356d6318e56ede4b557c059) ([merge request](gitlab-org/security/gitlab!3112))
|
||||
- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@13f9c6231cea956f73355c5b5b820163f523e7d8) ([merge request](gitlab-org/security/gitlab!3100))
|
||||
- [Fix security report authorization](gitlab-org/security/gitlab@19baab85c7a5a64a09e3e4808e8550fc72e18323) ([merge request](gitlab-org/security/gitlab!3105))
|
||||
- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@be5491c5db05161e4b14d53900dd19b66848de48) ([merge request](gitlab-org/security/gitlab!3131))
|
||||
|
||||
## 15.8.4 (2023-03-02)
|
||||
|
||||
### Security (12 changes)
|
||||
|
|
|
|||
|
|
@ -517,7 +517,6 @@ export default {
|
|||
ref="deleteCiVariable"
|
||||
variant="danger"
|
||||
category="secondary"
|
||||
data-qa-selector="ci_variable_delete_button"
|
||||
@click="deleteVarAndClose"
|
||||
>{{ __('Delete variable') }}</gl-button
|
||||
>
|
||||
|
|
|
|||
|
|
@ -175,12 +175,7 @@ export default {
|
|||
v-if="glFeatures.ciVariablesPages"
|
||||
class="ci-variable-actions gl-display-flex gl-justify-content-end gl-my-3"
|
||||
>
|
||||
<gl-button
|
||||
v-if="!isTableEmpty"
|
||||
data-qa-selector="reveal_ci_variable_value_button"
|
||||
@click="toggleHiddenState"
|
||||
>{{ valuesButtonText }}</gl-button
|
||||
>
|
||||
<gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button>
|
||||
<gl-button
|
||||
v-gl-modal-directive="$options.modalId"
|
||||
class="gl-mx-3"
|
||||
|
|
@ -317,12 +312,7 @@ export default {
|
|||
@click="setSelectedVariable()"
|
||||
>{{ __('Add variable') }}</gl-button
|
||||
>
|
||||
<gl-button
|
||||
v-if="!isTableEmpty"
|
||||
data-qa-selector="reveal_ci_variable_value_button"
|
||||
@click="toggleHiddenState"
|
||||
>{{ valuesButtonText }}</gl-button
|
||||
>
|
||||
<gl-button v-if="!isTableEmpty" @click="toggleHiddenState">{{ valuesButtonText }}</gl-button>
|
||||
</div>
|
||||
<div v-else class="gl-display-flex gl-justify-content-center gl-mt-6">
|
||||
<gl-keyset-pagination
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ export const GENERIC_ERROR = __('Something went wrong on our end. Please try aga
|
|||
export const LOAD_SINGLE_DIFF_FAILED = s__(
|
||||
'MergeRequest|Encountered an issue while trying to fetch the single file diff.',
|
||||
);
|
||||
export const DISCUSSION_SINGLE_DIFF_FAILED = s__(
|
||||
"MergeRequest|Can't fetch the single file diff for the discussion. Please reload this page.",
|
||||
);
|
||||
|
||||
export const DIFF_FILE_HEADER = {
|
||||
optionsDropdownTitle: __('Options'),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ import {
|
|||
TRACKING_SINGLE_FILE_MODE,
|
||||
TRACKING_MULTIPLE_FILES_MODE,
|
||||
} from '../constants';
|
||||
import { LOAD_SINGLE_DIFF_FAILED } from '../i18n';
|
||||
import { DISCUSSION_SINGLE_DIFF_FAILED, LOAD_SINGLE_DIFF_FAILED } from '../i18n';
|
||||
import eventHub from '../event_hub';
|
||||
import { isCollapsed } from '../utils/diff_file';
|
||||
import { markFileReview, setReviewsForMergeRequest } from '../utils/file_reviews';
|
||||
|
|
@ -896,6 +896,24 @@ export function moveToNeighboringCommit({ dispatch, state }, { direction }) {
|
|||
}
|
||||
}
|
||||
|
||||
export const rereadNoteHash = ({ state, dispatch }) => {
|
||||
const urlHash = window?.location?.hash;
|
||||
|
||||
if (isUrlHashNoteLink(urlHash)) {
|
||||
dispatch('setCurrentDiffFileIdFromNote', urlHash.split('_').pop())
|
||||
.then(() => {
|
||||
if (state.viewDiffsFileByFile) {
|
||||
dispatch('fetchFileByFile');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createAlert({
|
||||
message: DISCUSSION_SINGLE_DIFF_FAILED,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const setCurrentDiffFileIdFromNote = ({ commit, getters, rootGetters }, noteId) => {
|
||||
const note = rootGetters.notesById[noteId];
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import '~/sourcegraph/load';
|
|||
import createStore from '~/code_navigation/store';
|
||||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -34,7 +34,7 @@ const initRefSwitcher = () => {
|
|||
|
||||
if (!refSwitcherEl) return false;
|
||||
|
||||
const { projectId, projectRootPath, ref } = refSwitcherEl.dataset;
|
||||
const { projectId, projectRootPath, ref, refType } = refSwitcherEl.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: refSwitcherEl,
|
||||
|
|
@ -42,7 +42,8 @@ const initRefSwitcher = () => {
|
|||
return createElement(RefSelector, {
|
||||
props: {
|
||||
projectId,
|
||||
value: ref,
|
||||
value: refType ? joinPaths('refs', refType, ref) : ref,
|
||||
useSymbolicRefNames: true,
|
||||
queryParams: { sort: 'updated_desc' },
|
||||
},
|
||||
on: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
|
||||
import PerformancePlugin from '~/performance/vue_performance_plugin';
|
||||
|
|
@ -128,7 +128,7 @@ export default function setupVueRepositoryList() {
|
|||
|
||||
if (!refSwitcherEl) return false;
|
||||
|
||||
const { projectId, projectRootPath } = refSwitcherEl.dataset;
|
||||
const { projectId, projectRootPath, refType } = refSwitcherEl.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: refSwitcherEl,
|
||||
|
|
@ -136,7 +136,8 @@ export default function setupVueRepositoryList() {
|
|||
return createElement(RefSelector, {
|
||||
props: {
|
||||
projectId,
|
||||
value: ref,
|
||||
value: refType ? joinPaths('refs', refType, ref) : ref,
|
||||
useSymbolicRefNames: true,
|
||||
queryParams: { sort: 'updated_desc' },
|
||||
},
|
||||
on: {
|
||||
|
|
|
|||
|
|
@ -16,22 +16,29 @@ const getNamespaceTargetRegex = (ref) => new RegExp(`(/-/(blob|tree))/${ref}/(.*
|
|||
* @param {string} selectedRef - The selected ref from the ref dropdown.
|
||||
*/
|
||||
export function generateRefDestinationPath(projectRootPath, ref, selectedRef) {
|
||||
const currentPath = window.location.pathname;
|
||||
const encodedHash = '%23';
|
||||
const url = new URL(window.location.href);
|
||||
const currentPath = url.pathname;
|
||||
let refType = null;
|
||||
let namespace = '/-/tree';
|
||||
let target;
|
||||
let actualRef = selectedRef;
|
||||
|
||||
const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/);
|
||||
if (matches) {
|
||||
[, refType, actualRef] = matches;
|
||||
}
|
||||
if (refType) {
|
||||
url.searchParams.set('ref_type', refType);
|
||||
} else {
|
||||
url.searchParams.delete('ref_type');
|
||||
}
|
||||
|
||||
const NAMESPACE_TARGET_REGEX = getNamespaceTargetRegex(ref);
|
||||
const match = NAMESPACE_TARGET_REGEX.exec(currentPath);
|
||||
if (match) {
|
||||
[, namespace, , target] = match;
|
||||
}
|
||||
url.pathname = joinPaths(projectRootPath, namespace, actualRef, target);
|
||||
|
||||
const destinationPath = joinPaths(
|
||||
projectRootPath,
|
||||
namespace,
|
||||
encodeURI(selectedRef).replace(/#/g, encodedHash),
|
||||
target,
|
||||
);
|
||||
|
||||
return `${destinationPath}${window.location.hash}`;
|
||||
return url.toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ConfirmEmailWarning
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
|
@ -17,11 +18,9 @@ module ConfirmEmailWarning
|
|||
return unless current_user
|
||||
return if current_user.confirmed?
|
||||
|
||||
email = current_user.unconfirmed_email || current_user.email
|
||||
|
||||
flash.now[:warning] = format(
|
||||
confirm_warning_message,
|
||||
email: email,
|
||||
email: email_to_display,
|
||||
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
|
||||
update_link: view_context.link_to(_('Update it'), profile_path)
|
||||
).html_safe
|
||||
|
|
@ -29,7 +28,16 @@ module ConfirmEmailWarning
|
|||
|
||||
private
|
||||
|
||||
def email
|
||||
current_user.unconfirmed_email || current_user.email
|
||||
end
|
||||
strong_memoize_attr :email
|
||||
|
||||
def confirm_warning_message
|
||||
_("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.")
|
||||
end
|
||||
|
||||
def email_to_display
|
||||
html_escape(email)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
|
||||
|
||||
before_action :commit, except: [:new, :create]
|
||||
before_action :check_for_ambiguous_ref, only: [:show]
|
||||
before_action :blob, except: [:new, :create]
|
||||
before_action :require_branch_head, only: [:edit, :update]
|
||||
before_action :editor_variables, except: [:show, :preview, :diff]
|
||||
|
|
@ -156,6 +157,15 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_for_ambiguous_ref
|
||||
@ref_type = ref_type
|
||||
|
||||
if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
|
||||
branch = @project.repository.find_branch(@ref)
|
||||
redirect_to project_blob_path(@project, File.join(branch.target, @path))
|
||||
end
|
||||
end
|
||||
|
||||
def commit
|
||||
@commit ||= @repository.commit(@ref)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
def show
|
||||
return render_404 unless @commit
|
||||
|
||||
@ref_type = ref_type
|
||||
if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
|
||||
branch = @project.repository.find_branch(@ref)
|
||||
if branch
|
||||
redirect_to project_tree_path(@project, branch.target)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if tree.entries.empty?
|
||||
if @repository.blob_at(@commit.id, @path)
|
||||
redirect_to project_blob_path(@project, File.join(@ref, @path))
|
||||
|
|
|
|||
|
|
@ -172,11 +172,19 @@ class ProjectsController < Projects::ApplicationController
|
|||
flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
|
||||
end
|
||||
|
||||
if ambiguous_ref?(@project, @ref)
|
||||
branch = @project.repository.find_branch(@ref)
|
||||
|
||||
# The files view would render a ref other than the default branch
|
||||
# This redirect can be removed once the view is fixed
|
||||
redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref"))
|
||||
return
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@notification_setting = current_user.notification_settings_for(@project) if current_user
|
||||
@project = @project.present(current_user: current_user)
|
||||
|
||||
render_landing_page
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -32,18 +32,9 @@ module Environments
|
|||
end
|
||||
|
||||
def namespace_environments
|
||||
# We assume reporter access is needed for the :read_environment permission
|
||||
# here. This expection is also present in
|
||||
# IssuableFinder::Params#min_access_level, which is used for filtering out
|
||||
# merge requests that don't have the right permissions.
|
||||
#
|
||||
# We use this approach so we don't need to load every project into memory
|
||||
# just to verify if we can see their environments. Doing so would not be
|
||||
# efficient, and possibly mess up pagination if certain projects are not
|
||||
# meant to be visible.
|
||||
projects = project_or_group
|
||||
.all_projects
|
||||
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
|
||||
.filter_by_feature_visibility(:environments, current_user)
|
||||
|
||||
Environment.for_project(projects)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class NotesFinder
|
|||
notes = init_collection
|
||||
notes = since_fetch_at(notes)
|
||||
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
|
||||
notes = redact_internal(notes)
|
||||
sort(notes)
|
||||
end
|
||||
|
||||
|
|
@ -181,6 +182,13 @@ class NotesFinder
|
|||
|
||||
notes.order_by(sort)
|
||||
end
|
||||
|
||||
def redact_internal(notes)
|
||||
subject = @project || target
|
||||
return notes if Ability.allowed?(@current_user, :read_internal_note, subject)
|
||||
|
||||
notes.not_internal
|
||||
end
|
||||
end
|
||||
|
||||
NotesFinder.prepend_mod_with('NotesFinder')
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module DashboardHelper
|
|||
|
||||
if doc_href.present?
|
||||
link_to_doc = link_to(
|
||||
sprite_icon('question'),
|
||||
sprite_icon('question-o'),
|
||||
doc_href,
|
||||
class: 'gl-ml-2',
|
||||
title: _('Documentation'),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,14 @@ module IssueAvailableFeatures
|
|||
raise ArgumentError, 'invalid feature'
|
||||
end
|
||||
|
||||
self.class.available_features_for_issue_types[feature].include?(issue_type)
|
||||
type_for_issue = if Feature.enabled?(:issue_type_uses_work_item_types_table)
|
||||
# The default will only be used in places where an issue is only build and not saved
|
||||
work_item_type_with_default.base_type
|
||||
else
|
||||
issue_type
|
||||
end
|
||||
|
||||
self.class.available_features_for_issue_types[feature].include?(type_for_issue)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,25 +24,37 @@ module Taskable
|
|||
(\s.+) # followed by whitespace and some text.
|
||||
}x.freeze
|
||||
|
||||
ITEM_PATTERN_UNTRUSTED =
|
||||
'^' \
|
||||
'(?:(?:>\s{0,4})*)' \
|
||||
'(?P<prefix>(?:\s*(?:[-+*]|(?:\d+\.)))+)' \
|
||||
'\s+' \
|
||||
'(?P<checkbox>' \
|
||||
"#{COMPLETE_PATTERN.source}|#{INCOMPLETE_PATTERN.source}" \
|
||||
')' \
|
||||
'(?P<label>\s.+)'.freeze
|
||||
|
||||
# ignore tasks in code or html comment blocks. HTML blocks
|
||||
# are ok as we allow tasks inside <detail> blocks
|
||||
REGEX = %r{
|
||||
#{::Gitlab::Regex.markdown_code_or_html_comments}
|
||||
|
|
||||
(?<task_item>
|
||||
#{ITEM_PATTERN}
|
||||
)
|
||||
}mx.freeze
|
||||
REGEX =
|
||||
"#{::Gitlab::Regex.markdown_code_or_html_comments_untrusted}" \
|
||||
"|" \
|
||||
"(?P<task_item>" \
|
||||
"#{ITEM_PATTERN_UNTRUSTED}" \
|
||||
")".freeze
|
||||
|
||||
def self.get_tasks(content)
|
||||
items = []
|
||||
|
||||
content.to_s.scan(REGEX) do
|
||||
next unless $~[:task_item]
|
||||
regex = Gitlab::UntrustedRegexp.new(REGEX, multiline: true)
|
||||
regex.scan(content.to_s).each do |match|
|
||||
next unless regex.extract_named_group(:task_item, match)
|
||||
|
||||
$~[:task_item].scan(ITEM_PATTERN) do |prefix, checkbox, label|
|
||||
items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
|
||||
end
|
||||
prefix = regex.extract_named_group(:prefix, match)
|
||||
checkbox = regex.extract_named_group(:checkbox, match)
|
||||
label = regex.extract_named_group(:label, match)
|
||||
|
||||
items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
|
||||
end
|
||||
|
||||
items
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class WebHook < ApplicationRecord
|
|||
after_initialize :initialize_url_variables
|
||||
|
||||
before_validation :reset_token
|
||||
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }
|
||||
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }, on: :update
|
||||
before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
|
||||
validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
|
||||
validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
|
||||
|
|
@ -105,7 +105,7 @@ class WebHook < ApplicationRecord
|
|||
# See app/validators/json_schemas/web_hooks_url_variables.json
|
||||
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
|
||||
|
||||
def interpolated_url
|
||||
def interpolated_url(url = self.url, url_variables = self.url_variables)
|
||||
return url unless url.include?('{')
|
||||
|
||||
vars = url_variables
|
||||
|
|
@ -131,7 +131,19 @@ class WebHook < ApplicationRecord
|
|||
end
|
||||
|
||||
def reset_url_variables
|
||||
self.url_variables = {} if url_changed? && !encrypted_url_variables_changed?
|
||||
interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were)
|
||||
|
||||
return if url_variables_were.empty? || interpolated_url_was == interpolated_url
|
||||
|
||||
self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
|
||||
end
|
||||
|
||||
def decrypt_url_was
|
||||
self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was))
|
||||
end
|
||||
|
||||
def url_variables_were
|
||||
self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was)
|
||||
end
|
||||
|
||||
def initialize_url_variables
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ class Issue < ApplicationRecord
|
|||
# Types of issues that should be displayed on issue board lists
|
||||
TYPES_FOR_BOARD_LIST = %w(issue incident).freeze
|
||||
|
||||
# This default came from the enum `issue_type` column. Defined as default in the DB
|
||||
DEFAULT_ISSUE_TYPE = :issue
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :namespace, inverse_of: :issues
|
||||
|
||||
|
|
@ -713,6 +716,12 @@ class Issue < ApplicationRecord
|
|||
project || namespace
|
||||
end
|
||||
|
||||
# Persisted records will always have a work_item_type. This method is useful
|
||||
# in places where we use a non persisted issue to perform feature checks
|
||||
def work_item_type_with_default
|
||||
work_item_type || WorkItems::Type.default_by_type(DEFAULT_ISSUE_TYPE)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def due_date_after_start_date
|
||||
|
|
@ -783,6 +792,8 @@ class Issue < ApplicationRecord
|
|||
def ensure_work_item_type
|
||||
return if work_item_type_id.present? || work_item_type_id_change&.last.present?
|
||||
|
||||
# TODO: We should switch to DEFAULT_ISSUE_TYPE here when the issue_type column is dropped
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/work_items/402700?iid_path=true
|
||||
self.work_item_type = WorkItems::Type.default_by_type(issue_type)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
module Npm
|
||||
class MetadataCache < ApplicationRecord
|
||||
belongs_to :project, inverse_of: :npm_metadata_caches
|
||||
|
||||
validates :file, :package_name, :project, :size, presence: true
|
||||
validates :package_name, uniqueness: { scope: :project_id }
|
||||
validates :package_name, format: { with: Gitlab::Regex.package_name_regex }
|
||||
validates :package_name, format: { with: Gitlab::Regex.npm_package_name_regex }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -261,6 +261,8 @@ class Project < ApplicationRecord
|
|||
has_many :debian_distributions,
|
||||
class_name: 'Packages::Debian::ProjectDistribution',
|
||||
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :npm_metadata_caches,
|
||||
class_name: 'Packages::Npm::MetadataCache'
|
||||
has_one :packages_cleanup_policy,
|
||||
class_name: 'Packages::Cleanup::Policy',
|
||||
inverse_of: :project
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ class ProjectFeature < ApplicationRecord
|
|||
merge_requests: Gitlab::Access::REPORTER,
|
||||
metrics_dashboard: Gitlab::Access::REPORTER,
|
||||
container_registry: Gitlab::Access::REPORTER,
|
||||
package_registry: Gitlab::Access::REPORTER
|
||||
package_registry: Gitlab::Access::REPORTER,
|
||||
environments: Gitlab::Access::REPORTER
|
||||
}.freeze
|
||||
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -421,7 +421,6 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { can?(:metrics_dashboard) }.policy do
|
||||
enable :read_prometheus
|
||||
enable :read_deployment
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,15 @@ module MergeRequests
|
|||
end
|
||||
|
||||
def validate_service
|
||||
errors << 'User is required' if current_user.nil?
|
||||
if current_user.nil?
|
||||
errors << 'User is required'
|
||||
return
|
||||
end
|
||||
|
||||
unless current_user&.can?(:read_code, target_project)
|
||||
errors << 'User access was denied'
|
||||
return
|
||||
end
|
||||
|
||||
unless target_project.merge_requests_enabled?
|
||||
errors << "Merge requests are not enabled for project #{target_project.full_path}"
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@
|
|||
- if show_version_check?
|
||||
.float-right
|
||||
.js-gitlab-version-check-badge{ data: { "size": "lg", "actionable": "true", "version": gitlab_version_check.to_json } }
|
||||
= link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
|
||||
= link_to(sprite_icon('question-o'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
|
||||
%p
|
||||
= link_to _('GitLab'), general_admin_application_settings_path
|
||||
%span.float-right
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
|
||||
- breadcrumb_title _('Connect a cluster')
|
||||
- page_title _('Connect a Kubernetes Cluster')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
|
||||
- breadcrumb_title _('Create a cluster')
|
||||
- page_title _('Create a Kubernetes cluster')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- add_to_breadcrumbs _('Kubernetes Clusters'), clusterable.index_path
|
||||
- breadcrumb_title @cluster.name
|
||||
- page_title _('Kubernetes Cluster')
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@
|
|||
%h5= _("Maximum page reached")
|
||||
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
|
||||
|
||||
= render Pajamas::ButtonComponent.new(href: request.params.merge(page: @max_page_number)) do
|
||||
= render Pajamas::ButtonComponent.new(href: safe_params.merge(page: @max_page_number)) do
|
||||
= _("Back to page %{number}") % { number: @max_page_number }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
.nav-block
|
||||
.tree-ref-container
|
||||
.tree-ref-holder
|
||||
#js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project), ref: current_ref } }
|
||||
#js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project), ref: current_ref, ref_type: @ref_type.to_s } }
|
||||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li.breadcrumb-item
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
|
||||
.tree-ref-holder.gl-max-w-26
|
||||
#js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } }
|
||||
#js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
|
||||
|
||||
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: db_load_balance_audit_event_streaming_worker
|
||||
introduced_by_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1811
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/402493
|
||||
milestone: '15.11'
|
||||
type: development
|
||||
group: group::scalability
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
table_name: packages_npm_metadata_caches
|
||||
classes:
|
||||
- Packages::Npm::MetadataCache
|
||||
feature_categories:
|
||||
- package_registry
|
||||
description: Store the metadata of npm packages to use later as a cache
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114312
|
||||
milestone: '15.11'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesNpmMetadataCaches < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
INDEX_NAME = 'index_npm_metadata_caches_on_package_name_project_id_unique'
|
||||
|
||||
def up
|
||||
create_table :packages_npm_metadata_caches do |t|
|
||||
t.timestamps_with_timezone
|
||||
|
||||
t.datetime_with_timezone :last_downloaded_at
|
||||
t.bigint :project_id, index: true
|
||||
t.integer :file_store, default: 1
|
||||
t.integer :size, null: false
|
||||
t.text :file, null: false, limit: 255
|
||||
t.text :package_name, null: false # rubocop:disable Migration/AddLimitToTextColumns
|
||||
|
||||
t.index %i[package_name project_id], name: INDEX_NAME, unique: true, where: 'project_id IS NOT NULL'
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :packages_npm_metadata_caches
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddColumnToUsersStatistisc < Gitlab::Database::Migration[2.1]
|
||||
def change
|
||||
add_column :users_statistics, :with_highest_role_guest_with_custom_role, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectIdForeignKeyToPackagesNpmMetadataCaches < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE = :packages_npm_metadata_caches
|
||||
TARGET_TABLE = :projects
|
||||
COLUMN = :project_id
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key SOURCE_TABLE, TARGET_TABLE, column: COLUMN, on_delete: :nullify
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key SOURCE_TABLE, column: COLUMN
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class NullifyLastErrorFromProjectMirrorData < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = 'NullifyLastErrorFromProjectMirrorData'
|
||||
INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 10_000
|
||||
SUB_BATCH_SIZE = 1_000
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:project_mirror_data,
|
||||
:id,
|
||||
job_interval: INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :project_mirror_data, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveTmpIndexVulnOccurrencesOnReportType < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99'
|
||||
REPORT_TYPES = {
|
||||
cluster_image_scanning: 7,
|
||||
custom: 99
|
||||
}
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :vulnerability_occurrences, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :vulnerability_occurrences, :id,
|
||||
where: "report_type IN (#{REPORT_TYPES.values.join(', ')})",
|
||||
name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
784f8f189eee7b5cf3136f0a859874a1d170d2b148f4c260f968b144816f1322
|
||||
|
|
@ -0,0 +1 @@
|
|||
d8a040d40d19bb75c8e5fc8bb867ea6354ceda22c9dfe5724a4231a4b005e373
|
||||
|
|
@ -0,0 +1 @@
|
|||
fc277fb3f02c01f57355cf6381a9883e6f67c339303242ea34c5a1b567b227d0
|
||||
|
|
@ -0,0 +1 @@
|
|||
7ec944ccdd85380bba7d17fbd9dbf37bea918d0ac7fbe03142f4a4c6561a77a9
|
||||
|
|
@ -0,0 +1 @@
|
|||
5bc014685295ca8af21450de34e39fb54e6cef2fc53943cce84ea24370a9955f
|
||||
|
|
@ -19561,6 +19561,28 @@ CREATE TABLE packages_npm_metadata (
|
|||
CONSTRAINT chk_rails_e5cbc301ae CHECK ((char_length((package_json)::text) < 20000))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_npm_metadata_caches (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
last_downloaded_at timestamp with time zone,
|
||||
project_id bigint,
|
||||
file_store integer DEFAULT 1,
|
||||
size integer NOT NULL,
|
||||
file text NOT NULL,
|
||||
package_name text NOT NULL,
|
||||
CONSTRAINT check_57aa07a4b2 CHECK ((char_length(file) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE packages_npm_metadata_caches_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE packages_npm_metadata_caches_id_seq OWNED BY packages_npm_metadata_caches.id;
|
||||
|
||||
CREATE TABLE packages_nuget_dependency_link_metadata (
|
||||
dependency_link_id bigint NOT NULL,
|
||||
target_framework text NOT NULL,
|
||||
|
|
@ -23522,7 +23544,8 @@ CREATE TABLE users_statistics (
|
|||
with_highest_role_owner integer DEFAULT 0 NOT NULL,
|
||||
bots integer DEFAULT 0 NOT NULL,
|
||||
blocked integer DEFAULT 0 NOT NULL,
|
||||
with_highest_role_minimal_access integer DEFAULT 0 NOT NULL
|
||||
with_highest_role_minimal_access integer DEFAULT 0 NOT NULL,
|
||||
with_highest_role_guest_with_custom_role integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE users_statistics_id_seq
|
||||
|
|
@ -25149,6 +25172,8 @@ ALTER TABLE ONLY packages_dependency_links ALTER COLUMN id SET DEFAULT nextval('
|
|||
|
||||
ALTER TABLE ONLY packages_maven_metadata ALTER COLUMN id SET DEFAULT nextval('packages_maven_metadata_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_npm_metadata_caches ALTER COLUMN id SET DEFAULT nextval('packages_npm_metadata_caches_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_package_file_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_package_file_build_infos_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY packages_package_files ALTER COLUMN id SET DEFAULT nextval('packages_package_files_id_seq'::regclass);
|
||||
|
|
@ -27379,6 +27404,9 @@ ALTER TABLE ONLY packages_helm_file_metadata
|
|||
ALTER TABLE ONLY packages_maven_metadata
|
||||
ADD CONSTRAINT packages_maven_metadata_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY packages_npm_metadata_caches
|
||||
ADD CONSTRAINT packages_npm_metadata_caches_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY packages_npm_metadata
|
||||
ADD CONSTRAINT packages_npm_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
|
|
@ -31223,6 +31251,8 @@ CREATE INDEX index_notification_settings_on_source_and_level_and_user ON notific
|
|||
|
||||
CREATE UNIQUE INDEX index_notifications_on_user_id_and_source_id_and_source_type ON notification_settings USING btree (user_id, source_id, source_type);
|
||||
|
||||
CREATE UNIQUE INDEX index_npm_metadata_caches_on_package_name_project_id_unique ON packages_npm_metadata_caches USING btree (package_name, project_id) WHERE (project_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_ns_root_stor_stats_on_registry_size_estimated ON namespace_root_storage_statistics USING btree (registry_size_estimated);
|
||||
|
||||
CREATE UNIQUE INDEX index_ns_user_callouts_feature ON user_namespace_callouts USING btree (user_id, feature_name, namespace_id);
|
||||
|
|
@ -31373,6 +31403,8 @@ CREATE INDEX index_packages_maven_metadata_on_package_id_and_path ON packages_ma
|
|||
|
||||
CREATE INDEX index_packages_maven_metadata_on_path ON packages_maven_metadata USING btree (path);
|
||||
|
||||
CREATE INDEX index_packages_npm_metadata_caches_on_project_id ON packages_npm_metadata_caches USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_packages_nuget_dl_metadata_on_dependency_link_id ON packages_nuget_dependency_link_metadata USING btree (dependency_link_id);
|
||||
|
||||
CREATE INDEX index_packages_on_available_pypi_packages ON packages_packages USING btree (project_id, id) WHERE ((status = ANY (ARRAY[0, 1])) AND (package_type = 5) AND (version IS NOT NULL));
|
||||
|
|
@ -32743,8 +32775,6 @@ CREATE INDEX tmp_idx_for_vulnerability_feedback_migration ON vulnerability_feedb
|
|||
|
||||
CREATE INDEX tmp_idx_package_files_on_non_zero_size ON packages_package_files USING btree (package_id, size) WHERE (size IS NOT NULL);
|
||||
|
||||
CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
|
||||
|
||||
CREATE INDEX tmp_index_ci_job_artifacts_on_expire_at_where_locked_unknown ON ci_job_artifacts USING btree (expire_at, job_id) WHERE ((locked = 2) AND (expire_at IS NOT NULL));
|
||||
|
||||
CREATE INDEX tmp_index_ci_job_artifacts_on_id_expire_at_file_type_trace ON ci_job_artifacts USING btree (id) WHERE (((date_part('day'::text, timezone('UTC'::text, expire_at)) = ANY (ARRAY[(21)::double precision, (22)::double precision, (23)::double precision])) AND (date_part('minute'::text, timezone('UTC'::text, expire_at)) = ANY (ARRAY[(0)::double precision, (30)::double precision, (45)::double precision])) AND (date_part('second'::text, timezone('UTC'::text, expire_at)) = (0)::double precision)) OR (file_type = 3));
|
||||
|
|
@ -34828,6 +34858,9 @@ ALTER TABLE ONLY merge_requests
|
|||
ALTER TABLE ONLY ml_experiments
|
||||
ADD CONSTRAINT fk_ad89c59858 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_npm_metadata_caches
|
||||
ADD CONSTRAINT fk_ada23b1d30 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY merge_request_metrics
|
||||
ADD CONSTRAINT fk_ae440388cc FOREIGN KEY (latest_closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,8 @@ The following API resources are available outside of project and group contexts
|
|||
| [Geo Nodes](geo_nodes.md) **(PREMIUM SELF)** | `/geo_nodes` |
|
||||
| [Group Activity Analytics](group_activity_analytics.md) | `/analytics/group_activity/{issues_count}` |
|
||||
| [Group repository storage moves](group_repository_storage_moves.md) **(PREMIUM SELF)** | `/group_repository_storage_moves` |
|
||||
| [Import repository from GitHub](import.md) | `/import/github` |
|
||||
| [Import repository from GitHub](import.md#import-repository-from-github) | `/import/github` |
|
||||
| [Import repository from Bitbucket Server](import.md#import-repository-from-bitbucket-server) | `/import/bitbucket_server` |
|
||||
| [Instance clusters](instance_clusters.md) **(FREE SELF)** | `/admin/clusters` |
|
||||
| [Instance-level CI/CD variables](instance_level_ci_variables.md) **(FREE SELF)** | `/admin/ci/variables` |
|
||||
| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
|
||||
|
|
|
|||
|
|
@ -7028,7 +7028,7 @@ references and their corresponding code points.</p>
|
|||
<copy-code></copy-code>
|
||||
</div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p> &amp; © Æ Ď</span>
|
||||
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p> &amp; © Æ Ď</span>
|
||||
<span id="LC2" class="line" lang="plaintext">¾ ℋ ⅆ</span>
|
||||
<span id="LC3" class="line" lang="plaintext">∲ ≧̸</p></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
|
|
@ -7344,11 +7344,11 @@ stripped in this way:</p>
|
|||
<div>
|
||||
<div><a href="#example-343">Example 343</a></div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
|
||||
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> b </code></p></span></code></pre>
|
||||
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> b </code></p></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -7356,12 +7356,12 @@ stripped in this way:</p>
|
|||
<div>
|
||||
<div><a href="#example-344">Example 344</a></div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
|
||||
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
|
||||
<span id="LC2" class="line" lang="plaintext">` `</span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> </code></span>
|
||||
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> </code></span>
|
||||
<span id="LC2" class="line" lang="plaintext"><code> </code></p></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
|
|
@ -7832,11 +7832,11 @@ not part of a [left-flanking delimiter run]:</p>
|
|||
<div>
|
||||
<div><a href="#example-363">Example 363</a></div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
|
||||
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p>* a *</p></span></code></pre>
|
||||
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p>* a *</p></span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -9790,7 +9790,7 @@ Other [Unicode whitespace] like non-breaking space doesn't work.</p>
|
|||
<div>
|
||||
<div><a href="#example-515">Example 515</a></div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
|
||||
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
|
||||
<copy-code></copy-code>
|
||||
</div>
|
||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||
|
|
|
|||
|
|
@ -203,6 +203,10 @@ module API
|
|||
render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
|
||||
end
|
||||
|
||||
unless can?(current_user, :read_code, target_project)
|
||||
forbidden!("You don't have access to this fork's parent project")
|
||||
end
|
||||
|
||||
cache_key = compare_cache_key(current_user, user_project, target_project, declared_params)
|
||||
|
||||
cache_action(cache_key, expires_in: 1.minute) do
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ module Banzai
|
|||
|
||||
def records_for_nodes(nodes)
|
||||
node_includes = [
|
||||
:work_item_type,
|
||||
:namespace,
|
||||
:author,
|
||||
:assignees,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
# Can be extended for different types of repository object, e.g. Project or Snippet
|
||||
module ExtractsRef
|
||||
InvalidPathError = Class.new(StandardError)
|
||||
|
||||
BRANCH_REF_TYPE = 'heads'
|
||||
TAG_REF_TYPE = 'tags'
|
||||
# Given a string containing both a Git tree-ish, such as a branch or tag, and
|
||||
# a filesystem path joined by forward slashes, attempts to separate the two.
|
||||
#
|
||||
|
|
@ -91,7 +92,7 @@ module ExtractsRef
|
|||
def ref_type
|
||||
return unless params[:ref_type].present?
|
||||
|
||||
params[:ref_type] == 'tags' ? 'tags' : 'heads'
|
||||
params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -154,4 +155,13 @@ module ExtractsRef
|
|||
def repository_container
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def ambiguous_ref?(project, ref)
|
||||
return true if project.repository.ambiguous_ref?(ref)
|
||||
|
||||
return false unless ref&.starts_with?('refs/')
|
||||
|
||||
unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '')
|
||||
project.repository.commit(unprefixed_ref).present?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Nullifies last_error value from project_mirror_data table as they
|
||||
# potentially included sensitive data.
|
||||
# https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3041
|
||||
class NullifyLastErrorFromProjectMirrorData < BatchedMigrationJob
|
||||
feature_category :source_code_management
|
||||
operation_name :update_all
|
||||
|
||||
def perform
|
||||
each_sub_batch { |rel| rel.update_all(last_error: nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -453,6 +453,17 @@ module Gitlab
|
|||
)
|
||||
}mx.freeze
|
||||
|
||||
# Code blocks:
|
||||
# ```
|
||||
# Anything, including `>>>` blocks which are ignored by this filter
|
||||
# ```
|
||||
MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED =
|
||||
'(?P<code>' \
|
||||
'^```\n' \
|
||||
'(?:\n|.)*?' \
|
||||
'\n```\ *$' \
|
||||
')'.freeze
|
||||
|
||||
MARKDOWN_HTML_BLOCK_REGEX = %r{
|
||||
(?<html>
|
||||
# HTML block:
|
||||
|
|
@ -466,27 +477,19 @@ module Gitlab
|
|||
)
|
||||
}mx.freeze
|
||||
|
||||
MARKDOWN_HTML_COMMENT_LINE_REGEX = %r{
|
||||
(?<html_comment_line>
|
||||
# HTML comment line:
|
||||
# <!-- some commented text -->
|
||||
# HTML comment line:
|
||||
# <!-- some commented text -->
|
||||
MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED =
|
||||
'(?P<html_comment_line>' \
|
||||
'^<!--\ .*?\ -->\ *$' \
|
||||
')'.freeze
|
||||
|
||||
^<!--\ .*?\ -->\ *$
|
||||
)
|
||||
}mx.freeze
|
||||
|
||||
MARKDOWN_HTML_COMMENT_BLOCK_REGEX = %r{
|
||||
(?<html_comment_block>
|
||||
# HTML comment block:
|
||||
# <!-- some commented text
|
||||
# additional text
|
||||
# -->
|
||||
|
||||
^<!--.*\n
|
||||
.+?
|
||||
\n-->\ *$
|
||||
)
|
||||
}mx.freeze
|
||||
MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED =
|
||||
'(?P<html_comment_block>' \
|
||||
'^<!--.*?\n' \
|
||||
'(?:\n|.)*?' \
|
||||
'\n.*?-->\ *$' \
|
||||
')'.freeze
|
||||
|
||||
def markdown_code_or_html_blocks
|
||||
@markdown_code_or_html_blocks ||= %r{
|
||||
|
|
@ -496,14 +499,13 @@ module Gitlab
|
|||
}mx.freeze
|
||||
end
|
||||
|
||||
def markdown_code_or_html_comments
|
||||
@markdown_code_or_html_comments ||= %r{
|
||||
#{MARKDOWN_CODE_BLOCK_REGEX}
|
||||
|
|
||||
#{MARKDOWN_HTML_COMMENT_LINE_REGEX}
|
||||
|
|
||||
#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX}
|
||||
}mx.freeze
|
||||
def markdown_code_or_html_comments_untrusted
|
||||
@markdown_code_or_html_comments_untrusted ||=
|
||||
"#{MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED}" \
|
||||
"|" \
|
||||
"#{MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED}" \
|
||||
"|" \
|
||||
"#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED}"
|
||||
end
|
||||
|
||||
# Based on Jira's project key format
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ module Gitlab
|
|||
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
|
||||
BIDI_REGEXP = /\p{Bidi Control}/.freeze
|
||||
|
||||
# Regular expression for identifying space characters
|
||||
#
|
||||
# In web browsers space characters can be confused with simple
|
||||
# spaces which may be misleading
|
||||
SPACE_REGEXP = /\p{Space_Separator}/.freeze
|
||||
|
||||
class << self
|
||||
# Warning message used to highlight bidi characters in the GUI
|
||||
def bidi_warning
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ module Gitlab
|
|||
RE2.Replace(text, regexp, rewrite)
|
||||
end
|
||||
|
||||
# #scan returns an array of the groups captured, rather than MatchData.
|
||||
# Use this to give the capture group name and grab the proper value
|
||||
def extract_named_group(name, match)
|
||||
return unless match
|
||||
|
||||
match_position = regexp.named_capturing_groups[name.to_s]
|
||||
raise RegexpError, "Invalid named capture group: #{name}" unless match_position
|
||||
|
||||
match[match_position - 1]
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.source == other.source
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,15 +2,37 @@
|
|||
|
||||
module Gitlab
|
||||
class UrlSanitizer
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
||||
ALLOWED_WEB_SCHEMES = %w[http https].freeze
|
||||
SCHEMIFIED_SCHEME = 'glschemelessuri'
|
||||
SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://".freeze
|
||||
# URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or
|
||||
# relative URLs. This section will match schemeless URIs with userinfo
|
||||
# e.g. user:pass@gitlab.com but will not match scp-style URIs e.g.
|
||||
# user@server:path/to/file)
|
||||
#
|
||||
# The userinfo part is very loose compared to URI's implementation so we
|
||||
# also match non-escaped userinfo e.g foo:b?r@gitlab.com which should be
|
||||
# encoded as foo:b%3Fr@gitlab.com
|
||||
URI_REGEXP = %r{
|
||||
(?:
|
||||
#{URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)}
|
||||
|
|
||||
(?:(?:(?!@)[%#{URI::REGEXP::PATTERN::UNRESERVED}#{URI::REGEXP::PATTERN::RESERVED}])+(?:@))
|
||||
(?# negative lookahead ensures this isn't an SCP-style URL: [host]:[rel_path|abs_path] server:path/to/file)
|
||||
(?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH}))
|
||||
#{URI::REGEXP::PATTERN::HOSTPORT}
|
||||
)
|
||||
}x
|
||||
|
||||
def self.sanitize(content)
|
||||
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
|
||||
|
||||
content.gsub(regexp) { |url| new(url).masked_url }
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
content.gsub(regexp, '')
|
||||
content.gsub(URI_REGEXP) do |url|
|
||||
new(url).masked_url
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
|
||||
|
|
@ -37,17 +59,6 @@ module Gitlab
|
|||
@url = parse_url(url)
|
||||
end
|
||||
|
||||
def sanitized_url
|
||||
@sanitized_url ||= safe_url.to_s
|
||||
end
|
||||
|
||||
def masked_url
|
||||
url = @url.dup
|
||||
url.password = "*****" if url.password.present?
|
||||
url.user = "*****" if url.user.present?
|
||||
url.to_s
|
||||
end
|
||||
|
||||
def credentials
|
||||
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
|
||||
end
|
||||
|
|
@ -56,15 +67,37 @@ module Gitlab
|
|||
credentials[:user]
|
||||
end
|
||||
|
||||
def full_url
|
||||
@full_url ||= generate_full_url.to_s
|
||||
def sanitized_url
|
||||
safe_url = @url.dup
|
||||
safe_url.password = nil
|
||||
safe_url.user = nil
|
||||
reverse_schemify(safe_url.to_s)
|
||||
end
|
||||
strong_memoize_attr :sanitized_url
|
||||
|
||||
def masked_url
|
||||
url = @url.dup
|
||||
url.password = "*****" if url.password.present?
|
||||
url.user = "*****" if url.user.present?
|
||||
reverse_schemify(url.to_s)
|
||||
end
|
||||
strong_memoize_attr :masked_url
|
||||
|
||||
def full_url
|
||||
return reverse_schemify(@url.to_s) unless valid_credentials?
|
||||
|
||||
url = @url.dup
|
||||
url.password = encode_percent(credentials[:password]) if credentials[:password].present?
|
||||
url.user = encode_percent(credentials[:user]) if credentials[:user].present?
|
||||
reverse_schemify(url.to_s)
|
||||
end
|
||||
strong_memoize_attr :full_url
|
||||
|
||||
private
|
||||
|
||||
def parse_url(url)
|
||||
url = url.to_s.strip
|
||||
match = url.match(%r{\A(?:git|ssh|http(?:s?))\://(?:(.+)(?:@))?(.+)})
|
||||
url = schemify(url.to_s.strip)
|
||||
match = url.match(%r{\A(?:(?:#{SCHEMIFIED_SCHEME}|git|ssh|http(?:s?)):)?//(?:(.+)(?:@))?(.+)}o)
|
||||
raw_credentials = match[1] if match
|
||||
|
||||
if raw_credentials.present?
|
||||
|
|
@ -83,24 +116,19 @@ module Gitlab
|
|||
url
|
||||
end
|
||||
|
||||
def generate_full_url
|
||||
return @url unless valid_credentials?
|
||||
|
||||
@url.dup.tap do |generated|
|
||||
generated.password = encode_percent(credentials[:password]) if credentials[:password].present?
|
||||
generated.user = encode_percent(credentials[:user]) if credentials[:user].present?
|
||||
end
|
||||
def schemify(url)
|
||||
# Prepend the placeholder scheme unless the URL has a scheme or is relative
|
||||
url.prepend(SCHEMIFY_PLACEHOLDER) unless url.starts_with?(%r{(?:#{URI::REGEXP::PATTERN::SCHEME}:)?//}o)
|
||||
url
|
||||
end
|
||||
|
||||
def safe_url
|
||||
safe_url = @url.dup
|
||||
safe_url.password = nil
|
||||
safe_url.user = nil
|
||||
safe_url
|
||||
def reverse_schemify(url)
|
||||
url.slice!(SCHEMIFY_PLACEHOLDER) if url.starts_with?(SCHEMIFY_PLACEHOLDER)
|
||||
url
|
||||
end
|
||||
|
||||
def valid_credentials?
|
||||
credentials && credentials.is_a?(Hash) && credentials.any?
|
||||
credentials.is_a?(Hash) && credentials.values.any?
|
||||
end
|
||||
|
||||
def encode_percent(string)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ module Rouge
|
|||
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
|
||||
|
||||
line.each do |token, value|
|
||||
yield highlight_unicode_control_characters(span(token, value.chomp! || value))
|
||||
value = value.chomp! || value
|
||||
value = replace_space_characters(value)
|
||||
|
||||
yield highlight_unicode_control_characters(span(token, value))
|
||||
end
|
||||
|
||||
yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present?
|
||||
|
|
@ -42,6 +45,10 @@ module Rouge
|
|||
%(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>)
|
||||
end
|
||||
|
||||
def replace_space_characters(text)
|
||||
text.gsub(Gitlab::Unicode::SPACE_REGEXP, ' ')
|
||||
end
|
||||
|
||||
def highlight_unicode_control_characters(text)
|
||||
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
|
||||
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
|
||||
|
|
|
|||
|
|
@ -2775,6 +2775,9 @@ msgstr ""
|
|||
msgid "AdminArea|Users with highest role"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminArea|Users with highest role guest and with a %{strongOpen}Custom Role%{strongClose}."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminArea|Users without a Group and Project"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27322,6 +27325,9 @@ msgstr ""
|
|||
msgid "MergeRequest|Approved by @%{username}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Can't fetch the single file diff for the discussion. Please reload this page."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequest|Can't show this merge request because of an internal error. Contact your administrator."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43764,6 +43770,9 @@ msgstr ""
|
|||
msgid "The default branch for this project has been changed. Please update your bookmarks."
|
||||
msgstr ""
|
||||
|
||||
msgid "The default branch of this project clashes with another ref"
|
||||
msgstr ""
|
||||
|
||||
msgid "The dependency list details information about the components used within your project."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ module QA
|
|||
element :ci_variable_key_field
|
||||
element :ci_variable_value_field
|
||||
element :ci_variable_save_button
|
||||
element :ci_variable_delete_button
|
||||
end
|
||||
|
||||
def fill_variable(key, value, masked = false)
|
||||
|
|
@ -37,14 +36,6 @@ module QA
|
|||
def click_ci_variable_save_button
|
||||
click_element :ci_variable_save_button
|
||||
end
|
||||
|
||||
def click_reveal_ci_variable_value_button
|
||||
click_element :reveal_ci_variable_value_button
|
||||
end
|
||||
|
||||
def click_ci_variable_delete_button
|
||||
click_element :ci_variable_delete_button
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,19 +18,6 @@ module QA
|
|||
@variable_type = 'env_var'
|
||||
end
|
||||
|
||||
def fabricate!
|
||||
project.visit!
|
||||
|
||||
Page::Project::Menu.perform(&:go_to_ci_cd_settings)
|
||||
|
||||
Page::Project::Settings::CiCd.perform do |setting|
|
||||
setting.expand_ci_variables do |page|
|
||||
page.click_add_variable
|
||||
page.fill_variable(key, value, masked)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
resource_web_url(api_get)
|
||||
rescue ResourceNotFoundError
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Verify' do
|
||||
describe 'Add or Remove CI variable via UI', :smoke, product_group: :pipeline_security do
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api_unless_fips! do |project|
|
||||
project.name = 'project-with-ci-variables'
|
||||
project.description = 'project with CI variables'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
project.visit!
|
||||
add_ci_variable
|
||||
end
|
||||
|
||||
it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348027' do
|
||||
Page::Project::Settings::CiVariables.perform do |ci_variable|
|
||||
expect(ci_variable).to have_text('VARIABLE_KEY')
|
||||
expect(ci_variable).not_to have_text('some_CI_variable')
|
||||
|
||||
ci_variable.click_reveal_ci_variable_value_button
|
||||
|
||||
expect(ci_variable).to have_text('some_CI_variable')
|
||||
end
|
||||
end
|
||||
|
||||
it 'user removes a CI variable', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348026' do
|
||||
Page::Project::Settings::CiVariables.perform do |ci_variable|
|
||||
ci_variable.click_edit_ci_variable
|
||||
ci_variable.click_ci_variable_delete_button
|
||||
|
||||
expect(ci_variable).to have_text('There are no variables yet', wait: 60)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_ci_variable
|
||||
Resource::CiVariable.fabricate_via_browser_ui! do |ci_variable|
|
||||
ci_variable.project = project
|
||||
ci_variable.key = 'VARIABLE_KEY'
|
||||
ci_variable.value = 'some_CI_variable'
|
||||
ci_variable.masked = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do
|
|||
enable_ssl_verification: false,
|
||||
url_variables: [
|
||||
{ key: 'token', value: 'some secret value' },
|
||||
{ key: 'baz', value: 'qux' },
|
||||
{ key: 'foo', value: nil }
|
||||
]
|
||||
}
|
||||
|
|
@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do
|
|||
expect(flash[:notice]).to include('was updated')
|
||||
expect(hook).to have_attributes(hook_params.except(:url_variables))
|
||||
expect(hook).to have_attributes(
|
||||
url_variables: { 'token' => 'some secret value', 'baz' => 'woo' }
|
||||
url_variables: { 'token' => 'some secret value', 'baz' => 'qux' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ConfirmEmailWarning do
|
||||
RSpec.describe ConfirmEmailWarning, feature_category: :system_access do
|
||||
before do
|
||||
stub_application_setting_enum('email_confirmation_setting', 'soft')
|
||||
end
|
||||
|
|
@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do
|
|||
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is being impersonated' do
|
||||
let(:impersonator) { create(:admin) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id })
|
||||
|
||||
get :index
|
||||
end
|
||||
|
||||
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||
|
||||
context 'when impersonated user email has html in their email' do
|
||||
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||
|
||||
it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not being impersonated' do
|
||||
before do
|
||||
get :index
|
||||
end
|
||||
|
||||
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||
|
||||
context 'when user email has html in their email' do
|
||||
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||
|
||||
it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::BlobController do
|
||||
RSpec.describe Projects::BlobController, feature_category: :source_code_management do
|
||||
include ProjectForksHelper
|
||||
|
||||
let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) }
|
||||
let(:previous_default_branch) { nil }
|
||||
|
||||
describe "GET show" do
|
||||
def request
|
||||
get(:show, params: { namespace_id: project.namespace, project_id: project, id: id })
|
||||
let(:params) { { namespace_id: project.namespace, project_id: project, id: id } }
|
||||
let(:request) do
|
||||
get(:show, params: params)
|
||||
end
|
||||
|
||||
render_views
|
||||
|
|
@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do
|
|||
context 'with file path' do
|
||||
before do
|
||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||
|
||||
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
|
||||
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
|
||||
request
|
||||
end
|
||||
|
||||
context 'when the ref is ambiguous' do
|
||||
let(:ref) { 'ambiguous_ref' }
|
||||
let(:path) { 'README.md' }
|
||||
let(:id) { "#{ref}/#{path}" }
|
||||
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
|
||||
|
||||
context 'and explicitly requesting a branch' do
|
||||
let(:ref_type) { 'heads' }
|
||||
|
||||
it 'redirects to blob#show with sha for the branch' do
|
||||
expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}"))
|
||||
end
|
||||
end
|
||||
|
||||
context 'and explicitly requesting a tag' do
|
||||
let(:ref_type) { 'tags' }
|
||||
|
||||
it 'responds with success' do
|
||||
expect(response).to be_ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "valid branch, valid file" do
|
||||
let(:id) { 'master/README.md' }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
|
|||
include GoogleApi::CloudPlatformHelpers
|
||||
include KubernetesHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:project) { create(:project) }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
|
|||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a public project' do
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
|
||||
end
|
||||
|
||||
context 'with guest user' do
|
||||
let(:prometheus_body) { nil }
|
||||
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns 404' do
|
||||
get :prometheus_proxy, params: prometheus_proxy_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Projects::Environments::PrometheusApiController do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:project) { create(:project) }
|
||||
let_it_be(:proxyable) { create(:environment, project: project) }
|
||||
|
||||
before do
|
||||
|
|
@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do
|
|||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a public project' do
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
|
||||
end
|
||||
|
||||
context 'with guest user' do
|
||||
let(:prometheus_body) { nil }
|
||||
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
it 'returns 404' do
|
||||
get :prometheus_proxy, params: prometheus_proxy_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::TreeController do
|
||||
RSpec.describe Projects::TreeController, feature_category: :source_code_management do
|
||||
let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) }
|
||||
let(:previous_default_branch) { nil }
|
||||
let(:user) { create(:user) }
|
||||
|
|
@ -15,15 +15,41 @@ RSpec.describe Projects::TreeController do
|
|||
end
|
||||
|
||||
describe "GET show" do
|
||||
let(:params) do
|
||||
{
|
||||
namespace_id: project.namespace.to_param, project_id: project, id: id
|
||||
}
|
||||
end
|
||||
|
||||
# Make sure any errors accessing the tree in our views bubble up to this spec
|
||||
render_views
|
||||
|
||||
before do
|
||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
|
||||
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
|
||||
get :show, params: params
|
||||
end
|
||||
|
||||
get :show, params: {
|
||||
namespace_id: project.namespace.to_param, project_id: project, id: id
|
||||
}
|
||||
context 'when the ref is ambiguous' do
|
||||
let(:id) { 'ambiguous_ref' }
|
||||
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
|
||||
|
||||
context 'and explicitly requesting a branch' do
|
||||
let(:ref_type) { 'heads' }
|
||||
|
||||
it 'redirects to blob#show with sha for the branch' do
|
||||
expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id))
|
||||
end
|
||||
end
|
||||
|
||||
context 'and explicitly requesting a tag' do
|
||||
let(:ref_type) { 'tags' }
|
||||
|
||||
it 'responds with success' do
|
||||
expect(response).to be_ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "valid branch, no path" do
|
||||
|
|
|
|||
|
|
@ -163,6 +163,69 @@ RSpec.describe ProjectsController, feature_category: :projects do
|
|||
expect(assigns(:notification_setting).level).to eq("watch")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a tag with the same name as the default branch' do
|
||||
let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) }
|
||||
let(:tree_with_default_branch) do
|
||||
branch = tagged_project.repository.find_branch(tagged_project.default_branch)
|
||||
project_tree_path(tagged_project, branch.target)
|
||||
end
|
||||
|
||||
before do
|
||||
tagged_project.repository.create_file(
|
||||
tagged_project.creator,
|
||||
'file_for_tag',
|
||||
'content for file',
|
||||
message: "Automatically created file",
|
||||
branch_name: 'branch-to-tag'
|
||||
)
|
||||
|
||||
tagged_project.repository.add_tag(
|
||||
tagged_project.creator,
|
||||
tagged_project.default_branch, # tag name
|
||||
'branch-to-tag' # target
|
||||
)
|
||||
end
|
||||
|
||||
it 'redirects to tree view for the default branch' do
|
||||
get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project }
|
||||
expect(response).to redirect_to(tree_with_default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the default branch name can resolve to another ref' do
|
||||
let!(:project_with_default_branch) do
|
||||
create(:project, :public, :custom_repo, files: ['somefile']).tap do |p|
|
||||
p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master')
|
||||
p.change_head("refs/heads/#{other_ref}")
|
||||
end.reload
|
||||
end
|
||||
|
||||
let(:other_ref) { 'branch-name' }
|
||||
|
||||
context 'but there is no other ref' do
|
||||
it 'responds with ok' do
|
||||
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
|
||||
expect(response).to be_ok
|
||||
end
|
||||
end
|
||||
|
||||
context 'and that other ref exists' do
|
||||
let(:tree_with_default_branch) do
|
||||
branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch)
|
||||
project_tree_path(project_with_default_branch, branch.target)
|
||||
end
|
||||
|
||||
before do
|
||||
project_with_default_branch.repository.create_branch(other_ref, 'master')
|
||||
end
|
||||
|
||||
it 'redirects to tree view for the default branch' do
|
||||
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
|
||||
expect(response).to redirect_to(tree_with_default_branch)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when project repository is disabled" do
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :requirement do
|
||||
issue_type { :requirement }
|
||||
association :work_item_type, :default, :requirement
|
||||
end
|
||||
|
||||
trait :task do
|
||||
issue_type { :task }
|
||||
association :work_item_type, :default, :task
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :npm_metadata_cache, class: 'Packages::Npm::MetadataCache' do
|
||||
project
|
||||
sequence(:package_name) { |n| "@#{project.root_namespace.path}/package-#{n}" }
|
||||
file { 'unnamed' }
|
||||
size { 100.kilobytes }
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
|||
project
|
||||
|
||||
trait :url_variables do
|
||||
url_variables { { 'abc' => 'supers3cret' } }
|
||||
url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } }
|
||||
end
|
||||
|
||||
trait :token do
|
||||
|
|
|
|||
|
|
@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
|
|||
icon = first('[data-testid="incognito-icon"]')
|
||||
expect(icon).not_to be nil
|
||||
end
|
||||
|
||||
context 'when viewing the confirm email warning', :js do
|
||||
let_it_be(:another_user) { create(:user, :unconfirmed) }
|
||||
|
||||
let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') }
|
||||
let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } }
|
||||
|
||||
context 'with an email that does not contain HTML' do
|
||||
before do
|
||||
subject
|
||||
end
|
||||
|
||||
it 'displays the warning alert including the email' do
|
||||
expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an email that contains HTML' do
|
||||
let(:malicious_email) { "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>" }
|
||||
let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) }
|
||||
|
||||
before do
|
||||
subject
|
||||
end
|
||||
|
||||
it 'displays the impersonation alert, excludes email, and disables links' do
|
||||
expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'ending impersonation' do
|
||||
|
|
|
|||
|
|
@ -208,17 +208,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
end
|
||||
|
||||
describe 'with two-factor authentication', :js do
|
||||
def enter_code(code)
|
||||
if page.has_content?("Sign in via 2FA code")
|
||||
click_on("Sign in via 2FA code")
|
||||
enter_code(code)
|
||||
else
|
||||
fill_in 'user_otp_attempt', with: code
|
||||
click_button 'Verify code'
|
||||
end
|
||||
def enter_code(code, only_two_factor_webauthn_enabled: false)
|
||||
click_on("Sign in via 2FA code") if only_two_factor_webauthn_enabled
|
||||
|
||||
fill_in 'user_otp_attempt', with: code
|
||||
click_button 'Verify code'
|
||||
end
|
||||
|
||||
shared_examples_for 'can login with recovery codes' do
|
||||
shared_examples_for 'can login with recovery codes' do |only_two_factor_webauthn_enabled: false|
|
||||
context 'using backup code' do
|
||||
let(:codes) { user.generate_otp_backup_codes! }
|
||||
|
||||
|
|
@ -235,7 +232,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
.to increment(:user_authenticated_counter)
|
||||
.and increment(:user_two_factor_authenticated_counter)
|
||||
|
||||
enter_code(codes.sample)
|
||||
enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
|
||||
|
||||
expect(page).to have_current_path root_path, ignore_query: true
|
||||
end
|
||||
|
|
@ -245,7 +242,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
.to increment(:user_authenticated_counter)
|
||||
.and increment(:user_two_factor_authenticated_counter)
|
||||
|
||||
expect { enter_code(codes.sample) }
|
||||
expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
|
||||
.to change { user.reload.otp_backup_codes.size }.by(-1)
|
||||
end
|
||||
|
||||
|
|
@ -256,13 +253,13 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
.and increment(:user_session_destroyed_counter)
|
||||
|
||||
random_code = codes.delete(codes.sample)
|
||||
expect { enter_code(random_code) }
|
||||
expect { enter_code(random_code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
|
||||
.to change { user.reload.otp_backup_codes.size }.by(-1)
|
||||
|
||||
gitlab_sign_out
|
||||
gitlab_sign_in(user)
|
||||
|
||||
expect { enter_code(codes.sample) }
|
||||
expect { enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled) }
|
||||
.to change { user.reload.otp_backup_codes.size }.by(-1)
|
||||
end
|
||||
|
||||
|
|
@ -272,7 +269,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
.and increment(:user_two_factor_authenticated_counter)
|
||||
expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
|
||||
|
||||
enter_code(codes.sample)
|
||||
enter_code(codes.sample, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -287,7 +284,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
user.save!(touch: false)
|
||||
expect(user.reload.otp_backup_codes.size).to eq 9
|
||||
|
||||
enter_code(code)
|
||||
enter_code(code, only_two_factor_webauthn_enabled: only_two_factor_webauthn_enabled)
|
||||
expect(page).to have_content('Invalid two-factor code.')
|
||||
end
|
||||
end
|
||||
|
|
@ -382,7 +379,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
context 'when user with only Webauthn enabled' do
|
||||
let(:user) { create(:user, :two_factor_via_webauthn, registrations_count: 1) }
|
||||
|
||||
include_examples 'can login with recovery codes'
|
||||
include_examples 'can login with recovery codes', only_two_factor_webauthn_enabled: true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
describe '#execute' do
|
||||
let!(:group) { create(:group) }
|
||||
let!(:public_project) { create(:project, :public, namespace: group) }
|
||||
let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) }
|
||||
let!(:private_project) { create(:project, :private, namespace: group) }
|
||||
let!(:user) { create(:user) }
|
||||
|
||||
|
|
@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
create(:environment, name: 'gprd', project: public_project)
|
||||
create(:environment, name: 'gprd', project: private_project)
|
||||
create(:environment, name: 'gcny', project: private_project)
|
||||
create(:environment, name: 'gprivprd', project: public_project_with_private_environments)
|
||||
create(:environment, name: 'gprivstg', project: public_project_with_private_environments)
|
||||
|
||||
public_project_with_private_environments.update!(namespace: group)
|
||||
public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE)
|
||||
end
|
||||
|
||||
context 'using a group' do
|
||||
|
|
@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
|
||||
names = described_class.new(group, user).execute
|
||||
|
||||
expect(names).to eq(%w[gcny gprd gstg])
|
||||
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
|
||||
names = described_class.new(group, user).execute
|
||||
|
||||
expect(names).to eq(%w[gcny gprd gstg])
|
||||
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a public project reporter which has private environments' do
|
||||
it 'returns environment names for public projects' do
|
||||
public_project_with_private_environments.add_reporter(user)
|
||||
|
||||
names = described_class.new(group, user).execute
|
||||
|
||||
expect(names).to eq(%w[gprd gprivprd gprivstg gstg])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group guest' do
|
||||
it 'returns environment names for all public projects' do
|
||||
it 'returns environment names for public projects' do
|
||||
group.add_guest(user)
|
||||
|
||||
names = described_class.new(group, user).execute
|
||||
|
|
@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
end
|
||||
|
||||
context 'with a non-member' do
|
||||
it 'returns environment names for all public projects' do
|
||||
it 'returns environment names for only public projects with public environments' do
|
||||
names = described_class.new(group, user).execute
|
||||
|
||||
expect(names).to eq(%w[gprd gstg])
|
||||
|
|
@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
|||
end
|
||||
|
||||
context 'without a user' do
|
||||
it 'returns environment names for all public projects' do
|
||||
it 'returns environment names for only public projects with public environments' do
|
||||
names = described_class.new(group).execute
|
||||
|
||||
expect(names).to eq(%w[gprd gstg])
|
||||
|
|
|
|||
|
|
@ -106,6 +106,26 @@ RSpec.describe NotesFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'for notes on public issue in public project' do
|
||||
let_it_be(:public_project) { create(:project, :public) }
|
||||
let_it_be(:guest_member) { create(:user) }
|
||||
let_it_be(:reporter_member) { create(:user) }
|
||||
let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) }
|
||||
let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) }
|
||||
let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) }
|
||||
let_it_be(:public_note) { create(:note_on_issue, project: public_project) }
|
||||
|
||||
it 'shows all notes when the current_user has reporter access' do
|
||||
notes = described_class.new(reporter_member, project: public_project).execute
|
||||
expect(notes).to contain_exactly internal_note, public_note
|
||||
end
|
||||
|
||||
it 'shows only public notes when the current_user has guest access' do
|
||||
notes = described_class.new(guest_member, project: public_project).execute
|
||||
expect(notes).to contain_exactly public_note
|
||||
end
|
||||
end
|
||||
|
||||
context 'for target type' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let!(:note1) { create :note_on_issue, project: project }
|
||||
|
|
|
|||
|
|
@ -1598,6 +1598,54 @@ describe('DiffsStoreActions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('rereadNoteHash', () => {
|
||||
beforeEach(() => {
|
||||
window.location.hash = 'note_123';
|
||||
});
|
||||
|
||||
it('dispatches setCurrentDiffFileIdFromNote if the hash is a note URL', () => {
|
||||
window.location.hash = 'note_123';
|
||||
|
||||
return testAction(
|
||||
diffActions.rereadNoteHash,
|
||||
{},
|
||||
{},
|
||||
[],
|
||||
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
|
||||
);
|
||||
});
|
||||
|
||||
it('dispatches fetchFileByFile if the app is in fileByFile mode', () => {
|
||||
window.location.hash = 'note_123';
|
||||
|
||||
return testAction(
|
||||
diffActions.rereadNoteHash,
|
||||
{},
|
||||
{ viewDiffsFileByFile: true },
|
||||
[],
|
||||
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }, { type: 'fetchFileByFile' }],
|
||||
);
|
||||
});
|
||||
|
||||
it('does not try to fetch the diff file if the app is not in fileByFile mode', () => {
|
||||
window.location.hash = 'note_123';
|
||||
|
||||
return testAction(
|
||||
diffActions.rereadNoteHash,
|
||||
{},
|
||||
{ viewDiffsFileByFile: false },
|
||||
[],
|
||||
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing if the hash is not a note URL', () => {
|
||||
window.location.hash = 'abcdef1234567890';
|
||||
|
||||
return testAction(diffActions.rereadNoteHash, {}, {}, [], []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCurrentDiffFileIdFromNote', () => {
|
||||
it('commits SET_CURRENT_DIFF_FILE', () => {
|
||||
const commit = jest.fn();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
import AuthorSelect from '~/projects/commits/components/author_select.vue';
|
||||
import { createStore } from '~/projects/commits/store';
|
||||
|
|
@ -64,39 +65,23 @@ describe('Author Select', () => {
|
|||
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
|
||||
|
||||
describe('user is searching via "filter by commit message"', () => {
|
||||
it('disables dropdown container', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ hasSearchParam: true });
|
||||
beforeEach(() => {
|
||||
setWindowLocation(`?search=foo`);
|
||||
createComponent();
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
it('does not disable dropdown container', () => {
|
||||
expect(findDropdownContainer().attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('has correct tooltip message', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ hasSearchParam: true });
|
||||
|
||||
await nextTick();
|
||||
it('has correct tooltip message', () => {
|
||||
expect(findDropdownContainer().attributes('title')).toBe(
|
||||
'Searching by both author and message is currently not supported.',
|
||||
);
|
||||
});
|
||||
|
||||
it('disables dropdown', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ hasSearchParam: false });
|
||||
|
||||
await nextTick();
|
||||
expect(findDropdown().attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('hasSearchParam if user types a truthy string', () => {
|
||||
wrapper.vm.setSearchParam('false');
|
||||
|
||||
expect(wrapper.vm.hasSearchParam).toBe(true);
|
||||
it('disables dropdown', () => {
|
||||
expect(findDropdown().attributes('disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -106,9 +91,8 @@ describe('Author Select', () => {
|
|||
});
|
||||
|
||||
it('displays the current selected author', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ currentAuthor });
|
||||
setWindowLocation(`?author=${currentAuthor}`);
|
||||
createComponent();
|
||||
|
||||
await nextTick();
|
||||
expect(findDropdown().attributes('text')).toBe(currentAuthor);
|
||||
|
|
@ -146,12 +130,14 @@ describe('Author Select', () => {
|
|||
expect(findDropdownItems().at(0).text()).toBe('Any Author');
|
||||
});
|
||||
|
||||
it('displays the project authors', async () => {
|
||||
await nextTick();
|
||||
it('displays the project authors', () => {
|
||||
expect(findDropdownItems()).toHaveLength(authors.length + 1);
|
||||
});
|
||||
|
||||
it('has the correct props', async () => {
|
||||
setWindowLocation(`?author=${currentAuthor}`);
|
||||
createComponent();
|
||||
|
||||
const [{ avatar_url: avatarUrl, username }] = authors;
|
||||
const result = {
|
||||
avatarUrl,
|
||||
|
|
@ -159,16 +145,11 @@ describe('Author Select', () => {
|
|||
isChecked: true,
|
||||
};
|
||||
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ currentAuthor });
|
||||
|
||||
await nextTick();
|
||||
expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result));
|
||||
});
|
||||
|
||||
it("display the author's name", async () => {
|
||||
await nextTick();
|
||||
it("display the author's name", () => {
|
||||
expect(findDropdownItems().at(1).text()).toBe(currentAuthor);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
|
||||
|
||||
const projectRootPath = 'root/Project1';
|
||||
|
|
@ -16,16 +17,38 @@ describe('generateRefDestinationPath', () => {
|
|||
${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`}
|
||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`}
|
||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
|
||||
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
|
||||
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
|
||||
setWindowLocation(currentPath);
|
||||
expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(result);
|
||||
expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(
|
||||
`${TEST_HOST}/${result}`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('when using symbolic ref names', () => {
|
||||
it.each`
|
||||
currentPath | nextRef | result
|
||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`}
|
||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
|
||||
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
|
||||
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||
`(
|
||||
'generates the correct destination path for $currentPath with ref type when it can be extracted',
|
||||
({ currentPath, result, nextRef }) => {
|
||||
setWindowLocation(currentPath);
|
||||
expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe(
|
||||
`${TEST_HOST}/${result}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes the selected ref', () => {
|
||||
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
|
||||
|
||||
expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
|
||||
result,
|
||||
`${TEST_HOST}/${result}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ RSpec.describe HooksHelper do
|
|||
it 'returns proper data' do
|
||||
expect(subject).to match(
|
||||
url: project_hook.url,
|
||||
url_variables: Gitlab::Json.dump([{ key: 'abc' }])
|
||||
url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor
|
|||
filter(link, context)
|
||||
end.count
|
||||
|
||||
expect(control_count).to eq 10
|
||||
expect(control_count).to eq 11
|
||||
|
||||
expect do
|
||||
filter("#{link} #{link2}", context)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength
|
||||
it 'nullifies last_error column on all rows' do
|
||||
namespaces = table(:namespaces)
|
||||
projects = table(:projects)
|
||||
project_import_states = table(:project_mirror_data)
|
||||
|
||||
group = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||
|
||||
project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||
project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||
project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||
|
||||
project_1 = projects.create!(
|
||||
namespace_id: group.id,
|
||||
project_namespace_id: project_namespace_1.id,
|
||||
name: 'test1'
|
||||
)
|
||||
project_2 = projects.create!(
|
||||
namespace_id: group.id,
|
||||
project_namespace_id: project_namespace_2.id,
|
||||
name: 'test2'
|
||||
)
|
||||
project_3 = projects.create!(
|
||||
namespace_id: group.id,
|
||||
project_namespace_id: project_namespace_3.id,
|
||||
name: 'test3'
|
||||
)
|
||||
|
||||
project_import_state_1 = project_import_states.create!(
|
||||
project_id: project_1.id,
|
||||
status: 0,
|
||||
last_update_started_at: 1.hour.ago,
|
||||
last_update_scheduled_at: 1.hour.ago,
|
||||
last_update_at: 1.hour.ago,
|
||||
last_successful_update_at: 2.days.ago,
|
||||
last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength
|
||||
correlation_id_value: SecureRandom.uuid,
|
||||
jid: SecureRandom.uuid
|
||||
)
|
||||
|
||||
project_import_states.create!(
|
||||
project_id: project_2.id,
|
||||
status: 1,
|
||||
last_update_started_at: 1.hour.ago,
|
||||
last_update_scheduled_at: 1.hour.ago,
|
||||
last_update_at: 1.hour.ago,
|
||||
last_successful_update_at: nil,
|
||||
next_execution_timestamp: 1.day.from_now,
|
||||
last_error: '',
|
||||
correlation_id_value: SecureRandom.uuid,
|
||||
jid: SecureRandom.uuid
|
||||
)
|
||||
|
||||
project_import_state_3 = project_import_states.create!(
|
||||
project_id: project_3.id,
|
||||
status: 2,
|
||||
last_update_started_at: 1.hour.ago,
|
||||
last_update_scheduled_at: 1.hour.ago,
|
||||
last_update_at: 1.hour.ago,
|
||||
last_successful_update_at: 1.hour.ago,
|
||||
next_execution_timestamp: 1.day.from_now,
|
||||
last_error: nil,
|
||||
correlation_id_value: SecureRandom.uuid,
|
||||
jid: SecureRandom.uuid
|
||||
)
|
||||
|
||||
migration = described_class.new(
|
||||
start_id: project_import_state_1.id,
|
||||
end_id: project_import_state_3.id,
|
||||
batch_table: :project_mirror_data,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 1,
|
||||
pause_ms: 0,
|
||||
connection: ApplicationRecord.connection
|
||||
)
|
||||
|
||||
w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord
|
||||
expect { migration.perform }.to change(&w_last_error_count).from(2).to(0)
|
||||
end
|
||||
end
|
||||
|
|
@ -711,6 +711,7 @@ project:
|
|||
- packages
|
||||
- package_files
|
||||
- rpm_repository_files
|
||||
- npm_metadata_caches
|
||||
- packages_cleanup_policy
|
||||
- alerting_setting
|
||||
- project_setting
|
||||
|
|
|
|||
|
|
@ -1171,7 +1171,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
|
|||
end
|
||||
|
||||
context 'HTML comment lines' do
|
||||
subject { described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX }
|
||||
subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED, multiline: true) }
|
||||
|
||||
let(:expected) { [['<!-- an HTML comment -->'], ['<!-- another HTML comment -->']] }
|
||||
let(:markdown) do
|
||||
|
|
@ -1189,20 +1189,20 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
|
|||
it { is_expected.to match(%(<!-- single line comment -->)) }
|
||||
it { is_expected.not_to match(%(<!--\nblock comment\n-->)) }
|
||||
it { is_expected.not_to match(%(must start in first column <!-- comment -->)) }
|
||||
it { expect(markdown.scan(subject)).to eq expected }
|
||||
it { expect(subject.scan(markdown)).to eq expected }
|
||||
end
|
||||
|
||||
context 'HTML comment blocks' do
|
||||
subject { described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX }
|
||||
subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED, multiline: true) }
|
||||
|
||||
let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\n-->) }
|
||||
let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\nmore text -->) }
|
||||
let(:markdown) do
|
||||
<<~MARKDOWN
|
||||
Regular text
|
||||
|
||||
<!-- the start of an HTML comment
|
||||
- [ ] list item commented out
|
||||
-->
|
||||
more text -->
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,38 @@ RSpec.describe Gitlab::UntrustedRegexp do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#extract_named_group' do
|
||||
let(:re) { described_class.new('(?P<name>\w+) (?P<age>\d+)|(?P<name_only>\w+)') }
|
||||
let(:text) { 'Bob 40' }
|
||||
|
||||
it 'returns values for both named groups' do
|
||||
matched = re.scan(text).first
|
||||
|
||||
expect(re.extract_named_group(:name, matched)).to eq 'Bob'
|
||||
expect(re.extract_named_group(:age, matched)).to eq '40'
|
||||
end
|
||||
|
||||
it 'returns nil if there was no match for group' do
|
||||
matched = re.scan('Bob').first
|
||||
|
||||
expect(re.extract_named_group(:name, matched)).to be_nil
|
||||
expect(re.extract_named_group(:age, matched)).to be_nil
|
||||
expect(re.extract_named_group(:name_only, matched)).to eq 'Bob'
|
||||
end
|
||||
|
||||
it 'returns nil if match is nil' do
|
||||
matched = '(?P<age>\d+)'.scan(text).first
|
||||
|
||||
expect(re.extract_named_group(:age, matched)).to be_nil
|
||||
end
|
||||
|
||||
it 'raises if name is not a capture group' do
|
||||
matched = re.scan(text).first
|
||||
|
||||
expect { re.extract_named_group(:foo, matched) }.to raise_error('Invalid named capture group: foo')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#match' do
|
||||
context 'when there are matches' do
|
||||
it 'returns a match object' do
|
||||
|
|
|
|||
|
|
@ -10,29 +10,36 @@ RSpec.describe Gitlab::UrlSanitizer do
|
|||
# We want to try with multi-line content because is how error messages are formatted
|
||||
described_class.sanitize(%Q{
|
||||
remote: Not Found
|
||||
fatal: repository '#{url}' not found
|
||||
fatal: repository `#{url}` not found
|
||||
})
|
||||
end
|
||||
|
||||
where(:input, :output) do
|
||||
'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/'
|
||||
'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/'
|
||||
'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git'
|
||||
|
||||
# git protocol does not support authentication but clean any details anyway
|
||||
'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git'
|
||||
'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git'
|
||||
# http(s), ssh, git, relative, and schemeless URLs should all be masked correctly
|
||||
urls = ['http://', 'https://', 'ssh://', 'git://', '//', ''].flat_map do |protocol|
|
||||
[
|
||||
["#{protocol}test.com", "#{protocol}test.com"],
|
||||
["#{protocol}test.com/", "#{protocol}test.com/"],
|
||||
["#{protocol}test.com/path/to/repo.git", "#{protocol}test.com/path/to/repo.git"],
|
||||
["#{protocol}user@test.com", "#{protocol}*****@test.com"],
|
||||
["#{protocol}user:pass@test.com", "#{protocol}*****:*****@test.com"],
|
||||
["#{protocol}user:@test.com", "#{protocol}*****@test.com"],
|
||||
["#{protocol}:pass@test.com", "#{protocol}:*****@test.com"]
|
||||
]
|
||||
end
|
||||
|
||||
# SCP-style URLs are left unmodified
|
||||
'user@server:project.git' | 'user@server:project.git'
|
||||
'user:pass@server:project.git' | 'user:pass@server:project.git'
|
||||
urls << ['user@server:project.git', 'user@server:project.git']
|
||||
urls << ['user:@server:project.git', 'user:@server:project.git']
|
||||
urls << [':pass@server:project.git', ':pass@server:project.git']
|
||||
urls << ['user:pass@server:project.git', 'user:pass@server:project.git']
|
||||
|
||||
# return an empty string for invalid URLs
|
||||
'ssh://' | ''
|
||||
urls << ['ssh://', '']
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { expect(sanitize_url(input)).to include("repository '#{output}' not found") }
|
||||
it { expect(sanitize_url(input)).to include("repository `#{output}` not found") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Rouge::Formatters::HTMLGitlab do
|
||||
RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_management do
|
||||
describe '#format' do
|
||||
subject { described_class.format(tokens, **options) }
|
||||
|
||||
|
|
@ -67,5 +67,24 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do
|
|||
is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times
|
||||
end
|
||||
end
|
||||
|
||||
context 'when space characters and zero-width spaces are used' do
|
||||
let(:lang) { 'ruby' }
|
||||
let(:tokens) { lexer.lex(code, continue: false) }
|
||||
|
||||
let(:code) do
|
||||
<<~JS
|
||||
def\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000hello
|
||||
JS
|
||||
end
|
||||
|
||||
it 'replaces the space characters with spaces' do
|
||||
is_expected.to eq(
|
||||
"<span id=\"LC1\" class=\"line\" lang=\"ruby\">" \
|
||||
"<span class=\"k\">def</span><span class=\"err\"> </span><span class=\"n\">hello</span>" \
|
||||
"</span>"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddProjectIdForeignKeyToPackagesNpmMetadataCaches,
|
||||
feature_category: :package_registry do
|
||||
let(:table) { described_class::SOURCE_TABLE }
|
||||
let(:column) { described_class::COLUMN }
|
||||
let(:foreign_key) { -> { described_class.new.foreign_keys_for(table, column).first } }
|
||||
|
||||
it 'creates and drops the foreign key' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> do
|
||||
expect(foreign_key.call).to be(nil)
|
||||
end
|
||||
|
||||
migration.after -> do
|
||||
expect(foreign_key.call).to have_attributes(column: column.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do
|
||||
let(:migration) { described_class::MIGRATION }
|
||||
|
||||
before do
|
||||
migrate!
|
||||
end
|
||||
|
||||
describe '#up' do
|
||||
it 'schedules background jobs for each batch of projects' do
|
||||
expect(migration).to(
|
||||
have_scheduled_batched_migration(
|
||||
table_name: :project_mirror_data,
|
||||
column_name: :id,
|
||||
interval: described_class::INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
before do
|
||||
schema_migrate_down!
|
||||
end
|
||||
|
||||
it 'deletes all batched migration records' do
|
||||
expect(migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -35,11 +35,7 @@ RSpec.describe Taskable, feature_category: :team_planning do
|
|||
TaskList::Item.new('- [ ]', 'First item'),
|
||||
TaskList::Item.new('- [x]', 'Second item'),
|
||||
TaskList::Item.new('* [x]', 'First item'),
|
||||
TaskList::Item.new('* [ ]', 'Second item'),
|
||||
TaskList::Item.new('+ [ ]', 'No-break space (U+00A0)'),
|
||||
TaskList::Item.new('+ [ ]', 'Figure space (U+2007)'),
|
||||
TaskList::Item.new('+ [ ]', 'Narrow no-break space (U+202F)'),
|
||||
TaskList::Item.new('+ [ ]', 'Thin space (U+2009)')
|
||||
TaskList::Item.new('* [ ]', 'Second item')
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -242,6 +242,22 @@ RSpec.describe WebHook, feature_category: :integrations do
|
|||
expect(hook.url_variables).to eq({})
|
||||
end
|
||||
|
||||
it 'resets url variables if url is changed and url variables are appended' do
|
||||
hook.url = 'http://suspicious.example.com/{abc}/{foo}'
|
||||
hook.url_variables = hook.url_variables.merge('foo' => 'bar')
|
||||
|
||||
expect(hook).not_to be_valid
|
||||
expect(hook.url_variables).to eq({})
|
||||
end
|
||||
|
||||
it 'resets url variables if url is changed and url variables are removed' do
|
||||
hook.url = 'http://suspicious.example.com/{abc}'
|
||||
hook.url_variables = hook.url_variables.except("def")
|
||||
|
||||
expect(hook).not_to be_valid
|
||||
expect(hook.url_variables).to eq({})
|
||||
end
|
||||
|
||||
it 'does not reset url variables if both url and url variables are changed' do
|
||||
hook.url = 'http://example.com/{one}/{two}'
|
||||
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
|
||||
|
|
@ -249,6 +265,18 @@ RSpec.describe WebHook, feature_category: :integrations do
|
|||
expect(hook).to be_valid
|
||||
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
|
||||
end
|
||||
|
||||
context 'without url variables' do
|
||||
subject(:hook) { build_stubbed(:project_hook, project: project, url: 'http://example.com') }
|
||||
|
||||
it 'does not reset url variables' do
|
||||
hook.url = 'http://example.com/{one}/{two}'
|
||||
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
|
||||
|
||||
expect(hook).to be_valid
|
||||
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "only consider these branch filter strategies are valid" do
|
||||
|
|
|
|||
|
|
@ -1773,6 +1773,36 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
it 'raises error when feature is invalid' do
|
||||
expect { issue.issue_type_supports?(:unkown_feature) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context 'when issue_type_uses_work_item_types_table feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(issue_type_uses_work_item_types_table: false)
|
||||
end
|
||||
|
||||
it 'uses the issue_type column' do
|
||||
expect(issue).to receive(:issue_type).and_call_original
|
||||
expect(issue).not_to receive(:work_item_type).and_call_original
|
||||
|
||||
issue.issue_type_supports?(:assignee)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue_type_uses_work_item_types_table feature flag is enabled' do
|
||||
it 'uses the work_item_types table' do
|
||||
expect(issue).not_to receive(:issue_type).and_call_original
|
||||
expect(issue).to receive(:work_item_type).and_call_original
|
||||
|
||||
issue.issue_type_supports?(:assignee)
|
||||
end
|
||||
|
||||
context 'when the issue is not persisted' do
|
||||
it 'uses the default work item type' do
|
||||
non_persisted_issue = build(:issue)
|
||||
|
||||
expect(non_persisted_issue.issue_type_supports?(:assignee)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#supports_time_tracking?' do
|
||||
|
|
@ -1920,4 +1950,10 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#work_item_type_with_default' do
|
||||
subject { Issue.new.work_item_type_with_default }
|
||||
|
||||
it { is_expected.to eq(WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Npm::MetadataCache, type: :model, feature_category: :package_registry do
|
||||
let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache) }
|
||||
|
||||
describe 'relationships' do
|
||||
it { is_expected.to belong_to(:project).inverse_of(:npm_metadata_caches) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:file) }
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_presence_of(:size) }
|
||||
|
||||
describe '#package_name' do
|
||||
it { is_expected.to validate_presence_of(:package_name) }
|
||||
it { is_expected.to validate_uniqueness_of(:package_name).scoped_to(:project_id) }
|
||||
it { is_expected.to allow_value('my.app-11.07.2018').for(:package_name) }
|
||||
it { is_expected.to allow_value('@group-1/package').for(:package_name) }
|
||||
it { is_expected.to allow_value('@any-scope/package').for(:package_name) }
|
||||
it { is_expected.to allow_value('unscoped-package').for(:package_name) }
|
||||
it { is_expected.not_to allow_value('my(dom$$$ain)com.my-app').for(:package_name) }
|
||||
it { is_expected.not_to allow_value('@inv@lid-scope/package').for(:package_name) }
|
||||
it { is_expected.not_to allow_value('@scope/../../package').for(:package_name) }
|
||||
it { is_expected.not_to allow_value('@scope%2e%2e%fpackage').for(:package_name) }
|
||||
it { is_expected.not_to allow_value('@scope/sub/package').for(:package_name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -143,6 +143,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
|
|||
it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
|
||||
it { is_expected.to have_many(:rpm_repository_files).class_name('Packages::Rpm::RepositoryFile').inverse_of(:project).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::ProjectDistribution').dependent(:destroy) }
|
||||
it { is_expected.to have_many(:npm_metadata_caches).class_name('Packages::Npm::MetadataCache') }
|
||||
it { is_expected.to have_one(:packages_cleanup_policy).class_name('Packages::Cleanup::Policy').inverse_of(:project) }
|
||||
it { is_expected.to have_many(:pipeline_artifacts).dependent(:restrict_with_error) }
|
||||
it { is_expected.to have_many(:terraform_states).class_name('Terraform::State').inverse_of(:project) }
|
||||
|
|
|
|||
|
|
@ -727,6 +727,39 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'read_prometheus', feature_category: :metrics do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
before do
|
||||
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
|
||||
end
|
||||
|
||||
let(:policy) { :read_prometheus }
|
||||
|
||||
where(:project_visibility, :role, :allowed) do
|
||||
:public | :anonymous | false
|
||||
:public | :guest | false
|
||||
:public | :reporter | true
|
||||
:internal | :anonymous | false
|
||||
:internal | :guest | false
|
||||
:internal | :reporter | true
|
||||
:private | :anonymous | false
|
||||
:private | :guest | false
|
||||
:private | :reporter | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:current_user) { public_send(role) }
|
||||
let(:project) { public_send("#{project_visibility}_project") }
|
||||
|
||||
if params[:allowed]
|
||||
it { is_expected.to be_allowed(policy) }
|
||||
else
|
||||
it { is_expected.not_to be_allowed(policy) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'update_max_artifacts_size' do
|
||||
context 'when no user' do
|
||||
let(:current_user) { anonymous }
|
||||
|
|
@ -1002,7 +1035,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_allowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_allowed(:read_prometheus) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
it { is_expected.to be_allowed(:read_deployment) }
|
||||
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
|
||||
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
|
||||
|
|
@ -1012,7 +1045,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { anonymous }
|
||||
|
||||
it { is_expected.to be_allowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_allowed(:read_prometheus) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
it { is_expected.to be_allowed(:read_deployment) }
|
||||
it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) }
|
||||
it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) }
|
||||
|
|
@ -1038,12 +1071,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
|
||||
context 'with anonymous' do
|
||||
let(:current_user) { anonymous }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1066,7 +1101,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_allowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_allowed(:read_prometheus) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
it { is_expected.to be_allowed(:read_deployment) }
|
||||
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
|
||||
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
|
||||
|
|
@ -1076,6 +1111,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { anonymous }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1098,12 +1134,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
|
||||
context 'with anonymous' do
|
||||
let(:current_user) { anonymous }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1122,12 +1160,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
let(:current_user) { guest }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
|
||||
context 'with anonymous' do
|
||||
let(:current_user) { anonymous }
|
||||
|
||||
it { is_expected.to be_disallowed(:metrics_dashboard) }
|
||||
it { is_expected.to be_disallowed(:read_prometheus) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2068,7 +2108,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
:public | ProjectFeature::ENABLED | :anonymous | true
|
||||
:public | ProjectFeature::PRIVATE | :maintainer | true
|
||||
:public | ProjectFeature::PRIVATE | :developer | true
|
||||
:public | ProjectFeature::PRIVATE | :guest | true
|
||||
:public | ProjectFeature::PRIVATE | :guest | false
|
||||
:public | ProjectFeature::PRIVATE | :anonymous | false
|
||||
:public | ProjectFeature::DISABLED | :maintainer | false
|
||||
:public | ProjectFeature::DISABLED | :developer | false
|
||||
|
|
@ -2080,7 +2120,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
:internal | ProjectFeature::ENABLED | :anonymous | false
|
||||
:internal | ProjectFeature::PRIVATE | :maintainer | true
|
||||
:internal | ProjectFeature::PRIVATE | :developer | true
|
||||
:internal | ProjectFeature::PRIVATE | :guest | true
|
||||
:internal | ProjectFeature::PRIVATE | :guest | false
|
||||
:internal | ProjectFeature::PRIVATE | :anonymous | false
|
||||
:internal | ProjectFeature::DISABLED | :maintainer | false
|
||||
:internal | ProjectFeature::DISABLED | :developer | false
|
||||
|
|
|
|||
|
|
@ -572,6 +572,22 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
|
|||
context 'when authenticated', 'as a developer' do
|
||||
it_behaves_like 'repository compare' do
|
||||
let(:current_user) { user }
|
||||
|
||||
context 'when user does not have read access to the parent project' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let(:forked_project) { fork_project(project, current_user, repository: true, namespace: group) }
|
||||
|
||||
before do
|
||||
forked_project.add_guest(current_user)
|
||||
end
|
||||
|
||||
it 'returns 403 error' do
|
||||
get api(route, current_user), params: { from: 'improve/awesome', to: 'feature', from_project_id: forked_project.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['message']).to eq("403 Forbidden - You don't have access to this fork's parent project")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -861,6 +861,21 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
|
|||
end
|
||||
end
|
||||
|
||||
describe 'when user does not have access to target project' do
|
||||
let(:push_options) { { create: true, target: 'my-branch' } }
|
||||
let(:changes) { default_branch_changes }
|
||||
|
||||
before do
|
||||
allow(user1).to receive(:can?).with(:read_code, project).and_return(false)
|
||||
end
|
||||
|
||||
it 'records an error', :sidekiq_inline do
|
||||
service.execute
|
||||
|
||||
expect(service.errors).to eq(["User access was denied"])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when MRs are not enabled' do
|
||||
let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } }
|
||||
let(:push_options) { { create: true } }
|
||||
|
|
|
|||
|
|
@ -130,8 +130,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state,
|
|||
context 'there is userinfo' do
|
||||
before do
|
||||
project_hook.update!(
|
||||
url: 'http://{one}:{two}@example.com',
|
||||
url_variables: { 'one' => 'a', 'two' => 'b' }
|
||||
url: 'http://{foo}:{bar}@example.com',
|
||||
url_variables: { 'foo' => 'a', 'bar' => 'b' }
|
||||
)
|
||||
stub_full_request('http://example.com', method: :post)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'explore/projects/page_out_of_bounds.html.haml', feature_category: :projects do
|
||||
let(:page_limit) { 10 }
|
||||
let(:unsafe_param) { 'hacked_using_unsafe_param!' }
|
||||
|
||||
before do
|
||||
assign(:max_page_number, page_limit)
|
||||
|
||||
controller.params[:action] = 'index'
|
||||
controller.params[:host] = unsafe_param
|
||||
controller.params[:protocol] = unsafe_param
|
||||
controller.params[:sort] = 'name_asc'
|
||||
end
|
||||
|
||||
it 'removes unsafe params from the link' do
|
||||
render
|
||||
|
||||
href = "/explore/projects?page=#{page_limit}&sort=name_asc"
|
||||
button_text = format(_("Back to page %{number}"), number: page_limit)
|
||||
expect(rendered).to have_link(button_text, href: href)
|
||||
expect(rendered).not_to include(unsafe_param)
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue