Update upstream source from tag 'upstream/13.12.6+ds1'
Update to upstream version '13.12.6+ds1'
with Debian dir 492090d23c
This commit is contained in:
commit
a0e6cc2626
78 changed files with 1033 additions and 308 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -2,6 +2,37 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 13.12.6 (2021-07-01)
|
||||||
|
|
||||||
|
### Added (1 change)
|
||||||
|
|
||||||
|
- [Added omniauth_user check when verifying user cap](gitlab-org/security/gitlab@a61062501630c35820301e9f79a036219d1e3074) ([merge request](gitlab-org/security/gitlab!1502)) **GitLab Enterprise Edition**
|
||||||
|
|
||||||
|
### Security (14 changes)
|
||||||
|
|
||||||
|
- [Bump rails gem version to 6.0.3.7](gitlab-org/security/gitlab@58d27ba819867baadf535e0d8d91d0cb818dc8b6) ([merge request](gitlab-org/security/gitlab!1515))
|
||||||
|
- [Update rdoc to 6.3.1](gitlab-org/security/gitlab@ead11a6974576b0b1a974985493c75143e3bd575) ([merge request](gitlab-org/security/gitlab!1534))
|
||||||
|
- [Add sanitizing for name field](gitlab-org/security/gitlab@2c5672eae4323c2682245485b327850e68e7e5b4) ([merge request](gitlab-org/security/gitlab!1490))
|
||||||
|
- [Forbid GET requests with mutations](gitlab-org/security/gitlab@2b01d6dc310451fa3022f1865470ca004bbd4c33) ([merge request](gitlab-org/security/gitlab!1529))
|
||||||
|
- [Copy feature visibility settings to a fork](gitlab-org/security/gitlab@5ee923ba64fb34fc38f831fc206a153d8f7eae91) ([merge request](gitlab-org/security/gitlab!1523))
|
||||||
|
- [Avoid disclosing project in web IDE](gitlab-org/security/gitlab@759d1361e7f359d681c4f55ea2b6f7e1d0bb1e53) ([merge request](gitlab-org/security/gitlab!1512))
|
||||||
|
- [Add new username validation](gitlab-org/security/gitlab@e79625541d04b0d6c94614f2afc6aaeb2ef40083) ([merge request](gitlab-org/security/gitlab!1495))
|
||||||
|
- [Allow only same-origin URLs for Edit Release Cancel button](gitlab-org/security/gitlab@e5bda0a7e03978afee494616e2054b8650b61d3e) ([merge request](gitlab-org/security/gitlab!1486))
|
||||||
|
- [Update Nokogiri to 1.11.4](gitlab-org/security/gitlab@d71973da1850df059b1ec1422d50bbccace21ff2) ([merge request](gitlab-org/security/gitlab!1479))
|
||||||
|
- [Fix deploy key fallback issue in protected branch](gitlab-org/security/gitlab@0411bc45885e1122c06dbff084b48bf03d78c6a8) ([merge request](gitlab-org/security/gitlab!1478))
|
||||||
|
- [Fix XSS on audit log for feature flag actions](gitlab-org/security/gitlab@22e2f903c821e54ce6d4b4b749a009d14abc4a13) ([merge request](gitlab-org/security/gitlab!1474))
|
||||||
|
- [Sanitize input on pasteGFM](gitlab-org/security/gitlab@7dc511ebc2e77c3d22cd34ca87449f32120a5229) ([merge request](gitlab-org/security/gitlab!1453))
|
||||||
|
- [Add total http read timeout](gitlab-org/security/gitlab@37c24c82d5dfa57fad03f265e7ba92f6ef250c30) ([merge request](gitlab-org/security/gitlab!1427))
|
||||||
|
- [Fix merge request diff display issue with unsupported encoding](gitlab-org/security/gitlab@7d05892daa6aaf951b941628e2af41e17977b140) ([merge request](gitlab-org/security/gitlab!1424))
|
||||||
|
|
||||||
|
## 13.12.5 (2021-06-21)
|
||||||
|
|
||||||
|
### Fixed (3 changes)
|
||||||
|
|
||||||
|
- [Fix failing spec](gitlab-org/gitlab@7d1a9b0155195eb082f5b33ba1310deed742a7a4) ([merge request](gitlab-org/gitlab!64488))
|
||||||
|
- [Advanced Search Settings page does not load if the ES url is unreachable](gitlab-org/gitlab@80b262f0e79f02a89724ed4e3988e686f53c959c) ([merge request](gitlab-org/gitlab!64488)) **GitLab Enterprise Edition**
|
||||||
|
- [Fix Password expired error on git fetch via SSH for LDAP user](gitlab-org/gitlab@19a7d7a6d3cd43f1c7559c729532ad3b9dafb75c) ([merge request](gitlab-org/gitlab!64488))
|
||||||
|
|
||||||
## 13.12.4 (2021-06-14)
|
## 13.12.4 (2021-06-14)
|
||||||
|
|
||||||
### Fixed (3 changes)
|
### Fixed (3 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
13.12.4
|
13.12.6
|
6
Gemfile
6
Gemfile
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'rails', '~> 6.0.3.6'
|
gem 'rails', '~> 6.0.3.7'
|
||||||
|
|
||||||
gem 'bootsnap', '~> 1.4.6'
|
gem 'bootsnap', '~> 1.4.6'
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
||||||
gem 'commonmarker', '~> 0.21'
|
gem 'commonmarker', '~> 0.21'
|
||||||
gem 'kramdown', '~> 2.3.1'
|
gem 'kramdown', '~> 2.3.1'
|
||||||
gem 'RedCloth', '~> 4.3.2'
|
gem 'RedCloth', '~> 4.3.2'
|
||||||
gem 'rdoc', '~> 6.1.2'
|
gem 'gitlab-rdoc', '~> 6.3.2', require: 'rdoc' # We need this fork until rdoc releases a new version. See https://gitlab.com/gitlab-org/gitlab/-/issues/334695
|
||||||
gem 'org-ruby', '~> 0.9.12'
|
gem 'org-ruby', '~> 0.9.12'
|
||||||
gem 'creole', '~> 0.5.0'
|
gem 'creole', '~> 0.5.0'
|
||||||
gem 'wikicloth', '0.8.1'
|
gem 'wikicloth', '0.8.1'
|
||||||
|
@ -168,7 +168,7 @@ gem 'asciidoctor-kroki', '~> 0.4.0', require: false
|
||||||
gem 'rouge', '~> 3.26.0'
|
gem 'rouge', '~> 3.26.0'
|
||||||
gem 'truncato', '~> 0.7.11'
|
gem 'truncato', '~> 0.7.11'
|
||||||
gem 'bootstrap_form', '~> 4.2.0'
|
gem 'bootstrap_form', '~> 4.2.0'
|
||||||
gem 'nokogiri', '~> 1.11.1'
|
gem 'nokogiri', '~> 1.11.4'
|
||||||
gem 'escape_utils', '~> 1.1'
|
gem 'escape_utils', '~> 1.1'
|
||||||
|
|
||||||
# Calendar rendering
|
# Calendar rendering
|
||||||
|
|
110
Gemfile.lock
110
Gemfile.lock
|
@ -12,59 +12,59 @@ GEM
|
||||||
abstract_type (0.0.7)
|
abstract_type (0.0.7)
|
||||||
acme-client (2.0.6)
|
acme-client (2.0.6)
|
||||||
faraday (>= 0.17, < 2.0.0)
|
faraday (>= 0.17, < 2.0.0)
|
||||||
actioncable (6.0.3.6)
|
actioncable (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.0.3.6)
|
actionmailbox (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
activejob (= 6.0.3.6)
|
activejob (= 6.0.3.7)
|
||||||
activerecord (= 6.0.3.6)
|
activerecord (= 6.0.3.7)
|
||||||
activestorage (= 6.0.3.6)
|
activestorage (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.0.3.6)
|
actionmailer (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
actionview (= 6.0.3.6)
|
actionview (= 6.0.3.7)
|
||||||
activejob (= 6.0.3.6)
|
activejob (= 6.0.3.7)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.0.3.6)
|
actionpack (6.0.3.7)
|
||||||
actionview (= 6.0.3.6)
|
actionview (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
rack (~> 2.0, >= 2.0.8)
|
rack (~> 2.0, >= 2.0.8)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.0.3.6)
|
actiontext (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
activerecord (= 6.0.3.6)
|
activerecord (= 6.0.3.7)
|
||||||
activestorage (= 6.0.3.6)
|
activestorage (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.0.3.6)
|
actionview (6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (6.0.3.6)
|
activejob (6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.0.3.6)
|
activemodel (6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
activerecord (6.0.3.6)
|
activerecord (6.0.3.7)
|
||||||
activemodel (= 6.0.3.6)
|
activemodel (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
activerecord-explain-analyze (0.1.0)
|
activerecord-explain-analyze (0.1.0)
|
||||||
activerecord (>= 4)
|
activerecord (>= 4)
|
||||||
pg
|
pg
|
||||||
activestorage (6.0.3.6)
|
activestorage (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
activejob (= 6.0.3.6)
|
activejob (= 6.0.3.7)
|
||||||
activerecord (= 6.0.3.6)
|
activerecord (= 6.0.3.7)
|
||||||
marcel (~> 1.0.0)
|
marcel (~> 1.0.0)
|
||||||
activesupport (6.0.3.6)
|
activesupport (6.0.3.7)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
|
@ -483,6 +483,7 @@ GEM
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.7)
|
||||||
omniauth (~> 1.9)
|
omniauth (~> 1.9)
|
||||||
openid_connect (~> 1.2)
|
openid_connect (~> 1.2)
|
||||||
|
gitlab-rdoc (6.3.2)
|
||||||
gitlab-sidekiq-fetcher (0.5.6)
|
gitlab-sidekiq-fetcher (0.5.6)
|
||||||
sidekiq (~> 5)
|
sidekiq (~> 5)
|
||||||
gitlab-styles (6.2.0)
|
gitlab-styles (6.2.0)
|
||||||
|
@ -781,7 +782,7 @@ GEM
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nio4r (2.5.4)
|
nio4r (2.5.4)
|
||||||
no_proxy_fix (0.1.2)
|
no_proxy_fix (0.1.2)
|
||||||
nokogiri (1.11.3)
|
nokogiri (1.11.4)
|
||||||
mini_portile2 (~> 2.5.0)
|
mini_portile2 (~> 2.5.0)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogumbo (2.0.2)
|
nokogumbo (2.0.2)
|
||||||
|
@ -962,20 +963,20 @@ GEM
|
||||||
rack-test (1.1.0)
|
rack-test (1.1.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-timeout (0.5.2)
|
rack-timeout (0.5.2)
|
||||||
rails (6.0.3.6)
|
rails (6.0.3.7)
|
||||||
actioncable (= 6.0.3.6)
|
actioncable (= 6.0.3.7)
|
||||||
actionmailbox (= 6.0.3.6)
|
actionmailbox (= 6.0.3.7)
|
||||||
actionmailer (= 6.0.3.6)
|
actionmailer (= 6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
actiontext (= 6.0.3.6)
|
actiontext (= 6.0.3.7)
|
||||||
actionview (= 6.0.3.6)
|
actionview (= 6.0.3.7)
|
||||||
activejob (= 6.0.3.6)
|
activejob (= 6.0.3.7)
|
||||||
activemodel (= 6.0.3.6)
|
activemodel (= 6.0.3.7)
|
||||||
activerecord (= 6.0.3.6)
|
activerecord (= 6.0.3.7)
|
||||||
activestorage (= 6.0.3.6)
|
activestorage (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
bundler (>= 1.3.0)
|
bundler (>= 1.3.0)
|
||||||
railties (= 6.0.3.6)
|
railties (= 6.0.3.7)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
|
@ -989,9 +990,9 @@ GEM
|
||||||
rails-i18n (6.0.0)
|
rails-i18n (6.0.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 7)
|
||||||
railties (6.0.3.6)
|
railties (6.0.3.7)
|
||||||
actionpack (= 6.0.3.6)
|
actionpack (= 6.0.3.7)
|
||||||
activesupport (= 6.0.3.6)
|
activesupport (= 6.0.3.7)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.20.3, < 2.0)
|
thor (>= 0.20.3, < 2.0)
|
||||||
|
@ -1008,7 +1009,6 @@ GEM
|
||||||
msgpack (>= 0.4.3)
|
msgpack (>= 0.4.3)
|
||||||
optimist (>= 3.0.0)
|
optimist (>= 3.0.0)
|
||||||
rchardet (1.8.0)
|
rchardet (1.8.0)
|
||||||
rdoc (6.1.2)
|
|
||||||
re2 (1.2.0)
|
re2 (1.2.0)
|
||||||
recaptcha (4.13.1)
|
recaptcha (4.13.1)
|
||||||
json
|
json
|
||||||
|
@ -1485,6 +1485,7 @@ DEPENDENCIES
|
||||||
gitlab-markup (~> 1.7.1)
|
gitlab-markup (~> 1.7.1)
|
||||||
gitlab-net-dns (~> 0.9.1)
|
gitlab-net-dns (~> 0.9.1)
|
||||||
gitlab-omniauth-openid-connect (~> 0.4.0)
|
gitlab-omniauth-openid-connect (~> 0.4.0)
|
||||||
|
gitlab-rdoc (~> 6.3.2)
|
||||||
gitlab-sidekiq-fetcher (= 0.5.6)
|
gitlab-sidekiq-fetcher (= 0.5.6)
|
||||||
gitlab-styles (~> 6.2.0)
|
gitlab-styles (~> 6.2.0)
|
||||||
gitlab_chronic_duration (~> 0.10.6.2)
|
gitlab_chronic_duration (~> 0.10.6.2)
|
||||||
|
@ -1544,7 +1545,7 @@ DEPENDENCIES
|
||||||
net-ldap (~> 0.16.3)
|
net-ldap (~> 0.16.3)
|
||||||
net-ntp
|
net-ntp
|
||||||
net-ssh (~> 6.0)
|
net-ssh (~> 6.0)
|
||||||
nokogiri (~> 1.11.1)
|
nokogiri (~> 1.11.4)
|
||||||
oauth2 (~> 1.4)
|
oauth2 (~> 1.4)
|
||||||
octokit (~> 4.15)
|
octokit (~> 4.15)
|
||||||
ohai (~> 16.10)
|
ohai (~> 16.10)
|
||||||
|
@ -1587,14 +1588,13 @@ DEPENDENCIES
|
||||||
rack-oauth2 (~> 1.16.0)
|
rack-oauth2 (~> 1.16.0)
|
||||||
rack-proxy (~> 0.6.0)
|
rack-proxy (~> 0.6.0)
|
||||||
rack-timeout (~> 0.5.1)
|
rack-timeout (~> 0.5.1)
|
||||||
rails (~> 6.0.3.6)
|
rails (~> 6.0.3.7)
|
||||||
rails-controller-testing
|
rails-controller-testing
|
||||||
rails-i18n (~> 6.0)
|
rails-i18n (~> 6.0)
|
||||||
rainbow (~> 3.0)
|
rainbow (~> 3.0)
|
||||||
raindrops (~> 0.18)
|
raindrops (~> 0.18)
|
||||||
rblineprof (~> 0.3.6)
|
rblineprof (~> 0.3.6)
|
||||||
rbtrace (~> 0.4)
|
rbtrace (~> 0.4)
|
||||||
rdoc (~> 6.1.2)
|
|
||||||
re2 (~> 1.2.0)
|
re2 (~> 1.2.0)
|
||||||
recaptcha (~> 4.11)
|
recaptcha (~> 4.11)
|
||||||
redis (~> 4.0)
|
redis (~> 4.0)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
13.12.4
|
13.12.6
|
|
@ -1,4 +1,5 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import { sanitize } from '~/lib/dompurify';
|
||||||
import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
|
import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
export class CopyAsGFM {
|
export class CopyAsGFM {
|
||||||
|
@ -69,7 +70,7 @@ export class CopyAsGFM {
|
||||||
} else {
|
} else {
|
||||||
// Due to the async copy call we are not able to produce gfm so we transform the cached HTML
|
// Due to the async copy call we are not able to produce gfm so we transform the cached HTML
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.innerHTML = gfmHtml;
|
div.innerHTML = sanitize(gfmHtml);
|
||||||
CopyAsGFM.nodeToGFM(div)
|
CopyAsGFM.nodeToGFM(div)
|
||||||
.then((transformedGfm) => {
|
.then((transformedGfm) => {
|
||||||
CopyAsGFM.insertPastedText(e.target, text, transformedGfm);
|
CopyAsGFM.insertPastedText(e.target, text, transformedGfm);
|
||||||
|
|
|
@ -535,3 +535,27 @@ export function getURLOrigin(url) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the given `url` resolves to the same origin the page is served
|
||||||
|
* from; otherwise, returns `false`.
|
||||||
|
*
|
||||||
|
* The `url` may be absolute or relative.
|
||||||
|
*
|
||||||
|
* @param {string} url The URL to check.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isSameOriginUrl(url) {
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { origin } = window.location;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(url, origin).origin === origin;
|
||||||
|
} catch {
|
||||||
|
// Invalid URLs cannot have the same origin
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { GlButton, GlFormInput, GlFormGroup, GlSprintf } from '@gitlab/ui';
|
import { GlButton, GlFormInput, GlFormGroup, GlSprintf } from '@gitlab/ui';
|
||||||
import { mapState, mapActions, mapGetters } from 'vuex';
|
import { mapState, mapActions, mapGetters } from 'vuex';
|
||||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||||
|
import { isSameOriginUrl } from '~/lib/utils/url_utility';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
|
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
|
||||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||||
|
@ -65,7 +66,13 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cancelPath() {
|
cancelPath() {
|
||||||
return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
|
const backUrl = getParameterByName(BACK_URL_PARAM);
|
||||||
|
|
||||||
|
if (isSameOriginUrl(backUrl)) {
|
||||||
|
return backUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.releasesPagePath;
|
||||||
},
|
},
|
||||||
saveButtonLabel() {
|
saveButtonLabel() {
|
||||||
return this.isExistingRelease ? __('Save changes') : __('Create release');
|
return this.isExistingRelease ? __('Save changes') : __('Create release');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="nothing-here-block">
|
<div class="nothing-here-block">
|
||||||
{{ __('This diff was suppressed by a .gitattributes entry.') }}
|
{{ __("File suppressed by a .gitattributes entry or the file's encoding is unsupported.") }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -208,7 +208,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||||
|
|
||||||
params[:application_setting][:import_sources]&.delete("")
|
params[:application_setting][:import_sources]&.delete("")
|
||||||
params[:application_setting][:restricted_visibility_levels]&.delete("")
|
params[:application_setting][:restricted_visibility_levels]&.delete("")
|
||||||
params[:application_setting][:required_instance_ci_template] = nil if params[:application_setting][:required_instance_ci_template].blank?
|
|
||||||
|
if params[:application_setting].key?(:required_instance_ci_template)
|
||||||
|
params[:application_setting][:required_instance_ci_template] = nil if params[:application_setting][:required_instance_ci_template].empty?
|
||||||
|
end
|
||||||
|
|
||||||
remove_blank_params_for!(:elasticsearch_aws_secret_access_key, :eks_secret_access_key)
|
remove_blank_params_for!(:elasticsearch_aws_secret_access_key, :eks_secret_access_key)
|
||||||
|
|
||||||
|
@ -217,9 +220,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||||
params.delete(:domain_denylist_raw) if params[:domain_denylist]
|
params.delete(:domain_denylist_raw) if params[:domain_denylist]
|
||||||
params.delete(:domain_allowlist_raw) if params[:domain_allowlist]
|
params.delete(:domain_allowlist_raw) if params[:domain_allowlist]
|
||||||
|
|
||||||
params.require(:application_setting).permit(
|
params[:application_setting].permit(visible_application_setting_attributes)
|
||||||
visible_application_setting_attributes
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def recheck_user_consent?
|
def recheck_user_consent?
|
||||||
|
|
|
@ -108,7 +108,7 @@ module MembershipActions
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize]
|
redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize.to_sym]
|
||||||
redirect_to redirect_path, notice: notice
|
redirect_to redirect_path, notice: notice
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,16 @@ class GraphqlController < ApplicationController
|
||||||
# around in GraphiQL.
|
# around in GraphiQL.
|
||||||
protect_from_forgery with: :null_session, only: :execute
|
protect_from_forgery with: :null_session, only: :execute
|
||||||
|
|
||||||
before_action :authorize_access_api!
|
# must come first: current_user is set up here
|
||||||
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
|
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
|
||||||
|
|
||||||
|
before_action :authorize_access_api!
|
||||||
before_action :set_user_last_activity
|
before_action :set_user_last_activity
|
||||||
before_action :track_vs_code_usage
|
before_action :track_vs_code_usage
|
||||||
before_action :disable_query_limiting
|
before_action :disable_query_limiting
|
||||||
|
|
||||||
|
before_action :disallow_mutations_for_get
|
||||||
|
|
||||||
# Since we deactivate authentication from the main ApplicationController and
|
# Since we deactivate authentication from the main ApplicationController and
|
||||||
# defer it to :authorize_access_api!, we need to override the bypass session
|
# defer it to :authorize_access_api!, we need to override the bypass session
|
||||||
# callback execution order here
|
# callback execution order here
|
||||||
|
@ -62,6 +66,25 @@ class GraphqlController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def disallow_mutations_for_get
|
||||||
|
return unless request.get? || request.head?
|
||||||
|
return unless any_mutating_query?
|
||||||
|
|
||||||
|
raise ::Gitlab::Graphql::Errors::ArgumentError, "Mutations are forbidden in #{request.request_method} requests"
|
||||||
|
end
|
||||||
|
|
||||||
|
def any_mutating_query?
|
||||||
|
if multiplex?
|
||||||
|
multiplex_queries.any? { |q| mutation?(q[:query], q[:operation_name]) }
|
||||||
|
else
|
||||||
|
mutation?(query)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mutation?(query_string, operation_name = params[:operationName])
|
||||||
|
::GraphQL::Query.new(GitlabSchema, query_string, operation_name: operation_name).mutation?
|
||||||
|
end
|
||||||
|
|
||||||
# Tests may mark some GraphQL queries as exempt from SQL query limits
|
# Tests may mark some GraphQL queries as exempt from SQL query limits
|
||||||
def disable_query_limiting
|
def disable_query_limiting
|
||||||
return unless Gitlab::QueryLimiting.enabled_for_env?
|
return unless Gitlab::QueryLimiting.enabled_for_env?
|
||||||
|
@ -130,7 +153,9 @@ class GraphqlController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_access_api!
|
def authorize_access_api!
|
||||||
access_denied!("API not accessible for user.") unless can?(current_user, :access_api)
|
return if can?(current_user, :access_api)
|
||||||
|
|
||||||
|
render_error('API not accessible for user', status: :forbidden)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overridden from the ApplicationController to make the response look like
|
# Overridden from the ApplicationController to make the response look like
|
||||||
|
|
|
@ -7,6 +7,8 @@ class IdeController < ApplicationController
|
||||||
include StaticObjectExternalStorageCSP
|
include StaticObjectExternalStorageCSP
|
||||||
include Gitlab::Utils::StrongMemoize
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
before_action :authorize_read_project!
|
||||||
|
|
||||||
before_action do
|
before_action do
|
||||||
push_frontend_feature_flag(:build_service_proxy)
|
push_frontend_feature_flag(:build_service_proxy)
|
||||||
push_frontend_feature_flag(:schema_linting)
|
push_frontend_feature_flag(:schema_linting)
|
||||||
|
@ -22,6 +24,10 @@ class IdeController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def authorize_read_project!
|
||||||
|
render_404 unless can?(current_user, :read_project, project)
|
||||||
|
end
|
||||||
|
|
||||||
def define_index_vars
|
def define_index_vars
|
||||||
return unless project
|
return unless project
|
||||||
|
|
||||||
|
|
33
app/graphql/mutations/echo.rb
Normal file
33
app/graphql/mutations/echo.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Mutations
|
||||||
|
class Echo < BaseMutation
|
||||||
|
graphql_name 'EchoCreate'
|
||||||
|
description <<~DOC
|
||||||
|
A mutation that does not perform any changes.
|
||||||
|
|
||||||
|
This is expected to be used for testing of endpoints, to verify
|
||||||
|
that a user has mutation access.
|
||||||
|
DOC
|
||||||
|
|
||||||
|
argument :errors,
|
||||||
|
type: [::GraphQL::STRING_TYPE],
|
||||||
|
required: false,
|
||||||
|
description: 'Errors to return to the user.'
|
||||||
|
|
||||||
|
argument :messages,
|
||||||
|
type: [::GraphQL::STRING_TYPE],
|
||||||
|
as: :echoes,
|
||||||
|
required: false,
|
||||||
|
description: 'Messages to return to the user.'
|
||||||
|
|
||||||
|
field :echoes,
|
||||||
|
type: [::GraphQL::STRING_TYPE],
|
||||||
|
null: true,
|
||||||
|
description: 'Messages returned to the user.'
|
||||||
|
|
||||||
|
def resolve(**args)
|
||||||
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -101,6 +101,7 @@ module Types
|
||||||
mount_mutation Mutations::Ci::Job::Retry
|
mount_mutation Mutations::Ci::Job::Retry
|
||||||
mount_mutation Mutations::Namespace::PackageSettings::Update
|
mount_mutation Mutations::Namespace::PackageSettings::Update
|
||||||
mount_mutation Mutations::UserCallouts::Create
|
mount_mutation Mutations::UserCallouts::Create
|
||||||
|
mount_mutation Mutations::Echo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ class AuditEvent < ApplicationRecord
|
||||||
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
|
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
|
||||||
|
|
||||||
after_initialize :initialize_details
|
after_initialize :initialize_details
|
||||||
|
|
||||||
|
before_validation :sanitize_message
|
||||||
|
|
||||||
# Note: The intention is to remove this once refactoring of AuditEvent
|
# Note: The intention is to remove this once refactoring of AuditEvent
|
||||||
# has proceeded further.
|
# has proceeded further.
|
||||||
#
|
#
|
||||||
|
@ -83,6 +86,14 @@ class AuditEvent < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def sanitize_message
|
||||||
|
message = details[:custom_message]
|
||||||
|
|
||||||
|
return unless message
|
||||||
|
|
||||||
|
self.details = details.merge(custom_message: Sanitize.clean(message))
|
||||||
|
end
|
||||||
|
|
||||||
def default_author_value
|
def default_author_value
|
||||||
::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name]))
|
::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name]))
|
||||||
end
|
end
|
||||||
|
|
|
@ -173,6 +173,7 @@ module Integrations
|
||||||
|
|
||||||
query_params[:os_authType] = 'basic'
|
query_params[:os_authType] = 'basic'
|
||||||
params[:basic_auth] = basic_auth
|
params[:basic_auth] = basic_auth
|
||||||
|
params[:use_read_total_timeout] = true
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,11 @@ class DroneCiService < CiService
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_reactive_cache(sha, ref)
|
def calculate_reactive_cache(sha, ref)
|
||||||
response = Gitlab::HTTP.try_get(commit_status_path(sha, ref),
|
response = Gitlab::HTTP.try_get(
|
||||||
|
commit_status_path(sha, ref),
|
||||||
verify: enable_ssl_verification,
|
verify: enable_ssl_verification,
|
||||||
extra_log_info: { project_id: project_id })
|
extra_log_info: { project_id: project_id },
|
||||||
|
use_read_total_timeout: true)
|
||||||
|
|
||||||
status =
|
status =
|
||||||
if response && response.code == 200 && response['status']
|
if response && response.code == 200 && response['status']
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ExternalWikiService < Integration
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(_data)
|
def execute(_data)
|
||||||
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
|
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true, use_read_total_timeout: true)
|
||||||
response.body if response.code == 200
|
response.body if response.code == 200
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
nil
|
nil
|
||||||
|
|
|
@ -106,7 +106,7 @@ class IssueTrackerService < Integration
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
begin
|
begin
|
||||||
response = Gitlab::HTTP.head(self.project_url, verify: true)
|
response = Gitlab::HTTP.head(self.project_url, verify: true, use_read_total_timeout: true)
|
||||||
|
|
||||||
if response
|
if response
|
||||||
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
|
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
|
||||||
|
|
|
@ -56,7 +56,7 @@ class MockCiService < CiService
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
def commit_status(sha, ref)
|
def commit_status(sha, ref)
|
||||||
response = Gitlab::HTTP.get(commit_status_path(sha), verify: false)
|
response = Gitlab::HTTP.get(commit_status_path(sha), verify: false, use_read_total_timeout: true)
|
||||||
read_commit_status(response)
|
read_commit_status(response)
|
||||||
rescue Errno::ECONNREFUSED
|
rescue Errno::ECONNREFUSED
|
||||||
:error
|
:error
|
||||||
|
|
|
@ -17,7 +17,7 @@ module SlackMattermost
|
||||||
class HTTPClient
|
class HTTPClient
|
||||||
def self.post(uri, params = {})
|
def self.post(uri, params = {})
|
||||||
params.delete(:http_options) # these are internal to the client and we do not want them
|
params.delete(:http_options) # these are internal to the client and we do not want them
|
||||||
Gitlab::HTTP.post(uri, body: params)
|
Gitlab::HTTP.post(uri, body: params, use_read_total_timeout: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -169,7 +169,7 @@ class TeamcityService < CiService
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_path(path)
|
def get_path(path)
|
||||||
Gitlab::HTTP.try_get(build_url(path), verify: false, basic_auth: basic_auth, extra_log_info: { project_id: project_id })
|
Gitlab::HTTP.try_get(build_url(path), verify: false, basic_auth: basic_auth, extra_log_info: { project_id: project_id }, use_read_total_timeout: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_to_build_queue(data, branch)
|
def post_to_build_queue(data, branch)
|
||||||
|
@ -179,7 +179,8 @@ class TeamcityService < CiService
|
||||||
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
|
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
|
||||||
'</build>',
|
'</build>',
|
||||||
headers: { 'Content-type' => 'application/xml' },
|
headers: { 'Content-type' => 'application/xml' },
|
||||||
basic_auth: basic_auth
|
basic_auth: basic_auth,
|
||||||
|
use_read_total_timeout: true
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,8 @@ class UnifyCircuitService < ChatNotificationService
|
||||||
response = Gitlab::HTTP.post(webhook, body: {
|
response = Gitlab::HTTP.post(webhook, body: {
|
||||||
subject: message.project_name,
|
subject: message.project_name,
|
||||||
text: message.summary,
|
text: message.summary,
|
||||||
markdown: true
|
markdown: true,
|
||||||
|
use_read_total_timeout: true
|
||||||
}.to_json)
|
}.to_json)
|
||||||
|
|
||||||
response if response.success?
|
response if response.success?
|
||||||
|
|
|
@ -43,7 +43,7 @@ class WebexTeamsService < ChatNotificationService
|
||||||
|
|
||||||
def notify(message, opts)
|
def notify(message, opts)
|
||||||
header = { 'Content-Type' => 'application/json' }
|
header = { 'Content-Type' => 'application/json' }
|
||||||
response = Gitlab::HTTP.post(webhook, headers: header, body: { markdown: message.summary }.to_json)
|
response = Gitlab::HTTP.post(webhook, headers: header, body: { markdown: message.summary }.to_json, use_read_total_timeout: true)
|
||||||
|
|
||||||
response if response.success?
|
response if response.success?
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ProtectedBranch::PushAccessLevel < ApplicationRecord
|
||||||
|
|
||||||
def check_access(user)
|
def check_access(user)
|
||||||
if user && deploy_key.present?
|
if user && deploy_key.present?
|
||||||
return true if user.can?(:read_project, project) && enabled_deploy_key_for_user?(deploy_key, user)
|
return user.can?(:read_project, project) && enabled_deploy_key_for_user?(deploy_key, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
|
|
|
@ -238,6 +238,7 @@ class User < ApplicationRecord
|
||||||
validate :owns_commit_email, if: :commit_email_changed?
|
validate :owns_commit_email, if: :commit_email_changed?
|
||||||
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
||||||
validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
|
validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
|
||||||
|
validate :check_username_format, if: :username_changed?
|
||||||
|
|
||||||
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
|
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
|
||||||
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
|
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
|
||||||
|
@ -1255,12 +1256,23 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize_attrs
|
def sanitize_attrs
|
||||||
|
sanitize_links
|
||||||
|
sanitize_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize_links
|
||||||
%i[skype linkedin twitter].each do |attr|
|
%i[skype linkedin twitter].each do |attr|
|
||||||
value = self[attr]
|
value = self[attr]
|
||||||
self[attr] = Sanitize.clean(value) if value.present?
|
self[attr] = Sanitize.clean(value) if value.present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sanitize_name
|
||||||
|
return unless self.name
|
||||||
|
|
||||||
|
self.name = self.name.gsub(%r{</?[^>]*>}, '')
|
||||||
|
end
|
||||||
|
|
||||||
def set_notification_email
|
def set_notification_email
|
||||||
if notification_email.blank? || all_emails.exclude?(notification_email)
|
if notification_email.blank? || all_emails.exclude?(notification_email)
|
||||||
self.notification_email = email
|
self.notification_email = email
|
||||||
|
@ -1873,6 +1885,12 @@ class User < ApplicationRecord
|
||||||
!!(password_expires_at && password_expires_at < Time.current)
|
!!(password_expires_at && password_expires_at < Time.current)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def password_expired_if_applicable?
|
||||||
|
return false unless allow_password_authentication?
|
||||||
|
|
||||||
|
password_expired?
|
||||||
|
end
|
||||||
|
|
||||||
def can_be_deactivated?
|
def can_be_deactivated?
|
||||||
active? && no_recent_activity? && !internal?
|
active? && no_recent_activity? && !internal?
|
||||||
end
|
end
|
||||||
|
@ -2066,6 +2084,12 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_username_format
|
||||||
|
return if username.blank? || Mime::EXTENSION_LOOKUP.keys.none? { |type| username.end_with?(type) }
|
||||||
|
|
||||||
|
errors.add(:username, _('ending with MIME type format is not allowed.'))
|
||||||
|
end
|
||||||
|
|
||||||
def groups_with_developer_maintainer_project_access
|
def groups_with_developer_maintainer_project_access
|
||||||
project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS]
|
project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS]
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ module PolicyActor
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_expired?
|
def password_expired_if_applicable?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ class GlobalPolicy < BasePolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
condition(:password_expired, scope: :user) do
|
condition(:password_expired, scope: :user) do
|
||||||
@user&.password_expired?
|
@user&.password_expired_if_applicable?
|
||||||
end
|
end
|
||||||
|
|
||||||
condition(:project_bot, scope: :user) { @user&.project_bot? }
|
condition(:project_bot, scope: :user) { @user&.project_bot? }
|
||||||
|
|
|
@ -49,9 +49,9 @@ module FeatureFlags
|
||||||
end
|
end
|
||||||
|
|
||||||
def created_scope_message(scope)
|
def created_scope_message(scope)
|
||||||
"Created rule <strong>#{scope.environment_scope}</strong> "\
|
"Created rule #{scope.environment_scope} "\
|
||||||
"and set it as <strong>#{scope.active ? "active" : "inactive"}</strong> "\
|
"and set it as #{scope.active ? "active" : "inactive"} "\
|
||||||
"with strategies <strong>#{scope.strategies}</strong>."
|
"with strategies #{scope.strategies}."
|
||||||
end
|
end
|
||||||
|
|
||||||
def feature_flag_by_name
|
def feature_flag_by_name
|
||||||
|
|
|
@ -22,8 +22,7 @@ module FeatureFlags
|
||||||
private
|
private
|
||||||
|
|
||||||
def audit_message(feature_flag)
|
def audit_message(feature_flag)
|
||||||
message_parts = ["Created feature flag <strong>#{feature_flag.name}</strong>",
|
message_parts = ["Created feature flag #{feature_flag.name} with description \"#{feature_flag.description}\"."]
|
||||||
"with description <strong>\"#{feature_flag.description}\"</strong>."]
|
|
||||||
|
|
||||||
message_parts += feature_flag.scopes.map do |scope|
|
message_parts += feature_flag.scopes.map do |scope|
|
||||||
created_scope_message(scope)
|
created_scope_message(scope)
|
||||||
|
|
|
@ -23,7 +23,7 @@ module FeatureFlags
|
||||||
end
|
end
|
||||||
|
|
||||||
def audit_message(feature_flag)
|
def audit_message(feature_flag)
|
||||||
"Deleted feature flag <strong>#{feature_flag.name}</strong>."
|
"Deleted feature flag #{feature_flag.name}."
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_destroy?(feature_flag)
|
def can_destroy?(feature_flag)
|
||||||
|
|
|
@ -45,14 +45,14 @@ module FeatureFlags
|
||||||
|
|
||||||
return if changes.empty?
|
return if changes.empty?
|
||||||
|
|
||||||
"Updated feature flag <strong>#{feature_flag.name}</strong>. " + changes.join(" ")
|
"Updated feature flag #{feature_flag.name}. " + changes.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def changed_attributes_messages(feature_flag)
|
def changed_attributes_messages(feature_flag)
|
||||||
feature_flag.changes.slice(*AUDITABLE_ATTRIBUTES).map do |attribute_name, changes|
|
feature_flag.changes.slice(*AUDITABLE_ATTRIBUTES).map do |attribute_name, changes|
|
||||||
"Updated #{attribute_name} "\
|
"Updated #{attribute_name} "\
|
||||||
"from <strong>\"#{changes.first}\"</strong> to "\
|
"from \"#{changes.first}\" to "\
|
||||||
"<strong>\"#{changes.second}\"</strong>."
|
"\"#{changes.second}\"."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -69,17 +69,17 @@ module FeatureFlags
|
||||||
end
|
end
|
||||||
|
|
||||||
def deleted_scope_message(scope)
|
def deleted_scope_message(scope)
|
||||||
"Deleted rule <strong>#{scope.environment_scope}</strong>."
|
"Deleted rule #{scope.environment_scope}."
|
||||||
end
|
end
|
||||||
|
|
||||||
def updated_scope_message(scope)
|
def updated_scope_message(scope)
|
||||||
changes = scope.changes.slice(*AUDITABLE_SCOPE_ATTRIBUTES_HUMAN_NAMES.keys)
|
changes = scope.changes.slice(*AUDITABLE_SCOPE_ATTRIBUTES_HUMAN_NAMES.keys)
|
||||||
return if changes.empty?
|
return if changes.empty?
|
||||||
|
|
||||||
message = "Updated rule <strong>#{scope.environment_scope}</strong> "
|
message = "Updated rule #{scope.environment_scope} "
|
||||||
message += changes.map do |attribute_name, change|
|
message += changes.map do |attribute_name, change|
|
||||||
name = AUDITABLE_SCOPE_ATTRIBUTES_HUMAN_NAMES[attribute_name]
|
name = AUDITABLE_SCOPE_ATTRIBUTES_HUMAN_NAMES[attribute_name]
|
||||||
"#{name} from <strong>#{change.first}</strong> to <strong>#{change.second}</strong>"
|
"#{name} from #{change.first} to #{change.second}"
|
||||||
end.join(' ')
|
end.join(' ')
|
||||||
|
|
||||||
message + '.'
|
message + '.'
|
||||||
|
|
|
@ -34,8 +34,9 @@ module Projects
|
||||||
new_project = CreateService.new(current_user, new_fork_params).execute
|
new_project = CreateService.new(current_user, new_fork_params).execute
|
||||||
return new_project unless new_project.persisted?
|
return new_project unless new_project.persisted?
|
||||||
|
|
||||||
builds_access_level = @project.project_feature.builds_access_level
|
new_project.project_feature.update!(
|
||||||
new_project.project_feature.update(builds_access_level: builds_access_level)
|
@project.project_feature.slice(ProjectFeature::FEATURES.map { |f| "#{f}_access_level" })
|
||||||
|
)
|
||||||
|
|
||||||
new_project
|
new_project
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,7 @@ class WebHookService
|
||||||
@hook_name = hook_name.to_s
|
@hook_name = hook_name.to_s
|
||||||
@request_options = {
|
@request_options = {
|
||||||
timeout: Gitlab.config.gitlab.webhook_timeout,
|
timeout: Gitlab.config.gitlab.webhook_timeout,
|
||||||
|
use_read_total_timeout: true,
|
||||||
allow_local_requests: hook.allow_local_requests?
|
allow_local_requests: hook.allow_local_requests?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -67,7 +68,7 @@ class WebHookService
|
||||||
{
|
{
|
||||||
status: :success,
|
status: :success,
|
||||||
http_status: response.code,
|
http_status: response.code,
|
||||||
message: response.to_s
|
message: response.body
|
||||||
}
|
}
|
||||||
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
||||||
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
|
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- add_page_specific_style 'page_bundles/import'
|
- add_page_specific_style 'page_bundles/import'
|
||||||
- provider = local_assigns.fetch(:provider)
|
- provider = local_assigns.fetch(:provider).to_sym
|
||||||
- extra_data = local_assigns.fetch(:extra_data, {})
|
- extra_data = local_assigns.fetch(:extra_data, {})
|
||||||
- filterable = local_assigns.fetch(:filterable, true)
|
- filterable = local_assigns.fetch(:filterable, true)
|
||||||
- paginatable = local_assigns.fetch(:paginatable, false)
|
- paginatable = local_assigns.fetch(:paginatable, false)
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
.nothing-here-block
|
.nothing-here-block
|
||||||
= _("This diff was suppressed by a .gitattributes entry.")
|
= _("File suppressed by a .gitattributes entry or the file's encoding is unsupported.")
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
- labels = issuable.labels
|
- labels = issuable.labels
|
||||||
- assignees = issuable.assignees
|
- assignees = issuable.assignees
|
||||||
- base_url_args = [project]
|
- base_url_args = [project]
|
||||||
- issuable_type_args = base_url_args + [issuable.class.table_name]
|
- issuable_type_args = base_url_args + [issuable.class.table_name.to_sym]
|
||||||
- issuable_url_args = base_url_args + [issuable]
|
- issuable_url_args = base_url_args + [issuable]
|
||||||
|
|
||||||
%li.issuable-row
|
%li.issuable-row
|
||||||
|
|
|
@ -1959,6 +1959,31 @@ Input type: `DismissVulnerabilityInput`
|
||||||
| <a id="mutationdismissvulnerabilityerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
| <a id="mutationdismissvulnerabilityerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||||
| <a id="mutationdismissvulnerabilityvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | The vulnerability after dismissal. |
|
| <a id="mutationdismissvulnerabilityvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | The vulnerability after dismissal. |
|
||||||
|
|
||||||
|
### `Mutation.echoCreate`
|
||||||
|
|
||||||
|
A mutation that does not perform any changes.
|
||||||
|
|
||||||
|
This is expected to be used for testing of endpoints, to verify
|
||||||
|
that a user has mutation access.
|
||||||
|
|
||||||
|
Input type: `EchoCreateInput`
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="mutationechocreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||||
|
| <a id="mutationechocreateerrors"></a>`errors` | [`[String!]`](#string) | Errors to return to the user. |
|
||||||
|
| <a id="mutationechocreatemessages"></a>`messages` | [`[String!]`](#string) | Messages to return to the user. |
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="mutationechocreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||||
|
| <a id="mutationechocreateechoes"></a>`echoes` | [`[String!]`](#string) | Messages returned to the user. |
|
||||||
|
| <a id="mutationechocreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||||
|
|
||||||
### `Mutation.environmentsCanaryIngressUpdate`
|
### `Mutation.environmentsCanaryIngressUpdate`
|
||||||
|
|
||||||
Input type: `EnvironmentsCanaryIngressUpdateInput`
|
Input type: `EnvironmentsCanaryIngressUpdateInput`
|
||||||
|
|
|
@ -96,6 +96,8 @@ module API
|
||||||
end
|
end
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
post ":id/members" do
|
post ":id/members" do
|
||||||
|
::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/333434')
|
||||||
|
|
||||||
source = find_source(source_type, params[:id])
|
source = find_source(source_type, params[:id])
|
||||||
authorize_admin_source!(source_type, source)
|
authorize_admin_source!(source_type, source)
|
||||||
|
|
||||||
|
|
|
@ -382,7 +382,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_user_login_with_non_expired_password?(user)
|
def can_user_login_with_non_expired_password?(user)
|
||||||
user.can?(:log_in) && !user.password_expired?
|
user.can?(:log_in) && !user.password_expired_if_applicable?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,6 +23,8 @@ module Gitlab
|
||||||
"Your primary email address is not confirmed. "\
|
"Your primary email address is not confirmed. "\
|
||||||
"Please check your inbox for the confirmation instructions. "\
|
"Please check your inbox for the confirmation instructions. "\
|
||||||
"In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}"
|
"In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}"
|
||||||
|
when :blocked
|
||||||
|
"Your account has been blocked."
|
||||||
when :password_expired
|
when :password_expired
|
||||||
"Your password expired. "\
|
"Your password expired. "\
|
||||||
"Please access GitLab from a web browser to update your password."
|
"Please access GitLab from a web browser to update your password."
|
||||||
|
@ -44,6 +46,8 @@ module Gitlab
|
||||||
:deactivated
|
:deactivated
|
||||||
elsif !@user.confirmed?
|
elsif !@user.confirmed?
|
||||||
:unconfirmed
|
:unconfirmed
|
||||||
|
elsif @user.blocked?
|
||||||
|
:blocked
|
||||||
elsif @user.password_expired?
|
elsif @user.password_expired?
|
||||||
:password_expired
|
:password_expired
|
||||||
else
|
else
|
||||||
|
|
|
@ -250,7 +250,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def diffable?
|
def diffable?
|
||||||
repository.attributes(file_path).fetch('diff') { true }
|
diffable_by_attribute? && !text_with_binary_notice?
|
||||||
end
|
end
|
||||||
|
|
||||||
def binary_in_repo?
|
def binary_in_repo?
|
||||||
|
@ -366,6 +366,15 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def diffable_by_attribute?
|
||||||
|
repository.attributes(file_path).fetch('diff') { true }
|
||||||
|
end
|
||||||
|
|
||||||
|
# NOTE: Files with unsupported encodings (e.g. UTF-16) are treated as binary by git, but they are recognized as text files during encoding detection. These files have `Binary files a/filename and b/filename differ' as their raw diff content which cannot be used. We need to handle this special case and avoid displaying incorrect diff.
|
||||||
|
def text_with_binary_notice?
|
||||||
|
text? && has_binary_notice?
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_blob(sha, path)
|
def fetch_blob(sha, path)
|
||||||
return unless sha
|
return unless sha
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
||||||
def parse(lines, diff_file: nil)
|
def parse(lines, diff_file: nil)
|
||||||
return [] if lines.blank?
|
return [] if lines.blank? || Git::Diff.has_binary_notice?(lines.first)
|
||||||
|
|
||||||
@lines = lines
|
@lines = lines
|
||||||
line_obj_index = 0
|
line_obj_index = 0
|
||||||
|
|
|
@ -33,6 +33,8 @@ module Gitlab
|
||||||
|
|
||||||
SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
|
SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
|
||||||
|
|
||||||
|
BINARY_NOTICE_PATTERN = %r(Binary files a\/(.*) and b\/(.*) differ).freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def between(repo, head, base, options = {}, *paths)
|
def between(repo, head, base, options = {}, *paths)
|
||||||
straight = options.delete(:straight) || false
|
straight = options.delete(:straight) || false
|
||||||
|
@ -131,8 +133,13 @@ module Gitlab
|
||||||
def patch_hard_limit_bytes
|
def patch_hard_limit_bytes
|
||||||
Gitlab::CurrentSettings.diff_max_patch_bytes
|
Gitlab::CurrentSettings.diff_max_patch_bytes
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
|
def has_binary_notice?(text)
|
||||||
|
return false unless text.present?
|
||||||
|
|
||||||
|
text.start_with?(BINARY_NOTICE_PATTERN)
|
||||||
|
end
|
||||||
|
end
|
||||||
def initialize(raw_diff, expanded: true)
|
def initialize(raw_diff, expanded: true)
|
||||||
@expanded = expanded
|
@expanded = expanded
|
||||||
|
|
||||||
|
@ -215,7 +222,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_binary_notice?
|
def has_binary_notice?
|
||||||
@diff.start_with?('Binary')
|
self.class.has_binary_notice?(@diff)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -8,9 +8,10 @@ module Gitlab
|
||||||
class HTTP
|
class HTTP
|
||||||
BlockedUrlError = Class.new(StandardError)
|
BlockedUrlError = Class.new(StandardError)
|
||||||
RedirectionTooDeep = Class.new(StandardError)
|
RedirectionTooDeep = Class.new(StandardError)
|
||||||
|
ReadTotalTimeout = Class.new(Net::ReadTimeout)
|
||||||
|
|
||||||
HTTP_TIMEOUT_ERRORS = [
|
HTTP_TIMEOUT_ERRORS = [
|
||||||
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
|
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Gitlab::HTTP::ReadTotalTimeout
|
||||||
].freeze
|
].freeze
|
||||||
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
|
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
|
||||||
SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
|
SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
|
||||||
|
@ -23,6 +24,7 @@ module Gitlab
|
||||||
read_timeout: 20,
|
read_timeout: 20,
|
||||||
write_timeout: 30
|
write_timeout: 30
|
||||||
}.freeze
|
}.freeze
|
||||||
|
DEFAULT_READ_TOTAL_TIMEOUT = 20.seconds
|
||||||
|
|
||||||
include HTTParty # rubocop:disable Gitlab/HTTParty
|
include HTTParty # rubocop:disable Gitlab/HTTParty
|
||||||
|
|
||||||
|
@ -41,7 +43,19 @@ module Gitlab
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
httparty_perform_request(http_method, path, options_with_timeouts, &block)
|
unless options.has_key?(:use_read_total_timeout)
|
||||||
|
return httparty_perform_request(http_method, path, options_with_timeouts, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
start_time = Gitlab::Metrics::System.monotonic_time
|
||||||
|
read_total_timeout = options.fetch(:timeout, DEFAULT_READ_TOTAL_TIMEOUT)
|
||||||
|
|
||||||
|
httparty_perform_request(http_method, path, options_with_timeouts) do |fragment|
|
||||||
|
elapsed = Gitlab::Metrics::System.monotonic_time - start_time
|
||||||
|
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds" if elapsed > read_total_timeout
|
||||||
|
|
||||||
|
block.call fragment if block
|
||||||
|
end
|
||||||
rescue HTTParty::RedirectionTooDeep
|
rescue HTTParty::RedirectionTooDeep
|
||||||
raise RedirectionTooDeep
|
raise RedirectionTooDeep
|
||||||
rescue *HTTP_ERRORS => e
|
rescue *HTTP_ERRORS => e
|
||||||
|
|
|
@ -52,7 +52,7 @@ module Gitlab
|
||||||
def valid_user?
|
def valid_user?
|
||||||
return true unless user?
|
return true unless user?
|
||||||
|
|
||||||
!actor.blocked? && (!actor.allow_password_authentication? || !actor.password_expired?)
|
!actor.blocked? && !actor.password_expired_if_applicable?
|
||||||
end
|
end
|
||||||
|
|
||||||
def authentication_payload(repository_http_path)
|
def authentication_payload(repository_http_path)
|
||||||
|
|
|
@ -14015,6 +14015,9 @@ msgstr ""
|
||||||
msgid "File renamed with no changes."
|
msgid "File renamed with no changes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "File suppressed by a .gitattributes entry or the file's encoding is unsupported."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "File synchronization concurrency limit"
|
msgid "File synchronization concurrency limit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -33097,9 +33100,6 @@ msgstr ""
|
||||||
msgid "This diff is collapsed."
|
msgid "This diff is collapsed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "This diff was suppressed by a .gitattributes entry."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "This directory"
|
msgid "This directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -38357,6 +38357,9 @@ msgstr ""
|
||||||
msgid "encrypted: needs to be a :required, :optional or :migrating!"
|
msgid "encrypted: needs to be a :required, :optional or :migrating!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ending with MIME type format is not allowed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "entries cannot be larger than 255 characters"
|
msgid "entries cannot be larger than 255 characters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ RSpec.describe GraphqlController do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns access denied template when user cannot access API' do
|
it 'returns forbidden when user cannot access API' do
|
||||||
# User cannot access API in a couple of cases
|
# User cannot access API in a couple of cases
|
||||||
# * When user is internal(like ghost users)
|
# * When user is internal(like ghost users)
|
||||||
# * When user is blocked
|
# * When user is blocked
|
||||||
|
@ -54,7 +54,9 @@ RSpec.describe GraphqlController do
|
||||||
post :execute
|
post :execute
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:forbidden)
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
expect(response).to render_template('errors/access_denied')
|
expect(json_response).to include(
|
||||||
|
'errors' => include(a_hash_including('message' => /API not accessible/))
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the users last_activity_on field' do
|
it 'updates the users last_activity_on field' do
|
||||||
|
|
|
@ -55,9 +55,9 @@ module DeprecationToolkitEnv
|
||||||
# one by one
|
# one by one
|
||||||
def self.allowed_kwarg_warning_paths
|
def self.allowed_kwarg_warning_paths
|
||||||
%w[
|
%w[
|
||||||
activerecord-6.0.3.6/lib/active_record/migration.rb
|
activerecord-6.0.3.7/lib/active_record/migration.rb
|
||||||
activesupport-6.0.3.6/lib/active_support/cache.rb
|
activesupport-6.0.3.7/lib/active_support/cache.rb
|
||||||
activerecord-6.0.3.6/lib/active_record/relation.rb
|
activerecord-6.0.3.7/lib/active_record/relation.rb
|
||||||
asciidoctor-2.0.12/lib/asciidoctor/extensions.rb
|
asciidoctor-2.0.12/lib/asciidoctor/extensions.rb
|
||||||
attr_encrypted-3.1.0/lib/attr_encrypted/adapters/active_record.rb
|
attr_encrypted-3.1.0/lib/attr_encrypted/adapters/active_record.rb
|
||||||
]
|
]
|
||||||
|
|
|
@ -253,7 +253,7 @@ RSpec.describe 'Expand and collapse diffs', :js do
|
||||||
click_link('Expand all')
|
click_link('Expand all')
|
||||||
|
|
||||||
# Wait for elements to appear to ensure full page reload
|
# Wait for elements to appear to ensure full page reload
|
||||||
expect(page).to have_content('This diff was suppressed by a .gitattributes entry')
|
expect(page).to have_content("File suppressed by a .gitattributes entry or the file's encoding is unsupported.")
|
||||||
expect(page).to have_content('This source diff could not be displayed because it is too large.')
|
expect(page).to have_content('This source diff could not be displayed because it is too large.')
|
||||||
expect(page).to have_content('too_large_image.jpg')
|
expect(page).to have_content('too_large_image.jpg')
|
||||||
find('.note-textarea')
|
find('.note-textarea')
|
||||||
|
|
|
@ -174,4 +174,14 @@ RSpec.describe 'Diff file viewer', :js, :with_clean_rails_cache do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the the encoding of the file is unsupported' do
|
||||||
|
before do
|
||||||
|
visit_commit('f05a98786e4274708e1fa118c7ad3a29d1d1b9a3')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows it is not diffable' do
|
||||||
|
expect(page).to have_content("File suppressed by a .gitattributes entry or the file's encoding is unsupported.")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,18 +65,6 @@ RSpec.describe 'Comments on personal snippets', :js do
|
||||||
expect(page).to have_content(user_name)
|
expect(page).to have_content(user_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the author name contains HTML' do
|
|
||||||
let(:user_name) { '<h1><a href="https://bad.link/malicious.exe" class="evil">Fake Content<img class="fake-icon" src="image.png"></a></h1>' }
|
|
||||||
|
|
||||||
it 'renders the name as plain text' do
|
|
||||||
visit snippet_path(snippet)
|
|
||||||
|
|
||||||
content = find("#note_#{snippet_notes[0].id} .note-header-author-name").text
|
|
||||||
|
|
||||||
expect(content).to eq user_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when submitting a note' do
|
context 'when submitting a note' do
|
||||||
|
|
|
@ -1,50 +1,54 @@
|
||||||
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
|
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
|
||||||
import * as commonUtils from '~/lib/utils/common_utils';
|
|
||||||
|
|
||||||
describe('CopyAsGFM', () => {
|
describe('CopyAsGFM', () => {
|
||||||
describe('CopyAsGFM.pasteGFM', () => {
|
describe('CopyAsGFM.pasteGFM', () => {
|
||||||
function callPasteGFM() {
|
let target;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
target = document.createElement('input');
|
||||||
|
target.value = 'This is code: ';
|
||||||
|
});
|
||||||
|
|
||||||
|
// When GFM code is copied, we put the regular plain text
|
||||||
|
// on the clipboard as `text/plain`, and the GFM as `text/x-gfm`.
|
||||||
|
// This emulates the behavior of `getData` with that data.
|
||||||
|
function callPasteGFM(data = { 'text/plain': 'code', 'text/x-gfm': '`code`' }) {
|
||||||
const e = {
|
const e = {
|
||||||
originalEvent: {
|
originalEvent: {
|
||||||
clipboardData: {
|
clipboardData: {
|
||||||
getData(mimeType) {
|
getData(mimeType) {
|
||||||
// When GFM code is copied, we put the regular plain text
|
return data[mimeType] || null;
|
||||||
// on the clipboard as `text/plain`, and the GFM as `text/x-gfm`.
|
|
||||||
// This emulates the behavior of `getData` with that data.
|
|
||||||
if (mimeType === 'text/plain') {
|
|
||||||
return 'code';
|
|
||||||
}
|
|
||||||
if (mimeType === 'text/x-gfm') {
|
|
||||||
return '`code`';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preventDefault() {},
|
preventDefault() {},
|
||||||
|
target,
|
||||||
};
|
};
|
||||||
|
|
||||||
CopyAsGFM.pasteGFM(e);
|
CopyAsGFM.pasteGFM(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('wraps pasted code when not already in code tags', () => {
|
it('wraps pasted code when not already in code tags', () => {
|
||||||
jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
|
|
||||||
const insertedText = textFunc('This is code: ', '');
|
|
||||||
|
|
||||||
expect(insertedText).toEqual('`code`');
|
|
||||||
});
|
|
||||||
|
|
||||||
callPasteGFM();
|
callPasteGFM();
|
||||||
|
|
||||||
|
expect(target.value).toBe('This is code: `code`');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not wrap pasted code when already in code tags', () => {
|
it('does not wrap pasted code when already in code tags', () => {
|
||||||
jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
|
target.value = 'This is code: `';
|
||||||
const insertedText = textFunc('This is code: `', '`');
|
|
||||||
|
|
||||||
expect(insertedText).toEqual('code');
|
|
||||||
});
|
|
||||||
|
|
||||||
callPasteGFM();
|
callPasteGFM();
|
||||||
|
|
||||||
|
expect(target.value).toBe('This is code: `code');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow xss in x-gfm-html', () => {
|
||||||
|
const testEl = document.createElement('div');
|
||||||
|
jest.spyOn(document, 'createElement').mockReturnValueOnce(testEl);
|
||||||
|
|
||||||
|
callPasteGFM({ 'text/plain': 'code', 'text/x-gfm-html': 'code<img/src/onerror=alert(1)>' });
|
||||||
|
|
||||||
|
expect(testEl.innerHTML).toBe('code<img src="">');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
import * as urlUtils from '~/lib/utils/url_utility';
|
import * as urlUtils from '~/lib/utils/url_utility';
|
||||||
|
|
||||||
const shas = {
|
const shas = {
|
||||||
|
@ -921,4 +922,37 @@ describe('URL utility', () => {
|
||||||
expect(urlUtils.encodeSaferUrl(input)).toBe(input);
|
expect(urlUtils.encodeSaferUrl(input)).toBe(input);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isSameOriginUrl', () => {
|
||||||
|
// eslint-disable-next-line no-script-url
|
||||||
|
const javascriptUrl = 'javascript:alert(1)';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setWindowLocation({ origin: TEST_HOST });
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
url | expected
|
||||||
|
${TEST_HOST} | ${true}
|
||||||
|
${`${TEST_HOST}/a/path`} | ${true}
|
||||||
|
${'//test.host/no-protocol'} | ${true}
|
||||||
|
${'/a/root/relative/path'} | ${true}
|
||||||
|
${'a/relative/path'} | ${true}
|
||||||
|
${'#hash'} | ${true}
|
||||||
|
${'?param=foo'} | ${true}
|
||||||
|
${''} | ${true}
|
||||||
|
${'../../../'} | ${true}
|
||||||
|
${`${TEST_HOST}:8080/wrong-port`} | ${false}
|
||||||
|
${'ws://test.host/wrong-protocol'} | ${false}
|
||||||
|
${'http://phishing.test'} | ${false}
|
||||||
|
${'//phishing.test'} | ${false}
|
||||||
|
${'//invalid:url'} | ${false}
|
||||||
|
${javascriptUrl} | ${false}
|
||||||
|
${'data:,Hello%2C%20World%21'} | ${false}
|
||||||
|
${null} | ${false}
|
||||||
|
${undefined} | ${false}
|
||||||
|
`('returns $expected given $url', ({ url, expected }) => {
|
||||||
|
expect(urlUtils.isSameOriginUrl(url)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
|
||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import { getJSONFixture } from 'helpers/fixtures';
|
import { getJSONFixture } from 'helpers/fixtures';
|
||||||
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
import * as commonUtils from '~/lib/utils/common_utils';
|
import * as commonUtils from '~/lib/utils/common_utils';
|
||||||
import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
|
import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
|
||||||
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
|
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
|
||||||
|
@ -11,6 +12,7 @@ import { BACK_URL_PARAM } from '~/releases/constants';
|
||||||
|
|
||||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||||
const originalMilestones = originalRelease.milestones;
|
const originalMilestones = originalRelease.milestones;
|
||||||
|
const releasesPagePath = 'path/to/releases/page';
|
||||||
|
|
||||||
describe('Release edit/new component', () => {
|
describe('Release edit/new component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -24,7 +26,7 @@ describe('Release edit/new component', () => {
|
||||||
state = {
|
state = {
|
||||||
release,
|
release,
|
||||||
markdownDocsPath: 'path/to/markdown/docs',
|
markdownDocsPath: 'path/to/markdown/docs',
|
||||||
releasesPagePath: 'path/to/releases/page',
|
releasesPagePath,
|
||||||
projectId: '8',
|
projectId: '8',
|
||||||
groupId: '42',
|
groupId: '42',
|
||||||
groupMilestonesAvailable: true,
|
groupMilestonesAvailable: true,
|
||||||
|
@ -75,6 +77,8 @@ describe('Release edit/new component', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
global.jsdom.reconfigure({ url: TEST_HOST });
|
||||||
|
|
||||||
mock = new MockAdapter(axios);
|
mock = new MockAdapter(axios);
|
||||||
gon.api_version = 'v4';
|
gon.api_version = 'v4';
|
||||||
|
|
||||||
|
@ -146,22 +150,33 @@ describe('Release edit/new component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`when the URL contains a "${BACK_URL_PARAM}" parameter`, () => {
|
// eslint-disable-next-line no-script-url
|
||||||
const backUrl = 'https://example.gitlab.com/back/url';
|
const xssBackUrl = 'javascript:alert(1)';
|
||||||
|
describe.each`
|
||||||
|
backUrl | expectedHref
|
||||||
|
${`${TEST_HOST}/back/url`} | ${`${TEST_HOST}/back/url`}
|
||||||
|
${`/back/url?page=2`} | ${`/back/url?page=2`}
|
||||||
|
${`back/url?page=3`} | ${`back/url?page=3`}
|
||||||
|
${'http://phishing.test/back/url'} | ${releasesPagePath}
|
||||||
|
${'//phishing.test/back/url'} | ${releasesPagePath}
|
||||||
|
${xssBackUrl} | ${releasesPagePath}
|
||||||
|
`(
|
||||||
|
`when the URL contains a "${BACK_URL_PARAM}=$backUrl" parameter`,
|
||||||
|
({ backUrl, expectedHref }) => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
global.jsdom.reconfigure({
|
||||||
|
url: `${TEST_HOST}?${BACK_URL_PARAM}=${encodeURIComponent(backUrl)}`,
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
await factory();
|
||||||
commonUtils.getParameterByName = jest
|
});
|
||||||
.fn()
|
|
||||||
.mockImplementation((paramToGet) => ({ [BACK_URL_PARAM]: backUrl }[paramToGet]));
|
|
||||||
|
|
||||||
await factory();
|
it(`renders a "Cancel" button with an href pointing to ${expectedHref}`, () => {
|
||||||
});
|
const cancelButton = wrapper.find('.js-cancel-button');
|
||||||
|
expect(cancelButton.attributes().href).toBe(expectedHref);
|
||||||
it('renders a "Cancel" button with an href pointing to the main Releases page', () => {
|
});
|
||||||
const cancelButton = wrapper.find('.js-cancel-button');
|
},
|
||||||
expect(cancelButton.attributes().href).toBe(backUrl);
|
);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating a new release', () => {
|
describe('when creating a new release', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
|
||||||
|
|
||||||
it 'does not highlight files marked as undiffable in .gitattributes' do
|
it 'does not highlight files marked as undiffable in .gitattributes' do
|
||||||
allow_next_instance_of(Gitlab::Diff::File) do |instance|
|
allow_next_instance_of(Gitlab::Diff::File) do |instance|
|
||||||
allow(instance).to receive(:diffable?).and_return(false)
|
allow(instance).to receive(:diffable_by_attribute?).and_return(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
expect_next_instance_of(Gitlab::Diff::File) do |instance|
|
expect_next_instance_of(Gitlab::Diff::File) do |instance|
|
||||||
|
|
|
@ -186,26 +186,46 @@ RSpec.describe Gitlab::Diff::File do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#diffable?' do
|
describe '#diffable?' do
|
||||||
let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
|
context 'when attributes exist' do
|
||||||
let(:diffs) { commit.diffs }
|
let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
|
||||||
|
let(:diffs) { commit.diffs }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
info_dir_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
info_dir_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||||
File.join(project.repository.path_to_repo, 'info')
|
File.join(project.repository.path_to_repo, 'info')
|
||||||
|
end
|
||||||
|
|
||||||
|
FileUtils.mkdir(info_dir_path) unless File.exist?(info_dir_path)
|
||||||
|
File.write(File.join(info_dir_path, 'attributes'), "*.md -diff\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
FileUtils.mkdir(info_dir_path) unless File.exist?(info_dir_path)
|
it "returns true for files that do not have attributes" do
|
||||||
File.write(File.join(info_dir_path, 'attributes'), "*.md -diff\n")
|
diff_file = diffs.diff_file_with_new_path('LICENSE')
|
||||||
|
expect(diff_file.diffable?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for files that have been marked as not being diffable in attributes" do
|
||||||
|
diff_file = diffs.diff_file_with_new_path('README.md')
|
||||||
|
expect(diff_file.diffable?).to be_falsey
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns true for files that do not have attributes" do
|
context 'when the text has binary notice' do
|
||||||
diff_file = diffs.diff_file_with_new_path('LICENSE')
|
let(:commit) { project.commit('f05a98786e4274708e1fa118c7ad3a29d1d1b9a3') }
|
||||||
expect(diff_file.diffable?).to be_truthy
|
let(:diff_file) { commit.diffs.diff_file_with_new_path('VERSION') }
|
||||||
|
|
||||||
|
it "returns false" do
|
||||||
|
expect(diff_file.diffable?).to be_falsey
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false for files that have been marked as not being diffable in attributes" do
|
context 'when the content is binary' do
|
||||||
diff_file = diffs.diff_file_with_new_path('README.md')
|
let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
|
||||||
expect(diff_file.diffable?).to be_falsey
|
let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') }
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
expect(diff_file.diffable?).to be_truthy
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -729,6 +749,18 @@ RSpec.describe Gitlab::Diff::File do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the the encoding of the file is unsupported' do
|
||||||
|
let(:commit) { project.commit('f05a98786e4274708e1fa118c7ad3a29d1d1b9a3') }
|
||||||
|
let(:diff_file) { commit.diffs.diff_file_with_new_path('VERSION') }
|
||||||
|
|
||||||
|
it 'returns a Not Diffable viewer' do
|
||||||
|
expect(diff_file.simple_viewer).to be_a(DiffViewer::NotDiffable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(diff_file.highlighted_diff_lines).to eq([]) }
|
||||||
|
it { expect(diff_file.parallel_diff_lines).to eq([]) }
|
||||||
|
end
|
||||||
|
|
||||||
describe '#diff_hunk' do
|
describe '#diff_hunk' do
|
||||||
context 'when first line is a match' do
|
context 'when first line is a match' do
|
||||||
let(:raw_diff) do
|
let(:raw_diff) do
|
||||||
|
|
|
@ -146,6 +146,16 @@ eos
|
||||||
it { expect(parser.parse(nil)).to eq([]) }
|
it { expect(parser.parse(nil)).to eq([]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when it is a binary notice' do
|
||||||
|
let(:diff) do
|
||||||
|
<<~END
|
||||||
|
Binary files a/test and b/test differ
|
||||||
|
END
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(parser.parse(diff.each_line)).to eq([]) }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'tolerates special diff markers in a content' do
|
describe 'tolerates special diff markers in a content' do
|
||||||
it "counts lines correctly" do
|
it "counts lines correctly" do
|
||||||
diff = <<~END
|
diff = <<~END
|
||||||
|
|
|
@ -440,6 +440,14 @@ RSpec.describe Gitlab::GitAccess do
|
||||||
expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
|
expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows ldap users with expired password to pull' do
|
||||||
|
project.add_maintainer(user)
|
||||||
|
user.update!(password_expires_at: 2.minutes.ago)
|
||||||
|
allow(user).to receive(:ldap_user?).and_return(true)
|
||||||
|
|
||||||
|
expect { pull_access_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the project repository does not exist' do
|
context 'when the project repository does not exist' do
|
||||||
before do
|
before do
|
||||||
project.add_guest(user)
|
project.add_guest(user)
|
||||||
|
@ -977,12 +985,26 @@ RSpec.describe Gitlab::GitAccess do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'disallows users with expired password to push' do
|
it 'disallows users with expired password to push' do
|
||||||
project.add_maintainer(user)
|
|
||||||
user.update!(password_expires_at: 2.minutes.ago)
|
user.update!(password_expires_at: 2.minutes.ago)
|
||||||
|
|
||||||
expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
|
expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows ldap users with expired password to push' do
|
||||||
|
user.update!(password_expires_at: 2.minutes.ago)
|
||||||
|
allow(user).to receive(:ldap_user?).and_return(true)
|
||||||
|
|
||||||
|
expect { push_access_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'disallows blocked ldap users with expired password to push' do
|
||||||
|
user.block
|
||||||
|
user.update!(password_expires_at: 2.minutes.ago)
|
||||||
|
allow(user).to receive(:ldap_user?).and_return(true)
|
||||||
|
|
||||||
|
expect { push_access_check }.to raise_forbidden("Your account has been blocked.")
|
||||||
|
end
|
||||||
|
|
||||||
it 'cleans up the files' do
|
it 'cleans up the files' do
|
||||||
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
|
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
|
||||||
expect { push_access_check }.not_to raise_error
|
expect { push_access_check }.not_to raise_error
|
||||||
|
|
|
@ -27,6 +27,47 @@ RSpec.describe Gitlab::HTTP do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when reading the response is too slow' do
|
||||||
|
before do
|
||||||
|
stub_const("#{described_class}::DEFAULT_READ_TOTAL_TIMEOUT", 0.001.seconds)
|
||||||
|
|
||||||
|
WebMock.stub_request(:post, /.*/).to_return do |request|
|
||||||
|
sleep 0.002.seconds
|
||||||
|
{ body: 'I\m slow', status: 200 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:options) { {} }
|
||||||
|
|
||||||
|
subject(:request_slow_responder) { described_class.post('http://example.org', **options) }
|
||||||
|
|
||||||
|
specify do
|
||||||
|
expect { request_slow_responder }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with use_read_total_timeout option' do
|
||||||
|
let(:options) { { use_read_total_timeout: true } }
|
||||||
|
|
||||||
|
it 'raises a timeout error' do
|
||||||
|
expect { request_slow_responder }.to raise_error(Gitlab::HTTP::ReadTotalTimeout, /Request timed out after ?([0-9]*[.])?[0-9]+ seconds/)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'and timeout option' do
|
||||||
|
let(:options) { { use_read_total_timeout: true, timeout: 10.seconds } }
|
||||||
|
|
||||||
|
it 'overrides the default timeout when timeout option is present' do
|
||||||
|
expect { request_slow_responder }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls a block' do
|
||||||
|
WebMock.stub_request(:post, /.*/)
|
||||||
|
|
||||||
|
expect { |b| described_class.post('http://example.org', &b) }.to yield_with_args
|
||||||
|
end
|
||||||
|
|
||||||
describe 'allow_local_requests_from_web_hooks_and_services is' do
|
describe 'allow_local_requests_from_web_hooks_and_services is' do
|
||||||
before do
|
before do
|
||||||
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
|
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe AuditEvent do
|
RSpec.describe AuditEvent do
|
||||||
let_it_be(:audit_event) { create(:project_audit_event) }
|
|
||||||
subject { audit_event }
|
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
include_examples 'validates IP address' do
|
include_examples 'validates IP address' do
|
||||||
let(:attribute) { :ip_address }
|
let(:attribute) { :ip_address }
|
||||||
|
@ -13,6 +10,15 @@ RSpec.describe AuditEvent do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sanitizes custom_message in the details hash' do
|
||||||
|
audit_event = create(:project_audit_event, details: { target_id: 678, custom_message: '<strong>Arnold</strong>' })
|
||||||
|
|
||||||
|
expect(audit_event.details).to include(
|
||||||
|
target_id: 678,
|
||||||
|
custom_message: 'Arnold'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#as_json' do
|
describe '#as_json' do
|
||||||
context 'ip_address' do
|
context 'ip_address' do
|
||||||
subject { build(:group_audit_event, ip_address: '192.168.1.1').as_json }
|
subject { build(:group_audit_event, ip_address: '192.168.1.1').as_json }
|
||||||
|
|
|
@ -44,7 +44,7 @@ RSpec.describe ProtectedBranch::PushAccessLevel do
|
||||||
let(:can_push) { true }
|
let(:can_push) { true }
|
||||||
|
|
||||||
before_all do
|
before_all do
|
||||||
project.add_guest(user)
|
project.add_maintainer(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when this push_access_level is tied to a deploy key' do
|
context 'when this push_access_level is tied to a deploy key' do
|
||||||
|
|
|
@ -376,6 +376,19 @@ RSpec.describe User do
|
||||||
expect(user.errors.full_messages).to eq(['Username has already been taken'])
|
expect(user.errors.full_messages).to eq(['Username has already been taken'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'validates format' do
|
||||||
|
Mime::EXTENSION_LOOKUP.keys.each do |type|
|
||||||
|
user = build(:user, username: "test.#{type}")
|
||||||
|
|
||||||
|
expect(user).not_to be_valid
|
||||||
|
expect(user.errors.full_messages).to include('Username ending with MIME type format is not allowed.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'validates format on updated record' do
|
||||||
|
expect(create(:user).update(username: 'profile.html')).to be_falsey
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a DB-level NOT NULL constraint on projects_limit' do
|
it 'has a DB-level NOT NULL constraint on projects_limit' do
|
||||||
|
@ -2877,7 +2890,7 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#sanitize_attrs' do
|
describe '#sanitize_attrs' do
|
||||||
let(:user) { build(:user, name: 'test & user', skype: 'test&user') }
|
let(:user) { build(:user, name: 'test <& user', skype: 'test&user') }
|
||||||
|
|
||||||
it 'encodes HTML entities in the Skype attribute' do
|
it 'encodes HTML entities in the Skype attribute' do
|
||||||
expect { user.sanitize_attrs }.to change { user.skype }.to('test&user')
|
expect { user.sanitize_attrs }.to change { user.skype }.to('test&user')
|
||||||
|
@ -2886,6 +2899,25 @@ RSpec.describe User do
|
||||||
it 'does not encode HTML entities in the name attribute' do
|
it 'does not encode HTML entities in the name attribute' do
|
||||||
expect { user.sanitize_attrs }.not_to change { user.name }
|
expect { user.sanitize_attrs }.not_to change { user.name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sanitizes attr from html tags' do
|
||||||
|
user = create(:user, name: '<a href="//example.com">Test<a>', twitter: '<a href="//evil.com">https://twitter.com<a>')
|
||||||
|
|
||||||
|
expect(user.name).to eq('Test')
|
||||||
|
expect(user.twitter).to eq('https://twitter.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sanitizes attr from js scripts' do
|
||||||
|
user = create(:user, name: '<script>alert("Test")</script>')
|
||||||
|
|
||||||
|
expect(user.name).to eq("alert(\"Test\")")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sanitizes attr from iframe scripts' do
|
||||||
|
user = create(:user, name: 'User"><iframe src=javascript:alert()><iframe>')
|
||||||
|
|
||||||
|
expect(user.name).to eq('User">')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#starred?' do
|
describe '#starred?' do
|
||||||
|
@ -5248,6 +5280,70 @@ RSpec.describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#password_expired_if_applicable?' do
|
||||||
|
let(:user) { build(:user, password_expires_at: password_expires_at) }
|
||||||
|
|
||||||
|
subject { user.password_expired_if_applicable? }
|
||||||
|
|
||||||
|
context 'when user is not ldap user' do
|
||||||
|
context 'when password_expires_at is not set' do
|
||||||
|
let(:password_expires_at) {}
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when password_expires_at is in the past' do
|
||||||
|
let(:password_expires_at) { 1.minute.ago }
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
is_expected.to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when password_expires_at is in the future' do
|
||||||
|
let(:password_expires_at) { 1.minute.from_now }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is ldap user' do
|
||||||
|
let(:user) { build(:user, password_expires_at: password_expires_at) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(user).to receive(:ldap_user?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when password_expires_at is not set' do
|
||||||
|
let(:password_expires_at) {}
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when password_expires_at is in the past' do
|
||||||
|
let(:password_expires_at) { 1.minute.ago }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when password_expires_at is in the future' do
|
||||||
|
let(:password_expires_at) { 1.minute.from_now }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#read_only_attribute?' do
|
describe '#read_only_attribute?' do
|
||||||
context 'when synced attributes metadata is present' do
|
context 'when synced attributes metadata is present' do
|
||||||
it 'delegates to synced_attributes_metadata' do
|
it 'delegates to synced_attributes_metadata' do
|
||||||
|
|
|
@ -239,12 +239,28 @@ RSpec.describe GlobalPolicy do
|
||||||
it { is_expected.not_to be_allowed(:access_api) }
|
it { is_expected.not_to be_allowed(:access_api) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a deactivated user' do
|
||||||
|
before do
|
||||||
|
current_user.deactivate!
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.not_to be_allowed(:access_api) }
|
||||||
|
end
|
||||||
|
|
||||||
context 'user with expired password' do
|
context 'user with expired password' do
|
||||||
before do
|
before do
|
||||||
current_user.update!(password_expires_at: 2.minutes.ago)
|
current_user.update!(password_expires_at: 2.minutes.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.not_to be_allowed(:access_api) }
|
it { is_expected.not_to be_allowed(:access_api) }
|
||||||
|
|
||||||
|
context 'when user is using ldap' do
|
||||||
|
before do
|
||||||
|
allow(current_user).to receive(:ldap_user?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(:access_api) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when terms are enforced' do
|
context 'when terms are enforced' do
|
||||||
|
@ -433,6 +449,14 @@ RSpec.describe GlobalPolicy do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.not_to be_allowed(:access_git) }
|
it { is_expected.not_to be_allowed(:access_git) }
|
||||||
|
|
||||||
|
context 'when user is using ldap' do
|
||||||
|
before do
|
||||||
|
allow(current_user).to receive(:ldap_user?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(:access_git) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -517,6 +541,14 @@ RSpec.describe GlobalPolicy do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.not_to be_allowed(:use_slash_commands) }
|
it { is_expected.not_to be_allowed(:use_slash_commands) }
|
||||||
|
|
||||||
|
context 'when user is using ldap' do
|
||||||
|
before do
|
||||||
|
allow(current_user).to receive(:ldap_user?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(:use_slash_commands) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Setting assignees of a merge request' do
|
RSpec.describe 'Setting assignees of a merge request', :clean_gitlab_redis_shared_state do
|
||||||
include GraphqlHelpers
|
include GraphqlHelpers
|
||||||
|
|
||||||
let_it_be(:project) { create(:project, :repository) }
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
|
@ -46,6 +46,8 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_mutation!
|
def run_mutation!
|
||||||
|
post_graphql_mutation(mutation, current_user: current_user) # warm-up
|
||||||
|
|
||||||
recorder = ActiveRecord::QueryRecorder.new do
|
recorder = ActiveRecord::QueryRecorder.new do
|
||||||
post_graphql_mutation(mutation, current_user: current_user)
|
post_graphql_mutation(mutation, current_user: current_user)
|
||||||
end
|
end
|
||||||
|
@ -68,7 +70,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
|
|
||||||
context 'when the current user does not have permission to add assignees' do
|
context 'when the current user does not have permission to add assignees' do
|
||||||
let(:current_user) { create(:user) }
|
let(:current_user) { create(:user) }
|
||||||
let(:db_query_limit) { 27 }
|
let(:db_query_limit) { 28 }
|
||||||
|
|
||||||
it 'does not change the assignees' do
|
it 'does not change the assignees' do
|
||||||
project.add_guest(current_user)
|
project.add_guest(current_user)
|
||||||
|
@ -80,7 +82,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with assignees already assigned' do
|
context 'with assignees already assigned' do
|
||||||
let(:db_query_limit) { 39 }
|
let(:db_query_limit) { 46 }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
merge_request.assignees = [assignee2]
|
merge_request.assignees = [assignee2]
|
||||||
|
@ -96,7 +98,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when passing an empty list of assignees' do
|
context 'when passing an empty list of assignees' do
|
||||||
let(:db_query_limit) { 31 }
|
let(:db_query_limit) { 33 }
|
||||||
let(:input) { { assignee_usernames: [] } }
|
let(:input) { { assignee_usernames: [] } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -115,7 +117,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
context 'when passing append as true' do
|
context 'when passing append as true' do
|
||||||
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
|
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
|
||||||
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
|
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
|
||||||
let(:db_query_limit) { 20 }
|
let(:db_query_limit) { 22 }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
# In CE, APPEND is a NOOP as you can't have multiple assignees
|
# In CE, APPEND is a NOOP as you can't have multiple assignees
|
||||||
|
@ -135,7 +137,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when passing remove as true' do
|
context 'when passing remove as true' do
|
||||||
let(:db_query_limit) { 31 }
|
let(:db_query_limit) { 33 }
|
||||||
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
|
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
|
||||||
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
|
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
|
||||||
let(:expected_result) { [] }
|
let(:expected_result) { [] }
|
||||||
|
|
|
@ -6,6 +6,9 @@ RSpec.describe 'GraphQL' do
|
||||||
include AfterNextHelpers
|
include AfterNextHelpers
|
||||||
|
|
||||||
let(:query) { graphql_query_for('echo', text: 'Hello world') }
|
let(:query) { graphql_query_for('echo', text: 'Hello world') }
|
||||||
|
let(:mutation) { 'mutation { echoCreate(input: { messages: ["hello", "world"] }) { echoes } }' }
|
||||||
|
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
describe 'logging' do
|
describe 'logging' do
|
||||||
shared_examples 'logging a graphql query' do
|
shared_examples 'logging a graphql query' do
|
||||||
|
@ -70,6 +73,139 @@ RSpec.describe 'GraphQL' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when executing mutations' do
|
||||||
|
let(:mutation_with_variables) do
|
||||||
|
<<~GQL
|
||||||
|
mutation($a: String!, $b: String!) {
|
||||||
|
echoCreate(input: { messages: [$a, $b] }) { echoes }
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with POST' do
|
||||||
|
it 'succeeds' do
|
||||||
|
post_graphql(mutation, current_user: user)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:echo_create, :echoes)).to eq %w[hello world]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with variables' do
|
||||||
|
it 'succeeds' do
|
||||||
|
post_graphql(mutation_with_variables, current_user: user, variables: { a: 'Yo', b: 'there' })
|
||||||
|
|
||||||
|
expect(graphql_data_at(:echo_create, :echoes)).to eq %w[Yo there]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with GET' do
|
||||||
|
it 'fails' do
|
||||||
|
get_graphql(mutation, current_user: user)
|
||||||
|
|
||||||
|
expect(graphql_errors).to include(a_hash_including('message' => /Mutations are forbidden/))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with variables' do
|
||||||
|
it 'fails' do
|
||||||
|
get_graphql(mutation_with_variables, current_user: user, variables: { a: 'Yo', b: 'there' })
|
||||||
|
|
||||||
|
expect(graphql_errors).to include(a_hash_including('message' => /Mutations are forbidden/))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when executing queries' do
|
||||||
|
context 'with POST' do
|
||||||
|
it 'succeeds' do
|
||||||
|
post_graphql(query, current_user: user)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:echo)).to include 'Hello world'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with GET' do
|
||||||
|
it 'succeeds' do
|
||||||
|
get_graphql(query, current_user: user)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:echo)).to include 'Hello world'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when selecting a query by operation name' do
|
||||||
|
let(:query) { "query A #{graphql_query_for('echo', text: 'Hello world')}" }
|
||||||
|
let(:mutation) { 'mutation B { echoCreate(input: { messages: ["hello", "world"] }) { echoes } }' }
|
||||||
|
|
||||||
|
let(:combined) { [query, mutation].join("\n\n") }
|
||||||
|
|
||||||
|
context 'with POST' do
|
||||||
|
it 'succeeds when selecting the query' do
|
||||||
|
post_graphql(combined, current_user: user, params: { operationName: 'A' })
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig('data', 'echo')).to include 'Hello world'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds when selecting the mutation' do
|
||||||
|
post_graphql(combined, current_user: user, params: { operationName: 'B' })
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig('data', 'echoCreate', 'echoes')).to eq %w[hello world]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with GET' do
|
||||||
|
it 'succeeds when selecting the query' do
|
||||||
|
get_graphql(combined, current_user: user, params: { operationName: 'A' })
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig('data', 'echo')).to include 'Hello world'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when selecting the mutation' do
|
||||||
|
get_graphql(combined, current_user: user, params: { operationName: 'B' })
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig('errors', 0, 'message')).to include "Mutations are forbidden"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when batching mutations and queries' do
|
||||||
|
let(:batched) do
|
||||||
|
[
|
||||||
|
{ query: "query A #{graphql_query_for('echo', text: 'Hello world')}" },
|
||||||
|
{ query: 'mutation B { echoCreate(input: { messages: ["hello", "world"] }) { echoes } }' }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with POST' do
|
||||||
|
it 'succeeds' do
|
||||||
|
post_multiplex(batched, current_user: user)
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig(0, 'data', 'echo')).to include 'Hello world'
|
||||||
|
expect(resp.dig(1, 'data', 'echoCreate', 'echoes')).to eq %w[hello world]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with GET' do
|
||||||
|
it 'fails with a helpful error message' do
|
||||||
|
get_multiplex(batched, current_user: user)
|
||||||
|
|
||||||
|
resp = json_response
|
||||||
|
|
||||||
|
expect(resp.dig('errors', 0, 'message')).to include "Mutations are forbidden"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with invalid variables' do
|
context 'with invalid variables' do
|
||||||
it 'returns an error' do
|
it 'returns an error' do
|
||||||
post_graphql(query, variables: "This is not JSON")
|
post_graphql(query, variables: "This is not JSON")
|
||||||
|
@ -80,8 +216,6 @@ RSpec.describe 'GraphQL' do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'authentication', :allow_forgery_protection do
|
describe 'authentication', :allow_forgery_protection do
|
||||||
let(:user) { create(:user) }
|
|
||||||
|
|
||||||
it 'allows access to public data without authentication' do
|
it 'allows access to public data without authentication' do
|
||||||
post_graphql(query)
|
post_graphql(query)
|
||||||
|
|
||||||
|
@ -109,11 +243,9 @@ RSpec.describe 'GraphQL' do
|
||||||
context 'with token authentication' do
|
context 'with token authentication' do
|
||||||
let(:token) { create(:personal_access_token) }
|
let(:token) { create(:personal_access_token) }
|
||||||
|
|
||||||
before do
|
|
||||||
stub_authentication_activity_metrics(debug: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'authenticates users with a PAT' do
|
it 'authenticates users with a PAT' do
|
||||||
|
stub_authentication_activity_metrics(debug: false)
|
||||||
|
|
||||||
expect(authentication_metrics)
|
expect(authentication_metrics)
|
||||||
.to increment(:user_authenticated_counter)
|
.to increment(:user_authenticated_counter)
|
||||||
.and increment(:user_session_override_counter)
|
.and increment(:user_session_override_counter)
|
||||||
|
@ -124,6 +256,14 @@ RSpec.describe 'GraphQL' do
|
||||||
expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
|
expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'prevents access by deactived users' do
|
||||||
|
token.user.deactivate!
|
||||||
|
|
||||||
|
post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
|
||||||
|
|
||||||
|
expect(graphql_errors).to include({ 'message' => /API not accessible/ })
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the personal access token has no api scope' do
|
context 'when the personal access token has no api scope' do
|
||||||
it 'does not log the user in' do
|
it 'does not log the user in' do
|
||||||
token.update!(scopes: [:read_user])
|
token.update!(scopes: [:read_user])
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe API::ImportBitbucketServer do
|
RSpec.describe API::ImportBitbucketServer do
|
||||||
let(:base_uri) { "https://test:7990" }
|
let(:base_uri) { "https://test:7990" }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user, bio: 'test') }
|
||||||
let(:token) { "asdasd12345" }
|
let(:token) { "asdasd12345" }
|
||||||
let(:secret) { "sekrettt" }
|
let(:secret) { "sekrettt" }
|
||||||
let(:project_key) { 'TES' }
|
let(:project_key) { 'TES' }
|
||||||
|
|
|
@ -56,7 +56,7 @@ RSpec.describe API::Projects do
|
||||||
let_it_be(:project, reload: true) { create(:project, :repository, namespace: user.namespace) }
|
let_it_be(:project, reload: true) { create(:project, :repository, namespace: user.namespace) }
|
||||||
let_it_be(:project2, reload: true) { create(:project, namespace: user.namespace) }
|
let_it_be(:project2, reload: true) { create(:project, namespace: user.namespace) }
|
||||||
let_it_be(:project_member) { create(:project_member, :developer, user: user3, project: project) }
|
let_it_be(:project_member) { create(:project_member, :developer, user: user3, project: project) }
|
||||||
let_it_be(:user4) { create(:user, username: 'user.with.dot') }
|
let_it_be(:user4) { create(:user, username: 'user.withdot') }
|
||||||
let_it_be(:project3, reload: true) do
|
let_it_be(:project3, reload: true) do
|
||||||
create(:project,
|
create(:project,
|
||||||
:private,
|
:private,
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe API::Users do
|
RSpec.describe API::Users do
|
||||||
let_it_be(:admin) { create(:admin) }
|
let_it_be(:admin) { create(:admin) }
|
||||||
let_it_be(:user, reload: true) { create(:user, username: 'user.with.dot') }
|
let_it_be(:user, reload: true) { create(:user, username: 'user.withdot') }
|
||||||
let_it_be(:key) { create(:key, user: user) }
|
let_it_be(:key) { create(:key, user: user) }
|
||||||
let_it_be(:gpg_key) { create(:gpg_key, user: user) }
|
let_it_be(:gpg_key) { create(:gpg_key, user: user) }
|
||||||
let_it_be(:email) { create(:email, user: user) }
|
let_it_be(:email) { create(:email, user: user) }
|
||||||
|
|
|
@ -36,16 +36,6 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when password is expired" do
|
|
||||||
it "responds to downloads with status 401 Unauthorized" do
|
|
||||||
user.update!(password_expires_at: 2.days.ago)
|
|
||||||
|
|
||||||
download(path, user: user.username, password: user.password) do |response|
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when user is blocked" do
|
context "when user is blocked" do
|
||||||
let(:user) { create(:user, :blocked) }
|
let(:user) { create(:user, :blocked) }
|
||||||
|
|
||||||
|
@ -68,6 +58,26 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'operations are not allowed with expired password' do
|
||||||
|
context "when password is expired" do
|
||||||
|
it "responds to downloads with status 401 Unauthorized" do
|
||||||
|
user.update!(password_expires_at: 2.days.ago)
|
||||||
|
|
||||||
|
download(path, user: user.username, password: user.password) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds to uploads with status 401 Unauthorized" do
|
||||||
|
user.update!(password_expires_at: 2.days.ago)
|
||||||
|
|
||||||
|
upload(path, user: user.username, password: user.password) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
shared_examples 'pushes require Basic HTTP Authentication' do
|
shared_examples 'pushes require Basic HTTP Authentication' do
|
||||||
context "when no credentials are provided" do
|
context "when no credentials are provided" do
|
||||||
it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do
|
it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do
|
||||||
|
@ -95,15 +105,6 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
|
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when password is expired" do
|
|
||||||
it "responds to uploads with status 401 Unauthorized" do
|
|
||||||
user.update!(password_expires_at: 2.days.ago)
|
|
||||||
upload(path, user: user.username, password: user.password) do |response|
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when authentication succeeds" do
|
context "when authentication succeeds" do
|
||||||
|
@ -212,6 +213,7 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||||
|
it_behaves_like 'operations are not allowed with expired password'
|
||||||
|
|
||||||
context 'when authenticated' do
|
context 'when authenticated' do
|
||||||
it 'rejects downloads and uploads with 404 Not Found' do
|
it 'rejects downloads and uploads with 404 Not Found' do
|
||||||
|
@ -306,6 +308,7 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||||
|
it_behaves_like 'operations are not allowed with expired password'
|
||||||
|
|
||||||
context 'when authenticated' do
|
context 'when authenticated' do
|
||||||
context 'and as a developer on the team' do
|
context 'and as a developer on the team' do
|
||||||
|
@ -473,6 +476,7 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||||
|
it_behaves_like 'operations are not allowed with expired password'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'but the repo is enabled' do
|
context 'but the repo is enabled' do
|
||||||
|
@ -488,6 +492,7 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||||
|
it_behaves_like 'operations are not allowed with expired password'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -508,6 +513,7 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||||
|
it_behaves_like 'operations are not allowed with expired password'
|
||||||
|
|
||||||
context "when username and password are provided" do
|
context "when username and password are provided" do
|
||||||
let(:env) { { user: user.username, password: 'nope' } }
|
let(:env) { { user: user.username, password: 'nope' } }
|
||||||
|
@ -1003,6 +1009,24 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
it_behaves_like 'pulls are allowed'
|
it_behaves_like 'pulls are allowed'
|
||||||
it_behaves_like 'pushes are allowed'
|
it_behaves_like 'pushes are allowed'
|
||||||
|
|
||||||
|
context "when password is expired" do
|
||||||
|
it "responds to downloads with status 200" do
|
||||||
|
user.update!(password_expires_at: 2.days.ago)
|
||||||
|
|
||||||
|
download(path, user: user.username, password: user.password) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds to uploads with status 200" do
|
||||||
|
user.update!(password_expires_at: 2.days.ago)
|
||||||
|
|
||||||
|
upload(path, user: user.username, password: user.password) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,14 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe IdeController do
|
RSpec.describe IdeController do
|
||||||
let_it_be(:project) { create(:project, :public) }
|
let_it_be(:reporter) { create(:user) }
|
||||||
|
|
||||||
|
let_it_be(:project) do
|
||||||
|
create(:project, :private).tap do |p|
|
||||||
|
p.add_reporter(reporter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
let_it_be(:creator) { project.creator }
|
let_it_be(:creator) { project.creator }
|
||||||
let_it_be(:other_user) { create(:user) }
|
let_it_be(:other_user) { create(:user) }
|
||||||
|
|
||||||
|
@ -14,48 +21,62 @@ RSpec.describe IdeController do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'increases the views counter' do
|
|
||||||
expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
|
|
||||||
|
|
||||||
get ide_url
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#index', :aggregate_failures do
|
describe '#index', :aggregate_failures do
|
||||||
subject { get route }
|
subject { get route }
|
||||||
|
|
||||||
shared_examples 'user cannot push code' do
|
shared_examples 'user access rights check' do
|
||||||
include ProjectForksHelper
|
context 'user can read project' do
|
||||||
|
it 'increases the views counter' do
|
||||||
|
expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
|
||||||
|
|
||||||
let(:user) { other_user }
|
|
||||||
|
|
||||||
context 'when user does not have fork' do
|
|
||||||
it 'instantiates fork_info instance var with fork_path and return 200' do
|
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
|
||||||
expect(assigns(:project)).to eq project
|
|
||||||
expect(assigns(:fork_info)).to eq({ fork_path: controller.helpers.ide_fork_and_edit_path(project, branch, '', with_notice: false) })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has nil fork_info if user cannot fork' do
|
context 'user can read project but cannot push code' do
|
||||||
project.project_feature.update!(forking_access_level: ProjectFeature::DISABLED)
|
include ProjectForksHelper
|
||||||
|
|
||||||
subject
|
let(:user) { reporter }
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
context 'when user does not have fork' do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
it 'instantiates fork_info instance var with fork_path and returns 200' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(assigns(:project)).to eq project
|
||||||
|
expect(assigns(:fork_info)).to eq({ fork_path: controller.helpers.ide_fork_and_edit_path(project, branch, '', with_notice: false) })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has nil fork_info if user cannot fork' do
|
||||||
|
project.project_feature.update!(forking_access_level: ProjectFeature::DISABLED)
|
||||||
|
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(assigns(:fork_info)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has fork' do
|
||||||
|
let!(:fork) { fork_project(project, user, repository: true, namespace: user.namespace) }
|
||||||
|
|
||||||
|
it 'instantiates fork_info instance var with ide_path and returns 200' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(assigns(:project)).to eq project
|
||||||
|
expect(assigns(:fork_info)).to eq({ ide_path: controller.helpers.ide_edit_path(fork, branch, '') })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user has fork' do
|
context 'user cannot read project' do
|
||||||
let!(:fork) { fork_project(project, user, repository: true, namespace: user.namespace) }
|
let(:user) { other_user }
|
||||||
|
|
||||||
it 'instantiates fork_info instance var with ide_path and return 200' do
|
it 'returns 404' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
expect(assigns(:project)).to eq project
|
|
||||||
expect(assigns(:fork_info)).to eq({ ide_path: controller.helpers.ide_edit_path(fork, branch, '') })
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -63,37 +84,27 @@ RSpec.describe IdeController do
|
||||||
context '/-/ide' do
|
context '/-/ide' do
|
||||||
let(:route) { '/-/ide' }
|
let(:route) { '/-/ide' }
|
||||||
|
|
||||||
it 'does not instantiate any instance var and return 200' do
|
it 'returns 404' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
expect(assigns(:project)).to be_nil
|
|
||||||
expect(assigns(:branch)).to be_nil
|
|
||||||
expect(assigns(:path)).to be_nil
|
|
||||||
expect(assigns(:merge_request)).to be_nil
|
|
||||||
expect(assigns(:fork_info)).to be_nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context '/-/ide/project' do
|
context '/-/ide/project' do
|
||||||
let(:route) { '/-/ide/project' }
|
let(:route) { '/-/ide/project' }
|
||||||
|
|
||||||
it 'does not instantiate any instance var and return 200' do
|
it 'returns 404' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
expect(assigns(:project)).to be_nil
|
|
||||||
expect(assigns(:branch)).to be_nil
|
|
||||||
expect(assigns(:path)).to be_nil
|
|
||||||
expect(assigns(:merge_request)).to be_nil
|
|
||||||
expect(assigns(:fork_info)).to be_nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context '/-/ide/project/:project' do
|
context '/-/ide/project/:project' do
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}" }
|
let(:route) { "/-/ide/project/#{project.full_path}" }
|
||||||
|
|
||||||
it 'instantiates project instance var and return 200' do
|
it 'instantiates project instance var and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -104,13 +115,13 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
|
|
||||||
%w(edit blob tree).each do |action|
|
%w(edit blob tree).each do |action|
|
||||||
context "/-/ide/project/:project/#{action}" do
|
context "/-/ide/project/:project/#{action}" do
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}/#{action}" }
|
let(:route) { "/-/ide/project/#{project.full_path}/#{action}" }
|
||||||
|
|
||||||
it 'instantiates project instance var and return 200' do
|
it 'instantiates project instance var and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -121,13 +132,13 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
|
|
||||||
context "/-/ide/project/:project/#{action}/:branch" do
|
context "/-/ide/project/:project/#{action}/:branch" do
|
||||||
let(:branch) { 'master' }
|
let(:branch) { 'master' }
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}" }
|
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}" }
|
||||||
|
|
||||||
it 'instantiates project and branch instance vars and return 200' do
|
it 'instantiates project and branch instance vars and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -138,13 +149,13 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
|
|
||||||
context "/-/ide/project/:project/#{action}/:branch/-" do
|
context "/-/ide/project/:project/#{action}/:branch/-" do
|
||||||
let(:branch) { 'branch/slash' }
|
let(:branch) { 'branch/slash' }
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-" }
|
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-" }
|
||||||
|
|
||||||
it 'instantiates project and branch instance vars and return 200' do
|
it 'instantiates project and branch instance vars and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -155,13 +166,13 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
|
|
||||||
context "/-/ide/project/:project/#{action}/:branch/-/:path" do
|
context "/-/ide/project/:project/#{action}/:branch/-/:path" do
|
||||||
let(:branch) { 'master' }
|
let(:branch) { 'master' }
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-/foo/.bar" }
|
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-/foo/.bar" }
|
||||||
|
|
||||||
it 'instantiates project, branch, and path instance vars and return 200' do
|
it 'instantiates project, branch, and path instance vars and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -172,7 +183,7 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -184,7 +195,7 @@ RSpec.describe IdeController do
|
||||||
|
|
||||||
let(:route) { "/-/ide/project/#{project.full_path}/merge_requests/#{merge_request.id}" }
|
let(:route) { "/-/ide/project/#{project.full_path}/merge_requests/#{merge_request.id}" }
|
||||||
|
|
||||||
it 'instantiates project and merge_request instance vars and return 200' do
|
it 'instantiates project and merge_request instance vars and returns 200' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
@ -195,7 +206,7 @@ RSpec.describe IdeController do
|
||||||
expect(assigns(:fork_info)).to be_nil
|
expect(assigns(:fork_info)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'user cannot push code'
|
it_behaves_like 'user access rights check'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,12 +68,12 @@ RSpec.describe FeatureFlags::CreateService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates audit event' do
|
it 'creates audit event' do
|
||||||
expected_message = 'Created feature flag <strong>feature_flag</strong> '\
|
expected_message = 'Created feature flag feature_flag '\
|
||||||
'with description <strong>"description"</strong>. '\
|
'with description "description". '\
|
||||||
'Created rule <strong>*</strong> and set it as <strong>active</strong> '\
|
'Created rule * and set it as active '\
|
||||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>. '\
|
'with strategies [{"name"=>"default", "parameters"=>{}}]. '\
|
||||||
'Created rule <strong>production</strong> and set it as <strong>inactive</strong> '\
|
'Created rule production and set it as inactive '\
|
||||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>.'
|
'with strategies [{"name"=>"default", "parameters"=>{}}].'
|
||||||
|
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(AuditEvent.last.details[:custom_message]).to eq(expected_message)
|
expect(AuditEvent.last.details[:custom_message]).to eq(expected_message)
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe FeatureFlags::DestroyService do
|
||||||
|
|
||||||
it 'creates audit log' do
|
it 'creates audit log' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to eq("Deleted feature flag <strong>#{feature_flag.name}</strong>.")
|
expect(audit_event_message).to eq("Deleted feature flag #{feature_flag.name}.")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is reporter' do
|
context 'when user is reporter' do
|
||||||
|
|
|
@ -38,9 +38,9 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
|
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to(
|
expect(audit_event_message).to(
|
||||||
eq("Updated feature flag <strong>new_name</strong>. "\
|
eq("Updated feature flag new_name. "\
|
||||||
"Updated name from <strong>\"#{name_was}\"</strong> "\
|
"Updated name from \"#{name_was}\" "\
|
||||||
"to <strong>\"new_name\"</strong>.")
|
"to \"new_name\".")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
it 'creates audit event with changed description' do
|
it 'creates audit event with changed description' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to(
|
expect(audit_event_message).to(
|
||||||
include("Updated description from <strong>\"\"</strong>"\
|
include("Updated description from \"\""\
|
||||||
" to <strong>\"new description\"</strong>.")
|
" to \"new description\".")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -110,7 +110,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
it 'creates audit event about changing active state' do
|
it 'creates audit event about changing active state' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to(
|
expect(audit_event_message).to(
|
||||||
include('Updated active from <strong>"true"</strong> to <strong>"false"</strong>.')
|
include('Updated active from "true" to "false".')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -132,8 +132,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
it 'creates audit event about changing active state' do
|
it 'creates audit event about changing active state' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to(
|
expect(audit_event_message).to(
|
||||||
include("Updated rule <strong>*</strong> active state "\
|
include("Updated rule * active state "\
|
||||||
"from <strong>true</strong> to <strong>false</strong>.")
|
"from true to false.")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -149,8 +149,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
it 'creates audit event with changed name' do
|
it 'creates audit event with changed name' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to(
|
expect(audit_event_message).to(
|
||||||
include("Updated rule <strong>staging</strong> environment scope "\
|
include("Updated rule staging environment scope "\
|
||||||
"from <strong>review</strong> to <strong>staging</strong>.")
|
"from review to staging.")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
|
|
||||||
it 'creates audit event with deleted scope' do
|
it 'creates audit event with deleted scope' do
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to include("Deleted rule <strong>review</strong>.")
|
expect(audit_event_message).to include("Deleted rule review.")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when scope can not be deleted' do
|
context 'when scope can not be deleted' do
|
||||||
|
@ -210,8 +210,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates audit event with new scope' do
|
it 'creates audit event with new scope' do
|
||||||
expected = 'Created rule <strong>review</strong> and set it as <strong>active</strong> '\
|
expected = 'Created rule review and set it as active '\
|
||||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>.'
|
'with strategies [{"name"=>"default", "parameters"=>{}}].'
|
||||||
|
|
||||||
subject
|
subject
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates an audit event' do
|
it 'creates an audit event' do
|
||||||
expected = %r{Updated rule <strong>sandbox</strong> strategies from <strong>.*</strong> to <strong>.*</strong>.}
|
expected = %r{Updated rule sandbox strategies from .* to .*.}
|
||||||
|
|
||||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||||
expect(audit_event_message).to match(expected)
|
expect(audit_event_message).to match(expected)
|
||||||
|
|
|
@ -184,14 +184,6 @@ RSpec.describe Projects::ForkService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'GitLab CI is enabled' do
|
|
||||||
it "forks and enables CI for fork" do
|
|
||||||
@from_project.enable_ci
|
|
||||||
@to_project = fork_project(@from_project, @to_user, using_service: true)
|
|
||||||
expect(@to_project.builds_enabled?).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "CI/CD settings" do
|
context "CI/CD settings" do
|
||||||
let(:to_project) { fork_project(@from_project, @to_user, using_service: true) }
|
let(:to_project) { fork_project(@from_project, @to_user, using_service: true) }
|
||||||
|
|
||||||
|
@ -366,6 +358,19 @@ RSpec.describe Projects::ForkService do
|
||||||
|
|
||||||
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
|
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'copies project features visibility settings to the fork', :aggregate_failures do
|
||||||
|
attrs = ProjectFeature::FEATURES.to_h do |f|
|
||||||
|
["#{f}_access_level", ProjectFeature::PRIVATE]
|
||||||
|
end
|
||||||
|
|
||||||
|
public_project.project_feature.update!(attrs)
|
||||||
|
|
||||||
|
user = create(:user, developer_projects: [public_project])
|
||||||
|
forked_project = described_class.new(public_project, user).execute
|
||||||
|
|
||||||
|
expect(forked_project.project_feature.slice(attrs.keys)).to eq(attrs)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ RSpec.describe Repositories::ChangelogService do
|
||||||
recorder = ActiveRecord::QueryRecorder.new { service.execute }
|
recorder = ActiveRecord::QueryRecorder.new { service.execute }
|
||||||
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
|
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
|
||||||
|
|
||||||
expect(recorder.count).to eq(11)
|
expect(recorder.count).to eq(12)
|
||||||
expect(changelog).to include('Title 1', 'Title 2')
|
expect(changelog).to include('Title 1', 'Title 2')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -396,8 +396,13 @@ module GraphqlHelpers
|
||||||
post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers
|
post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_graphql(query, current_user: nil, variables: nil, headers: {}, token: {})
|
def get_multiplex(queries, current_user: nil, headers: {})
|
||||||
params = { query: query, variables: serialize_variables(variables) }
|
path = "/?#{queries.to_query('_json')}"
|
||||||
|
get api(path, current_user, version: 'graphql'), headers: headers
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_graphql(query, current_user: nil, variables: nil, headers: {}, token: {}, params: {})
|
||||||
|
params = params.merge(query: query, variables: serialize_variables(variables))
|
||||||
post api('/', current_user, version: 'graphql', **token), params: params, headers: headers
|
post api('/', current_user, version: 'graphql', **token), params: params, headers: headers
|
||||||
|
|
||||||
return unless graphql_errors
|
return unless graphql_errors
|
||||||
|
@ -406,6 +411,18 @@ module GraphqlHelpers
|
||||||
expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error'))
|
expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_graphql(query, current_user: nil, variables: nil, headers: {}, token: {}, params: {})
|
||||||
|
vars = "variables=#{CGI.escape(serialize_variables(variables))}" if variables
|
||||||
|
params = params.to_a.map { |k, v| v.to_query(k) }
|
||||||
|
path = ["/?query=#{CGI.escape(query)}", vars, *params].join('&')
|
||||||
|
get api(path, current_user, version: 'graphql', **token), headers: headers
|
||||||
|
|
||||||
|
return unless graphql_errors
|
||||||
|
|
||||||
|
# Errors are acceptable, but not this one:
|
||||||
|
expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error'))
|
||||||
|
end
|
||||||
|
|
||||||
def post_graphql_mutation(mutation, current_user: nil, token: {})
|
def post_graphql_mutation(mutation, current_user: nil, token: {})
|
||||||
post_graphql(mutation.query,
|
post_graphql(mutation.query,
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
|
|
Loading…
Reference in a new issue