Commit 678a6698 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-geo-show-event-log-data

parents d532f659 e5e3f88e
Please view this file on the master branch, on stable branches it's out of date.
## 9.5.3 (2017-09-03)
- [FIXED] Check if table exists before loading the current license. !2783
- [FIXED] Extend early adopters feature set.
## 9.5.2 (2017-08-28)
- [FIXED] Fix LDAP backwards-compatibility when using "method" or when "verify_certificates" is not defined. !2690
......
......@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 9.5.3 (2017-09-03)
- [SECURITY] Filter additional secrets from Rails logs.
- [FIXED] Make username update fail if the namespace update fails. !13642
- [FIXED] Fix failure when issue is authored by a deleted user. !13807
- [FIXED] Reverts changes made to signin_enabled. !13956
- [FIXED] Fix Merge when pipeline succeeds button dropdown caret icon horizontal alignment.
- [FIXED] Fixed diff changes bar buttons from showing/hiding whilst scrolling.
- [FIXED] Fix events error importing GitLab projects.
- [FIXED] Fix pipeline trigger via API fails with 500 Internal Server Error in 9.5.
- [FIXED] Fixed fly-out nav flashing in & out.
- [FIXED] Remove closing external issues by reference error.
- [FIXED] Re-allow appearances.description_html to be NULL.
- [CHANGED] Update and fix resolvable note icons for easier recognition.
- [OTHER] Eager load head pipeline projects for MRs index.
- [OTHER] Instrument MergeRequest#fetch_ref.
- [OTHER] Instrument MergeRequest#ensure_ref_fetched.
## 9.5.2 (2017-08-28)
- [FIXED] Fix signing in using LDAP when attribute mapping uses simple strings instead of arrays.
......
......@@ -752,7 +752,7 @@ GEM
retriable (1.4.1)
rinku (2.0.0)
rotp (2.1.2)
rouge (2.2.0)
rouge (2.2.1)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
......
......@@ -486,7 +486,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.shouldPropagate = function(e) {
var $target;
if (this.options.multiSelect) {
if (this.options.multiSelect || this.options.shouldPropagate === false) {
$target = $(e.target);
if ($target && !$target.hasClass('dropdown-menu-close') &&
!$target.hasClass('dropdown-menu-close-icon') &&
......@@ -546,10 +546,10 @@ GitLabDropdown = (function() {
};
GitLabDropdown.prototype.positionMenuAbove = function() {
var $button = $(this.el);
var $menu = this.dropdown.find('.dropdown-menu');
$menu.css('top', ($button.height() + $menu.height()) * -1);
$menu.css('top', 'initial');
$menu.css('bottom', '100%');
};
GitLabDropdown.prototype.hidden = function(e) {
......@@ -713,7 +713,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.noResults = function() {
var html;
return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
return html = '<li class="dropdown-menu-empty-link"><a href="#" class="is-focused">No matching results</a></li>';
};
GitLabDropdown.prototype.rowClicked = function(el) {
......
......@@ -11,8 +11,6 @@ import ZenMode from './zen_mode';
(function() {
this.IssuableForm = (function() {
IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?';
IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
function IssuableForm(form) {
......@@ -28,7 +26,6 @@ import ZenMode from './zen_mode';
new ZenMode();
this.titleField = this.form.find("input[name*='[title]']");
this.descriptionField = this.form.find("textarea[name*='[description]']");
this.issueMoveField = this.form.find("#move_to_project_id");
if (!(this.titleField.length && this.descriptionField.length)) {
return;
}
......@@ -36,7 +33,6 @@ import ZenMode from './zen_mode';
this.form.on("submit", this.handleSubmit);
this.form.on("click", ".btn-cancel", this.resetAutosave);
this.initWip();
this.initMoveDropdown();
$issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) {
calendar = new Pikaday({
......@@ -58,12 +54,6 @@ import ZenMode from './zen_mode';
};
IssuableForm.prototype.handleSubmit = function() {
var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null;
if ((parseInt(fieldId, 10) || 0) > 0) {
if (!confirm(this.issueMoveConfirmMsg)) {
return false;
}
}
return this.resetAutosave();
};
......@@ -115,48 +105,6 @@ import ZenMode from './zen_mode';
return this.titleField.val("WIP: " + (this.titleField.val()));
};
IssuableForm.prototype.initMoveDropdown = function() {
var $moveDropdown, pageSize;
$moveDropdown = $('.js-move-dropdown');
if ($moveDropdown.length) {
pageSize = $moveDropdown.data('page-size');
return $('.js-move-dropdown').select2({
ajax: {
url: $moveDropdown.data('projects-url'),
quietMillis: 125,
data: function(term, page, context) {
return {
search: term,
offset_id: context
};
},
results: function(data) {
var context,
more;
if (data.length >= pageSize)
more = true;
if (data[data.length - 1])
context = data[data.length - 1].id;
return {
results: data,
more: more,
context: context
};
}
},
formatResult: function(project) {
return project.name_with_namespace;
},
formatSelection: function(project) {
return project.name_with_namespace;
}
});
}
};
return IssuableForm;
})();
}).call(window);
......@@ -17,10 +17,6 @@ export default {
required: true,
type: String,
},
canMove: {
required: true,
type: Boolean,
},
canUpdate: {
required: true,
type: Boolean,
......@@ -96,10 +92,6 @@ export default {
type: String,
required: true,
},
projectsAutocompleteUrl: {
type: String,
required: true,
},
},
data() {
const store = new Store({
......@@ -142,7 +134,6 @@ export default {
confidential: this.isConfidential,
description: this.state.descriptionText,
lockedWarningVisible: false,
move_to_project_id: 0,
updateLoading: false,
});
}
......@@ -151,16 +142,6 @@ export default {
this.showForm = false;
},
updateIssuable() {
const canPostUpdate = this.store.formState.move_to_project_id !== 0 ?
confirm('Are you sure you want to move this issue to another project?') : true; // eslint-disable-line no-alert
if (!canPostUpdate) {
this.store.setFormState({
updateLoading: false,
});
return;
}
this.service.updateIssuable(this.store.formState)
.then(res => res.json())
.then((data) => {
......@@ -239,14 +220,12 @@ export default {
<form-component
v-if="canUpdate && showForm"
:form-state="formState"
:can-move="canMove"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
:markdown-docs="markdownDocs"
:markdown-preview-url="markdownPreviewUrl"
:project-path="projectPath"
:project-namespace="projectNamespace"
:projects-autocomplete-url="projectsAutocompleteUrl"
/>
<div v-else>
<title-component
......
<script>
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
formState: {
type: Object,
required: true,
},
projectsAutocompleteUrl: {
type: String,
required: true,
},
},
mounted() {
const $moveDropdown = $(this.$refs['move-dropdown']);
$moveDropdown.select2({
ajax: {
url: this.projectsAutocompleteUrl,
quietMillis: 125,
data(term, page, context) {
return {
search: term,
offset_id: context,
};
},
results(data) {
const more = data.length >= 50;
const context = data[data.length - 1] ? data[data.length - 1].id : null;
return {
results: data,
more,
context,
};
},
},
formatResult(project) {
return project.name_with_namespace;
},
formatSelection(project) {
return project.name_with_namespace;
},
})
.on('change', (e) => {
this.formState.move_to_project_id = parseInt(e.target.value, 10);
});
},
beforeDestroy() {
$(this.$refs['move-dropdown']).select2('destroy');
},
};
</script>
<template>
<fieldset>
<label
for="issuable-move"
class="sr-only">
Move
</label>
<div class="issuable-form-select-holder append-right-5">
<input
ref="move-dropdown"
type="hidden"
id="issuable-move"
data-placeholder="Move to a different project" />
</div>
<span
v-tooltip
data-placement="auto top"
title="Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.">
<i
class="fa fa-question-circle"
aria-hidden="true">
</i>
</span>
</fieldset>
</template>
......@@ -4,15 +4,10 @@
import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
import descriptionTemplate from './fields/description_template.vue';
import projectMove from './fields/project_move.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
export default {
props: {
canMove: {
type: Boolean,
required: true,
},
canDestroy: {
type: Boolean,
required: true,
......@@ -42,10 +37,6 @@
type: String,
required: true,
},
projectsAutocompleteUrl: {
type: String,
required: true,
},
},
components: {
lockedWarning,
......@@ -53,7 +44,6 @@
descriptionField,
descriptionTemplate,
editActions,
projectMove,
confidentialCheckbox,
},
computed: {
......@@ -93,10 +83,6 @@
:markdown-docs="markdownDocs" />
<confidential-checkbox
:form-state="formState" />
<project-move
v-if="canMove"
:form-state="formState"
:projects-autocomplete-url="projectsAutocompleteUrl" />
<edit-actions
:form-state="formState"
:can-destroy="canDestroy" />
......
......@@ -28,7 +28,6 @@ document.addEventListener('DOMContentLoaded', () => {
props: {
canUpdate: this.canUpdate,
canDestroy: this.canDestroy,
canMove: this.canMove,
endpoint: this.endpoint,
issuableRef: this.issuableRef,
initialTitleHtml: this.initialTitleHtml,
......@@ -41,7 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
markdownDocs: this.markdownDocs,
projectPath: this.projectPath,
projectNamespace: this.projectNamespace,
projectsAutocompleteUrl: this.projectsAutocompleteUrl,
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
......
......@@ -6,7 +6,6 @@ export default class Store {
confidential: false,
description: '',
lockedWarningVisible: false,
move_to_project_id: 0,
updateLoading: false,
};
}
......
......@@ -2,19 +2,20 @@ import _ from 'underscore';
(() => {
/*
* TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints,
* stringifyTime condensed or non-condensed, abbreviateTimelengths)
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* non-condensed, abbreviateTimelengths)
* */
const utils = window.gl.utils = gl.utils || {};
const prettyTime = utils.prettyTime = {
/*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero.
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length.
*/
parseSeconds(seconds) {
const DAYS_PER_WEEK = 5;
const HOURS_PER_DAY = 8;
parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = hoursPerDay;
const MINUTES_PER_HOUR = 60;
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
......
......@@ -65,10 +65,6 @@ import Cookies from 'js-cookie';
return _this.changeProject($(e.currentTarget).val());
};
})(this));
return $('.js-projects-dropdown-toggle').on('click', function(e) {
e.preventDefault();
return $('.js-projects-dropdown').select2('open');
});
};
Project.prototype.changeProject = function(url) {
......
......@@ -5,51 +5,7 @@ import ProjectSelectComboButton from './project_select_combo_button';
(function () {
this.ProjectSelect = (function () {
function ProjectSelect() {
$('.js-projects-dropdown-toggle').each(function (i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return $dropdown.glDropdown({
filterable: true,
filterRemote: true,
search: {
fields: ['name_with_namespace']
},
data: function (term, callback) {
var finalCallback, projectsCallback;
var orderBy = $dropdown.data('order-by');
finalCallback = function (projects) {
return callback(projects);
};
if (this.includeGroups) {
projectsCallback = function (projects) {
var groupsCallback;
groupsCallback = function (groups) {
var data;
data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
}
if (this.groupId) {
return Api.groupProjects(this.groupId, term, projectsCallback);
} else {
return Api.projects(term, {
order_by: orderBy
}, projectsCallback);
}
},
url: function (project) {
return project.web_url;
},
text: function (project) {
return project.name_with_namespace;
}
});
});
$('.ajax-project-select').each(function (i, select) {
$('.ajax-project-select').each(function(i, select) {
var placeholder;
this.groupId = $(select).data('group-id');
this.includeGroups = $(select).data('include-groups');
......
......@@ -157,11 +157,16 @@ import SidebarHeightManager from './sidebar_height_manager';
Sidebar.prototype.openDropdown = function(blockOrName) {
var $block;
$block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
$block.find('.edit-link').trigger('click');
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
return this.toggleSidebar('open');
this.toggleSidebar('open');
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
});
};
Sidebar.prototype.setCollapseAfterUpdate = function($block) {
......
......@@ -36,7 +36,7 @@ export default {
/>
<a
v-if="editable"
class="edit-link pull-right"
class="js-sidebar-dropdown-toggle edit-link pull-right"
href="#"
>
Edit
......
/* global Flash */
function isValidProjectId(id) {
return id > 0;
}
class SidebarMoveIssue {
constructor(mediator, dropdownToggle, confirmButton) {
this.mediator = mediator;
this.$dropdownToggle = $(dropdownToggle);
this.$confirmButton = $(confirmButton);
this.onConfirmClickedWrapper = this.onConfirmClicked.bind(this);
}
init() {
this.initDropdown();
this.addEventListeners();
}
destroy() {
this.removeEventListeners();
}
initDropdown() {
this.$dropdownToggle.glDropdown({
search: {
fields: ['name_with_namespace'],
},
showMenuAbove: true,
selectable: true,
filterable: true,
filterRemote: true,
multiSelect: false,
// Keep the dropdown open after selecting an option
shouldPropagate: false,
data: (searchTerm, callback) => {
this.mediator.fetchAutocompleteProjects(searchTerm)
.then(callback)
.catch(() => new Flash('An error occured while fetching projects autocomplete.'));
},
renderRow: project => `
<li>
<a href="#" class="js-move-issue-dropdown-item">
${project.name_with_namespace}
</a>
</li>
`,
clicked: (options) => {
const project = options.selectedObj;
const selectedProjectId = options.isMarking ? project.id : 0;
this.mediator.setMoveToProjectId(selectedProjectId);
this.$confirmButton.attr('disabled', !isValidProjectId(selectedProjectId));
},
});
}
addEventListeners() {
this.$confirmButton.on('click', this.onConfirmClickedWrapper);
}
removeEventListeners() {
this.$confirmButton.off('click', this.onConfirmClickedWrapper);
}
onConfirmClicked() {
if (isValidProjectId(this.mediator.store.moveToProjectId)) {
this.$confirmButton
.disable()
.addClass('is-loading');
this.mediator.moveIssue()
.catch(() => {
Flash('An error occured while moving the issue.');
this.$confirmButton
.enable()
.removeClass('is-loading');
});
}
}
}
export default SidebarMoveIssue;
......@@ -4,9 +4,11 @@ import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class SidebarService {
constructor(endpoint) {
constructor(endpointMap) {
if (!SidebarService.singleton) {
this.endpoint = endpoint;
this.endpoint = endpointMap.endpoint;
this.moveIssueEndpoint = endpointMap.moveIssueEndpoint;
this.projectsAutocompleteEndpoint = endpointMap.projectsAutocompleteEndpoint;
SidebarService.singleton = this;
}
......@@ -25,4 +27,18 @@ export default class SidebarService {
emulateJSON: true,
});
}
getProjectsAutocomplete(searchTerm) {
return Vue.http.get(this.projectsAutocompleteEndpoint, {
params: {
search: searchTerm,
},
});
}
moveIssue(moveToProjectId) {
return Vue.http.post(this.moveIssueEndpoint, {
move_to_project_id: moveToProjectId,
});
}
}
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
import Mediator from './sidebar_mediator';
......@@ -31,6 +32,12 @@ function domContentLoaded() {
service: mediator.service,
},
}).$mount(confidentialEl);
new SidebarMoveIssue(
mediator,
$('.js-move-issue'),
$('.js-move-issue-confirmation-button'),
).init();
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
......
......@@ -7,7 +7,11 @@ export default class SidebarMediator {
constructor(options) {
if (!SidebarMediator.singleton) {
this.store = new Store(options);
this.service = new Service(options.endpoint);
this.service = new Service({
endpoint: options.endpoint,
moveIssueEndpoint: options.moveIssueEndpoint,
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
});
SidebarMediator.singleton = this;
}
......@@ -26,6 +30,10 @@ export default class SidebarMediator {
return this.service.update(field, selected.length === 0 ? [0] : selected);
}
setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId);
}
fetch() {
this.service.get()
.then(response => response.json())
......@@ -35,4 +43,23 @@ export default class SidebarMediator {
})
.catch(() => new Flash('Error occured when fetching sidebar data'));
}
fetchAutocompleteProjects(searchTerm) {
return this.service.getProjectsAutocomplete(searchTerm)
.then(response => response.json())
.then((data) => {
this.store.setAutocompleteProjects(data);
return this.store.autocompleteProjects;
});
}
moveIssue() {
return this.service.moveIssue(this.store.moveToProjectId)
.then(response => response.json())
.then((data) => {
if (location.pathname !== data.web_url) {
gl.utils.visitUrl(data.web_url);
}
});
}
}
......@@ -13,6 +13,8 @@ export default class SidebarStore {
this.isFetching = {
assignees: true,
};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
SidebarStore.singleton = this;
}
......@@ -53,4 +55,12 @@ export default class SidebarStore {
removeAllAssignees() {
this.assignees = [];
}
setAutocompleteProjects(projects) {
this.autocompleteProjects = projects;
}
setMoveToProjectId(moveToProjectId) {
this.moveToProjectId = moveToProjectId;
}
}
......@@ -193,7 +193,7 @@
min-width: 240px;
max-width: 500px;
margin-top: 2px;
margin-bottom: 0;
margin-bottom: 2px;
font-size: 14px;
font-weight: $gl-font-weight-normal;
padding: 8px 0;
......@@ -618,6 +618,11 @@
border-top: 1px solid $dropdown-divider-color;
}
.dropdown-footer-content {
padding-left: 10px;
padding-right: 10px;
}
.dropdown-due-date-footer {
padding-top: 0;
margin-left: 10px;
......@@ -729,6 +734,7 @@
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
li {
display: block;
padding: 0 1px;
&:hover {
......
......@@ -279,14 +279,25 @@
// TODO: change global style
.ajax-project-dropdown,
.ajax-users-dropdown,
body[data-page="projects:settings:repository:show"] #select2-drop,
body[data-page="projects:new"] #select2-drop,
body[data-page="projects:merge_requests:edit"] #select2-drop,
body[data-page="projects:blob:new"] #select2-drop,
body[data-page="profiles:show"] #select2-drop,
body[data-page="admin:groups:show"] #select2-drop,
body[data-page="projects:blob:edit"] #select2-drop {
&.select2-drop {
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
color: $gl-text-color;
}
&.select2-drop-above {
border-top: none;
margin-top: -4px;
}
.select2-results {
.select2-no-results,
.select2-searching,
......
......@@ -707,6 +707,8 @@
}
.boards-switcher {
@include new-style-dropdown;
padding-right: 10px;
}
......
......@@ -473,7 +473,7 @@
padding-top: 6px;
}
.open .dropdown-menu {
.dropdown-menu {
width: 100%;
}
}
......@@ -486,6 +486,24 @@
}
}
.sidebar-move-issue-dropdown {
@include new-style-dropdown;
}
.sidebar-move-issue-confirmation-button {
width: 100%;
&.is-loading {
.sidebar-move-issue-confirmation-loading-icon {
display: inline-block;
}
}
}
.sidebar-move-issue-confirmation-loading-icon {
display: none;
}
.detail-page-description {
padding: 16px 0;
......
......@@ -61,6 +61,10 @@
display: -webkit-flex;
display: flex;
}
.dropdown-menu.dropdown-menu-align-right {
margin-top: -2px;
}
}
.form-horizontal {
......@@ -356,3 +360,7 @@
}
}
}
.member-form-control {
@include new-style-dropdown;
}
......@@ -290,6 +290,7 @@
.dropdown-toggle {
.fa {
margin-left: 0;
color: inherit;
}
}
......@@ -723,7 +724,14 @@
.approvers-list {
display: flex;
align-items: center;
margin-right: 5px;
}
.approvers-list {
.link-to-member-avatar:not(:first-child) {
img {
margin-left: 0;
}
}
}
.unapprove-btn {
......@@ -797,3 +805,7 @@
}
}
}
.merge-request-form {
@include new-style-dropdown;
}
......@@ -815,6 +815,8 @@ a.allowed-to-push {
.new-protected-branch,
.new-protected-tag {
@include new-style-dropdown;
label {
margin-top: 6px;
font-weight: $gl-font-weight-normal;
......@@ -841,19 +843,9 @@ a.allowed-to-push {
.protected-branches-list,
.protected-tags-list {
margin-bottom: 30px;
a {
color: $gl-text-color;
&:hover {
color: $gl-link-color;
}
@include new-style-dropdown;
&.is-active {
font-weight: $gl-font-weight-bold;
}
}
margin-bottom: 30px;
.settings-message {
margin: 0;
......
......@@ -117,11 +117,14 @@ class Admin::UsersController < Admin::ApplicationController
user_params_with_pass = user_params.dup
if params[:user][:password].present?
user_params_with_pass.merge!(
password_params = {
password: params[:user][:password],
password_confirmation: params[:user][:password_confirmation],
password_expires_at: Time.now
)
password_confirmation: params[:user][:password_confirmation]
}
password_params[:password_expires_at] = Time.now unless changing_own_password?
user_params_with_pass.merge!(password_params)
end
respond_to do |format|
......@@ -167,6 +170,10 @@ class Admin::UsersController < Admin::ApplicationController
protected
def changing_own_password?
user == current_user
end
def user
@user ||= User.find_by!(username: params[:id])
end
......
......@@ -210,7 +210,7 @@ class ApplicationController < ActionController::Base
end
def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication?
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
return redirect_to new_profile_password_path
end
end
......
......@@ -45,12 +45,6 @@ class AutocompleteController < ApplicationController
project = Project.find_by_id(params[:project_id])
projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id])
no_project = {
id: 0,
name_with_namespace: 'No project'
}
projects.unshift(no_project) unless params[:offset_id].present?
render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
end
......
module RequiresWhitelistedMonitoringClient
extend ActiveSupport::Concern
include Gitlab::CurrentSettings
included do
before_action :validate_ip_whitelisted_or_valid_token!
end
......
class PasswordsController < Devise::PasswordsController
include Gitlab::CurrentSettings
before_action :resource_from_email, only: [:create]
before_action :check_password_authentication_available, only: [:create]
before_action :prevent_ldap_reset, only: [:create]
before_action :throttle_reset, only: [:create]
def edit
......@@ -40,11 +38,11 @@ class PasswordsController < Devise::PasswordsController
self.resource = resource_class.find_by_email(email)
end
def check_password_authentication_available
return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?)
def prevent_ldap_reset
return unless resource&.ldap_user?
redirect_to after_sending_reset_password_instructions_path_for(resource_name),
alert: "Password authentication is unavailable."
alert: "Cannot reset password for LDAP user."
end
def throttle_reset
......
......@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
end
def authorize_change_password!
render_404 unless @user.allow_password_authentication?
render_404 if @user.ldap_user?
end
def user_params
......
......@@ -94,6 +94,6 @@ class Projects::ApplicationController < ApplicationController
end
def require_pages_enabled!
not_found unless Gitlab.config.pages.enabled
not_found unless @project.pages_available?
end
end
......@@ -17,7 +17,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue
before_action :authorize_update_issue!, only: [:edit, :update]
before_action :authorize_update_issue!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
......@@ -131,25 +131,33 @@ class Projects::IssuesController < Projects::ApplicationController
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
respond_to do |format|
format.html do
recaptcha_check_with_fallback { render :edit }
end
format.json do
render_issue_json
end
end
rescue ActiveRecord::StaleObjectError
render_conflict_response
end
def move
params.require(:move_to_project_id)
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
return render_404 unless issue.can_move?(current_user, new_project)
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
@issue = Issues::UpdateService.new(project, current_user, target_project: new_project).execute(issue)
end
respond_to do |format|
format.html do
recaptcha_check_with_fallback { render :edit }
end
format.json do
if @issue.valid?
render json: IssueSerializer.new.represent(@issue)
else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
end
render_issue_json
end
end
......@@ -260,6 +268,14 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.feature_available?(:issues, current_user)
end
def render_issue_json
if @issue.valid?
render json: IssueSerializer.new.represent(@issue)
else
render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
end
end
def issue_params
params.require(:issue).permit(*issue_params_attributes)
end
......
class Projects::LfsApiController < Projects::GitHttpClientController
include ApplicationSettingsHelper
include ApplicationHelper
include GitlabRoutingHelper
include LfsRequest
skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
def batch
unless objects.present?
......@@ -90,4 +94,16 @@ class Projects::LfsApiController < Projects::GitHttpClientController
}
}
end
def lfs_check_batch_operation!
if upload_request? && Gitlab::Geo.secondary?
render(
json: {
message: "You cannot write to a secondary GitLab Geo instance. Please use #{geo_primary_default_url_to_repo(project)} instead."
},
content_type: "application/vnd.git-lfs+json",
status: 403
)
end
end
end
......@@ -305,7 +305,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :failed
end
merge_request_service = MergeRequests::MergeService.new(@project, current_user, merge_params)
merge_request_service = ::MergeRequests::MergeService.new(@project, current_user, merge_params)
unless merge_request_service.hooks_validation_pass?(@merge_request)
return :hook_validation_error
......@@ -313,7 +313,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
@merge_request.update(merge_error: nil, squash: merge_params[:squash])
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
if params[:merge_when_pipeline_succeeds].present?
return :failed unless @merge_request.head_pipeline
......
......@@ -205,7 +205,7 @@ module ApplicationHelper
end
def support_url
current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
end
def page_filter_path(options = {})
......
......@@ -2,6 +2,8 @@ module ApplicationSettingsHelper
prepend EE::ApplicationSettingsHelper
extend self
include Gitlab::CurrentSettings
delegate :gravatar_enabled?,
:signup_enabled?,
:password_authentication_enabled?,
......@@ -83,6 +85,18 @@ module ApplicationSettingsHelper
end
end
def key_restriction_options_for_select(type)
bit_size_options = Gitlab::SSHPublicKey.supported_sizes(type).map do |bits|
["Must be at least #{bits} bits", bits]
end
[
['Are allowed', 0],
*bit_size_options,
['Are forbidden', ApplicationSetting::FORBIDDEN_KEY_VALUE]
]
end
def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, storage|
["#{name} - #{storage['path']}", name]
......@@ -115,6 +129,9 @@ module ApplicationSettingsHelper
:domain_blacklist_enabled,
:domain_blacklist_raw,
:domain_whitelist_raw,
:dsa_key_restriction,
:ecdsa_key_restriction,
:ed25519_key_restriction,
:email_author_in_body,
:enabled_git_access_protocol,
:gravatar_enabled,
......@@ -158,6 +175,7 @@ module ApplicationSettingsHelper
:repository_storages,
:require_two_factor_authentication,
:restricted_visibility_levels,
:rsa_key_restriction,
:send_user_confirmation_email,
:sentry_dsn,
:sentry_enabled,
......
module AuthHelper
include Gitlab::CurrentSettings
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze
......
......@@ -106,9 +106,11 @@ module DropdownsHelper
end
end
def dropdown_footer(&block)
def dropdown_footer(add_content_class: false, &block)
content_tag(:div, class: "dropdown-footer") do
if block
if add_content_class
content_tag(:div, capture(&block), class: "dropdown-footer-content")
else
capture(&block)
end
end
......
module FormHelper
prepend ::EE::FormHelper
def form_errors(model)
def form_errors(model, type: 'form')
return unless model.errors.any?
pluralized = 'error'.pluralize(model.errors.count)
headline = "The form contains the following #{pluralized}:"
headline = "The #{type} contains the following #{pluralized}:"
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
......
......@@ -215,12 +215,10 @@ module IssuablesHelper
endpoint: project_issue_path(@project, issuable),
canUpdate: can?(current_user, :update_issue, issuable),
canDestroy: can?(current_user, :destroy_issue, issuable),
canMove: current_user ? issuable.can_move?(current_user) : false,
issuableRef: issuable.to_reference,
isConfidential: issuable.confidential,
markdownPreviewUrl: preview_markdown_path(@project),
markdownDocs: help_page_path('user/markdown'),
projectsAutocompleteUrl: autocomplete_projects_path(project_id: @project.id),
issuableTemplates: issuable_templates(issuable),
projectPath: ref_project.path,
projectNamespace: ref_project.namespace.full_path,
......@@ -369,6 +367,8 @@ module IssuablesHelper
def issuable_sidebar_options(issuable, can_edit_issuable)
{
endpoint: "#{issuable_json_path(issuable)}?basic=true",
moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable),
projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id),
editable: can_edit_issuable,
currentUser: current_user.as_json(only: [:username, :id, :name], methods: :avatar_url),
rootPath: root_path,
......
......@@ -77,15 +77,14 @@ module LicenseHelper
def show_promotions?(selected_user = current_user)
return false unless selected_user
return @show_promotions if defined?(@show_promotions)
@show_promotions =
if current_application_settings.should_check_namespace_plan?
true
else
license = License.current
license.nil? || license.expired?
end
if Gitlab::CurrentSettings.current_application_settings
.should_check_namespace_plan?
true
else
license = License.current
license.nil? || license.expired?
end
end
def show_project_feature_promotion?(project_feature, callout_id = nil)
......
module ProjectsHelper
include Gitlab::CurrentSettings
def link_to_project(project)
link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
title = content_tag(:span, project.name, class: 'project-name')
......@@ -70,12 +72,6 @@ module ProjectsHelper
output.html_safe
end
if current_user
project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do
icon("chevron-down")
end
end
"#{namespace_link} / #{project_link}".html_safe
end
......
class BaseMailer < ActionMailer::Base
include Gitlab::CurrentSettings
around_action :render_with_default_locale
helper ApplicationHelper
helper MarkupHelper
attr_accessor :current_user
helper_method :current_user, :can?
helper_method :current_user, :can?, :current_application_settings
default from: proc { default_sender_address.format }
default reply_to: proc { default_reply_to_address.format }
......
......@@ -14,6 +14,11 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters
}x
# Setting a key restriction to `-1` means that all keys of this type are
# forbidden.
FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
......@@ -159,6 +164,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
validates :allowed_key_types, presence: true
validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level|
unless Gitlab::VisibilityLevel.options.value?(level)
......@@ -184,6 +195,7 @@ class ApplicationSetting < ActiveRecord::Base
end
before_validation :ensure_uuid!
before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
......@@ -234,6 +246,9 @@ class ApplicationSetting < ActiveRecord::Base
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
ed25519_key_restriction: 0,
gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
help_page_hide_commercial_content: false,
......@@ -252,6 +267,7 @@ class ApplicationSetting < ActiveRecord::Base
max_attachment_size: Settings.gitlab['max_attachment_size'],
password_authentication_enabled: Settings.gitlab['password_authentication_enabled'],
performance_bar_allowed_group_id: nil,
rsa_key_restriction: 0,
plantuml_enabled: false,
plantuml_url: nil,
project_export_enabled: true,
......@@ -460,6 +476,18 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_can_be_configured? && super
end
def allowed_key_types
SUPPORTED_KEY_TYPES.select do |type|
key_restriction_for(type) != FORBIDDEN_KEY_VALUE
end
end
def key_restriction_for(type)
attr_name = "#{type}_key_restriction"
has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
end
private
def ensure_uuid!
......
......@@ -3,6 +3,7 @@ module Ci
include TokenAuthenticatable
include AfterCommitQueue
include Presentable
include Importable
prepend EE::Build
belongs_to :runner
......@@ -29,6 +30,7 @@ module Ci
validates :coverage, numericality: true, allow_blank: true
validates :ref, presence: true
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
......@@ -38,6 +40,7 @@ module Ci
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :codequality, ->() { where(name: %w[codequality codeclimate]) }
scope :ref_protected, -> { where(protected: true) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
......
......@@ -48,6 +48,7 @@ module Ci
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? }
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
......
......@@ -6,7 +6,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze
has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......@@ -36,11 +36,17 @@ module Ci
end
validate :tag_constraints
validates :access_level, presence: true
acts_as_taggable
after_destroy :cleanup_runner_queue
enum access_level: {
not_protected: 0,
ref_protected: 1
}
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
......@@ -107,6 +113,8 @@ module Ci
end
def can_pick?(build)
return false if self.ref_protected? && !build.protected?
assignable_for?(build.project) && accepting_tags?(build)
end
......
......@@ -251,6 +251,28 @@ class Commit
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
end
def cherry_pick_description(user)
message_body = "(cherry picked from commit #{sha})"
if merged_merge_request?(user)
commits_in_merge_request = merged_merge_request(user).commits
if commits_in_merge_request.present?
message_body << "\n"
commits_in_merge_request.reverse.each do |commit_in_merge|
message_body << "\n#{commit_in_merge.short_id} #{commit_in_merge.title}"
end
end
end
message_body
end
def cherry_pick_message(user)
%Q{#{message}\n\n#{cherry_pick_description(user)}}
end
def revert_description(user)
if merged_merge_request?(user)
"This reverts merge request #{merged_merge_request(user).to_reference}"
......
module Elastic
module ApplicationSearch
extend ActiveSupport::Concern
extend Gitlab::CurrentSettings
included do
include Elasticsearch::Model
include Gitlab::CurrentSettings
index_name [Rails.application.class.parent_name.downcase, Rails.env].join('-')
......
......@@ -28,7 +28,7 @@ module Spammable
def submittable_as_spam?
if user_agent_detail
user_agent_detail.submittable? && current_application_settings.akismet_enabled
user_agent_detail.submittable? && Gitlab::CurrentSettings.current_application_settings.akismet_enabled
else
false
end
......
......@@ -303,7 +303,13 @@ class Issue < ActiveRecord::Base
end
end
def update_project_counter_caches?
state_changed? || confidential_changed?
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache
end
......
class IssueAssignee < ActiveRecord::Base
extend Gitlab::CurrentSettings
belongs_to :issue
belongs_to :assignee, class_name: "User", foreign_key: :user_id
......@@ -9,7 +7,7 @@ class IssueAssignee < ActiveRecord::Base
# EE-specific
def update_elasticsearch_index
if current_application_settings.elasticsearch_indexing?
if Gitlab::CurrentSettings.current_application_settings.elasticsearch_indexing?
ElasticIndexerWorker.perform_async(
:update,
'Issue',
......
require 'digest/md5'
class Key < ActiveRecord::Base
include Gitlab::CurrentSettings
include Sortable
LAST_USED_AT_REFRESH_TIME = 1.day.to_i
......@@ -12,14 +13,19 @@ class Key < ActiveRecord::Base
validates :title,
presence: true,
length: { maximum: 255 }
validates :key,
presence: true,
length: { maximum: 5000 },
format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :fingerprint,
uniqueness: true,
presence: { message: 'cannot be generated' }
validate :key_meets_restrictions
# EE-only
scope :ldap, -> { where(type: 'LDAPKey') }
delegate :name, :email, to: :user, prefix: true
......@@ -82,6 +88,10 @@ class Key < ActiveRecord::Base
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
def public_key
@public_key ||= Gitlab::SSHPublicKey.new(key)
end
private
def generate_fingerprint
......@@ -89,7 +99,27 @@ class Key < ActiveRecord::Base
return unless self.key.present?
self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint
self.fingerprint = public_key.fingerprint
end
def key_meets_restrictions
restriction = current_application_settings.key_restriction_for(public_key.type)
if restriction == ApplicationSetting::FORBIDDEN_KEY_VALUE
errors.add(:key, forbidden_key_type_message)
elsif public_key.bits < restriction
errors.add(:key, "must be at least #{restriction} bits")
end
end
def forbidden_key_type_message
allowed_types =
current_application_settings
.allowed_key_types
.map(&:upcase)
.to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')
"type is forbidden. Must be #{allowed_types}"
end
def notify_user
......
......@@ -130,12 +130,15 @@ class License < ActiveRecord::Base
# Early adopters should not earn new features as they're
# introduced.
EARLY_ADOPTER_FEATURES = [
{ ADMIN_AUDIT_LOG_FEATURE => 1 },
{ AUDIT_EVENTS_FEATURE => 1 },
{ AUDITOR_USER_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ CROSS_PROJECT_PIPELINES_FEATURE => 1 },
{ DB_LOAD_BALANCING_FEATURE => 1 },
{ DEPLOY_BOARD_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ FILE_LOCKS_FEATURE => 1 },
......@@ -146,6 +149,7 @@ class License < ActiveRecord::Base
{ ISSUE_BOARD_MILESTONE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ JENKINS_INTEGRATION_FEATURE => 1 },
{ LDAP_EXTRAS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
......@@ -154,8 +158,11 @@ class License < ActiveRecord::Base
{ OBJECT_STORAGE_FEATURE => 1 },
{ PROTECTED_REFS_FOR_USERS_FEATURE => 1 },
{ PUSH_RULES_FEATURE => 1 },
{ RELATED_ISSUES_FEATURE => 1 },
{ REPOSITORY_MIRRORS_FEATURE => 1 },
{ SERVICE_DESK_FEATURE => 1 }
{ REPOSITORY_SIZE_LIMIT_FEATURE => 1 },
{ SERVICE_DESK_FEATURE => 1 },
{ VARIABLE_ENVIRONMENT_SCOPE_FEATURE => 1 }
].freeze
FEATURES_BY_PLAN = {
......
......@@ -630,6 +630,8 @@ class MergeRequest < ActiveRecord::Base
self.merge_requests_closing_issues.delete_all
closes_issues(current_user).each do |issue|
next if issue.is_a?(ExternalIssue)
self.merge_requests_closing_issues.create!(issue: issue)
end
end
......@@ -971,7 +973,13 @@ class MergeRequest < ActiveRecord::Base
@base_pipeline ||= project.pipelines.find_by(sha: merge_request_diff&.base_commit_sha)
end
def update_project_counter_caches?
state_changed?
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end
......
......@@ -200,6 +200,10 @@ class Namespace < ActiveRecord::Base
parent.present?
end
def subgroup?
has_parent?
end
def soft_delete_without_removing_associations
# We can't use paranoia's `#destroy` since this will hard-delete projects.
# Project uses `pending_delete` instead of the acts_as_paranoia gem.
......
......@@ -22,6 +22,7 @@ class Project < ActiveRecord::Base
prepend EE::Project
extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
BoardLimitExceeded = Class.new(StandardError)
......@@ -1231,6 +1232,10 @@ class Project < ActiveRecord::Base
File.join(pages_path, 'public')
end
def pages_available?
Gitlab.config.pages.enabled && !namespace.subgroup?
end
def remove_private_deploy_keys
exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
......
......@@ -51,7 +51,7 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
after_commit on: :update do
if current_application_settings.elasticsearch_indexing?
if Gitlab::CurrentSettings.current_application_settings.elasticsearch_indexing?
ElasticIndexerWorker.perform_async(:update, 'Project', project_id)
end
end
......
......@@ -3,6 +3,8 @@ class ProtectedBranch < ActiveRecord::Base
include ProtectedRef
prepend EE::ProtectedRef
extend Gitlab::CurrentSettings
protected_ref_access_levels :merge, :push
# Check if branch name is marked as protected in the system
......
......@@ -934,7 +934,7 @@ class Repository
committer = user_to_committer(user)
create_commit(message: commit.message,
create_commit(message: commit.cherry_pick_message(user),
author: {
email: commit.author_email,
name: commit.author_name,
......
......@@ -11,6 +11,8 @@ class Snippet < ActiveRecord::Base
include Spammable
include Editable
extend Gitlab::CurrentSettings
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
cache_markdown_field :content
......
......@@ -2,6 +2,7 @@ require 'carrierwave/orm/activerecord'
class User < ActiveRecord::Base
extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
include Gitlab::ConfigHelper
include Gitlab::CurrentSettings
......@@ -621,7 +622,7 @@ class User < ActiveRecord::Base
end
def require_personal_access_token_creation_for_git_auth?
return false if allow_password_authentication? || ldap_user?
return false if current_application_settings.password_authentication_enabled? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end
......
......@@ -84,7 +84,7 @@ class WikiPage
# The formatted title of this page.
def title
if @attributes[:title]
self.class.unhyphenize(@attributes[:title])
CGI.unescape_html(self.class.unhyphenize(@attributes[:title]))
else
""
end
......
require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
......@@ -15,7 +13,7 @@ class BasePolicy < DeclarativePolicy::Base
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Gitlab::CurrentSettings.current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
# EE Extensions
......
class AkismetService
include Gitlab::CurrentSettings
attr_accessor :owner, :text, :options
def initialize(owner, text, options = {})
......
module Auth
class ContainerRegistryAuthenticationService < BaseService
include Gitlab::CurrentSettings
extend Gitlab::CurrentSettings
AUDIENCE = 'container_registry'.freeze
......
......@@ -12,7 +12,8 @@ module Ci
tag: tag?,
trigger_requests: Array(trigger_request),
user: current_user,
pipeline_schedule: schedule
pipeline_schedule: schedule,
protected: project.protected_for?(ref)
)
result = validate(current_user,
......
......@@ -78,7 +78,9 @@ module Ci
end
def new_builds
Ci::Build.pending.unstarted
builds = Ci::Build.pending.unstarted
builds = builds.ref_protected if runner.ref_protected?
builds
end
def shared_runner_build_limits_feature_enabled?
......
......@@ -3,7 +3,7 @@ module Ci
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
allow_failure stage_id stage stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list].freeze
description tag_list protected].freeze
def execute(build)
reprocess!(build).tap do |new_build|
......
......@@ -58,6 +58,7 @@ class IssuableBaseService < BaseService
params.delete(:assignee_id)
params.delete(:due_date)
params.delete(:canonical_issue_id)
params.delete(:project)
end
filter_assignee(issuable)
......
......@@ -6,7 +6,7 @@ module Issues
handle_move_between_iids(issue)
filter_spam_check_params
change_issue_duplicate(issue)
update(issue)
move_issue_to_new_project(issue) || update(issue)
end
def before_update(issue)
......@@ -74,6 +74,17 @@ module Issues
end
end
def move_issue_to_new_project(issue)
target_project = params.delete(:target_project)
return unless target_project &&
issue.can_move?(current_user, target_project) &&
target_project != issue.project
update(issue)
Issues::MoveService.new(project, current_user).execute(issue, target_project)
end
private
def get_issue_if_allowed(project, iid)
......
module Projects
class AfterImportService
RESERVED_REFS_REGEXP =
%r{\Arefs/(?:#{Regexp.union(*Repository::RESERVED_REFS_NAMES)})/}
RESERVED_REF_PREFIXES = Repository::RESERVED_REFS_NAMES.map { |n| File.join('refs', n, '/') }
def initialize(project)
@project = project
......@@ -9,7 +8,7 @@ module Projects
def execute
Projects::HousekeepingService.new(@project).execute do
repository.delete_refs(*garbage_refs)
repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end
rescue Projects::HousekeepingService::LeaseTaken => e
Rails.logger.info(
......@@ -18,10 +17,6 @@ module Projects
private
def garbage_refs
@garbage_refs ||= repository.all_ref_names_except(RESERVED_REFS_REGEXP)
end
def repository
@repository ||= @project.repository
end
......
module Projects
class UpdatePagesService < BaseService
include Gitlab::CurrentSettings
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'.freeze
......
......@@ -506,6 +506,24 @@ module QuickActions
end
end
desc 'Move this issue to another project.'
explanation do |path_to_project|
"Moves this issue to #{path_to_project}."
end
params 'path/to/project'
condition do
issuable.is_a?(Issue) &&
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :move do |target_project_path|
target_project = Project.find_by_full_path(target_project_path)
if target_project.present?
@updates[:target_project] = target_project
end
end
def extract_users(params)
return [] if params.nil?
......
......@@ -36,7 +36,8 @@ module SlashCommands
def valid_token?
ActiveSupport::SecurityUtils.variable_size_secure_compare(
current_application_settings.slack_app_verification_token,
Gitlab::CurrentSettings.current_application_settings
.slack_app_verification_token,
params[:token]
)
end
......
class UploadService
include Gitlab::CurrentSettings
def initialize(model, file, uploader_class = FileUploader)
@model, @file, @uploader_class = model, file, uploader_class
end
......
module Users
class BuildService < BaseService
prepend ::EE::Users::BuildService
include Gitlab::CurrentSettings
def initialize(current_user, params = {})
@current_user = current_user
......
......@@ -29,9 +29,7 @@ class AddressableUrlValidator < ActiveModel::EachValidator
private
def valid_url?(value)
return false unless value
valid_protocol?(value) && valid_uri?(value)
valid_uri?(value) && valid_protocol?(value)
end
def valid_uri?(value)
......
class KeyRestrictionValidator < ActiveModel::EachValidator
FORBIDDEN = -1
def self.supported_sizes(type)
Gitlab::SSHPublicKey.supported_sizes(type)
end
def self.supported_key_restrictions(type)
[0, *supported_sizes(type), FORBIDDEN]
end
def validate_each(record, attribute, value)
unless valid_restriction?(value)
record.errors.add(attribute, "must be forbidden, allowed, or one of these sizes: #{supported_sizes_message}")
end
end
private
def supported_sizes_message
sizes = self.class.supported_sizes(options[:type])
sizes.to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')
end
def valid_restriction?(value)
choices = self.class.supported_key_restrictions(options[:type])
choices.include?(value)
end
end
......@@ -42,12 +42,7 @@
= link_to "(?)", help_page_path("integration/bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration/gitlab")
.form-group
%label.control-label.col-sm-2 Enabled Git access protocols
.col-sm-10
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.help-block#clone-protocol-help
Allow only the selected protocols to be used for Git access.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
......@@ -55,6 +50,7 @@
= f.check_box :project_export_enabled
Project export enabled
-# EE-only
- if ldap_enabled?
.form-group
= f.label :allow_group_owners_to_manage_ldap, 'LDAP settings', class: 'control-label col-sm-2'
......@@ -67,6 +63,21 @@
If checked, group owners can manage LDAP group links and LDAP member overrides
= link_to icon('question-circle'), help_page_path('administration/auth/ldap-ee')
.form-group
%label.control-label.col-sm-2 Enabled Git access protocols
.col-sm-10
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.help-block#clone-protocol-help
Allow only the selected protocols to be used for Git access.
- ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
- field_name = :"#{type}_key_restriction"
.form-group
= f.label field_name, "#{type.upcase} SSH keys", class: 'control-label col-sm-2'
.col-sm-10
= f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control'
%fieldset
%legend Account and Limit Settings
.form-group
......@@ -180,7 +191,7 @@
.checkbox
= f.label :password_authentication_enabled do
= f.check_box :password_authentication_enabled
Password authentication enabled
Sign-in enabled
- if omniauth_enabled? && button_based_providers.any?
.form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
......
......@@ -20,8 +20,10 @@
Account
- if current_application_settings.should_check_namespace_plan?
= nav_link(controller: :billings) do
= link_to profile_billings_path, title: 'Billing' do
%span
= sidebar_link profile_billings_path, title: _('Billing') do
.nav-icon-container
= custom_icon('credit_card')
%span.nav-item-name
Billing
- if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do
......
......@@ -213,7 +213,7 @@
= link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
%span
CI / CD
- if Gitlab.config.pages.enabled
- if @project.pages_available?
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do
%span
......
......@@ -34,7 +34,7 @@
= link_to profile_emails_path, title: 'Emails' do
%span
Emails
- if current_user.allow_password_authentication?
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
%span
......
......@@ -15,12 +15,4 @@
window.uploads_path = "#{project_uploads_path(project)}";
window.preview_markdown_path = "#{preview_markdown_path(project)}";
- content_for :header_content do
.js-dropdown-menu-projects
.dropdown-menu.dropdown-select.dropdown-menu-projects
= dropdown_title("Go to a project")
= dropdown_filter("Search your projects")
= dropdown_content
= dropdown_loading
= render template: "layouts/application"
%li.key-list-item
.pull-left.append-right-10
= icon 'key', class: "settings-list-icon hidden-xs"
- if key.valid?
= icon 'key', class: 'settings-list-icon hidden-xs'
- else
= icon 'exclamation-triangle', class: 'settings-list-icon hidden-xs has-tooltip',
title: key.errors.full_messages.join(', ')
.key-list-item-info
= link_to path_to_key(key, is_admin), class: "title" do
= key.title
......
......@@ -16,6 +16,7 @@
%strong= @key.last_used_at.try(:to_s, :medium) || 'N/A'
.col-md-8
= form_errors(@key, type: 'key') unless @key.valid?
%p
%span.light Fingerprint:
%code.key-fingerprint= @key.fingerprint
......
......@@ -3,7 +3,7 @@
Due date
- if can?(current_user, :admin_issue, @project)
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "edit-link pull-right"
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value
.value-content
%span.no-value{ "v-if" => "!issue.dueDate" }
......
......@@ -3,7 +3,7 @@
Labels
- if can?(current_user, :admin_issue, @project)
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "edit-link pull-right"
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value.issuable-show-labels
%span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
None
......
......@@ -3,7 +3,7 @@
Milestone
- if can?(current_user, :admin_issue, @project)
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "edit-link pull-right"
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value
%span.no-value{ "v-if" => "!issue.milestone" }
None
......
......@@ -6,6 +6,12 @@
.checkbox
= f.check_box :active
%span.light Paused Runners don't accept new jobs
.form-group
= label :protected, "Protected", class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :access_level, {}, 'ref_protected', 'not_protected'
%span.light This runner will only run on pipelines trigged on protected branches
.form-group
= label :run_untagged, 'Run untagged jobs', class: 'control-label'
.col-sm-10
......
......@@ -19,6 +19,9 @@
%tr
%td Active
%td= @runner.active? ? 'Yes' : 'No'
%tr
%td Protected
%td= @runner.ref_protected? ? 'Yes' : 'No'
%tr
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
......
......@@ -23,7 +23,7 @@
= link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
%span
Pipelines
- if Gitlab.config.pages.enabled
- if @project.pages_available?
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do
%span
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></svg>
......@@ -29,18 +29,6 @@
= render 'shared/issuable/form/metadata', issuable: issuable, form: form
- if issuable.can_move?(current_user)
%hr
.form-group
= label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10
.issuable-form-select-holder
= hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE }
&nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
= icon('question-circle')
= render 'shared/issuable/approvals', issuable: issuable, form: form
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
......
......@@ -34,7 +34,7 @@
Milestone
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
- if issuable.milestone
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
......@@ -60,7 +60,7 @@
Due date
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
%span.value-content
- if issuable.due_date
......@@ -95,7 +95,7 @@
Labels
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
= link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- selected_labels.each do |label|
......@@ -168,5 +168,22 @@
%cite{ title: project_ref }
= project_ref
= clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left")
- if current_user && issuable.can_move?(current_user)
.block.js-sidebar-move-issue-block
.sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body' }, title: 'Move issue' }
= custom_icon('icon_arrow_right')
.dropdown.sidebar-move-issue-dropdown.hide-collapsed
%button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
data: { toggle: 'dropdown' } }
Move issue
.dropdown-menu.dropdown-menu-selectable
= dropdown_title('Move issue')
= dropdown_filter('Search project', search_id: 'sidebar-move-issue-dropdown-search')
= dropdown_content
= dropdown_loading
= dropdown_footer add_content_class: true do
%button.btn.btn-new.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true }
Move
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
%script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe
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.
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.
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.
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.
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.
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.
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.
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.
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