New upstream version 13.12.6+ds1
This commit is contained in:
parent
3b6cf8664c
commit
3eea648134
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
|
||||
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)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
|
|
@ -1 +1 @@
|
|||
13.12.4
|
||||
13.12.6
|
6
Gemfile
6
Gemfile
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'rails', '~> 6.0.3.6'
|
||||
gem 'rails', '~> 6.0.3.7'
|
||||
|
||||
gem 'bootsnap', '~> 1.4.6'
|
||||
|
||||
|
@ -157,7 +157,7 @@ gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
|||
gem 'commonmarker', '~> 0.21'
|
||||
gem 'kramdown', '~> 2.3.1'
|
||||
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 'creole', '~> 0.5.0'
|
||||
gem 'wikicloth', '0.8.1'
|
||||
|
@ -168,7 +168,7 @@ gem 'asciidoctor-kroki', '~> 0.4.0', require: false
|
|||
gem 'rouge', '~> 3.26.0'
|
||||
gem 'truncato', '~> 0.7.11'
|
||||
gem 'bootstrap_form', '~> 4.2.0'
|
||||
gem 'nokogiri', '~> 1.11.1'
|
||||
gem 'nokogiri', '~> 1.11.4'
|
||||
gem 'escape_utils', '~> 1.1'
|
||||
|
||||
# Calendar rendering
|
||||
|
|
110
Gemfile.lock
110
Gemfile.lock
|
@ -12,59 +12,59 @@ GEM
|
|||
abstract_type (0.0.7)
|
||||
acme-client (2.0.6)
|
||||
faraday (>= 0.17, < 2.0.0)
|
||||
actioncable (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
actioncable (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
activejob (= 6.0.3.6)
|
||||
activerecord (= 6.0.3.6)
|
||||
activestorage (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
actionmailbox (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
actionview (= 6.0.3.6)
|
||||
activejob (= 6.0.3.6)
|
||||
actionmailer (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3.6)
|
||||
actionview (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
actionpack (6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
activerecord (= 6.0.3.6)
|
||||
activestorage (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
actiontext (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
actionview (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
activejob (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
activerecord (6.0.3.6)
|
||||
activemodel (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
activemodel (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
activerecord (6.0.3.7)
|
||||
activemodel (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
activerecord-explain-analyze (0.1.0)
|
||||
activerecord (>= 4)
|
||||
pg
|
||||
activestorage (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
activejob (= 6.0.3.6)
|
||||
activerecord (= 6.0.3.6)
|
||||
activestorage (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (6.0.3.6)
|
||||
activesupport (6.0.3.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -483,6 +483,7 @@ GEM
|
|||
addressable (~> 2.7)
|
||||
omniauth (~> 1.9)
|
||||
openid_connect (~> 1.2)
|
||||
gitlab-rdoc (6.3.2)
|
||||
gitlab-sidekiq-fetcher (0.5.6)
|
||||
sidekiq (~> 5)
|
||||
gitlab-styles (6.2.0)
|
||||
|
@ -781,7 +782,7 @@ GEM
|
|||
netrc (0.11.0)
|
||||
nio4r (2.5.4)
|
||||
no_proxy_fix (0.1.2)
|
||||
nokogiri (1.11.3)
|
||||
nokogiri (1.11.4)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
racc (~> 1.4)
|
||||
nokogumbo (2.0.2)
|
||||
|
@ -962,20 +963,20 @@ GEM
|
|||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-timeout (0.5.2)
|
||||
rails (6.0.3.6)
|
||||
actioncable (= 6.0.3.6)
|
||||
actionmailbox (= 6.0.3.6)
|
||||
actionmailer (= 6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
actiontext (= 6.0.3.6)
|
||||
actionview (= 6.0.3.6)
|
||||
activejob (= 6.0.3.6)
|
||||
activemodel (= 6.0.3.6)
|
||||
activerecord (= 6.0.3.6)
|
||||
activestorage (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
rails (6.0.3.7)
|
||||
actioncable (= 6.0.3.7)
|
||||
actionmailbox (= 6.0.3.7)
|
||||
actionmailer (= 6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
actiontext (= 6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activemodel (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3.6)
|
||||
railties (= 6.0.3.7)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -989,9 +990,9 @@ GEM
|
|||
rails-i18n (6.0.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 7)
|
||||
railties (6.0.3.6)
|
||||
actionpack (= 6.0.3.6)
|
||||
activesupport (= 6.0.3.6)
|
||||
railties (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
|
@ -1008,7 +1009,6 @@ GEM
|
|||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
rdoc (6.1.2)
|
||||
re2 (1.2.0)
|
||||
recaptcha (4.13.1)
|
||||
json
|
||||
|
@ -1485,6 +1485,7 @@ DEPENDENCIES
|
|||
gitlab-markup (~> 1.7.1)
|
||||
gitlab-net-dns (~> 0.9.1)
|
||||
gitlab-omniauth-openid-connect (~> 0.4.0)
|
||||
gitlab-rdoc (~> 6.3.2)
|
||||
gitlab-sidekiq-fetcher (= 0.5.6)
|
||||
gitlab-styles (~> 6.2.0)
|
||||
gitlab_chronic_duration (~> 0.10.6.2)
|
||||
|
@ -1544,7 +1545,7 @@ DEPENDENCIES
|
|||
net-ldap (~> 0.16.3)
|
||||
net-ntp
|
||||
net-ssh (~> 6.0)
|
||||
nokogiri (~> 1.11.1)
|
||||
nokogiri (~> 1.11.4)
|
||||
oauth2 (~> 1.4)
|
||||
octokit (~> 4.15)
|
||||
ohai (~> 16.10)
|
||||
|
@ -1587,14 +1588,13 @@ DEPENDENCIES
|
|||
rack-oauth2 (~> 1.16.0)
|
||||
rack-proxy (~> 0.6.0)
|
||||
rack-timeout (~> 0.5.1)
|
||||
rails (~> 6.0.3.6)
|
||||
rails (~> 6.0.3.7)
|
||||
rails-controller-testing
|
||||
rails-i18n (~> 6.0)
|
||||
rainbow (~> 3.0)
|
||||
raindrops (~> 0.18)
|
||||
rblineprof (~> 0.3.6)
|
||||
rbtrace (~> 0.4)
|
||||
rdoc (~> 6.1.2)
|
||||
re2 (~> 1.2.0)
|
||||
recaptcha (~> 4.11)
|
||||
redis (~> 4.0)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
13.12.4
|
||||
13.12.6
|
|
@ -1,4 +1,5 @@
|
|||
import $ from 'jquery';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import { getSelectedFragment, insertText } from '~/lib/utils/common_utils';
|
||||
|
||||
export class CopyAsGFM {
|
||||
|
@ -69,7 +70,7 @@ export class CopyAsGFM {
|
|||
} else {
|
||||
// Due to the async copy call we are not able to produce gfm so we transform the cached HTML
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = gfmHtml;
|
||||
div.innerHTML = sanitize(gfmHtml);
|
||||
CopyAsGFM.nodeToGFM(div)
|
||||
.then((transformedGfm) => {
|
||||
CopyAsGFM.insertPastedText(e.target, text, transformedGfm);
|
||||
|
|
|
@ -535,3 +535,27 @@ export function getURLOrigin(url) {
|
|||
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 { mapState, mapActions, mapGetters } from 'vuex';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import { isSameOriginUrl } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
|
||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||
|
@ -65,7 +66,13 @@ export default {
|
|||
},
|
||||
},
|
||||
cancelPath() {
|
||||
return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
|
||||
const backUrl = getParameterByName(BACK_URL_PARAM);
|
||||
|
||||
if (isSameOriginUrl(backUrl)) {
|
||||
return backUrl;
|
||||
}
|
||||
|
||||
return this.releasesPagePath;
|
||||
},
|
||||
saveButtonLabel() {
|
||||
return this.isExistingRelease ? __('Save changes') : __('Create release');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -208,7 +208,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
|
||||
params[:application_setting][:import_sources]&.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)
|
||||
|
||||
|
@ -217,9 +220,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
params.delete(:domain_denylist_raw) if params[:domain_denylist]
|
||||
params.delete(:domain_allowlist_raw) if params[:domain_allowlist]
|
||||
|
||||
params.require(:application_setting).permit(
|
||||
visible_application_setting_attributes
|
||||
)
|
||||
params[:application_setting].permit(visible_application_setting_attributes)
|
||||
end
|
||||
|
||||
def recheck_user_consent?
|
||||
|
|
|
@ -108,7 +108,7 @@ module MembershipActions
|
|||
|
||||
respond_to do |format|
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -20,12 +20,16 @@ class GraphqlController < ApplicationController
|
|||
# around in GraphiQL.
|
||||
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 :authorize_access_api!
|
||||
before_action :set_user_last_activity
|
||||
before_action :track_vs_code_usage
|
||||
before_action :disable_query_limiting
|
||||
|
||||
before_action :disallow_mutations_for_get
|
||||
|
||||
# Since we deactivate authentication from the main ApplicationController and
|
||||
# defer it to :authorize_access_api!, we need to override the bypass session
|
||||
# callback execution order here
|
||||
|
@ -62,6 +66,25 @@ class GraphqlController < ApplicationController
|
|||
|
||||
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
|
||||
def disable_query_limiting
|
||||
return unless Gitlab::QueryLimiting.enabled_for_env?
|
||||
|
@ -130,7 +153,9 @@ class GraphqlController < ApplicationController
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
# Overridden from the ApplicationController to make the response look like
|
||||
|
|
|
@ -7,6 +7,8 @@ class IdeController < ApplicationController
|
|||
include StaticObjectExternalStorageCSP
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
before_action :authorize_read_project!
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:build_service_proxy)
|
||||
push_frontend_feature_flag(:schema_linting)
|
||||
|
@ -22,6 +24,10 @@ class IdeController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def authorize_read_project!
|
||||
render_404 unless can?(current_user, :read_project, project)
|
||||
end
|
||||
|
||||
def define_index_vars
|
||||
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::Namespace::PackageSettings::Update
|
||||
mount_mutation Mutations::UserCallouts::Create
|
||||
mount_mutation Mutations::Echo
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ class AuditEvent < ApplicationRecord
|
|||
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
|
||||
|
||||
after_initialize :initialize_details
|
||||
|
||||
before_validation :sanitize_message
|
||||
|
||||
# Note: The intention is to remove this once refactoring of AuditEvent
|
||||
# has proceeded further.
|
||||
#
|
||||
|
@ -83,6 +86,14 @@ class AuditEvent < ApplicationRecord
|
|||
|
||||
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
|
||||
::Gitlab::Audit::NullAuthor.for(author_id, (self[:author_name] || details[:author_name]))
|
||||
end
|
||||
|
|
|
@ -173,6 +173,7 @@ module Integrations
|
|||
|
||||
query_params[:os_authType] = 'basic'
|
||||
params[:basic_auth] = basic_auth
|
||||
params[:use_read_total_timeout] = true
|
||||
params
|
||||
end
|
||||
|
||||
|
|
|
@ -50,9 +50,11 @@ class DroneCiService < CiService
|
|||
end
|
||||
|
||||
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,
|
||||
extra_log_info: { project_id: project_id })
|
||||
extra_log_info: { project_id: project_id },
|
||||
use_read_total_timeout: true)
|
||||
|
||||
status =
|
||||
if response && response.code == 200 && response['status']
|
||||
|
|
|
@ -38,7 +38,7 @@ class ExternalWikiService < Integration
|
|||
end
|
||||
|
||||
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
|
||||
rescue StandardError
|
||||
nil
|
||||
|
|
|
@ -106,7 +106,7 @@ class IssueTrackerService < Integration
|
|||
result = false
|
||||
|
||||
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
|
||||
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)
|
||||
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)
|
||||
rescue Errno::ECONNREFUSED
|
||||
:error
|
||||
|
|
|
@ -17,7 +17,7 @@ module SlackMattermost
|
|||
class HTTPClient
|
||||
def self.post(uri, params = {})
|
||||
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
|
||||
|
|
|
@ -169,7 +169,7 @@ class TeamcityService < CiService
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def post_to_build_queue(data, branch)
|
||||
|
@ -179,7 +179,8 @@ class TeamcityService < CiService
|
|||
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
|
||||
'</build>',
|
||||
headers: { 'Content-type' => 'application/xml' },
|
||||
basic_auth: basic_auth
|
||||
basic_auth: basic_auth,
|
||||
use_read_total_timeout: true
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ class UnifyCircuitService < ChatNotificationService
|
|||
response = Gitlab::HTTP.post(webhook, body: {
|
||||
subject: message.project_name,
|
||||
text: message.summary,
|
||||
markdown: true
|
||||
markdown: true,
|
||||
use_read_total_timeout: true
|
||||
}.to_json)
|
||||
|
||||
response if response.success?
|
||||
|
|
|
@ -43,7 +43,7 @@ class WebexTeamsService < ChatNotificationService
|
|||
|
||||
def notify(message, opts)
|
||||
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?
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ class ProtectedBranch::PushAccessLevel < ApplicationRecord
|
|||
|
||||
def check_access(user)
|
||||
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
|
||||
|
||||
super
|
||||
|
|
|
@ -238,6 +238,7 @@ class User < ApplicationRecord
|
|||
validate :owns_commit_email, if: :commit_email_changed?
|
||||
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_username_format, if: :username_changed?
|
||||
|
||||
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
|
||||
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
|
||||
|
@ -1255,12 +1256,23 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def sanitize_attrs
|
||||
sanitize_links
|
||||
sanitize_name
|
||||
end
|
||||
|
||||
def sanitize_links
|
||||
%i[skype linkedin twitter].each do |attr|
|
||||
value = self[attr]
|
||||
self[attr] = Sanitize.clean(value) if value.present?
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_name
|
||||
return unless self.name
|
||||
|
||||
self.name = self.name.gsub(%r{</?[^>]*>}, '')
|
||||
end
|
||||
|
||||
def set_notification_email
|
||||
if notification_email.blank? || all_emails.exclude?(notification_email)
|
||||
self.notification_email = email
|
||||
|
@ -1873,6 +1885,12 @@ class User < ApplicationRecord
|
|||
!!(password_expires_at && password_expires_at < Time.current)
|
||||
end
|
||||
|
||||
def password_expired_if_applicable?
|
||||
return false unless allow_password_authentication?
|
||||
|
||||
password_expired?
|
||||
end
|
||||
|
||||
def can_be_deactivated?
|
||||
active? && no_recent_activity? && !internal?
|
||||
end
|
||||
|
@ -2066,6 +2084,12 @@ class User < ApplicationRecord
|
|||
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
|
||||
project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS]
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ module PolicyActor
|
|||
false
|
||||
end
|
||||
|
||||
def password_expired?
|
||||
def password_expired_if_applicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ class GlobalPolicy < BasePolicy
|
|||
end
|
||||
|
||||
condition(:password_expired, scope: :user) do
|
||||
@user&.password_expired?
|
||||
@user&.password_expired_if_applicable?
|
||||
end
|
||||
|
||||
condition(:project_bot, scope: :user) { @user&.project_bot? }
|
||||
|
|
|
@ -49,9 +49,9 @@ module FeatureFlags
|
|||
end
|
||||
|
||||
def created_scope_message(scope)
|
||||
"Created rule <strong>#{scope.environment_scope}</strong> "\
|
||||
"and set it as <strong>#{scope.active ? "active" : "inactive"}</strong> "\
|
||||
"with strategies <strong>#{scope.strategies}</strong>."
|
||||
"Created rule #{scope.environment_scope} "\
|
||||
"and set it as #{scope.active ? "active" : "inactive"} "\
|
||||
"with strategies #{scope.strategies}."
|
||||
end
|
||||
|
||||
def feature_flag_by_name
|
||||
|
|
|
@ -22,8 +22,7 @@ module FeatureFlags
|
|||
private
|
||||
|
||||
def audit_message(feature_flag)
|
||||
message_parts = ["Created feature flag <strong>#{feature_flag.name}</strong>",
|
||||
"with description <strong>\"#{feature_flag.description}\"</strong>."]
|
||||
message_parts = ["Created feature flag #{feature_flag.name} with description \"#{feature_flag.description}\"."]
|
||||
|
||||
message_parts += feature_flag.scopes.map do |scope|
|
||||
created_scope_message(scope)
|
||||
|
|
|
@ -23,7 +23,7 @@ module FeatureFlags
|
|||
end
|
||||
|
||||
def audit_message(feature_flag)
|
||||
"Deleted feature flag <strong>#{feature_flag.name}</strong>."
|
||||
"Deleted feature flag #{feature_flag.name}."
|
||||
end
|
||||
|
||||
def can_destroy?(feature_flag)
|
||||
|
|
|
@ -45,14 +45,14 @@ module FeatureFlags
|
|||
|
||||
return if changes.empty?
|
||||
|
||||
"Updated feature flag <strong>#{feature_flag.name}</strong>. " + changes.join(" ")
|
||||
"Updated feature flag #{feature_flag.name}. " + changes.join(" ")
|
||||
end
|
||||
|
||||
def changed_attributes_messages(feature_flag)
|
||||
feature_flag.changes.slice(*AUDITABLE_ATTRIBUTES).map do |attribute_name, changes|
|
||||
"Updated #{attribute_name} "\
|
||||
"from <strong>\"#{changes.first}\"</strong> to "\
|
||||
"<strong>\"#{changes.second}\"</strong>."
|
||||
"from \"#{changes.first}\" to "\
|
||||
"\"#{changes.second}\"."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -69,17 +69,17 @@ module FeatureFlags
|
|||
end
|
||||
|
||||
def deleted_scope_message(scope)
|
||||
"Deleted rule <strong>#{scope.environment_scope}</strong>."
|
||||
"Deleted rule #{scope.environment_scope}."
|
||||
end
|
||||
|
||||
def updated_scope_message(scope)
|
||||
changes = scope.changes.slice(*AUDITABLE_SCOPE_ATTRIBUTES_HUMAN_NAMES.keys)
|
||||
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|
|
||||
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(' ')
|
||||
|
||||
message + '.'
|
||||
|
|
|
@ -34,8 +34,9 @@ module Projects
|
|||
new_project = CreateService.new(current_user, new_fork_params).execute
|
||||
return new_project unless new_project.persisted?
|
||||
|
||||
builds_access_level = @project.project_feature.builds_access_level
|
||||
new_project.project_feature.update(builds_access_level: builds_access_level)
|
||||
new_project.project_feature.update!(
|
||||
@project.project_feature.slice(ProjectFeature::FEATURES.map { |f| "#{f}_access_level" })
|
||||
)
|
||||
|
||||
new_project
|
||||
end
|
||||
|
|
|
@ -41,6 +41,7 @@ class WebHookService
|
|||
@hook_name = hook_name.to_s
|
||||
@request_options = {
|
||||
timeout: Gitlab.config.gitlab.webhook_timeout,
|
||||
use_read_total_timeout: true,
|
||||
allow_local_requests: hook.allow_local_requests?
|
||||
}
|
||||
end
|
||||
|
@ -67,7 +68,7 @@ class WebHookService
|
|||
{
|
||||
status: :success,
|
||||
http_status: response.code,
|
||||
message: response.to_s
|
||||
message: response.body
|
||||
}
|
||||
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
||||
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- 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, {})
|
||||
- filterable = local_assigns.fetch(:filterable, true)
|
||||
- paginatable = local_assigns.fetch(:paginatable, false)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
.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
|
||||
- assignees = issuable.assignees
|
||||
- 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]
|
||||
|
||||
%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="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`
|
||||
|
||||
Input type: `EnvironmentsCanaryIngressUpdateInput`
|
||||
|
|
|
@ -96,6 +96,8 @@ module API
|
|||
end
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
post ":id/members" do
|
||||
::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/333434')
|
||||
|
||||
source = find_source(source_type, params[:id])
|
||||
authorize_admin_source!(source_type, source)
|
||||
|
||||
|
|
|
@ -382,7 +382,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -23,6 +23,8 @@ module Gitlab
|
|||
"Your primary email address is not confirmed. "\
|
||||
"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}"
|
||||
when :blocked
|
||||
"Your account has been blocked."
|
||||
when :password_expired
|
||||
"Your password expired. "\
|
||||
"Please access GitLab from a web browser to update your password."
|
||||
|
@ -44,6 +46,8 @@ module Gitlab
|
|||
:deactivated
|
||||
elsif !@user.confirmed?
|
||||
:unconfirmed
|
||||
elsif @user.blocked?
|
||||
:blocked
|
||||
elsif @user.password_expired?
|
||||
:password_expired
|
||||
else
|
||||
|
|
|
@ -250,7 +250,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def diffable?
|
||||
repository.attributes(file_path).fetch('diff') { true }
|
||||
diffable_by_attribute? && !text_with_binary_notice?
|
||||
end
|
||||
|
||||
def binary_in_repo?
|
||||
|
@ -366,6 +366,15 @@ module Gitlab
|
|||
|
||||
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)
|
||||
return unless sha
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
include Enumerable
|
||||
|
||||
def parse(lines, diff_file: nil)
|
||||
return [] if lines.blank?
|
||||
return [] if lines.blank? || Git::Diff.has_binary_notice?(lines.first)
|
||||
|
||||
@lines = lines
|
||||
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
|
||||
|
||||
BINARY_NOTICE_PATTERN = %r(Binary files a\/(.*) and b\/(.*) differ).freeze
|
||||
|
||||
class << self
|
||||
def between(repo, head, base, options = {}, *paths)
|
||||
straight = options.delete(:straight) || false
|
||||
|
@ -131,8 +133,13 @@ module Gitlab
|
|||
def patch_hard_limit_bytes
|
||||
Gitlab::CurrentSettings.diff_max_patch_bytes
|
||||
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)
|
||||
@expanded = expanded
|
||||
|
||||
|
@ -215,7 +222,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def has_binary_notice?
|
||||
@diff.start_with?('Binary')
|
||||
self.class.has_binary_notice?(@diff)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,9 +8,10 @@ module Gitlab
|
|||
class HTTP
|
||||
BlockedUrlError = Class.new(StandardError)
|
||||
RedirectionTooDeep = Class.new(StandardError)
|
||||
ReadTotalTimeout = Class.new(Net::ReadTimeout)
|
||||
|
||||
HTTP_TIMEOUT_ERRORS = [
|
||||
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
|
||||
Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Gitlab::HTTP::ReadTotalTimeout
|
||||
].freeze
|
||||
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
|
||||
SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
|
||||
|
@ -23,6 +24,7 @@ module Gitlab
|
|||
read_timeout: 20,
|
||||
write_timeout: 30
|
||||
}.freeze
|
||||
DEFAULT_READ_TOTAL_TIMEOUT = 20.seconds
|
||||
|
||||
include HTTParty # rubocop:disable Gitlab/HTTParty
|
||||
|
||||
|
@ -41,7 +43,19 @@ module Gitlab
|
|||
options
|
||||
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
|
||||
raise RedirectionTooDeep
|
||||
rescue *HTTP_ERRORS => e
|
||||
|
|
|
@ -52,7 +52,7 @@ module Gitlab
|
|||
def valid_user?
|
||||
return true unless user?
|
||||
|
||||
!actor.blocked? && (!actor.allow_password_authentication? || !actor.password_expired?)
|
||||
!actor.blocked? && !actor.password_expired_if_applicable?
|
||||
end
|
||||
|
||||
def authentication_payload(repository_http_path)
|
||||
|
|
|
@ -14015,6 +14015,9 @@ msgstr ""
|
|||
msgid "File renamed with no changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "File suppressed by a .gitattributes entry or the file's encoding is unsupported."
|
||||
msgstr ""
|
||||
|
||||
msgid "File synchronization concurrency limit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -33097,9 +33100,6 @@ msgstr ""
|
|||
msgid "This diff is collapsed."
|
||||
msgstr ""
|
||||
|
||||
msgid "This diff was suppressed by a .gitattributes entry."
|
||||
msgstr ""
|
||||
|
||||
msgid "This directory"
|
||||
msgstr ""
|
||||
|
||||
|
@ -38357,6 +38357,9 @@ msgstr ""
|
|||
msgid "encrypted: needs to be a :required, :optional or :migrating!"
|
||||
msgstr ""
|
||||
|
||||
msgid "ending with MIME type format is not allowed."
|
||||
msgstr ""
|
||||
|
||||
msgid "entries cannot be larger than 255 characters"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ RSpec.describe GraphqlController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
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
|
||||
# * When user is internal(like ghost users)
|
||||
# * When user is blocked
|
||||
|
@ -54,7 +54,9 @@ RSpec.describe GraphqlController do
|
|||
post :execute
|
||||
|
||||
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
|
||||
|
||||
it 'updates the users last_activity_on field' do
|
||||
|
|
|
@ -55,9 +55,9 @@ module DeprecationToolkitEnv
|
|||
# one by one
|
||||
def self.allowed_kwarg_warning_paths
|
||||
%w[
|
||||
activerecord-6.0.3.6/lib/active_record/migration.rb
|
||||
activesupport-6.0.3.6/lib/active_support/cache.rb
|
||||
activerecord-6.0.3.6/lib/active_record/relation.rb
|
||||
activerecord-6.0.3.7/lib/active_record/migration.rb
|
||||
activesupport-6.0.3.7/lib/active_support/cache.rb
|
||||
activerecord-6.0.3.7/lib/active_record/relation.rb
|
||||
asciidoctor-2.0.12/lib/asciidoctor/extensions.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')
|
||||
|
||||
# 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('too_large_image.jpg')
|
||||
find('.note-textarea')
|
||||
|
|
|
@ -174,4 +174,14 @@ RSpec.describe 'Diff file viewer', :js, :with_clean_rails_cache do
|
|||
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
|
||||
|
|
|
@ -65,18 +65,6 @@ RSpec.describe 'Comments on personal snippets', :js do
|
|||
expect(page).to have_content(user_name)
|
||||
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
|
||||
|
||||
context 'when submitting a note' do
|
||||
|
|
|
@ -1,50 +1,54 @@
|
|||
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
|
||||
describe('CopyAsGFM', () => {
|
||||
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 = {
|
||||
originalEvent: {
|
||||
clipboardData: {
|
||||
getData(mimeType) {
|
||||
// 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.
|
||||
if (mimeType === 'text/plain') {
|
||||
return 'code';
|
||||
}
|
||||
if (mimeType === 'text/x-gfm') {
|
||||
return '`code`';
|
||||
}
|
||||
return null;
|
||||
return data[mimeType] || null;
|
||||
},
|
||||
},
|
||||
},
|
||||
preventDefault() {},
|
||||
target,
|
||||
};
|
||||
|
||||
CopyAsGFM.pasteGFM(e);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
expect(target.value).toBe('This is code: `code`');
|
||||
});
|
||||
|
||||
it('does not wrap pasted code when already in code tags', () => {
|
||||
jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
|
||||
const insertedText = textFunc('This is code: `', '`');
|
||||
|
||||
expect(insertedText).toEqual('code');
|
||||
});
|
||||
target.value = 'This is code: `';
|
||||
|
||||
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';
|
||||
|
||||
const shas = {
|
||||
|
@ -921,4 +922,37 @@ describe('URL utility', () => {
|
|||
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 Vuex from 'vuex';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
import ReleaseEditNewApp from '~/releases/components/app_edit_new.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 originalMilestones = originalRelease.milestones;
|
||||
const releasesPagePath = 'path/to/releases/page';
|
||||
|
||||
describe('Release edit/new component', () => {
|
||||
let wrapper;
|
||||
|
@ -24,7 +26,7 @@ describe('Release edit/new component', () => {
|
|||
state = {
|
||||
release,
|
||||
markdownDocsPath: 'path/to/markdown/docs',
|
||||
releasesPagePath: 'path/to/releases/page',
|
||||
releasesPagePath,
|
||||
projectId: '8',
|
||||
groupId: '42',
|
||||
groupMilestonesAvailable: true,
|
||||
|
@ -75,6 +77,8 @@ describe('Release edit/new component', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
global.jsdom.reconfigure({ url: TEST_HOST });
|
||||
|
||||
mock = new MockAdapter(axios);
|
||||
gon.api_version = 'v4';
|
||||
|
||||
|
@ -146,22 +150,33 @@ describe('Release edit/new component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(`when the URL contains a "${BACK_URL_PARAM}" parameter`, () => {
|
||||
const backUrl = 'https://example.gitlab.com/back/url';
|
||||
// eslint-disable-next-line no-script-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 () => {
|
||||
commonUtils.getParameterByName = jest
|
||||
.fn()
|
||||
.mockImplementation((paramToGet) => ({ [BACK_URL_PARAM]: backUrl }[paramToGet]));
|
||||
await factory();
|
||||
});
|
||||
|
||||
await factory();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
it(`renders a "Cancel" button with an href pointing to ${expectedHref}`, () => {
|
||||
const cancelButton = wrapper.find('.js-cancel-button');
|
||||
expect(cancelButton.attributes().href).toBe(expectedHref);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('when creating a new release', () => {
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
|
|||
|
||||
it 'does not highlight files marked as undiffable in .gitattributes' do
|
||||
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
|
||||
|
||||
expect_next_instance_of(Gitlab::Diff::File) do |instance|
|
||||
|
|
|
@ -186,26 +186,46 @@ RSpec.describe Gitlab::Diff::File do
|
|||
end
|
||||
|
||||
describe '#diffable?' do
|
||||
let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
|
||||
let(:diffs) { commit.diffs }
|
||||
context 'when attributes exist' do
|
||||
let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
|
||||
let(:diffs) { commit.diffs }
|
||||
|
||||
before do
|
||||
info_dir_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||
File.join(project.repository.path_to_repo, 'info')
|
||||
before do
|
||||
info_dir_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||
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
|
||||
|
||||
FileUtils.mkdir(info_dir_path) unless File.exist?(info_dir_path)
|
||||
File.write(File.join(info_dir_path, 'attributes'), "*.md -diff\n")
|
||||
it "returns true for files that do not have attributes" do
|
||||
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
|
||||
|
||||
it "returns true for files that do not have attributes" do
|
||||
diff_file = diffs.diff_file_with_new_path('LICENSE')
|
||||
expect(diff_file.diffable?).to be_truthy
|
||||
context 'when the text has binary notice' do
|
||||
let(:commit) { project.commit('f05a98786e4274708e1fa118c7ad3a29d1d1b9a3') }
|
||||
let(:diff_file) { commit.diffs.diff_file_with_new_path('VERSION') }
|
||||
|
||||
it "returns false" do
|
||||
expect(diff_file.diffable?).to be_falsey
|
||||
end
|
||||
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
|
||||
context 'when the content is binary' do
|
||||
let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
|
||||
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
|
||||
|
||||
|
@ -729,6 +749,18 @@ RSpec.describe Gitlab::Diff::File do
|
|||
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
|
||||
context 'when first line is a match' do
|
||||
let(:raw_diff) do
|
||||
|
|
|
@ -146,6 +146,16 @@ eos
|
|||
it { expect(parser.parse(nil)).to eq([]) }
|
||||
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
|
||||
it "counts lines correctly" do
|
||||
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.")
|
||||
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
|
||||
before do
|
||||
project.add_guest(user)
|
||||
|
@ -977,12 +985,26 @@ RSpec.describe Gitlab::GitAccess do
|
|||
end
|
||||
|
||||
it 'disallows users with expired password to push' do
|
||||
project.add_maintainer(user)
|
||||
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.")
|
||||
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
|
||||
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
|
||||
expect { push_access_check }.not_to raise_error
|
||||
|
|
|
@ -27,6 +27,47 @@ RSpec.describe Gitlab::HTTP do
|
|||
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
|
||||
before do
|
||||
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe AuditEvent do
|
||||
let_it_be(:audit_event) { create(:project_audit_event) }
|
||||
subject { audit_event }
|
||||
|
||||
describe 'validations' do
|
||||
include_examples 'validates IP address' do
|
||||
let(:attribute) { :ip_address }
|
||||
|
@ -13,6 +10,15 @@ RSpec.describe AuditEvent do
|
|||
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
|
||||
context 'ip_address' do
|
||||
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 }
|
||||
|
||||
before_all do
|
||||
project.add_guest(user)
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
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'])
|
||||
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
|
||||
|
||||
it 'has a DB-level NOT NULL constraint on projects_limit' do
|
||||
|
@ -2877,7 +2890,7 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
expect { user.sanitize_attrs }.not_to change { user.name }
|
||||
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
|
||||
|
||||
describe '#starred?' do
|
||||
|
@ -5248,6 +5280,70 @@ RSpec.describe User do
|
|||
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
|
||||
context 'when synced attributes metadata is present' 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) }
|
||||
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
|
||||
before do
|
||||
current_user.update!(password_expires_at: 2.minutes.ago)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
context 'when terms are enforced' do
|
||||
|
@ -433,6 +449,14 @@ RSpec.describe GlobalPolicy do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
@ -517,6 +541,14 @@ RSpec.describe GlobalPolicy do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
@ -46,6 +46,8 @@ RSpec.describe 'Setting assignees of a merge request' do
|
|||
end
|
||||
|
||||
def run_mutation!
|
||||
post_graphql_mutation(mutation, current_user: current_user) # warm-up
|
||||
|
||||
recorder = ActiveRecord::QueryRecorder.new do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
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
|
||||
let(:current_user) { create(:user) }
|
||||
let(:db_query_limit) { 27 }
|
||||
let(:db_query_limit) { 28 }
|
||||
|
||||
it 'does not change the assignees' do
|
||||
project.add_guest(current_user)
|
||||
|
@ -80,7 +82,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
|||
end
|
||||
|
||||
context 'with assignees already assigned' do
|
||||
let(:db_query_limit) { 39 }
|
||||
let(:db_query_limit) { 46 }
|
||||
|
||||
before do
|
||||
merge_request.assignees = [assignee2]
|
||||
|
@ -96,7 +98,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
|||
end
|
||||
|
||||
context 'when passing an empty list of assignees' do
|
||||
let(:db_query_limit) { 31 }
|
||||
let(:db_query_limit) { 33 }
|
||||
let(:input) { { assignee_usernames: [] } }
|
||||
|
||||
before do
|
||||
|
@ -115,7 +117,7 @@ RSpec.describe 'Setting assignees of a merge request' do
|
|||
context 'when passing append as true' do
|
||||
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
|
||||
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
|
||||
let(:db_query_limit) { 20 }
|
||||
let(:db_query_limit) { 22 }
|
||||
|
||||
before do
|
||||
# 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
|
||||
|
||||
context 'when passing remove as true' do
|
||||
let(:db_query_limit) { 31 }
|
||||
let(:db_query_limit) { 33 }
|
||||
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
|
||||
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
|
||||
let(:expected_result) { [] }
|
||||
|
|
|
@ -6,6 +6,9 @@ RSpec.describe 'GraphQL' do
|
|||
include AfterNextHelpers
|
||||
|
||||
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
|
||||
shared_examples 'logging a graphql query' do
|
||||
|
@ -70,6 +73,139 @@ RSpec.describe 'GraphQL' do
|
|||
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
|
||||
it 'returns an error' do
|
||||
post_graphql(query, variables: "This is not JSON")
|
||||
|
@ -80,8 +216,6 @@ RSpec.describe 'GraphQL' do
|
|||
end
|
||||
|
||||
describe 'authentication', :allow_forgery_protection do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'allows access to public data without authentication' do
|
||||
post_graphql(query)
|
||||
|
||||
|
@ -109,11 +243,9 @@ RSpec.describe 'GraphQL' do
|
|||
context 'with token authentication' do
|
||||
let(:token) { create(:personal_access_token) }
|
||||
|
||||
before do
|
||||
stub_authentication_activity_metrics(debug: false)
|
||||
end
|
||||
|
||||
it 'authenticates users with a PAT' do
|
||||
stub_authentication_activity_metrics(debug: false)
|
||||
|
||||
expect(authentication_metrics)
|
||||
.to increment(:user_authenticated_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")
|
||||
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
|
||||
it 'does not log the user in' do
|
||||
token.update!(scopes: [:read_user])
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::ImportBitbucketServer do
|
||||
let(:base_uri) { "https://test:7990" }
|
||||
let(:user) { create(:user) }
|
||||
let(:user) { create(:user, bio: 'test') }
|
||||
let(:token) { "asdasd12345" }
|
||||
let(:secret) { "sekrettt" }
|
||||
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(:project2, reload: true) { create(:project, namespace: user.namespace) }
|
||||
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
|
||||
create(:project,
|
||||
:private,
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::Users do
|
||||
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(:gpg_key) { create(:gpg_key, user: user) }
|
||||
let_it_be(:email) { create(:email, user: user) }
|
||||
|
|
|
@ -36,16 +36,6 @@ RSpec.describe 'Git HTTP requests' do
|
|||
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
|
||||
let(:user) { create(:user, :blocked) }
|
||||
|
||||
|
@ -68,6 +58,26 @@ RSpec.describe 'Git HTTP requests' do
|
|||
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
|
||||
context "when no credentials are provided" 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 ')
|
||||
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
|
||||
|
||||
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 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
|
||||
context 'when authenticated' 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 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
|
||||
context 'when authenticated' 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 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
end
|
||||
|
||||
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 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -508,6 +513,7 @@ RSpec.describe 'Git HTTP requests' do
|
|||
|
||||
it_behaves_like 'pulls 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
|
||||
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 '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
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
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(:other_user) { create(:user) }
|
||||
|
||||
|
@ -14,48 +21,62 @@ RSpec.describe IdeController do
|
|||
sign_in(user)
|
||||
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
|
||||
subject { get route }
|
||||
|
||||
shared_examples 'user cannot push code' do
|
||||
include ProjectForksHelper
|
||||
shared_examples 'user access rights check' do
|
||||
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
|
||||
|
||||
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)
|
||||
context 'user can read project but cannot push code' do
|
||||
include ProjectForksHelper
|
||||
|
||||
subject
|
||||
let(:user) { reporter }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:fork_info)).to be_nil
|
||||
context 'when user does not have fork' do
|
||||
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
|
||||
|
||||
context 'when user has fork' do
|
||||
let!(:fork) { fork_project(project, user, repository: true, namespace: user.namespace) }
|
||||
context 'user cannot read project' do
|
||||
let(:user) { other_user }
|
||||
|
||||
it 'instantiates fork_info instance var with ide_path and return 200' do
|
||||
it 'returns 404' 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, '') })
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -63,37 +84,27 @@ RSpec.describe IdeController do
|
|||
context '/-/ide' do
|
||||
let(:route) { '/-/ide' }
|
||||
|
||||
it 'does not instantiate any instance var and return 200' do
|
||||
it 'returns 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
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
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context '/-/ide/project' do
|
||||
let(:route) { '/-/ide/project' }
|
||||
|
||||
it 'does not instantiate any instance var and return 200' do
|
||||
it 'returns 404' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
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
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context '/-/ide/project/:project' do
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -104,13 +115,13 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
|
||||
%w(edit blob tree).each do |action|
|
||||
context "/-/ide/project/:project/#{action}" do
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -121,13 +132,13 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
|
||||
context "/-/ide/project/:project/#{action}/:branch" do
|
||||
let(:branch) { 'master' }
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -138,13 +149,13 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
|
||||
context "/-/ide/project/:project/#{action}/:branch/-" do
|
||||
let(:branch) { 'branch/slash' }
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -155,13 +166,13 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
|
||||
context "/-/ide/project/:project/#{action}/:branch/-/:path" do
|
||||
let(:branch) { 'master' }
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -172,7 +183,7 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -184,7 +195,7 @@ RSpec.describe IdeController do
|
|||
|
||||
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
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -195,7 +206,7 @@ RSpec.describe IdeController do
|
|||
expect(assigns(:fork_info)).to be_nil
|
||||
end
|
||||
|
||||
it_behaves_like 'user cannot push code'
|
||||
it_behaves_like 'user access rights check'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,12 +68,12 @@ RSpec.describe FeatureFlags::CreateService do
|
|||
end
|
||||
|
||||
it 'creates audit event' do
|
||||
expected_message = 'Created feature flag <strong>feature_flag</strong> '\
|
||||
'with description <strong>"description"</strong>. '\
|
||||
'Created rule <strong>*</strong> and set it as <strong>active</strong> '\
|
||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>. '\
|
||||
'Created rule <strong>production</strong> and set it as <strong>inactive</strong> '\
|
||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>.'
|
||||
expected_message = 'Created feature flag feature_flag '\
|
||||
'with description "description". '\
|
||||
'Created rule * and set it as active '\
|
||||
'with strategies [{"name"=>"default", "parameters"=>{}}]. '\
|
||||
'Created rule production and set it as inactive '\
|
||||
'with strategies [{"name"=>"default", "parameters"=>{}}].'
|
||||
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(AuditEvent.last.details[:custom_message]).to eq(expected_message)
|
||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe FeatureFlags::DestroyService do
|
|||
|
||||
it 'creates audit log' do
|
||||
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
|
||||
|
||||
context 'when user is reporter' do
|
||||
|
|
|
@ -38,9 +38,9 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(audit_event_message).to(
|
||||
eq("Updated feature flag <strong>new_name</strong>. "\
|
||||
"Updated name from <strong>\"#{name_was}\"</strong> "\
|
||||
"to <strong>\"new_name\"</strong>.")
|
||||
eq("Updated feature flag new_name. "\
|
||||
"Updated name from \"#{name_was}\" "\
|
||||
"to \"new_name\".")
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -94,8 +94,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
it 'creates audit event with changed description' do
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(audit_event_message).to(
|
||||
include("Updated description from <strong>\"\"</strong>"\
|
||||
" to <strong>\"new description\"</strong>.")
|
||||
include("Updated description from \"\""\
|
||||
" to \"new description\".")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -110,7 +110,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
it 'creates audit event about changing active state' do
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(audit_event_message).to(
|
||||
include('Updated active from <strong>"true"</strong> to <strong>"false"</strong>.')
|
||||
include('Updated active from "true" to "false".')
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -132,8 +132,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
it 'creates audit event about changing active state' do
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(audit_event_message).to(
|
||||
include("Updated rule <strong>*</strong> active state "\
|
||||
"from <strong>true</strong> to <strong>false</strong>.")
|
||||
include("Updated rule * active state "\
|
||||
"from true to false.")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -149,8 +149,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
it 'creates audit event with changed name' do
|
||||
expect { subject }.to change { AuditEvent.count }.by(1)
|
||||
expect(audit_event_message).to(
|
||||
include("Updated rule <strong>staging</strong> environment scope "\
|
||||
"from <strong>review</strong> to <strong>staging</strong>.")
|
||||
include("Updated rule staging environment scope "\
|
||||
"from review to staging.")
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -185,7 +185,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
|
||||
it 'creates audit event with deleted scope' do
|
||||
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
|
||||
|
||||
context 'when scope can not be deleted' do
|
||||
|
@ -210,8 +210,8 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
end
|
||||
|
||||
it 'creates audit event with new scope' do
|
||||
expected = 'Created rule <strong>review</strong> and set it as <strong>active</strong> '\
|
||||
'with strategies <strong>[{"name"=>"default", "parameters"=>{}}]</strong>.'
|
||||
expected = 'Created rule review and set it as active '\
|
||||
'with strategies [{"name"=>"default", "parameters"=>{}}].'
|
||||
|
||||
subject
|
||||
|
||||
|
@ -260,7 +260,7 @@ RSpec.describe FeatureFlags::UpdateService do
|
|||
end
|
||||
|
||||
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(audit_event_message).to match(expected)
|
||||
|
|
|
@ -184,14 +184,6 @@ RSpec.describe Projects::ForkService do
|
|||
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
|
||||
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)
|
||||
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
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ RSpec.describe Repositories::ChangelogService do
|
|||
recorder = ActiveRecord::QueryRecorder.new { service.execute }
|
||||
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')
|
||||
end
|
||||
|
||||
|
|
|
@ -396,8 +396,13 @@ module GraphqlHelpers
|
|||
post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers
|
||||
end
|
||||
|
||||
def post_graphql(query, current_user: nil, variables: nil, headers: {}, token: {})
|
||||
params = { query: query, variables: serialize_variables(variables) }
|
||||
def get_multiplex(queries, current_user: nil, headers: {})
|
||||
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
|
||||
|
||||
return unless graphql_errors
|
||||
|
@ -406,6 +411,18 @@ module GraphqlHelpers
|
|||
expect(graphql_errors).not_to include(a_hash_including('message' => 'Internal server error'))
|
||||
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: {})
|
||||
post_graphql(mutation.query,
|
||||
current_user: current_user,
|
||||
|
|
Loading…
Reference in a new issue