Commit 79aaf516 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2017-10-18

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 74a2c395 f9b3cf17
Please view this file on the master branch, on stable branches it's out of date.
## 10.0.4 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues. !541
- [SECURITY] Escape user name in filtered search bar.
## 10.0.3 (2017-10-05)
- [FIXED] Rewrite Geo database rake tasks so they operate on the correct database. !3052
......@@ -61,6 +66,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add group issue boards.
- Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page.
## 9.5.9 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues.
- Escape user name in filtered search bar.
## 9.5.8 (2017-10-04)
- [FIXED] Fix EE delta size check handling with annotated tags.
......@@ -137,6 +147,12 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix rebase button when merge request is created from a fork.
- Skip oAuth authorization for trusted applications.
## 9.4.7 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues.
- Fix when pushing without a branch name. !2879
- Escape user name in filtered search bar.
## 9.4.6 (2017-09-06)
- [FIXED] Validate branch name push rule when pushing branch without commits. !2685
......
......@@ -2,6 +2,12 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.0.4 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
## 10.0.3 (2017-10-05)
- [FIXED] find_user Users helper method no longer overrides find_user API helper method. !14418
......@@ -212,6 +218,14 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418
## 9.5.9 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
- [FIXED] Allow using newlines in pipeline email service recipients. !14250
- Escape user name in filtered search bar.
## 9.5.8 (2017-10-04)
- [FIXED] Fixed fork button being disabled for users who can fork to a group.
......@@ -457,6 +471,15 @@ entry.
- Use a specialized class for querying events to improve performance.
- Update build badges to be pipeline badges and display passing instead of success.
## 9.4.7 (2017-10-16)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
- [SECURITY] Move project repositories between namespaces when renaming users.
- [SECURITY] Prevent an open redirect on project pages.
- [SECURITY] Prevent a persistent XSS in user-provided markup.
- [FIXED] Allow using newlines in pipeline email service recipients. !14250
- Escape user name in filtered search bar.
## 9.4.6 (2017-09-06)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
......
......@@ -9,6 +9,7 @@ import Flash from '../../flash';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees';
import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue';
const Store = gl.issueBoards.BoardsStore;
......@@ -113,7 +114,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
mounted () {
new IssuableContext(this.currentUser);
new MilestoneSelect();
new gl.DueDateSelectors();
new DueDateSelectors();
new LabelsSelect();
new Sidebar();
gl.Subscription.bindAll('.subscription');
......
......@@ -89,6 +89,7 @@ import ShortcutsIssuable from './shortcuts_issuable';
import U2FAuthenticate from './u2f/authenticate';
import Members from './members';
import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
// EE-only
import ApproversSelect from './approvers_select';
......@@ -257,7 +258,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'groups:milestones:edit':
case 'groups:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new DueDateSelectors();
new GLForm($('.milestone-form'), true);
break;
case 'projects:compare:show':
......@@ -590,7 +591,7 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'profiles:personal_access_tokens:index':
case 'admin:impersonation_tokens:index':
new gl.DueDateSelectors();
new DueDateSelectors();
break;
case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters')
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
/* global dateFormat */
import Pikaday from 'pikaday';
import DateFix from './lib/utils/datefix';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
......@@ -17,8 +16,8 @@ class DueDateSelect {
this.$value = $block.find('.value');
this.$valueContent = $block.find('.value-content');
this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
this.fieldName = $dropdown.data('field-name'),
this.abilityName = $dropdown.data('ability-name'),
this.fieldName = $dropdown.data('field-name');
this.abilityName = $dropdown.data('ability-name');
this.issueUpdateURL = $dropdown.data('issue-update');
this.rawSelectedDate = null;
......@@ -39,20 +38,20 @@ class DueDateSelect {
hidden: () => {
this.$selectbox.hide();
this.$value.css('display', '');
}
},
});
}
initDatePicker() {
const $dueDateInput = $(`input[name='${this.fieldName}']`);
const dateFix = DateFix.dashedFix($dueDateInput.val());
const calendar = new Pikaday({
field: $dueDateInput.get(0),
theme: 'gitlab-theme',
format: 'yyyy-mm-dd',
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: (dateText) => {
const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
$dueDateInput.val(formattedDate);
$dueDateInput.val(calendar.toString(dateText));
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
......@@ -60,10 +59,10 @@ class DueDateSelect {
} else {
this.saveDueDate(true);
}
}
},
});
calendar.setDate(dateFix);
calendar.setDate(parsePikadayDate($dueDateInput.val()));
this.$datePicker.append(calendar.el);
this.$datePicker.data('pikaday', calendar);
}
......@@ -79,8 +78,8 @@ class DueDateSelect {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
} else {
$("input[name='" + this.fieldName + "']").val('');
return this.saveDueDate(false);
$(`input[name='${this.fieldName}']`).val('');
this.saveDueDate(false);
}
});
}
......@@ -111,7 +110,7 @@ class DueDateSelect {
this.datePayload = datePayload;
}
updateIssueBoardIssue () {
updateIssueBoardIssue() {
this.$loading.fadeIn();
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
......@@ -149,8 +148,8 @@ class DueDateSelect {
return selectedDateValue.length ?
$('.js-remove-due-date-holder').removeClass('hidden') :
$('.js-remove-due-date-holder').addClass('hidden');
}
}).done((data) => {
},
}).done(() => {
if (isDropdown) {
this.$dropdown.trigger('loaded.gl.dropdown');
this.$dropdown.dropdown('toggle');
......@@ -160,27 +159,28 @@ class DueDateSelect {
}
}
class DueDateSelectors {
export default class DueDateSelectors {
constructor() {
this.initMilestoneDatePicker();
this.initIssuableSelect();
}
// eslint-disable-next-line class-methods-use-this
initMilestoneDatePicker() {
$('.datepicker').each(function() {
$('.datepicker').each(function initPikadayMilestone() {
const $datePicker = $(this);
const dateFix = DateFix.dashedFix($datePicker.val());
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $datePicker.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect(dateText) {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
$datePicker.val(calendar.toString(dateText));
},
});
calendar.setDate(dateFix);
calendar.setDate(parsePikadayDate($datePicker.val()));
$datePicker.data('pikaday', calendar);
});
......@@ -191,19 +191,17 @@ class DueDateSelectors {
calendar.setDate(null);
});
}
// eslint-disable-next-line class-methods-use-this
initIssuableSelect() {
const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
$('.js-due-date-select').each((i, dropdown) => {
const $dropdown = $(dropdown);
// eslint-disable-next-line no-new
new DueDateSelect({
$dropdown,
$loading
$loading,
});
});
}
}
window.gl = window.gl || {};
window.gl.DueDateSelectors = DueDateSelectors;
......@@ -123,8 +123,8 @@ class FilteredSearchVisualTokens {
/* eslint-disable no-param-reassign */
tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt="${user.name}'s avatar">
${user.name}
<img class="avatar s20" src="${user.avatar_url}" alt="">
${_.escape(user.name)}
`;
/* eslint-enable no-param-reassign */
})
......
......@@ -5,6 +5,8 @@
/* global IssuableContext */
/* global Sidebar */
import DueDateSelectors from './due_date_select';
export default () => {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
......@@ -15,6 +17,6 @@ export default () => {
new WeightSelect();
new IssuableContext(sidebarOptions.currentUser);
gl.Subscription.bindAll('.subscription');
new gl.DueDateSelectors();
new DueDateSelectors();
window.sidebar = new Sidebar();
};
......@@ -2,12 +2,12 @@
/* global GitLab */
/* global Autosave */
/* global GroupsSelect */
/* global dateFormat */
import Pikaday from 'pikaday';
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
(function() {
this.IssuableForm = (function() {
......@@ -40,11 +40,13 @@ import ZenMode from './zen_mode';
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $issuableDueDate.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: function(dateText) {
$issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
$issuableDueDate.val(calendar.toString(dateText));
}
});
calendar.setDate(new Date($issuableDueDate.val()));
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
}
......
const DateFix = {
dashedFix(val) {
const [y, m, d] = val.split('-');
return new Date(y, m - 1, d);
},
export const pad = (val, len = 2) => (`0${val}`).slice(-len);
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
export const parsePikadayDate = (dateString) => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
};
export default DateFix;
/**
* Used `onSelect` method in pickaday
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
export const pikadayToString = (date) => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
return `${year}-${month}-${day}`;
};
......@@ -51,8 +51,6 @@ import './confirm_danger_modal';
import './copy_as_gfm';
import './copy_to_clipboard';
import './diff';
import './dropzone_input';
import './due_date_select';
import './files_comment_button';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
......
/* global dateFormat */
import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
......@@ -22,8 +21,10 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
format: 'yyyy-mm-dd',
minDate: new Date(),
container: $input.parent().get(0),
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect(dateText) {
$input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
$input.val(calendar.toString(dateText));
$input.trigger('change');
......@@ -31,7 +32,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
},
});
calendar.setDate(new Date($input.val()));
calendar.setDate(parsePikadayDate($input.val()));
$input.data('pikaday', calendar);
});
......
......@@ -7,6 +7,7 @@
Sample configuration:
<user-avatar-image
:lazy="true"
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
......@@ -16,11 +17,17 @@
*/
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarImage',
props: {
lazy: {
type: Boolean,
required: false,
default: false,
},
imgSrc: {
type: String,
required: false,
......@@ -56,18 +63,21 @@ export default {
tooltip,
},
computed: {
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
tooltipContainer() {
return this.tooltipText ? 'body' : null;
},
avatarSizeClass() {
return `s${this.size}`;
},
// API response sends null when gravatar is disabled and
// we provide an empty string when we use it inside user avatar link.
// In both cases we should render the defaultAvatarUrl
imageSource() {
return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
},
},
};
</script>
......@@ -76,11 +86,16 @@ export default {
<img
v-tooltip
class="avatar"
:class="[avatarSizeClass, cssClasses]"
:src="imageSource"
:class="{
lazy,
[avatarSizeClass]: true,
[cssClasses]: true
}"
:src="resultantSrcAttribute"
:width="size"
:height="size"
:alt="imgAlt"
:data-src="sanitizedSource"
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
:title="tooltipText"
......
......@@ -61,6 +61,11 @@
border: 1px solid $dropdown-border-color;
min-width: 175px;
color: $gl-text-color;
z-index: 999;
}
.select2-drop-mask {
z-index: 998;
}
.select2-drop.select2-drop-above.select2-drop-active {
......
......@@ -2,7 +2,6 @@ class Projects::ApplicationController < ApplicationController
include RoutableActions
skip_before_action :authenticate_user!
before_action :redirect_git_extension
before_action :project
before_action :repository
layout 'project'
......@@ -11,15 +10,6 @@ class Projects::ApplicationController < ApplicationController
private
def redirect_git_extension
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
redirect_to url_for(params.merge(format: nil)) if params[:format] == 'git'
end
def project
return @project if @project
return nil unless params[:project_id] || params[:id]
......
......@@ -75,17 +75,36 @@ class Projects::MirrorsController < Projects::ApplicationController
@remote_mirror = @project.remote_mirrors.first_or_initialize
end
def remote_mirror_attributes
{ remote_mirrors_attributes: %i[url id enabled] }
end
def mirror_params_attributes
attributes = [
:mirror,
:import_url,
:username_only_import_url,
:mirror_user_id,
:mirror_trigger_builds,
import_data_attributes: %i[
id
auth_method
password
ssh_known_hosts
regenerate_ssh_private_key
]
]
if can?(current_user, :admin_remote_mirror, project)
attributes << remote_mirror_attributes
end
attributes
end
def mirror_params
params.require(:project)
.permit(
:mirror,
:import_url,
:username_only_import_url,
:mirror_user_id,
:mirror_trigger_builds,
import_data_attributes: [:id, :auth_method, :password, :ssh_known_hosts, :regenerate_ssh_private_key],
remote_mirrors_attributes: [:url, :id, :enabled]
)
params.require(:project).permit(mirror_params_attributes)
end
def safe_mirror_params
......
......@@ -5,6 +5,7 @@ class ProjectsController < Projects::ApplicationController
prepend EE::ProjectsController
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create]
before_action :repository, except: [:index, :new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
......@@ -399,4 +400,13 @@ class ProjectsController < Projects::ApplicationController
def project_export_enabled
render_404 unless current_application_settings.project_export_enabled?
end
def redirect_git_extension
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git'
end
end
......@@ -7,6 +7,8 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
expires_full_path_cache
# Move the namespace directory in all storage paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
......
class Geo::BaseFdw < Geo::TrackingBase
self.abstract_class = true
end
class Geo::BaseRegistry < ActiveRecord::Base
class Geo::BaseRegistry < Geo::TrackingBase
self.abstract_class = true
if Gitlab::Geo.geo_database_configured?
establish_connection Rails.configuration.geo_database
end
def self.connection
raise 'Geo secondary database is not configured' unless Gitlab::Geo.geo_database_configured?
super
end
end
module Geo
module Fdw
class LfsObject < ::Geo::BaseFdw
self.table_name = Gitlab::Geo.fdw_table('lfs_objects')
end
end
end
module Geo
module Fdw
class Upload < ::Geo::BaseFdw
self.table_name = Gitlab::Geo.fdw_table('uploads')
end
end
end
# This module is intended to centralize all database access to the secondary
# tracking database for Geo.
module Geo
class TrackingBase < ActiveRecord::Base
self.abstract_class = true
if ::Gitlab::Geo.geo_database_configured?
establish_connection Rails.configuration.geo_database
end
def self.connection
raise 'Geo secondary database is not configured' unless ::Gitlab::Geo.geo_database_configured?
super
end
end
end
......@@ -47,6 +47,7 @@ class License < ActiveRecord::Base
object_storage
service_desk
variable_environment_scope
reject_unsigned_commits
].freeze
EEU_FEATURES = EEP_FEATURES
......
......@@ -175,7 +175,7 @@ class Note < ActiveRecord::Base
end
def cross_reference?
system? && SystemNoteService.cross_reference?(note)
system? && matches_cross_reference_regex?
end
def diff_note?
......
......@@ -26,8 +26,8 @@ class ProjectMirrorData < ActiveRecord::Base
def set_next_execution_timestamp!
timestamp = Time.now
retry_factor = [1, self.retry_count].max
delay = [base_delay(timestamp) * retry_factor, Gitlab::Mirror.max_delay].min
delay = [delay, Gitlab::Mirror.min_delay].max
delay = [base_delay(timestamp), Gitlab::Mirror.min_delay].max
delay = [delay * retry_factor, Gitlab::Mirror.max_delay].min
self.next_execution_timestamp = timestamp + delay
end
......
......@@ -22,6 +22,7 @@ class PushRule < ActiveRecord::Base
end
def commit_signature_allowed?(commit)
return true unless available?(:reject_unsigned_commits)
return true unless reject_unsigned_commits
commit.has_signature?
......@@ -74,6 +75,14 @@ class PushRule < ActiveRecord::Base
is_sample?
end
def available?(feature_sym)
if global?
License.feature_available?(feature_sym)
else
project&.feature_available?(feature_sym)
end
end
private
def data_match?(data, regex)
......
......@@ -17,6 +17,7 @@ class RemoteMirror < ActiveRecord::Base
validate :url_availability, if: -> (mirror) { mirror.url_changed? || mirror.enabled? }
after_save :set_override_remote_mirror_available, unless: -> { Gitlab::CurrentSettings.current_application_settings.remote_mirror_available }
after_save :refresh_remote, if: :mirror_url_changed?
after_update :reset_fields, if: :mirror_url_changed?
after_destroy :remove_remote
......@@ -85,6 +86,7 @@ class RemoteMirror < ActiveRecord::Base
def enabled
return false unless project && super
return false unless project.remote_mirror_available?
return false unless project.repository_exists?
return false if project.pending_delete?
......@@ -147,6 +149,12 @@ class RemoteMirror < ActiveRecord::Base
)
end
def set_override_remote_mirror_available
enabled = read_attribute(:enabled)
project.update(remote_mirror_available_overridden: enabled)
end
def refresh_remote
return unless project
......
......@@ -10,7 +10,7 @@ module Geo
repository_storage_name: project.repository.storage,
repository_storage_path: project.repository_storage_path,
repo_path: project.disk_path,
wiki_path: ("#{project.disk_path}.wiki" if project.wiki_enabled?),
wiki_path: (project.wiki.disk_path if project.wiki_enabled?),
project_name: project.name
)
end
......
......@@ -31,7 +31,7 @@ module Geo
end
def new_wiki_path_with_namespace
"#{project.disk_path}.wiki"
project.wiki.disk_path
end
end
end
......@@ -126,7 +126,7 @@ class GitPushService < BaseService
protected
def update_remote_mirrors
return if @project.remote_mirrors.empty?
return unless @project.has_remote_mirror?
@project.mark_stuck_remote_mirrors_as_failed!
@project.update_remote_mirrors
......
......@@ -11,7 +11,7 @@ module Projects
result = move_storage(project.disk_path, new_storage_path)
if project.wiki.repository_exists?
result &&= move_storage("#{project.disk_path}.wiki", new_storage_path)
result &&= move_storage(project.wiki.disk_path, new_storage_path)
end
if result
......@@ -44,7 +44,7 @@ module Projects
if wiki.repository_exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage_path,
"#{disk_path}.wiki",
wiki.disk_path,
"#{new_project_path}.wiki")
end
end
......
......@@ -162,7 +162,6 @@ module SystemNoteService
# "changed time estimate to 3d 5h"
#
# Returns the created Note object
def change_time_estimate(noteable, project, author)
parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
body = if noteable.time_estimate == 0
......@@ -188,7 +187,6 @@ module SystemNoteService
# "added 2h 30m of time spent"
#
# Returns the created Note object
def change_time_spent(noteable, project, author)
time_spent = noteable.time_spent
......@@ -453,10 +451,6 @@ module SystemNoteService
end
end
def cross_reference?(note_text)
note_text =~ /\A#{cross_reference_note_prefix}/i
end
# Check if a cross-reference is disallowed
#
# This method prevents adding a "mentioned in !1" note on every single commit
......@@ -486,7 +480,6 @@ module SystemNoteService
# mentioner - Mentionable object
#
# Returns Boolean
def cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
......
......@@ -126,7 +126,9 @@
Enabling this will only make licensed EE features available to projects if the project namespace's plan
includes the feature or if the project is public.
= render partial: 'repository_mirrors_form', locals: { f: f }
- if License.feature_available?(:repository_mirrors)
= render partial: 'repository_mirrors_form', locals: { f: f }
= render partial: 'repository_remote_mirrors_form', locals: { f: f }
%fieldset
%legend Sign-up Restrictions
......
%fieldset
%legend Repository Remote mirror settings
.form-group
= f.label :remote_mirror_available, 'Enable remote mirror configuration', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :remote_mirror_available do
= f.check_box :remote_mirror_available
Allow remote mirrors to be setup for projects
%span.help-block
If disabled, only admins will be able to setup remote mirrors in projects.
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository')
......@@ -11,4 +11,4 @@
.alert.alert-danger
- @push_rule.errors.full_messages.each do |msg|
%p= msg
= render "shared/push_rules_form", f: f
= render "shared/push_rules/form", f: f
......@@ -3,4 +3,5 @@
- if @project.feature_available?(:repository_mirrors)
= render 'projects/mirrors/pull'
= render 'projects/mirrors/push'
- if can?(current_user, :admin_remote_mirror, @project)
= render 'projects/mirrors/push'
......@@ -15,4 +15,4 @@
= form_for [@project.namespace.becomes(Namespace), @project, @push_rule] do |f|
= form_errors(@push_rule)
= render "shared/push_rules_form", f: f
= render "shared/push_rules/form", f: f
.form-group
= f.check_box :reject_unsigned_commits, class: "pull-left", disabled: !can_change_reject_unsigned_commits?(f.object)
.prepend-left-20
= f.label :reject_unsigned_commits, class: "label-light append-bottom-0" do
Reject unsigned commits
%p.light.append-bottom-0
= reject_unsigned_commits_description(f.object)
= render 'shared/push_rules/reject_unsigned_commits_setting', form: f, push_rule: f.object
.form-group
= f.check_box :deny_delete_tag, class: "pull-left"
......
- return unless push_rule.available?(:reject_unsigned_commits)
- form = local_assigns.fetch(:form)
- push_rule = local_assigns.fetch(:push_rule)
.form-group
= form.check_box :reject_unsigned_commits, class: "pull-left", disabled: !can_change_reject_unsigned_commits?(push_rule)
.prepend-left-20
= form.label :reject_unsigned_commits, class: "label-light append-bottom-0" do
Reject unsigned commits
%p.light.append-bottom-0
= reject_unsigned_commits_description(push_rule)
......@@ -21,9 +21,9 @@ module Geo
def find_unsynced_objects
lfs_object_ids = find_lfs_object_ids
objects_ids = find_object_ids
upload_objects_ids = find_upload_object_ids
interleave(lfs_object_ids, objects_ids)
interleave(lfs_object_ids, upload_objects_ids)
end
def find_failed_objects
......@@ -33,38 +33,82 @@ module Geo
.pluck(:file_id, :file_type)
end
def find_object_ids
unsynced_downloads = filter_registry_ids(
current_node.uploads,
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name
)
def selective_sync
current_node.restricted_project_ids
end
def find_lfs_object_ids
# Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if Gitlab::Geo.fdw? && !selective_sync
fdw_find_lfs_object_ids
else
legacy_find_lfs_object_ids
end
relation
.limit(db_retrieve_batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
end
unsynced_downloads
def find_upload_object_ids
# Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if Gitlab::Geo.fdw? && !selective_sync
fdw_find_upload_object_ids
else
legacy_find_upload_object_ids
end
relation
.limit(db_retrieve_batch_size)
.pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end
def find_lfs_object_ids
unsynced_downloads = filter_registry_ids(
def fdw_find_lfs_object_ids
fdw_table = Geo::Fdw::LfsObject.table_name
# Filter out objects in object storage (this is done in GeoNode#lfs_objects)
Geo::Fdw::LfsObject.joins("LEFT OUTER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id AND file_registry.file_type = 'lfs'")
.where("#{fdw_table}.file_store IS NULL OR #{fdw_table}.file_store = #{LfsObjectUploader::LOCAL_STORE}")
.where('file_registry.file_id IS NULL')
end
def fdw_find_upload_object_ids
fdw_table = Geo::Fdw::Upload.table_name
obj_types = Geo::FileService::DEFAULT_OBJECT_TYPES.map { |val| "'#{val}'" }.join(',')
Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id AND file_registry.file_type IN (#{obj_types})")
.where('file_registry.file_id IS NULL')
.order(created_at: :desc)
end
def legacy_find_upload_object_ids
legacy_filter_registry_ids(
current_node.uploads,
Geo::FileService::DEFAULT_OBJECT_TYPES,
Upload.table_name
)
end
def legacy_find_lfs_object_ids
legacy_filter_registry_ids(
current_node.lfs_objects,
[:lfs],
LfsObject.table_name
)
unsynced_downloads
.limit(db_retrieve_batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
end
# This query requires data from two different databases, and unavoidably
# plucks a list of file IDs from one into the other. This will not scale
# well with the number of synchronized files--the query will increase
# linearly in size--so this should be replaced with postgres_fdw ASAP.
def filter_registry_ids(objects, file_types, table_name)
registry_ids = pluck_registry_ids(Geo::FileRegistry, file_types)
def legacy_filter_registry_ids(objects, file_types, table_name)
registry_ids = legacy_pluck_registry_ids(Geo::FileRegistry, file_types)
return objects if registry_ids.empty?
......@@ -78,7 +122,7 @@ module Geo
joined_relation.where(file_registry: { registry_present: [nil, false] })
end
def pluck_registry_ids(relation, file_types)
def legacy_pluck_registry_ids(relation, file_types)
ids = relation.where(file_type: file_types).pluck(:file_id)
(ids + scheduled_file_ids(file_types)).uniq
end
......
---
title: Allow admins to globally disable all remote mirrors from application settings
page.
merge_request:
author:
type: added
---
title: Reconfigure the Geo tracking database pool size when running as Sidekiq
merge_request: 3181
author:
type: fixed
---
title: Use PostgreSQL FDW for Geo downloads
merge_request:
author:
type: added
---
title: Decreases z-index of select2 to a lower number of our navigation bar
merge_request:
author:
type: fixed
---
title: Fix timezone bug in Pikaday and upgrade Pikaday version
merge_request:
author:
type: fixed
---
title: Add lazy option to UserAvatarImage
merge_request: 14895
author:
type: changed
......@@ -464,3 +464,10 @@
:why: Our own library - https://gitlab.com/gitlab-org/gitlab-svgs
:versions: []
:when: 2017-09-19 14:36:32.795496000 Z
- - :license
- pikaday
- MIT
- :who:
:why:
:versions: []
:when: 2017-10-17 17:46:12.367554000 Z
......@@ -52,6 +52,14 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.establish_connection(config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# EE only
if Gitlab::Geo.geo_database_configured?
Rails.configuration.geo_database['pool'] = Sidekiq.options[:concurrency]
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{Geo::TrackingBase.connection_pool.size} (Geo tracking database)")
end
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
# https://github.com/mikel/mail/issues/912#issuecomment-214850355
Mail.eager_autoload!
......
class AddRemoteMirrorAvailableToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :remote_mirror_available, :boolean, default: true, allow_null: false)
end
def down
remove_column(:application_settings, :remote_mirror_available)
end
end
class AddRemoteMirrorAvailableOverriddenToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column(:projects, :remote_mirror_available_overridden, :boolean)
end
def down
remove_column(:projects, :remote_mirror_available_overridden)
end
end
......@@ -11,7 +11,11 @@
#
# It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 20171012101043) do
=======
ActiveRecord::Schema.define(version: 20171017130239) do
>>>>>>> origin/master
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -157,10 +161,14 @@ ActiveRecord::Schema.define(version: 20171012101043) do
t.boolean "hashed_storage_enabled", default: false, null: false
t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: false, null: false
<<<<<<< HEAD
t.integer "circuitbreaker_failure_count_threshold", default: 160
t.integer "circuitbreaker_failure_wait_time", default: 30
t.integer "circuitbreaker_failure_reset_time", default: 1800
t.integer "circuitbreaker_storage_timeout", default: 30
=======
t.boolean "remote_mirror_available", default: true, null: false
>>>>>>> origin/master
end
create_table "approvals", force: :cascade do |t|
......@@ -1607,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20171012101043) do
t.boolean "disable_overriding_approvers_per_merge_request"
t.integer "storage_version", limit: 2
t.boolean "resolve_outdated_diff_discussions"
t.boolean "remote_mirror_available_overridden"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
......@@ -18,7 +18,7 @@ in the merge request widget area:
For instance, consider the following workflow:
1. Your backend team member starts a new implementation for making certain feature in your app faster
1. With Code Quality reports, they analize how their implementation is impacting the code quality
1. With Code Quality reports, they analyze how their implementation is impacting the code quality
1. The metrics show that their code degrade the quality in 10 points
1. You ask a co-worker to help them with this modification
1. They both work on the changes until Code Quality report displays no degradations, only improvements
......
......@@ -22,7 +22,8 @@ module EE
:slack_app_id,
:slack_app_secret,
:slack_app_verification_token,
:allow_group_owners_to_manage_ldap
:allow_group_owners_to_manage_ldap,
:remote_mirror_available
]
end
......
......@@ -40,7 +40,8 @@ module EE
mirror_max_delay: Settings.gitlab['mirror_max_delay'],
mirror_max_capacity: Settings.gitlab['mirror_max_capacity'],
mirror_capacity_threshold: Settings.gitlab['mirror_capacity_threshold'],
allow_group_owners_to_manage_ldap: true
allow_group_owners_to_manage_ldap: true,
remote_mirror_available: true
)
end
end
......
......@@ -132,7 +132,9 @@ module EE
end
def has_remote_mirror?
feature_available?(:repository_mirrors) && remote_mirrors.enabled.exists?
feature_available?(:repository_mirrors) &&
remote_mirror_available? &&
remote_mirrors.enabled.exists?
end
def updating_remote_mirror?
......@@ -140,7 +142,7 @@ module EE
end
def update_remote_mirrors
return unless feature_available?(:repository_mirrors)
return unless feature_available?(:repository_mirrors) && remote_mirror_available?
remote_mirrors.enabled.each(&:sync)
end
......@@ -463,6 +465,11 @@ module EE
@disabled_services
end
def remote_mirror_available?
remote_mirror_available_overridden ||
current_application_settings.remote_mirror_available
end
private
def licensed_feature_available?(feature)
......
......@@ -20,6 +20,11 @@ module EE
!PushRule.global&.reject_unsigned_commits
end
with_scope :global
condition(:remote_mirror_available) do
::Gitlab::CurrentSettings.current_application_settings.remote_mirror_available
end
rule { admin }.enable :change_repository_storage
rule { support_bot }.enable :guest_access
......@@ -49,6 +54,8 @@ module EE
rule { can?(:developer_access) }.enable :admin_board
rule { (remote_mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
rule { deploy_board_disabled & ~is_development }.prevent :read_deploy_board
rule { can?(:master_access) }.policy do
......
......@@ -17,7 +17,7 @@ module EE
def process_wiki_repository_update
if ::Gitlab::Geo.primary?
# Create wiki repository updated event on Geo event log
::Geo::RepositoryUpdatedEventStore.new(project, source: Geo::RepositoryUpdatedEvent::WIKI).create
::Geo::RepositoryUpdatedEventStore.new(project, source: ::Geo::RepositoryUpdatedEvent::WIKI).create
end
end
end
......
......@@ -22,7 +22,7 @@ module EE
if ::Gitlab::Geo.enabled?
# Create wiki repository updated event on Geo event log
::Geo::RepositoryUpdatedEventStore.new(post_received.project, source: Geo::RepositoryUpdatedEvent::WIKI).create
::Geo::RepositoryUpdatedEventStore.new(post_received.project, source: ::Geo::RepositoryUpdatedEvent::WIKI).create
end
end
......
......@@ -101,50 +101,50 @@ module Backup
end
def unpack
Dir.chdir(backup_path)
# check for existing backups in the backup dir
if backup_file_list.empty?
$progress.puts "No backups found in #{backup_path}"
$progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}"
exit 1
elsif backup_file_list.many? && ENV["BACKUP"].nil?
$progress.puts 'Found more than one backup, please specify which one you want to restore:'
$progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup'
exit 1
end
Dir.chdir(backup_path) do
# check for existing backups in the backup dir
if backup_file_list.empty?
$progress.puts "No backups found in #{backup_path}"
$progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}"
exit 1
elsif backup_file_list.many? && ENV["BACKUP"].nil?
$progress.puts 'Found more than one backup, please specify which one you want to restore:'
$progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup'
exit 1
end
tar_file = if ENV['BACKUP'].present?
"#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
else
backup_file_list.first
end
tar_file = if ENV['BACKUP'].present?
"#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
else
backup_file_list.first
end
unless File.exist?(tar_file)
$progress.puts "The backup file #{tar_file} does not exist!"
exit 1
end
unless File.exist?(tar_file)
$progress.puts "The backup file #{tar_file} does not exist!"
exit 1
end
$progress.print 'Unpacking backup ... '
$progress.print 'Unpacking backup ... '
unless Kernel.system(*%W(tar -xf #{tar_file}))
$progress.puts 'unpacking backup failed'.color(:red)
exit 1
else
$progress.puts 'done'.color(:green)
end
unless Kernel.system(*%W(tar -xf #{tar_file}))
$progress.puts 'unpacking backup failed'.color(:red)
exit 1
else
$progress.puts 'done'.color(:green)
end
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems
if settings[:gitlab_version] != Gitlab::VERSION
$progress.puts 'GitLab version mismatch:'.color(:red)
$progress.puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".color(:red)
$progress.puts ' Please switch to the following version and try again:'.color(:red)
$progress.puts " version: #{settings[:gitlab_version]}".color(:red)
$progress.puts
$progress.puts "Hint: git checkout v#{settings[:gitlab_version]}"
exit 1
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems
if settings[:gitlab_version] != Gitlab::VERSION
$progress.puts 'GitLab version mismatch:'.color(:red)
$progress.puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".color(:red)
$progress.puts ' Please switch to the following version and try again:'.color(:red)
$progress.puts " version: #{settings[:gitlab_version]}".color(:red)
$progress.puts
$progress.puts "Hint: git checkout v#{settings[:gitlab_version]}"
exit 1
end
end
end
......
......@@ -75,9 +75,19 @@ module Banzai
begin
node['href'] = node['href'].strip
uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
return unless uri.scheme
# Remove all invalid scheme characters before checking against the
# list of unsafe protocols.
#
# See https://tools.ietf.org/html/rfc3986#section-3.1
scheme = uri.scheme
.strip
.downcase
.gsub(/[^A-Za-z0-9\+\.\-]+/, '')
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
rescue Addressable::URI::InvalidURIError
node.remove_attribute('href')
end
......
......@@ -14,6 +14,8 @@ module Gitlab
SECONDARY_JOBS = %i(repository_sync_job file_download_job).freeze
FDW_SCHEMA = 'gitlab_secondary'.freeze
def self.current_node
self.cache_value(:geo_node_current) do
GeoNode.find_by(host: Gitlab.config.gitlab.host,
......@@ -77,13 +79,13 @@ module Gitlab
def self.fdw?
self.cache_value(:geo_fdw?) do
::Geo::BaseRegistry.connection.execute(
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{self.fdw_schema}' AND table_type = 'FOREIGN TABLE'"
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{FDW_SCHEMA}' AND table_type = 'FOREIGN TABLE'"
).first.fetch('count').to_i.positive?
end
end
def self.fdw_schema
'gitlab_secondary'.freeze
def self.fdw_table(table_name)
FDW_SCHEMA + ".#{table_name}"
end
def self.repository_sync_job
......
......@@ -105,6 +105,7 @@ excluded_attributes:
- :mirror_user_id
- :mirror_trigger_builds
- :storage_version
- :remote_mirror_available_overridden
snippets:
- :expired_at
merge_request_diff:
......
......@@ -64,9 +64,9 @@ module Gitlab
deployments: Deployment.count,
environments: ::Environment.count,
gcp_clusters: ::Gcp::Cluster.count,
geo_nodes: GeoNode.count,
gcp_clusters_enabled: ::Gcp::Cluster.enabled.count,
gcp_clusters_disabled: ::Gcp::Cluster.disabled.count,
geo_nodes: GeoNode.count,
in_review_folder: ::Environment.in_review_folder.count,
groups: Group.count,
issues: Issue.count,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
require 'rubocop-rspec'
require_relative '../../spec_helpers'
module RuboCop
module Cop
module RSpec
# This cop checks for ENV assignment in specs
#
# @example
#
# # bad
# before do
# ENV['FOO'] = 'bar'
# end
#
# # good
# before do
# stub_env('FOO', 'bar')
# end
class EnvAssignment < Cop
include SpecHelpers
MESSAGE = "Don't assign to ENV, use `stub_env` instead.".freeze
def_node_search :env_assignment?, <<~PATTERN
(send (const nil? :ENV) :[]= ...)
PATTERN
# Following is what node.children looks like on a match
# [s(:const, nil, :ENV), :[]=, s(:str, "key"), s(:str, "value")]
def on_send(node)
return unless in_spec?(node)
return unless env_assignment?(node)
add_offense(node, :expression, MESSAGE)
end
def autocorrect(node)
lambda do |corrector|
corrector.replace(node.loc.expression, stub_env(env_key(node), env_value(node)))
end
end
def env_key(node)
node.children[2].source
end
def env_value(node)
node.children[3].source
end
def stub_env(key, value)
"stub_env(#{key}, #{value})"
end
end
end
end
end
require_relative 'cop/active_record_dependent'
require_relative 'cop/active_record_serialize'
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
require_relative 'cop/active_record_serialize'
require_relative 'cop/redirect_with_status'
require_relative 'cop/in_batches'
require_relative 'cop/polymorphic_associations'
require_relative 'cop/project_path_helper'
require_relative 'cop/active_record_dependent'
require_relative 'cop/in_batches'
require_relative 'cop/redirect_with_status'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
......@@ -13,12 +13,13 @@ require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default'
require_relative 'cop/migration/safer_boolean_column'
require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/single_line_hook'
require_relative 'cop/rspec/verbose_include_metadata'
module RuboCop
module SpecHelpers
SPEC_HELPERS = %w[spec_helper.rb rails_helper.rb].freeze
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
path = node.location.expression.source_buffer.name
!SPEC_HELPERS.include?(File.basename(path)) && path.start_with?(File.join(Dir.pwd, 'spec'))
end
end
end
require('spec_helper')
describe ProfilesController do
describe "PUT update" do
it "allows an email update from a user without an external email address" do
user = create(:user)
describe ProfilesController, :request_store do
let(:user) { create(:user) }
describe 'PUT update' do
it 'allows an email update from a user without an external email address' do
sign_in(user)
put :update,
......@@ -29,7 +30,7 @@ describe ProfilesController do
expect(user.unconfirmed_email).to eq nil
end
it "ignores an email update from a user with an external email address" do
it 'ignores an email update from a user with an external email address' do
stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
stub_omniauth_setting(sync_profile_attributes: true)
......@@ -46,7 +47,7 @@ describe ProfilesController do
expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com')
end
it "ignores an email and name update but allows a location update from a user with external email and name, but not external location" do
it 'ignores an email and name update but allows a location update from a user with external email and name, but not external location' do
stub_omniauth_setting(sync_profile_from_provider: ['ldap'])
stub_omniauth_setting(sync_profile_attributes: true)
......@@ -65,4 +66,35 @@ describe ProfilesController do
expect(ldap_user.location).to eq('City, Country')
end
end
describe 'PUT update_username' do
let(:namespace) { user.namespace }
let(:project) { create(:project_empty_repo, namespace: namespace) }
let(:gitlab_shell) { Gitlab::Shell.new }
let(:new_username) { 'renamedtosomethingelse' }
it 'allows username change' do
sign_in(user)
put :update_username,
user: { username: new_username }
user.reload
expect(response.status).to eq(302)
expect(user.username).to eq(new_username)
end
it 'moves dependent projects to new namespace' do
sign_in(user)
put :update_username,
user: { username: new_username }
user.reload
expect(response.status).to eq(302)
expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy
end
end
end
......@@ -850,47 +850,107 @@ describe Projects::IssuesController do
describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
context 'when authenticated' do
before do
project.add_developer(user)
sign_in(user)
end
before do
project.add_developer(user)
sign_in(user)
end
it 'returns discussion json' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
it 'returns discussion json' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes individual_note])
end
expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note])
end
context 'with cross-reference system note', :request_store do
let(:new_issue) { create(:issue) }
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
context 'with cross-reference system note', :request_store do
let(:new_issue) { create(:issue) }
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
before do
create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference)
end
before do
create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference)
it 'filters notes that the user should not see' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(JSON.parse(response.body).count).to eq(1)
end
it 'does not result in N+1 queries' do
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
RequestStore.clear!
control_count = ActiveRecord::QueryRecorder.new do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
end.count
RequestStore.clear!
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
end
end
end
it 'filters notes that the user should not see' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
context 'with a related system note' do
let(:confidential_issue) { create(:issue, :confidential, project: project) }
let!(:system_note) { SystemNoteService.relate_issue(issue, confidential_issue, user) }
shared_examples 'user can see confidential issue' do |access_level|
context "when a user is a #{access_level}" do
before do
project.add_user(user, access_level)
end
it 'displays related notes' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(JSON.parse(response.body).count).to eq(1)
discussions = json_response
notes = discussions.flat_map {|d| d['notes']}
expect(discussions.count).to equal(2)
expect(notes).to include(a_hash_including('id' => system_note.id))
end
end
end
it 'does not result in N+1 queries' do
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
shared_examples 'user cannot see confidential issue' do |access_level|
context "when a user is a #{access_level}" do
before do
project.add_user(user, access_level)
end
RequestStore.clear!
it 'redacts note related to a confidential issue' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
control_count = ActiveRecord::QueryRecorder.new do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
end.count
discussions = json_response
notes = discussions.flat_map {|d| d['notes']}
expect(discussions.count).to equal(1)
expect(notes).not_to include(a_hash_including('id' => system_note.id))
end
end
end
context 'when authenticated' do
before do
sign_in(user)
end
RequestStore.clear!
%i(reporter developer master).each do |access|
it_behaves_like 'user can see confidential issue', access
end
it_behaves_like 'user cannot see confidential issue', :guest
end
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
context 'when unauthenticated' do
let(:project) { create(:project, :public) }
expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
it_behaves_like 'user cannot see confidential issue', Gitlab::Access::NO_ACCESS
end
end
end
......
This diff is collapsed.
This diff is collapsed.
......@@ -6,7 +6,7 @@ describe Projects::DestroyService do
let!(:project_id) { project.id }
let!(:project_name) { project.name }
let!(:project_path) { project.disk_path }
let!(:wiki_path) { project.disk_path + '.wiki' }
let!(:wiki_path) { project.wiki.disk_path }
let!(:storage_name) { project.repository_storage }
let!(:storage_path) { project.repository_storage_path }
......
......@@ -25,8 +25,8 @@ describe WikiPages::CreateService do
end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create)
expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute
end
......
......@@ -18,8 +18,8 @@ describe WikiPages::DestroyService do
end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create)
expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: ::Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute(page)
end
......
......@@ -26,8 +26,8 @@ describe WikiPages::UpdateService do
end
it 'triggers Geo::RepositoryUpdatedEventStore when Geo is enabled' do
expect(Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(Geo::RepositoryUpdatedEventStore).to receive(:create)
expect(::Geo::RepositoryUpdatedEventStore).to receive(:new).with(instance_of(Project), source: Geo::RepositoryUpdatedEvent::WIKI).and_call_original
expect_any_instance_of(::Geo::RepositoryUpdatedEventStore).to receive(:create)
service.execute(page)
end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment