New upstream version 12.6.6
This commit is contained in:
parent
54d8419492
commit
a3564ef0fd
78 changed files with 1274 additions and 199 deletions
|
@ -1,5 +1,13 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
## 12.6.5
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.6.4
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.6.3
|
||||
|
||||
- No changes.
|
||||
|
|
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -2,6 +2,38 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 12.6.6
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Update workhorse to v8.20.0.
|
||||
|
||||
|
||||
## 12.6.5
|
||||
|
||||
### Security (19 changes, 1 of them is from the community)
|
||||
|
||||
- Update rack-cors to 1.0.6.
|
||||
- Update rdoc to 6.1.2.
|
||||
- Bump rubyzip to 2.0.0. (Utkarsh Gupta)
|
||||
- Cleanup todos for users from a removed linked group.
|
||||
- Disable access to last_pipeline in commits API for users without read permissions.
|
||||
- Add constraint to group dependency proxy endpoint param.
|
||||
- Limit number of AsciiDoc includes per document.
|
||||
- Prevent API access for unconfirmed users.
|
||||
- Enforce permission check when counting activity events.
|
||||
- Prevent gafana integration token from being displayed as a plain text to other project maintainers, by only displaying a masked version of it.
|
||||
- Fix xss on frequent groups dropdown.
|
||||
- Fix XSS vulnerability on custom project templates form.
|
||||
- Protect internal CI builds from external overrides.
|
||||
- ImportExport::ExportService to require admin_project permission.
|
||||
- Make sure that only system notes where all references are visible to user are exposed in GraphQL API.
|
||||
- Disable caching of repository/files/:file_path/raw API endpoint.
|
||||
- Make cross-repository comparisons happen in the source repository.
|
||||
- Update excon to 0.71.1 to fix CVE-2019-16779.
|
||||
- Add workhorse request verification to package upload endpoints.
|
||||
|
||||
|
||||
## 12.6.4
|
||||
|
||||
### Security (1 change)
|
||||
|
|
|
@ -1 +1 @@
|
|||
8.18.0
|
||||
8.20.0
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -65,7 +65,7 @@ gem 'u2f', '~> 0.2.1'
|
|||
|
||||
# GitLab Pages
|
||||
gem 'validates_hostname', '~> 1.0.6'
|
||||
gem 'rubyzip', '~> 1.3.0', require: 'zip'
|
||||
gem 'rubyzip', '~> 2.0.0', require: 'zip'
|
||||
# GitLab Pages letsencrypt support
|
||||
gem 'acme-client', '~> 2.0.2'
|
||||
|
||||
|
@ -142,7 +142,7 @@ gem 'gitlab-markup', '~> 1.7.0'
|
|||
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
||||
gem 'commonmarker', '~> 0.20'
|
||||
gem 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~> 6.0'
|
||||
gem 'rdoc', '~> 6.1.2'
|
||||
gem 'org-ruby', '~> 0.9.12'
|
||||
gem 'creole', '~> 0.5.0'
|
||||
gem 'wikicloth', '0.8.1'
|
||||
|
|
13
Gemfile.lock
13
Gemfile.lock
|
@ -260,7 +260,7 @@ GEM
|
|||
et-orbi (1.2.1)
|
||||
tzinfo
|
||||
eventmachine (1.2.7)
|
||||
excon (0.62.0)
|
||||
excon (0.71.1)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
extended-markdown-filter (0.6.0)
|
||||
|
@ -763,7 +763,8 @@ GEM
|
|||
rack (>= 0.4)
|
||||
rack-attack (6.2.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.0.2)
|
||||
rack-cors (1.0.6)
|
||||
rack (>= 1.6.0)
|
||||
rack-oauth2 (1.9.3)
|
||||
activesupport
|
||||
attr_required
|
||||
|
@ -820,7 +821,7 @@ GEM
|
|||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rdoc (6.0.4)
|
||||
rdoc (6.1.2)
|
||||
re2 (1.1.1)
|
||||
recaptcha (4.13.1)
|
||||
json
|
||||
|
@ -929,7 +930,7 @@ GEM
|
|||
sexp_processor (~> 4.9)
|
||||
rubyntlm (0.6.2)
|
||||
rubypants (0.2.0)
|
||||
rubyzip (1.3.0)
|
||||
rubyzip (2.0.0)
|
||||
rugged (0.28.4.1)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (4.6.6)
|
||||
|
@ -1299,7 +1300,7 @@ DEPENDENCIES
|
|||
raindrops (~> 0.18)
|
||||
rblineprof (~> 0.3.6)
|
||||
rbtrace (~> 0.4)
|
||||
rdoc (~> 6.0)
|
||||
rdoc (~> 6.1.2)
|
||||
re2 (~> 1.1.1)
|
||||
recaptcha (~> 4.11)
|
||||
redis (~> 4.0)
|
||||
|
@ -1323,7 +1324,7 @@ DEPENDENCIES
|
|||
ruby-prof (~> 1.0.0)
|
||||
ruby-progressbar
|
||||
ruby_parser (~> 3.8)
|
||||
rubyzip (~> 1.3.0)
|
||||
rubyzip (~> 2.0.0)
|
||||
rugged (~> 0.28)
|
||||
sanitize (~> 4.6)
|
||||
sassc-rails (~> 2.1.0)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
12.6.4
|
||||
12.6.6
|
||||
|
|
|
@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
|
|||
import eventHub from '../event_hub';
|
||||
import store from '../store/';
|
||||
import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants';
|
||||
import { isMobile, updateExistingFrequentItem } from '../utils';
|
||||
import { isMobile, updateExistingFrequentItem, sanitizeItem } from '../utils';
|
||||
import FrequentItemsSearchInput from './frequent_items_search_input.vue';
|
||||
import FrequentItemsList from './frequent_items_list.vue';
|
||||
import frequentItemsMixin from './frequent_items_mixin';
|
||||
|
@ -64,7 +64,9 @@ export default {
|
|||
this.fetchFrequentItems();
|
||||
}
|
||||
},
|
||||
logItemAccess(storageKey, item) {
|
||||
logItemAccess(storageKey, unsanitizedItem) {
|
||||
const item = sanitizeItem(unsanitizedItem);
|
||||
|
||||
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import FrequentItemsListItem from './frequent_items_list_item.vue';
|
||||
import frequentItemsMixin from './frequent_items_mixin';
|
||||
import { sanitizeItem } from '../utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -48,6 +49,9 @@ export default {
|
|||
? this.translations.itemListErrorMessage
|
||||
: this.translations.itemListEmptyMessage;
|
||||
},
|
||||
sanitizedItems() {
|
||||
return this.items.map(sanitizeItem);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -59,7 +63,7 @@ export default {
|
|||
{{ listEmptyMessage }}
|
||||
</li>
|
||||
<frequent-items-list-item
|
||||
v-for="item in items"
|
||||
v-for="item in sanitizedItems"
|
||||
v-else
|
||||
:key="item.id"
|
||||
:item-id="item.id"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'underscore';
|
||||
import bp from '~/breakpoints';
|
||||
import sanitize from 'sanitize-html';
|
||||
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
|
||||
|
||||
export const isMobile = () => {
|
||||
|
@ -47,3 +48,9 @@ export const updateExistingFrequentItem = (frequentItem, item) => {
|
|||
lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn,
|
||||
};
|
||||
};
|
||||
|
||||
export const sanitizeItem = item => ({
|
||||
...item,
|
||||
name: sanitize(item.name.toString(), { allowedTags: [] }),
|
||||
namespace: sanitize(item.namespace.toString(), { allowedTags: [] }),
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import Api from './api';
|
||||
import { escape } from 'lodash';
|
||||
import { normalizeHeaders } from './lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
|
@ -75,10 +76,12 @@ const groupsSelect = () => {
|
|||
}
|
||||
},
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
return `<div class='group-result'> <div class='group-name'>${escape(
|
||||
object.full_name,
|
||||
)}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection(object) {
|
||||
return object.full_name;
|
||||
return escape(object.full_name);
|
||||
},
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'fogbugz'
|
|||
|
||||
class ApplicationController < ActionController::Base
|
||||
include Gitlab::GonHelper
|
||||
include Gitlab::NoCacheHeaders
|
||||
include GitlabRoutingHelper
|
||||
include PageLayoutHelper
|
||||
include SafeParamsHelper
|
||||
|
@ -54,7 +55,6 @@ class ApplicationController < ActionController::Base
|
|||
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
|
||||
# concerns due to caching private data.
|
||||
DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store"
|
||||
DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache"
|
||||
|
||||
rescue_from Encoding::CompatibilityError do |exception|
|
||||
log_exception(exception)
|
||||
|
@ -246,9 +246,9 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def no_cache_headers
|
||||
headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE
|
||||
headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
|
||||
headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||
DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v|
|
||||
headers[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
def default_headers
|
||||
|
|
|
@ -19,7 +19,7 @@ class DashboardController < Dashboard::ApplicationController
|
|||
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count)
|
||||
pager_json('events/_events', @events.count { |event| event.visible_to_user?(current_user) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -37,6 +37,7 @@ class DashboardController < Dashboard::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
.map(&:present)
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events)
|
||||
end
|
||||
|
|
|
@ -91,7 +91,7 @@ class GroupsController < Groups::ApplicationController
|
|||
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count)
|
||||
pager_json("events/_events", @events.count { |event| event.visible_to_user?(current_user) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -209,8 +209,9 @@ class GroupsController < Groups::ApplicationController
|
|||
.includes(:namespace)
|
||||
|
||||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups)
|
||||
.to_a
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter, groups: groups)
|
||||
.to_a
|
||||
.map(&:present)
|
||||
|
||||
Events::RenderService
|
||||
.new(current_user)
|
||||
|
|
|
@ -119,7 +119,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
format.html
|
||||
format.json do
|
||||
load_events
|
||||
pager_json('events/_events', @events.count)
|
||||
pager_json('events/_events', @events.count { |event| event.visible_to_user?(current_user) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -340,6 +340,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
.map(&:present)
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
|
|
|
@ -368,8 +368,8 @@ module ProjectsHelper
|
|||
@project.grafana_integration&.grafana_url
|
||||
end
|
||||
|
||||
def grafana_integration_token
|
||||
@project.grafana_integration&.token
|
||||
def grafana_integration_masked_token
|
||||
@project.grafana_integration&.masked_token
|
||||
end
|
||||
|
||||
def grafana_integration_enabled?
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GenericCommitStatus < CommitStatus
|
||||
EXTERNAL_STAGE_IDX = 1_000_000
|
||||
|
||||
before_validation :set_default_values
|
||||
|
||||
validates :target_url, addressable_url: true,
|
||||
length: { maximum: 255 },
|
||||
allow_nil: true
|
||||
validate :name_uniqueness_across_types, unless: :importing?
|
||||
|
||||
# GitHub compatible API
|
||||
alias_attribute :context, :name
|
||||
|
@ -13,7 +16,7 @@ class GenericCommitStatus < CommitStatus
|
|||
def set_default_values
|
||||
self.context ||= 'default'
|
||||
self.stage ||= 'external'
|
||||
self.stage_idx ||= 1000000
|
||||
self.stage_idx ||= EXTERNAL_STAGE_IDX
|
||||
end
|
||||
|
||||
def tags
|
||||
|
@ -25,4 +28,14 @@ class GenericCommitStatus < CommitStatus
|
|||
.new(self, current_user)
|
||||
.fabricate!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def name_uniqueness_across_types
|
||||
return if !pipeline || name.blank?
|
||||
|
||||
if pipeline.statuses.by_name(name).where.not(type: type).exists?
|
||||
errors.add(:name, :taken)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,11 +8,13 @@ class GrafanaIntegration < ApplicationRecord
|
|||
algorithm: 'aes-256-gcm',
|
||||
key: Settings.attr_encrypted_db_key_base_32
|
||||
|
||||
before_validation :check_token_changes
|
||||
|
||||
validates :grafana_url,
|
||||
length: { maximum: 1024 },
|
||||
addressable_url: { enforce_sanitization: true, ascii_only: true }
|
||||
|
||||
validates :token, :project, presence: true
|
||||
validates :encrypted_token, :project, presence: true
|
||||
|
||||
validates :enabled, inclusion: { in: [true, false] }
|
||||
|
||||
|
@ -23,4 +25,28 @@ class GrafanaIntegration < ApplicationRecord
|
|||
|
||||
@client ||= ::Grafana::Client.new(api_url: grafana_url.chomp('/'), token: token)
|
||||
end
|
||||
|
||||
def masked_token
|
||||
mask(encrypted_token)
|
||||
end
|
||||
|
||||
def masked_token_was
|
||||
mask(encrypted_token_was)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def token
|
||||
decrypt(:token, encrypted_token)
|
||||
end
|
||||
|
||||
def check_token_changes
|
||||
return unless [encrypted_token_was, masked_token_was].include?(token)
|
||||
|
||||
clear_attribute_changes [:token, :encrypted_token, :encrypted_token_iv]
|
||||
end
|
||||
|
||||
def mask(token)
|
||||
token&.squish&.gsub(/./, '*')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -551,7 +551,8 @@ class Note < ApplicationRecord
|
|||
# if they are not equal, then there are private/confidential references as well
|
||||
user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count
|
||||
else
|
||||
referenced_mentionables(user).any?
|
||||
refs = all_references(user)
|
||||
refs.all.any? && refs.stateful_not_visible_counter == 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2333,6 +2333,10 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def template_source?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def closest_namespace_setting(name)
|
||||
|
|
|
@ -21,6 +21,14 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
with_options scope: :user, score: 0
|
||||
condition(:deactivated) { @user&.deactivated? }
|
||||
|
||||
desc "User email is unconfirmed or user account is locked"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:inactive) do
|
||||
Feature.enabled?(:inactive_policy_condition, default_enabled: true) &&
|
||||
@user &&
|
||||
!@user&.active_for_authentication?
|
||||
end
|
||||
|
||||
with_options scope: :user, score: 0
|
||||
condition(:external_user) { @user.nil? || @user.external? }
|
||||
|
||||
|
|
|
@ -36,6 +36,13 @@ class GlobalPolicy < BasePolicy
|
|||
enable :use_slash_commands
|
||||
end
|
||||
|
||||
rule { inactive }.policy do
|
||||
prevent :log_in
|
||||
prevent :access_api
|
||||
prevent :access_git
|
||||
prevent :use_slash_commands
|
||||
end
|
||||
|
||||
rule { blocked | internal }.policy do
|
||||
prevent :log_in
|
||||
prevent :access_api
|
||||
|
|
|
@ -3,6 +3,18 @@
|
|||
class EventPresenter < Gitlab::View::Presenter::Delegated
|
||||
presents :event
|
||||
|
||||
def initialize(subject, **attributes)
|
||||
super
|
||||
|
||||
@visible_to_user_cache = ActiveSupport::Cache::MemoryStore.new
|
||||
end
|
||||
|
||||
# Caching `visible_to_user?` method in the presenter beause it might be called multiple times.
|
||||
def visible_to_user?(user = nil)
|
||||
@visible_to_user_cache.fetch(user&.id) { super(user) }
|
||||
end
|
||||
|
||||
# implement cache here
|
||||
def resource_parent_name
|
||||
resource_parent&.full_name || ''
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ class CompareService
|
|||
return unless raw_compare && raw_compare.base && raw_compare.head
|
||||
|
||||
Compare.new(raw_compare,
|
||||
target_project,
|
||||
start_project,
|
||||
base_sha: base_sha,
|
||||
straight: straight)
|
||||
end
|
||||
|
|
|
@ -6,6 +6,12 @@ module Projects
|
|||
def execute(group_link)
|
||||
return false unless group_link
|
||||
|
||||
if group_link.project.private?
|
||||
TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
|
||||
else
|
||||
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
|
||||
end
|
||||
|
||||
group_link.destroy
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,12 @@ module Projects
|
|||
module ImportExport
|
||||
class ExportService < BaseService
|
||||
def execute(after_export_strategy = nil, options = {})
|
||||
unless project.template_source? || can?(current_user, :admin_project, project)
|
||||
raise ::Gitlab::ImportExport::Error.new(
|
||||
"User with ID: %s does not have permission to Project %s with ID: %s." %
|
||||
[current_user.id, project.name, project.id])
|
||||
end
|
||||
|
||||
@shared = project.import_export_shared
|
||||
|
||||
save_all!
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
|
||||
grafana_integration: { url: grafana_integration_url, token: grafana_integration_token, enabled: grafana_integration_enabled?.to_s } } }
|
||||
grafana_integration: { url: grafana_integration_url, token: grafana_integration_masked_token, enabled: grafana_integration_enabled?.to_s } } }
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
## master (unreleased)
|
||||
|
||||
## 1.6.0
|
||||
|
||||
- Added the `perf:app` command to compare commits within the same application. (https://github.com/schneems/derailed_benchmarks/pull/157)
|
||||
- Allow Rails < 7 and 1.0 <= Thor < 2 (https://github.com/schneems/derailed_benchmarks/pull/168)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
- Test `perf:library` results against 99% confidence interval in addition to 95% (https://github.com/schneems/derailed_benchmarks/pull/165)
|
||||
|
|
|
@ -415,13 +415,23 @@ or point it at your local copy:
|
|||
gem 'rails', path: "<path/to/your/local/copy/rails>"
|
||||
```
|
||||
|
||||
To run your test:
|
||||
To run your tests within the context of your current app/repo:
|
||||
|
||||
```
|
||||
$ bundle exec derailed exec perf:app
|
||||
```
|
||||
|
||||
This will automatically test the two latest commits of your library/current directory.
|
||||
|
||||
If you'd like to test the Rails library instead, make sure that `ENV[DERAILED_PATH_TO_LIBRARY]` is unset.
|
||||
|
||||
```
|
||||
$ bundle exec derailed exec perf:library
|
||||
```
|
||||
|
||||
This will automatically test the two latest commits of Rails (or the library you've specified). If you would like to compare against different SHAs you can manually specify them:
|
||||
This will automatically test the two latest commits of Rails.
|
||||
|
||||
If you would also like to compare against different SHAs you can manually specify them:
|
||||
|
||||
```
|
||||
$ SHAS_TO_TEST="7b4d80cb373e,13d6aa3a7b70" bundle exec derailed exec perf:library
|
||||
|
|
|
@ -28,12 +28,12 @@ Gem::Specification.new do |gem|
|
|||
gem.add_dependency "benchmark-ips", "~> 2"
|
||||
gem.add_dependency "rack", ">= 1"
|
||||
gem.add_dependency "rake", "> 10", "< 14"
|
||||
gem.add_dependency "thor", "~> 0.19"
|
||||
gem.add_dependency "thor", ">= 0.19", "< 2"
|
||||
gem.add_dependency "ruby-statistics", ">= 2.1"
|
||||
|
||||
gem.add_development_dependency "capybara", "~> 2"
|
||||
gem.add_development_dependency "m"
|
||||
gem.add_development_dependency "rails", "> 3", "<= 6"
|
||||
gem.add_development_dependency "rails", "> 3", "<= 7"
|
||||
gem.add_development_dependency "devise", "> 3", "< 6"
|
||||
gem.add_development_dependency "appraisal", "2.2.0"
|
||||
end
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
require_relative 'load_tasks'
|
||||
|
||||
namespace :perf do
|
||||
desc "runs the performance test against two most recent commits of the current app"
|
||||
task :app do
|
||||
ENV["DERAILED_PATH_TO_LIBRARY"] = '.'
|
||||
Rake::Task["perf:library"].invoke
|
||||
end
|
||||
|
||||
desc "runs the same test against two different branches for statistical comparison"
|
||||
task :library do
|
||||
begin
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DerailedBenchmarks
|
||||
VERSION = "1.5.0"
|
||||
VERSION = "1.6.0"
|
||||
end
|
||||
|
|
|
@ -13,6 +13,13 @@ class TasksTest < ActiveSupport::TestCase
|
|||
FileUtils.remove_entry_secure(rails_app_path('tmp'))
|
||||
end
|
||||
|
||||
def run!(cmd)
|
||||
puts "Running: #{cmd}"
|
||||
out = `#{cmd}`
|
||||
raise "Could not run #{cmd}, output: #{out}" unless $?.success?
|
||||
out
|
||||
end
|
||||
|
||||
def rake(cmd, options = {})
|
||||
assert_success = options.key?(:assert_success) ? options[:assert_success] : true
|
||||
env = options[:env] || {}
|
||||
|
@ -55,6 +62,13 @@ class TasksTest < ActiveSupport::TestCase
|
|||
rake "perf:test"
|
||||
end
|
||||
|
||||
test 'app' do
|
||||
skip unless ENV['USING_RAILS_GIT']
|
||||
run!("cd #{rails_app_path} && git init . && git add . && git commit -m first && git commit --allow-empty -m second")
|
||||
env = { "TEST_COUNT" => 10, "DERAILED_SCRIPT_COUNT" => 2 }
|
||||
puts rake "perf:app", { env: env }
|
||||
end
|
||||
|
||||
test 'TEST_COUNT' do
|
||||
result = rake "perf:test", env: { "TEST_COUNT" => 1 }
|
||||
assert_match "1 derailed requests", result
|
||||
|
|
|
@ -221,6 +221,11 @@ include::basics.adoc[]
|
|||
include::https://example.org/installation.adoc[]
|
||||
```
|
||||
|
||||
To guarantee good system performance and prevent malicious documents causing
|
||||
problems, GitLab enforces a **maximum limit** on the number of include directives
|
||||
processed in any one document. Currently a total of 32 documents can be
|
||||
included, a number that is inclusive of transitive dependencies.
|
||||
|
||||
### Blocks
|
||||
|
||||
```asciidoc
|
||||
|
|
|
@ -85,6 +85,8 @@ module API
|
|||
protected: @project.protected_for?(ref))
|
||||
end
|
||||
|
||||
authorize! :update_pipeline, pipeline
|
||||
|
||||
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
|
||||
project: @project,
|
||||
pipeline: pipeline,
|
||||
|
|
|
@ -154,7 +154,7 @@ module API
|
|||
|
||||
not_found! 'Commit' unless commit
|
||||
|
||||
present commit, with: Entities::CommitDetail, stats: params[:stats]
|
||||
present commit, with: Entities::CommitDetail, stats: params[:stats], current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Get the diff for a specific commit of a project' do
|
||||
|
|
|
@ -476,8 +476,18 @@ module API
|
|||
class CommitDetail < Commit
|
||||
expose :stats, using: Entities::CommitStats, if: :stats
|
||||
expose :status
|
||||
expose :last_pipeline, using: 'API::Entities::PipelineBasic'
|
||||
expose :project_id
|
||||
|
||||
expose :last_pipeline do |commit, options|
|
||||
pipeline = commit.last_pipeline if can_read_pipeline?
|
||||
::API::Entities::PipelineBasic.represent(pipeline, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_read_pipeline?
|
||||
Ability.allowed?(options[:current_user], :read_pipeline, object.last_pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
class CommitSignature < Grape::Entity
|
||||
|
|
|
@ -127,6 +127,7 @@ module API
|
|||
get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
|
||||
assign_file_vars!
|
||||
|
||||
no_cache_headers
|
||||
set_http_headers(blob_data)
|
||||
|
||||
send_git_blob @repo, @blob
|
||||
|
|
|
@ -256,11 +256,21 @@ module API
|
|||
end
|
||||
|
||||
def require_gitlab_workhorse!
|
||||
verify_workhorse_api!
|
||||
|
||||
unless env['HTTP_GITLAB_WORKHORSE'].present?
|
||||
forbidden!('Request should be executed via GitLab Workhorse')
|
||||
end
|
||||
end
|
||||
|
||||
def verify_workhorse_api!
|
||||
Gitlab::Workhorse.verify_api_request!(request.headers)
|
||||
rescue => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
|
||||
forbidden!
|
||||
end
|
||||
|
||||
def require_pages_enabled!
|
||||
not_found! unless user_project.pages_available?
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module API
|
||||
module Helpers
|
||||
module HeadersHelpers
|
||||
include Gitlab::NoCacheHeaders
|
||||
|
||||
def set_http_headers(header_data)
|
||||
header_data.each do |key, value|
|
||||
if value.is_a?(Enumerable)
|
||||
|
@ -12,6 +14,12 @@ module API
|
|||
header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def no_cache_headers
|
||||
DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v|
|
||||
header k, v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -201,12 +201,14 @@ module Banzai
|
|||
gather_references(nodes)
|
||||
end
|
||||
|
||||
# Gathers the references for the given HTML nodes.
|
||||
# Gathers the references for the given HTML nodes. Returns visible
|
||||
# references and a list of nodes which are not visible to the user
|
||||
def gather_references(nodes)
|
||||
nodes = nodes_user_can_reference(current_user, nodes)
|
||||
nodes = nodes_visible_to_user(current_user, nodes)
|
||||
visible = nodes_visible_to_user(current_user, nodes)
|
||||
not_visible = nodes - visible
|
||||
|
||||
referenced_by(nodes)
|
||||
{ visible: referenced_by(visible), not_visible: not_visible }
|
||||
end
|
||||
|
||||
# Returns a Hash containing the projects for a given list of HTML nodes.
|
||||
|
|
|
@ -11,6 +11,7 @@ module Gitlab
|
|||
# the resulting HTML through HTML pipeline filters.
|
||||
module Asciidoc
|
||||
MAX_INCLUDE_DEPTH = 5
|
||||
MAX_INCLUDES = 32
|
||||
DEFAULT_ADOC_ATTRS = {
|
||||
'showtitle' => true,
|
||||
'sectanchors' => true,
|
||||
|
@ -40,6 +41,7 @@ module Gitlab
|
|||
extensions: extensions }
|
||||
|
||||
context[:pipeline] = :ascii_doc
|
||||
context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
|
||||
|
||||
plantuml_setup
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@ module Gitlab
|
|||
super(logger: Gitlab::AppLogger)
|
||||
|
||||
@context = context
|
||||
@repository = context[:project].try(:repository)
|
||||
@repository = context[:repository] || context[:project].try(:repository)
|
||||
@max_includes = context[:max_includes].to_i
|
||||
@included = []
|
||||
|
||||
# Note: Asciidoctor calls #freeze on extensions, so we can't set new
|
||||
# instance variables after initialization.
|
||||
|
@ -28,8 +30,11 @@ module Gitlab
|
|||
def include_allowed?(target, reader)
|
||||
doc = reader.document
|
||||
|
||||
return false if doc.attributes.fetch('max-include-depth').to_i < 1
|
||||
max_include_depth = doc.attributes.fetch('max-include-depth').to_i
|
||||
|
||||
return false if max_include_depth < 1
|
||||
return false if target_uri?(target)
|
||||
return false if included.size >= max_includes
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -62,7 +67,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_accessor :context, :repository, :cache
|
||||
attr_reader :context, :repository, :cache, :max_includes, :included
|
||||
|
||||
# Gets a Blob at a path for a specific revision.
|
||||
# This method will check that the Blob exists and contains readable text.
|
||||
|
@ -77,6 +82,8 @@ module Gitlab
|
|||
raise 'Blob not found' unless blob
|
||||
raise 'File is not readable' unless blob.readable_text?
|
||||
|
||||
included << filename
|
||||
|
||||
blob
|
||||
end
|
||||
|
||||
|
|
56
lib/gitlab/git/cross_repo_comparer.rb
Normal file
56
lib/gitlab/git/cross_repo_comparer.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Git
|
||||
class CrossRepoComparer
|
||||
attr_reader :source_repo, :target_repo
|
||||
|
||||
def initialize(source_repo, target_repo)
|
||||
@source_repo = source_repo
|
||||
@target_repo = target_repo
|
||||
end
|
||||
|
||||
def compare(source_ref, target_ref, straight:)
|
||||
ensuring_ref_in_source(target_ref) do |target_commit_id|
|
||||
Gitlab::Git::Compare.new(
|
||||
source_repo,
|
||||
target_commit_id,
|
||||
source_ref,
|
||||
straight: straight
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensuring_ref_in_source(ref, &blk)
|
||||
return yield ref if source_repo == target_repo
|
||||
|
||||
# If the commit doesn't exist in the target, there's nothing we can do
|
||||
commit_id = target_repo.commit(ref)&.sha
|
||||
return unless commit_id
|
||||
|
||||
# The commit pointed to by ref may exist in the source even when they
|
||||
# are different repositories. This is particularly true of close forks,
|
||||
# but may also be the case if a temporary ref for this comparison has
|
||||
# already been created in the past, and the result hasn't been GC'd yet.
|
||||
return yield commit_id if source_repo.commit(commit_id)
|
||||
|
||||
# Worst case: the commit is not in the source repo so we need to fetch
|
||||
# it. Use a temporary ref and clean up afterwards
|
||||
with_commit_in_source_tmp(commit_id, &blk)
|
||||
end
|
||||
|
||||
# Fetch the ref into source_repo from target_repo, using a temporary ref
|
||||
# name that will be deleted once the method completes. This is a no-op if
|
||||
# fetching the source branch fails
|
||||
def with_commit_in_source_tmp(commit_id, &blk)
|
||||
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
|
||||
|
||||
yield commit_id if source_repo.fetch_source_branch!(target_repo, commit_id, tmp_ref)
|
||||
ensure
|
||||
source_repo.delete_refs(tmp_ref) # best-effort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -746,29 +746,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
|
||||
reachable_ref =
|
||||
if source_repository == self
|
||||
source_branch_name
|
||||
else
|
||||
# If a tmp ref was created before for a separate repo comparison (forks),
|
||||
# we're able to short-circuit the tmp ref re-creation:
|
||||
# 1. Take the SHA from the source repo
|
||||
# 2. Read that in the current "target" repo
|
||||
# 3. If that SHA is still known (readable), it means GC hasn't
|
||||
# cleaned it up yet, so we can use it instead re-writing the tmp ref.
|
||||
source_commit_id = source_repository.commit(source_branch_name)&.sha
|
||||
commit(source_commit_id)&.sha if source_commit_id
|
||||
end
|
||||
|
||||
return compare(target_branch_name, reachable_ref, straight: straight) if reachable_ref
|
||||
|
||||
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
|
||||
|
||||
return unless fetch_source_branch!(source_repository, source_branch_name, tmp_ref)
|
||||
|
||||
compare(target_branch_name, tmp_ref, straight: straight)
|
||||
ensure
|
||||
delete_refs(tmp_ref) if tmp_ref
|
||||
CrossRepoComparer
|
||||
.new(source_repository, self)
|
||||
.compare(source_branch_name, target_branch_name, straight: straight)
|
||||
end
|
||||
|
||||
def write_ref(ref_path, ref, old_ref: nil)
|
||||
|
@ -1045,13 +1025,6 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def compare(base_ref, head_ref, straight:)
|
||||
Gitlab::Git::Compare.new(self,
|
||||
base_ref,
|
||||
head_ref,
|
||||
straight: straight)
|
||||
end
|
||||
|
||||
def empty_diff_stats
|
||||
Gitlab::Git::DiffStatsCollection.new([])
|
||||
end
|
||||
|
|
15
lib/gitlab/no_cache_headers.rb
Normal file
15
lib/gitlab/no_cache_headers.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module NoCacheHeaders
|
||||
DEFAULT_GITLAB_NO_CACHE_HEADERS = {
|
||||
'Cache-Control' => "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store, no-cache",
|
||||
'Pragma' => 'no-cache', # HTTP 1.0 compatibility
|
||||
'Expires' => 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||
}.freeze
|
||||
|
||||
def no_cache_headers
|
||||
raise "#no_cache_headers is not implemented for this object"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,11 +6,16 @@ module Gitlab
|
|||
REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project
|
||||
merge_request snippet commit commit_range directly_addressed_user epic).freeze
|
||||
attr_accessor :project, :current_user, :author
|
||||
# This counter is increased by a number of references filtered out by
|
||||
# banzai reference exctractor. Note that this counter is stateful and
|
||||
# not idempotent and is increased whenever you call `references`.
|
||||
attr_reader :stateful_not_visible_counter
|
||||
|
||||
def initialize(project, current_user = nil)
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@references = {}
|
||||
@stateful_not_visible_counter = 0
|
||||
|
||||
super()
|
||||
end
|
||||
|
@ -20,11 +25,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def references(type)
|
||||
super(type, project, current_user)
|
||||
refs = super(type, project, current_user)
|
||||
@stateful_not_visible_counter += refs[:not_visible].count
|
||||
|
||||
refs[:visible]
|
||||
end
|
||||
|
||||
def reset_memoized_values
|
||||
@references = {}
|
||||
@stateful_not_visible_counter = 0
|
||||
super()
|
||||
end
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
"jszip": "^3.1.3",
|
||||
"jszip-utils": "^0.0.2",
|
||||
"katex": "^0.10.0",
|
||||
"lodash": "^4.17.15",
|
||||
"marked": "^0.3.12",
|
||||
"mermaid": "^8.4.2",
|
||||
"monaco-editor": "^0.15.6",
|
||||
|
|
|
@ -59,5 +59,48 @@ module QA
|
|||
a_hash_including(message: '202 Accepted')
|
||||
)
|
||||
end
|
||||
|
||||
describe 'raw file access' do
|
||||
let(:svg_file) do
|
||||
<<-SVG
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
|
||||
<script type="text/javascript">
|
||||
alert("surprise");
|
||||
</script>
|
||||
</svg>
|
||||
SVG
|
||||
end
|
||||
|
||||
it 'sets no-cache headers as expected' do
|
||||
create_project_request = Runtime::API::Request.new(@api_client, '/projects')
|
||||
post create_project_request.url, path: project_name, name: project_name
|
||||
|
||||
create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg")
|
||||
post create_file_request.url, branch: 'master', content: svg_file, commit_message: 'Add test.svg'
|
||||
|
||||
get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: 'master')
|
||||
|
||||
3.times do
|
||||
response = get get_file_request.url
|
||||
|
||||
# Subsequent responses aren't cached, so headers should match from
|
||||
# request to request, especially a 200 response rather than a 304
|
||||
# (indicating a cached response.) Further, :content_disposition
|
||||
# should include `attachment` for all responses.
|
||||
#
|
||||
expect(response.headers[:cache_control]).to include("no-store")
|
||||
expect(response.headers[:cache_control]).to include("no-cache")
|
||||
expect(response.headers[:pragma]).to eq("no-cache")
|
||||
expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
|
||||
expect(response.headers[:content_disposition]).to include("attachment")
|
||||
expect(response.headers[:content_disposition]).not_to include("inline")
|
||||
expect(response.headers[:content_type]).to include("image/svg+xml")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,47 @@ describe DashboardController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET activity as JSON" do
|
||||
render_views
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
|
||||
|
||||
before do
|
||||
create(:event, :created, project: project, target: create(:issue))
|
||||
|
||||
sign_in(user)
|
||||
|
||||
request.cookies[:event_filter] = 'all'
|
||||
end
|
||||
|
||||
context 'when user has permission to see the event' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns count' do
|
||||
get :activity, params: { format: :json }
|
||||
|
||||
expect(json_response['count']).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no permission to see the event' do
|
||||
it 'filters out invisible event' do
|
||||
get :activity, params: { format: :json }
|
||||
|
||||
expect(json_response['html']).to include(_('No activities found'))
|
||||
end
|
||||
|
||||
it 'filters out invisible event when calculating the count' do
|
||||
get :activity, params: { format: :json }
|
||||
|
||||
expect(json_response['count']).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
|
||||
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ describe GroupsController do
|
|||
|
||||
it 'assigns events for all the projects in the group', :sidekiq_might_not_need_inline do
|
||||
subject
|
||||
expect(assigns(:events)).to contain_exactly(event)
|
||||
expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -119,12 +119,12 @@ describe GroupsController do
|
|||
describe 'GET #activity' do
|
||||
render_views
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project
|
||||
end
|
||||
|
||||
context 'as json' do
|
||||
before do
|
||||
sign_in(user)
|
||||
project
|
||||
end
|
||||
|
||||
it 'includes events from all projects in group and subgroups', :sidekiq_might_not_need_inline do
|
||||
2.times do
|
||||
project = create(:project, group: group)
|
||||
|
@ -141,6 +141,31 @@ describe GroupsController do
|
|||
expect(assigns(:projects).limit_value).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no permission to see the event' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
|
||||
let(:project_with_restricted_access) do
|
||||
create(:project, :public, issues_access_level: ProjectFeature::PRIVATE, group: group)
|
||||
end
|
||||
|
||||
before do
|
||||
create(:event, project: project)
|
||||
create(:event, :created, project: project_with_restricted_access, target: create(:issue))
|
||||
|
||||
group.add_guest(user)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'filters out invisible event' do
|
||||
get :activity, params: { id: group.to_param }, format: :json
|
||||
|
||||
expect(json_response['count']).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
|
|
|
@ -64,6 +64,46 @@ describe ProjectsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET #activity as JSON" do
|
||||
render_views
|
||||
|
||||
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
|
||||
|
||||
before do
|
||||
create(:event, :created, project: project, target: create(:issue))
|
||||
|
||||
sign_in(user)
|
||||
|
||||
request.cookies[:event_filter] = 'all'
|
||||
end
|
||||
|
||||
context 'when user has permission to see the event' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns count' do
|
||||
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
|
||||
|
||||
expect(json_response['count']).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no permission to see the event' do
|
||||
it 'filters out invisible event' do
|
||||
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
|
||||
|
||||
expect(json_response['html']).to eq("\n")
|
||||
end
|
||||
|
||||
it 'filters out invisible event when calculating the count' do
|
||||
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
|
||||
|
||||
expect(json_response['count']).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET show" do
|
||||
context "user not project member" do
|
||||
before do
|
||||
|
|
|
@ -935,14 +935,14 @@ describe ProjectsHelper do
|
|||
helper.instance_variable_set(:@project, project)
|
||||
end
|
||||
|
||||
subject { helper.grafana_integration_token }
|
||||
subject { helper.grafana_integration_masked_token }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
|
||||
context 'grafana integration exists' do
|
||||
let!(:grafana_integration) { create(:grafana_integration, project: project) }
|
||||
|
||||
it { is_expected.to eq(grafana_integration.token) }
|
||||
it { is_expected.to eq(grafana_integration.masked_token) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import bp from '~/breakpoints';
|
||||
import { isMobile, getTopFrequentItems, updateExistingFrequentItem } from '~/frequent_items/utils';
|
||||
import {
|
||||
isMobile,
|
||||
getTopFrequentItems,
|
||||
updateExistingFrequentItem,
|
||||
sanitizeItem,
|
||||
} from '~/frequent_items/utils';
|
||||
import { HOUR_IN_MS, FREQUENT_ITEMS } from '~/frequent_items/constants';
|
||||
import { mockProject, unsortedFrequentItems, sortedFrequentItems } from './mock_data';
|
||||
|
||||
|
@ -86,4 +91,16 @@ describe('Frequent Items utils spec', () => {
|
|||
expect(result.frequency).toBe(mockedProject.frequency);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeItem', () => {
|
||||
it('strips HTML tags for name and namespace', () => {
|
||||
const input = {
|
||||
name: '<br><b>test</b>',
|
||||
namespace: '<br>test',
|
||||
id: 1,
|
||||
};
|
||||
|
||||
expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
|
|||
it 'returns empty array' do
|
||||
link['data-group'] = project.group.id.to_s
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
|
|||
end
|
||||
|
||||
it 'returns groups' do
|
||||
expect(subject.gather_references([link])).to eq([group])
|
||||
expect_gathered_references(subject.gather_references([link]), [group], 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
|
|||
it 'returns an empty Array' do
|
||||
link['data-group'] = 'test-non-existing'
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
|
|||
it 'returns empty Array' do
|
||||
link['data-project'] = project.id.to_s
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
|
|||
end
|
||||
|
||||
it 'returns an Array of referenced projects' do
|
||||
expect(subject.gather_references([link])).to eq([project])
|
||||
expect_gathered_references(subject.gather_references([link]), [project], 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
|
|||
it 'returns an empty Array' do
|
||||
link['data-project'] = 'inexisting-project-id'
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
|
|||
end
|
||||
|
||||
it 'returns empty list of users' do
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
|
|||
end
|
||||
|
||||
it 'returns empty list of users' do
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
|
|||
it 'returns an Array of users' do
|
||||
link['data-user'] = user.id.to_s
|
||||
|
||||
expect(subject.referenced_by([link])).to eq([user])
|
||||
expect_gathered_references(subject.gather_references([link]), [user], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ describe Banzai::ReferenceParser::ProjectParser do
|
|||
it 'returns an Array of projects' do
|
||||
link['data-project'] = project.id.to_s
|
||||
|
||||
expect(subject.gather_references([link])).to eq([project])
|
||||
expect_gathered_references(subject.gather_references([link]), [project], 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,7 +25,7 @@ describe Banzai::ReferenceParser::ProjectParser do
|
|||
it 'returns an empty Array' do
|
||||
link['data-project'] = ''
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::ProjectParser do
|
|||
|
||||
link['data-project'] = private_project.id.to_s
|
||||
|
||||
expect(subject.gather_references([link])).to eq([])
|
||||
expect_gathered_references(subject.gather_references([link]), [], 1)
|
||||
end
|
||||
|
||||
it 'returns an Array when authorized' do
|
||||
|
@ -43,7 +43,7 @@ describe Banzai::ReferenceParser::ProjectParser do
|
|||
|
||||
link['data-project'] = private_project.id.to_s
|
||||
|
||||
expect(subject.gather_references([link])).to eq([private_project])
|
||||
expect_gathered_references(subject.gather_references([link]), [private_project], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
44
spec/lib/gitlab/asciidoc/include_processor_spec.rb
Normal file
44
spec/lib/gitlab/asciidoc/include_processor_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'nokogiri'
|
||||
|
||||
describe Gitlab::Asciidoc::IncludeProcessor do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:processor_context) do
|
||||
{
|
||||
project: project,
|
||||
max_includes: max_includes,
|
||||
ref: ref
|
||||
}
|
||||
end
|
||||
let(:ref) { project.repository.root_ref }
|
||||
let(:max_includes) { 10 }
|
||||
|
||||
let(:reader) { Asciidoctor::PreprocessorReader.new(document, lines, 'file.adoc') }
|
||||
let(:document) { Asciidoctor::Document.new(lines) }
|
||||
|
||||
subject(:processor) { described_class.new(processor_context) }
|
||||
|
||||
let(:a_blob) { double(:Blob, readable_text?: true, data: a_data) }
|
||||
let(:a_data) { StringIO.new('include::b.adoc[]') }
|
||||
|
||||
let(:lines) { [':max-include-depth: 1000'] + Array.new(10, 'include::a.adoc[]') }
|
||||
|
||||
before do
|
||||
allow(project.repository).to receive(:blob_at).with(ref, 'a.adoc').and_return(a_blob)
|
||||
end
|
||||
|
||||
describe '#include_allowed?' do
|
||||
it 'allows the first include' do
|
||||
expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_truthy
|
||||
end
|
||||
|
||||
it 'disallows the Nth + 1 include' do
|
||||
max_includes.times { processor.send(:read_blob, ref, 'a.adoc') }
|
||||
|
||||
expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
|
@ -425,6 +425,24 @@ module Gitlab
|
|||
create_file(current_file, "= AsciiDoc\n")
|
||||
end
|
||||
|
||||
def many_includes(target)
|
||||
Array.new(10, "include::#{target}[]").join("\n")
|
||||
end
|
||||
|
||||
context 'cyclic imports' do
|
||||
before do
|
||||
create_file('doc/api/a.adoc', many_includes('b.adoc'))
|
||||
create_file('doc/api/b.adoc', many_includes('a.adoc'))
|
||||
end
|
||||
|
||||
let(:include_path) { 'a.adoc' }
|
||||
let(:requested_path) { 'doc/api/README.md' }
|
||||
|
||||
it 'completes successfully' do
|
||||
is_expected.to include('<p>Include this:</p>')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with path to non-existing file' do
|
||||
let(:include_path) { 'not-exists.adoc' }
|
||||
|
||||
|
|
117
spec/lib/gitlab/git/cross_repo_comparer_spec.rb
Normal file
117
spec/lib/gitlab/git/cross_repo_comparer_spec.rb
Normal file
|
@ -0,0 +1,117 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Git::CrossRepoComparer do
|
||||
let(:source_project) { create(:project, :repository) }
|
||||
let(:target_project) { create(:project, :repository) }
|
||||
|
||||
let(:source_repo) { source_project.repository.raw_repository }
|
||||
let(:target_repo) { target_project.repository.raw_repository }
|
||||
|
||||
let(:source_branch) { 'feature' }
|
||||
let(:target_branch) { 'master' }
|
||||
let(:straight) { false }
|
||||
|
||||
let(:source_commit) { source_repo.commit(source_branch) }
|
||||
let(:target_commit) { source_repo.commit(target_branch) }
|
||||
|
||||
subject(:result) { described_class.new(source_repo, target_repo).compare(source_branch, target_branch, straight: straight) }
|
||||
|
||||
describe '#compare' do
|
||||
context 'within a single repository' do
|
||||
let(:target_project) { source_project }
|
||||
|
||||
context 'a non-straight comparison' do
|
||||
it 'compares without fetching from another repo' do
|
||||
expect(source_repo).not_to receive(:fetch_source_branch!)
|
||||
|
||||
expect_compare(result, from: source_commit, to: target_commit)
|
||||
expect(result.straight).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a straight comparison' do
|
||||
let(:straight) { true }
|
||||
|
||||
it 'compares without fetching from another repo' do
|
||||
expect(source_repo).not_to receive(:fetch_source_branch!)
|
||||
|
||||
expect_compare(result, from: source_commit, to: target_commit)
|
||||
expect(result.straight).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'across two repositories' do
|
||||
context 'target ref exists in source repo' do
|
||||
it 'compares without fetching from another repo' do
|
||||
expect(source_repo).not_to receive(:fetch_source_branch!)
|
||||
expect(source_repo).not_to receive(:delete_refs)
|
||||
|
||||
expect_compare(result, from: source_commit, to: target_commit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'target ref does not exist in source repo' do
|
||||
it 'compares in the source repo by fetching from the target to a temporary ref' do
|
||||
new_commit_id = create_commit(target_project.owner, target_repo, target_branch)
|
||||
new_commit = target_repo.commit(new_commit_id)
|
||||
|
||||
# This is how the temporary ref is generated
|
||||
expect(SecureRandom).to receive(:hex).at_least(:once).and_return('foo')
|
||||
|
||||
expect(source_repo)
|
||||
.to receive(:fetch_source_branch!)
|
||||
.with(target_repo, new_commit_id, 'refs/tmp/foo')
|
||||
.and_call_original
|
||||
|
||||
expect(source_repo).to receive(:delete_refs).with('refs/tmp/foo').and_call_original
|
||||
|
||||
expect_compare(result, from: source_commit, to: new_commit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'source ref does not exist in source repo' do
|
||||
let(:source_branch) { 'does-not-exist' }
|
||||
|
||||
it 'returns an empty comparison' do
|
||||
expect(source_repo).not_to receive(:fetch_source_branch!)
|
||||
expect(source_repo).not_to receive(:delete_refs)
|
||||
|
||||
expect(result).to be_a(::Gitlab::Git::Compare)
|
||||
expect(result.commits.size).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'target ref does not exist in target repo' do
|
||||
let(:target_branch) { 'does-not-exist' }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(source_repo).not_to receive(:fetch_source_branch!)
|
||||
expect(source_repo).not_to receive(:delete_refs)
|
||||
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_compare(of, from:, to:)
|
||||
expect(of).to be_a(::Gitlab::Git::Compare)
|
||||
expect(from).to be_a(::Gitlab::Git::Commit)
|
||||
expect(to).to be_a(::Gitlab::Git::Commit)
|
||||
|
||||
expect(of.commits).not_to be_empty
|
||||
expect(of.head).to eq(from)
|
||||
expect(of.base).to eq(to)
|
||||
end
|
||||
|
||||
def create_commit(user, repo, branch)
|
||||
action = { action: :create, file_path: '/FILE', content: 'content' }
|
||||
|
||||
result = repo.multi_action(user, branch_name: branch, message: 'Commit', actions: [action])
|
||||
|
||||
result.newrev
|
||||
end
|
||||
end
|
|
@ -1962,66 +1962,15 @@ describe Gitlab::Git::Repository, :seed_helper do
|
|||
end
|
||||
|
||||
describe '#compare_source_branch' do
|
||||
let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') }
|
||||
it 'delegates to Gitlab::Git::CrossRepoComparer' do
|
||||
expect_next_instance_of(::Gitlab::Git::CrossRepoComparer) do |instance|
|
||||
expect(instance.source_repo).to eq(:source_repository)
|
||||
expect(instance.target_repo).to eq(repository)
|
||||
|
||||
context 'within same repository' do
|
||||
it 'does not create a temp ref' do
|
||||
expect(repository).not_to receive(:fetch_source_branch!)
|
||||
expect(repository).not_to receive(:delete_refs)
|
||||
|
||||
compare = repository.compare_source_branch('master', repository, 'feature', straight: false)
|
||||
expect(compare).to be_a(Gitlab::Git::Compare)
|
||||
expect(compare.commits.count).to be > 0
|
||||
expect(instance).to receive(:compare).with('feature', 'master', straight: :straight)
|
||||
end
|
||||
|
||||
it 'returns empty commits when source ref does not exist' do
|
||||
compare = repository.compare_source_branch('master', repository, 'non-existent-branch', straight: false)
|
||||
|
||||
expect(compare.commits).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with different repositories' do
|
||||
context 'when ref is known by source repo, but not by target' do
|
||||
before do
|
||||
mutable_repository.write_ref('another-branch', 'feature')
|
||||
end
|
||||
|
||||
it 'creates temp ref' do
|
||||
expect(repository).not_to receive(:fetch_source_branch!)
|
||||
expect(repository).not_to receive(:delete_refs)
|
||||
|
||||
compare = repository.compare_source_branch('master', mutable_repository, 'another-branch', straight: false)
|
||||
expect(compare).to be_a(Gitlab::Git::Compare)
|
||||
expect(compare.commits.count).to be > 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ref is known by source and target repos' do
|
||||
before do
|
||||
mutable_repository.write_ref('another-branch', 'feature')
|
||||
repository.write_ref('another-branch', 'feature')
|
||||
end
|
||||
|
||||
it 'does not create a temp ref' do
|
||||
expect(repository).not_to receive(:fetch_source_branch!)
|
||||
expect(repository).not_to receive(:delete_refs)
|
||||
|
||||
compare = repository.compare_source_branch('master', mutable_repository, 'another-branch', straight: false)
|
||||
expect(compare).to be_a(Gitlab::Git::Compare)
|
||||
expect(compare.commits.count).to be > 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ref is unknown by source repo' do
|
||||
it 'returns nil when source ref does not exist' do
|
||||
expect(repository).to receive(:fetch_source_branch!).and_call_original
|
||||
expect(repository).to receive(:delete_refs).and_call_original
|
||||
|
||||
compare = repository.compare_source_branch('master', mutable_repository, 'non-existent-branch', straight: false)
|
||||
expect(compare).to be_nil
|
||||
end
|
||||
end
|
||||
repository.compare_source_branch('master', :source_repository, 'feature', straight: :straight)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
17
spec/lib/gitlab/no_cache_headers_spec.rb
Normal file
17
spec/lib/gitlab/no_cache_headers_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::NoCacheHeaders do
|
||||
class NoCacheTester
|
||||
include Gitlab::NoCacheHeaders
|
||||
end
|
||||
|
||||
describe "#no_cache_headers" do
|
||||
subject { NoCacheTester.new }
|
||||
|
||||
it "raises a RuntimeError" do
|
||||
expect { subject.no_cache_headers }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::ReferenceExtractor do
|
||||
let(:project) { create(:project) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_developer(project.creator)
|
||||
|
@ -293,4 +293,34 @@ describe Gitlab::ReferenceExtractor do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#references' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let(:text) { "Ref. #{issue.to_reference}" }
|
||||
|
||||
subject { described_class.new(project, user) }
|
||||
|
||||
before do
|
||||
subject.analyze(text)
|
||||
end
|
||||
|
||||
context 'when references are visible' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns visible references of given type' do
|
||||
expect(subject.references(:issue)).to eq([issue])
|
||||
end
|
||||
|
||||
it 'does not increase stateful_not_visible_counter' do
|
||||
expect { subject.references(:issue) }.not_to change { subject.stateful_not_visible_counter }
|
||||
end
|
||||
end
|
||||
|
||||
it 'increases stateful_not_visible_counter' do
|
||||
expect { subject.references(:issue) }.to change { subject.stateful_not_visible_counter }.by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,74 @@ describe GenericCommitStatus do
|
|||
it { is_expected.not_to allow_value('javascript:alert(1)').for(:target_url) }
|
||||
end
|
||||
|
||||
describe '#name_uniqueness_across_types' do
|
||||
let(:attributes) { {} }
|
||||
let(:commit_status) { described_class.new(attributes) }
|
||||
let(:status_name) { 'test-job' }
|
||||
|
||||
subject(:errors) { commit_status.errors[:name] }
|
||||
|
||||
shared_examples 'it does not have uniqueness errors' do
|
||||
it 'does not return errors' do
|
||||
commit_status.valid?
|
||||
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'without attributes' do
|
||||
it_behaves_like 'it does not have uniqueness errors'
|
||||
end
|
||||
|
||||
context 'with only a pipeline' do
|
||||
let(:attributes) { { pipeline: pipeline } }
|
||||
|
||||
context 'without name' do
|
||||
it_behaves_like 'it does not have uniqueness errors'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with only a name' do
|
||||
let(:attributes) { { name: status_name } }
|
||||
|
||||
context 'without pipeline' do
|
||||
it_behaves_like 'it does not have uniqueness errors'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with pipeline and name' do
|
||||
let(:attributes) do
|
||||
{
|
||||
pipeline: pipeline,
|
||||
name: status_name
|
||||
}
|
||||
end
|
||||
|
||||
context 'without other statuses' do
|
||||
it_behaves_like 'it does not have uniqueness errors'
|
||||
end
|
||||
|
||||
context 'with generic statuses' do
|
||||
before do
|
||||
create(:generic_commit_status, pipeline: pipeline, name: status_name)
|
||||
end
|
||||
|
||||
it_behaves_like 'it does not have uniqueness errors'
|
||||
end
|
||||
|
||||
context 'with ci_build statuses' do
|
||||
before do
|
||||
create(:ci_build, pipeline: pipeline, name: status_name)
|
||||
end
|
||||
|
||||
it 'returns name error' do
|
||||
expect(commit_status).to be_invalid
|
||||
is_expected.to include('has already been taken')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#context' do
|
||||
subject { generic_commit_status.context }
|
||||
|
||||
|
@ -79,6 +147,12 @@ describe GenericCommitStatus do
|
|||
|
||||
it { is_expected.not_to be_nil }
|
||||
end
|
||||
|
||||
describe '#stage_idx' do
|
||||
subject { generic_commit_status.stage_idx }
|
||||
|
||||
it { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#present' do
|
||||
|
|
|
@ -9,7 +9,7 @@ describe GrafanaIntegration do
|
|||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_presence_of(:token) }
|
||||
it { is_expected.to validate_presence_of(:encrypted_token) }
|
||||
|
||||
it 'disallows invalid urls for grafana_url' do
|
||||
unsafe_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
|
||||
|
@ -66,4 +66,24 @@ describe GrafanaIntegration do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'attribute encryption' do
|
||||
subject(:grafana_integration) { create(:grafana_integration, token: 'super-secret') }
|
||||
|
||||
context 'token' do
|
||||
it 'encrypts original value into encrypted_token attribute' do
|
||||
expect(grafana_integration.encrypted_token).not_to be_nil
|
||||
end
|
||||
|
||||
it 'locks access to raw value in private method', :aggregate_failures do
|
||||
expect { grafana_integration.token }.to raise_error(NoMethodError, /private method .token. called/)
|
||||
expect(grafana_integration.send(:token)).to eql('super-secret')
|
||||
end
|
||||
|
||||
it 'prevents overriding token value with its encrypted or masked version', :aggregate_failures do
|
||||
expect { grafana_integration.update(token: grafana_integration.encrypted_token) }.not_to change { grafana_integration.reload.send(:token) }
|
||||
expect { grafana_integration.update(token: grafana_integration.masked_token) }.not_to change { grafana_integration.reload.send(:token) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -350,12 +350,12 @@ describe Note do
|
|||
end
|
||||
|
||||
describe "cross_reference_not_visible_for?" do
|
||||
let(:private_user) { create(:user) }
|
||||
let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
|
||||
let(:private_issue) { create(:issue, project: private_project) }
|
||||
let_it_be(:private_user) { create(:user) }
|
||||
let_it_be(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
|
||||
let_it_be(:private_issue) { create(:issue, project: private_project) }
|
||||
|
||||
let(:ext_proj) { create(:project, :public) }
|
||||
let(:ext_issue) { create(:issue, project: ext_proj) }
|
||||
let_it_be(:ext_proj) { create(:project, :public) }
|
||||
let_it_be(:ext_issue) { create(:issue, project: ext_proj) }
|
||||
|
||||
shared_examples "checks references" do
|
||||
it "returns true" do
|
||||
|
@ -393,10 +393,24 @@ describe Note do
|
|||
it_behaves_like "checks references"
|
||||
end
|
||||
|
||||
context "when there are two references in note" do
|
||||
context "when there is a reference to a label" do
|
||||
let_it_be(:private_label) { create(:label, project: private_project) }
|
||||
let(:note) do
|
||||
create :note,
|
||||
noteable: ext_issue, project: ext_proj,
|
||||
note: "added label #{private_label.to_reference(ext_proj)}",
|
||||
system: true
|
||||
end
|
||||
let!(:system_note_metadata) { create(:system_note_metadata, note: note, action: :label) }
|
||||
|
||||
it_behaves_like "checks references"
|
||||
end
|
||||
|
||||
context "when there are two references in note" do
|
||||
let_it_be(:ext_issue2) { create(:issue, project: ext_proj) }
|
||||
let(:note) do
|
||||
create :note,
|
||||
noteable: ext_issue2, project: ext_proj,
|
||||
note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \
|
||||
"public issue #{ext_issue.to_reference(ext_proj)}",
|
||||
system: true
|
||||
|
|
|
@ -141,6 +141,34 @@ describe GlobalPolicy do
|
|||
it { is_expected.to be_allowed(:access_api) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'inactive user' do
|
||||
before do
|
||||
current_user.update!(confirmed_at: nil, confirmation_sent_at: 5.days.ago)
|
||||
end
|
||||
|
||||
context 'when within the confirmation grace period' do
|
||||
before do
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return(10.days)
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed(:access_api) }
|
||||
end
|
||||
|
||||
context 'when confirmation grace period is expired' do
|
||||
before do
|
||||
allow(User).to receive(:allow_unconfirmed_access_for).and_return(2.days)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:access_api) }
|
||||
end
|
||||
|
||||
it 'when `inactive_policy_condition` feature flag is turned off' do
|
||||
stub_feature_flags(inactive_policy_condition: false)
|
||||
|
||||
is_expected.to be_allowed(:access_api)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'receive notifications' do
|
||||
|
@ -202,6 +230,20 @@ describe GlobalPolicy do
|
|||
it { is_expected.not_to be_allowed(:access_git) }
|
||||
end
|
||||
|
||||
describe 'inactive user' do
|
||||
before do
|
||||
current_user.update!(confirmed_at: nil)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:access_git) }
|
||||
|
||||
it 'when `inactive_policy_condition` feature flag is turned off' do
|
||||
stub_feature_flags(inactive_policy_condition: false)
|
||||
|
||||
is_expected.to be_allowed(:access_git)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when terms are enforced' do
|
||||
before do
|
||||
enforce_terms
|
||||
|
@ -298,6 +340,20 @@ describe GlobalPolicy do
|
|||
it { is_expected.not_to be_allowed(:use_slash_commands) }
|
||||
end
|
||||
|
||||
describe 'inactive user' do
|
||||
before do
|
||||
current_user.update!(confirmed_at: nil)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:use_slash_commands) }
|
||||
|
||||
it 'when `inactive_policy_condition` feature flag is turned off' do
|
||||
stub_feature_flags(inactive_policy_condition: false)
|
||||
|
||||
is_expected.to be_allowed(:use_slash_commands)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access locked' do
|
||||
before do
|
||||
current_user.lock_access!
|
||||
|
|
|
@ -164,6 +164,7 @@ describe API::CommitStatuses do
|
|||
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
expect(job.status).to eq('pending')
|
||||
expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -331,6 +332,29 @@ describe API::CommitStatuses do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when updating a protected ref' do
|
||||
before do
|
||||
create(:protected_branch, project: project, name: 'master')
|
||||
post api(post_url, user), params: { state: 'running', ref: 'master' }
|
||||
end
|
||||
|
||||
context 'with user as developer' do
|
||||
let(:user) { developer }
|
||||
|
||||
it 'does not create commit status' do
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with user as maintainer' do
|
||||
let(:user) { create_user(:maintainer) }
|
||||
|
||||
it 'creates commit status' do
|
||||
expect(response).to have_gitlab_http_status(201)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when commit SHA is invalid' do
|
||||
let(:sha) { 'invalid_sha' }
|
||||
|
||||
|
@ -372,6 +396,22 @@ describe API::CommitStatuses do
|
|||
.to include 'is blocked: Only allowed schemes are http, https'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to update a status of a different type' do
|
||||
let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') }
|
||||
let!(:ci_build) { create(:ci_build, pipeline: pipeline, name: 'test-job') }
|
||||
let(:params) { { state: 'pending', name: 'test-job' } }
|
||||
|
||||
before do
|
||||
post api(post_url, developer), params: params
|
||||
end
|
||||
|
||||
it 'responds with bad request status and validation errors' do
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(json_response['message']['name'])
|
||||
.to include 'has already been taken'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'reporter user' do
|
||||
|
|
|
@ -8,6 +8,7 @@ describe API::Commits do
|
|||
|
||||
let(:user) { create(:user) }
|
||||
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
|
||||
let(:developer) { create(:user).tap { |u| project.add_developer(u) } }
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
|
||||
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
|
||||
|
@ -964,6 +965,56 @@ describe API::Commits do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'ref with pipeline' do
|
||||
let!(:pipeline) do
|
||||
project
|
||||
.ci_pipelines
|
||||
.create!(source: :push, ref: 'master', sha: commit.sha, protected: false)
|
||||
end
|
||||
|
||||
it 'includes status as "created" and a last_pipeline object' do
|
||||
get api(route, current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/commit/detail')
|
||||
expect(json_response['status']).to eq('created')
|
||||
expect(json_response['last_pipeline']['id']).to eq(pipeline.id)
|
||||
expect(json_response['last_pipeline']['ref']).to eq(pipeline.ref)
|
||||
expect(json_response['last_pipeline']['sha']).to eq(pipeline.sha)
|
||||
expect(json_response['last_pipeline']['status']).to eq(pipeline.status)
|
||||
end
|
||||
|
||||
context 'when pipeline succeeds' do
|
||||
before do
|
||||
pipeline.update!(status: 'success')
|
||||
end
|
||||
|
||||
it 'includes a "success" status' do
|
||||
get api(route, current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/commit/detail')
|
||||
expect(json_response['status']).to eq('success')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'ref with unaccessible pipeline' do
|
||||
let!(:pipeline) do
|
||||
project
|
||||
.ci_pipelines
|
||||
.create!(source: :push, ref: 'master', sha: commit.sha, protected: false)
|
||||
end
|
||||
|
||||
it 'does not include last_pipeline' do
|
||||
get api(route, current_user)
|
||||
|
||||
expect(response).to match_response_schema('public_api/v4/commit/detail')
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response['last_pipeline']).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat param' do
|
||||
let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
|
||||
|
||||
|
@ -993,6 +1044,15 @@ describe API::Commits do
|
|||
let(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref commit'
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
||||
context 'with private builds' do
|
||||
before do
|
||||
project.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref with unaccessible pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is private' do
|
||||
|
@ -1006,6 +1066,17 @@ describe API::Commits do
|
|||
let(:current_user) { user }
|
||||
|
||||
it_behaves_like 'ref commit'
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
||||
context 'when builds are disabled' do
|
||||
before do
|
||||
project
|
||||
.project_feature
|
||||
.update!(builds_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref with unaccessible pipeline'
|
||||
end
|
||||
|
||||
context 'when branch contains a dot' do
|
||||
let(:commit) { project.repository.commit(branch_with_dot.name) }
|
||||
|
@ -1041,35 +1112,53 @@ describe API::Commits do
|
|||
it_behaves_like 'ref commit'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the ref has a pipeline' do
|
||||
let!(:pipeline) { project.ci_pipelines.create(source: :push, ref: 'master', sha: commit.sha, protected: false) }
|
||||
context 'when authenticated', 'as a developer' do
|
||||
let(:current_user) { developer }
|
||||
|
||||
it 'includes a "created" status' do
|
||||
get api(route, current_user)
|
||||
it_behaves_like 'ref commit'
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/commit/detail')
|
||||
expect(json_response['status']).to eq('created')
|
||||
expect(json_response['last_pipeline']['id']).to eq(pipeline.id)
|
||||
expect(json_response['last_pipeline']['ref']).to eq(pipeline.ref)
|
||||
expect(json_response['last_pipeline']['sha']).to eq(pipeline.sha)
|
||||
expect(json_response['last_pipeline']['status']).to eq(pipeline.status)
|
||||
context 'with private builds' do
|
||||
before do
|
||||
project.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
context 'when pipeline succeeds' do
|
||||
before do
|
||||
pipeline.update(status: 'success')
|
||||
end
|
||||
it_behaves_like 'ref with pipeline'
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes a "success" status' do
|
||||
get api(route, current_user)
|
||||
context 'when authenticated', 'as a guest' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('public_api/v4/commit/detail')
|
||||
expect(json_response['status']).to eq('success')
|
||||
end
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { get api(route, guest) }
|
||||
let(:message) { '403 Forbidden' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as a non member' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { get api(route, guest) }
|
||||
let(:message) { '403 Forbidden' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated', 'as non_member and project is public' do
|
||||
let(:current_user) { create(:user) }
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
||||
context 'with private builds' do
|
||||
before do
|
||||
project.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref with unaccessible pipeline'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -447,6 +447,18 @@ describe API::Files do
|
|||
expect(response).to have_gitlab_http_status(200)
|
||||
end
|
||||
|
||||
it 'sets no-cache headers' do
|
||||
url = route('.gitignore') + "/raw"
|
||||
expect(Gitlab::Workhorse).to receive(:send_git_blob)
|
||||
|
||||
get api(url, current_user), params: params
|
||||
|
||||
expect(response.headers["Cache-Control"]).to include("no-store")
|
||||
expect(response.headers["Cache-Control"]).to include("no-cache")
|
||||
expect(response.headers["Pragma"]).to eq("no-cache")
|
||||
expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
|
||||
end
|
||||
|
||||
context 'when mandatory params are not given' do
|
||||
it_behaves_like '400 response' do
|
||||
let(:request) { get api(route("any%2Ffile"), current_user) }
|
||||
|
|
|
@ -30,26 +30,40 @@ describe 'OAuth tokens' do
|
|||
end
|
||||
end
|
||||
|
||||
context "when user is blocked" do
|
||||
it "does not create an access token" do
|
||||
user = create(:user)
|
||||
shared_examples 'does not create an access token' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it { expect(response).to have_gitlab_http_status(401) }
|
||||
end
|
||||
|
||||
context 'when user is blocked' do
|
||||
before do
|
||||
user.block
|
||||
|
||||
request_oauth_token(user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(401)
|
||||
end
|
||||
|
||||
include_examples 'does not create an access token'
|
||||
end
|
||||
|
||||
context "when user is ldap_blocked" do
|
||||
it "does not create an access token" do
|
||||
user = create(:user)
|
||||
context 'when user is ldap_blocked' do
|
||||
before do
|
||||
user.ldap_block
|
||||
|
||||
request_oauth_token(user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(401)
|
||||
end
|
||||
|
||||
include_examples 'does not create an access token'
|
||||
end
|
||||
|
||||
context 'when user account is not confirmed' do
|
||||
before do
|
||||
user.update!(confirmed_at: nil)
|
||||
|
||||
request_oauth_token(user)
|
||||
end
|
||||
|
||||
include_examples 'does not create an access token'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1509,7 +1509,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
|
|||
|
||||
authorize_artifacts
|
||||
|
||||
expect(response).to have_gitlab_http_status(500)
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
|
||||
context 'authorization token is invalid' do
|
||||
|
@ -1639,6 +1639,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
context 'Is missing GitLab Workhorse token headers' do
|
||||
let(:jwt_token) { JWT.encode({ 'iss' => 'invalid-header' }, Gitlab::Workhorse.secret, 'HS256') }
|
||||
|
||||
it 'fails to post artifacts without GitLab-Workhorse' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).once
|
||||
|
||||
upload_artifacts(file_upload, headers_with_token)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when setting an expire date' do
|
||||
let(:default_artifacts_expire_in) {}
|
||||
let(:post_data) do
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::GroupLinks::DestroyService, '#execute' do
|
||||
let(:group_link) { create :project_group_link }
|
||||
let(:project) { group_link.project }
|
||||
let(:project) { create(:project, :private) }
|
||||
let!(:group_link) { create(:project_group_link, project: project) }
|
||||
let(:user) { create :user }
|
||||
let(:subject) { described_class.new(project, user) }
|
||||
|
||||
|
@ -15,4 +15,39 @@ describe Projects::GroupLinks::DestroyService, '#execute' do
|
|||
it 'returns false if group_link is blank' do
|
||||
expect { subject.execute(nil) }.not_to change { project.project_group_links.count }
|
||||
end
|
||||
|
||||
describe 'todos cleanup' do
|
||||
context 'when project is private' do
|
||||
it 'triggers todos cleanup' do
|
||||
expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id)
|
||||
expect(project.private?).to be true
|
||||
|
||||
subject.execute(group_link)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is public or internal' do
|
||||
shared_examples_for 'removes confidential todos' do
|
||||
it 'does not trigger todos cleanup' do
|
||||
expect(TodosDestroyer::ProjectPrivateWorker).not_to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id)
|
||||
expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, nil, project.id)
|
||||
expect(project.private?).to be false
|
||||
|
||||
subject.execute(group_link)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it_behaves_like 'removes confidential todos'
|
||||
end
|
||||
|
||||
context 'when project is internal' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
it_behaves_like 'removes confidential todos'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,10 @@ describe Projects::ImportExport::ExportService do
|
|||
let(:service) { described_class.new(project, user) }
|
||||
let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'saves the version' do
|
||||
expect(Gitlab::ImportExport::VersionSaver).to receive(:new).and_call_original
|
||||
|
||||
|
@ -133,5 +137,18 @@ describe Projects::ImportExport::ExportService do
|
|||
expect(service).not_to receive(:execute_after_export_action)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have admin_project permission' do
|
||||
let!(:another_user) { create(:user) }
|
||||
|
||||
subject(:service) { described_class.new(project, another_user) }
|
||||
|
||||
it 'fails' do
|
||||
expected_message =
|
||||
"User with ID: %s does not have permission to Project %s with ID: %s." %
|
||||
[another_user.id, project.name, project.id]
|
||||
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error).with_message(expected_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -210,7 +210,7 @@ describe Projects::Operations::UpdateService do
|
|||
integration = project.reload.grafana_integration
|
||||
|
||||
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
|
||||
expect(integration.token).to eq(expected_attrs[:token])
|
||||
expect(integration.send(:token)).to eq(expected_attrs[:token])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,7 +226,7 @@ describe Projects::Operations::UpdateService do
|
|||
integration = project.reload.grafana_integration
|
||||
|
||||
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
|
||||
expect(integration.token).to eq(expected_attrs[:token])
|
||||
expect(integration.send(:token)).to eq(expected_attrs[:token])
|
||||
end
|
||||
|
||||
context 'with all grafana attributes blank in params' do
|
||||
|
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
|||
describe Projects::UpdatePagesService do
|
||||
set(:project) { create(:project, :repository) }
|
||||
set(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
|
||||
set(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
|
||||
let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
|
||||
let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
|
||||
|
||||
let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
|
||||
|
@ -242,6 +242,32 @@ describe Projects::UpdatePagesService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when file size is spoofed' do
|
||||
let(:metadata) { spy('metadata') }
|
||||
|
||||
include_context 'pages zip with spoofed size'
|
||||
|
||||
before do
|
||||
file = fixture_file_upload(fake_zip_path, 'pages.zip')
|
||||
metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
|
||||
|
||||
create(:ci_job_artifact, :archive, file: file, job: build)
|
||||
create(:ci_job_artifact, :metadata, file: metafile, job: build)
|
||||
|
||||
allow(build).to receive(:artifacts_metadata_entry)
|
||||
.and_return(metadata)
|
||||
allow(metadata).to receive(:total_size).and_return(100)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
subject.execute
|
||||
end.to raise_error(Projects::UpdatePagesService::FailedToExtractError,
|
||||
'Entry public/index.html should be 1B but is larger when inflated')
|
||||
expect(deploy_status).to be_script_failure
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_status
|
||||
GenericCommitStatus.find_by(name: 'pages:deploy')
|
||||
end
|
||||
|
|
|
@ -5,6 +5,11 @@ module ReferenceParserHelpers
|
|||
Nokogiri::HTML.fragment('<a></a>').children[0]
|
||||
end
|
||||
|
||||
def expect_gathered_references(result, visible, not_visible_count)
|
||||
expect(result[:visible]).to eq(visible)
|
||||
expect(result[:not_visible].count).to eq(not_visible_count)
|
||||
end
|
||||
|
||||
shared_examples 'no project N+1 queries' do
|
||||
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
|
||||
context = Banzai::RenderContext.new(project, user)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# the idea of creating zip archive with spoofed size is borrowed from
|
||||
# https://github.com/rubyzip/rubyzip/pull/403/files#diff-118213fb4baa6404a40f89e1147661ebR88
|
||||
RSpec.shared_context 'pages zip with spoofed size' do
|
||||
let(:real_zip_path) { Tempfile.new(['real', '.zip']).path }
|
||||
let(:fake_zip_path) { Tempfile.new(['fake', '.zip']).path }
|
||||
|
||||
before do
|
||||
full_file_name = 'public/index.html'
|
||||
true_size = 500_000
|
||||
fake_size = 1
|
||||
|
||||
::Zip::File.open(real_zip_path, ::Zip::File::CREATE) do |zf|
|
||||
zf.get_output_stream(full_file_name) do |os|
|
||||
os.write 'a' * true_size
|
||||
end
|
||||
end
|
||||
|
||||
compressed_size = nil
|
||||
::Zip::File.open(real_zip_path) do |zf|
|
||||
a_entry = zf.find_entry(full_file_name)
|
||||
compressed_size = a_entry.compressed_size
|
||||
end
|
||||
|
||||
true_size_bytes = [compressed_size, true_size, full_file_name.size].pack('LLS')
|
||||
fake_size_bytes = [compressed_size, fake_size, full_file_name.size].pack('LLS')
|
||||
|
||||
data = File.binread(real_zip_path)
|
||||
data.gsub! true_size_bytes, fake_size_bytes
|
||||
|
||||
File.open(fake_zip_path, 'wb') do |file|
|
||||
file.write data
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
File.delete(real_zip_path) if File.exist?(real_zip_path)
|
||||
File.delete(fake_zip_path) if File.exist?(fake_zip_path)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue