Commit 4e55bd46 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge remote-tracking branch 'dev/master'

parents 8a263f52 e9205f98
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 10.8.2 (2018-05-28)
### Security (3 changes)
- Fixed XSS in protected branches & tags access dropdown.
- Escape name in merge request approvers dropdown.
- Fixes include directive to not allow SSRF requests.
## 10.8.1 (2018-05-23) ## 10.8.1 (2018-05-23)
### Fixed (4 changes) ### Fixed (4 changes)
...@@ -88,6 +97,15 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -88,6 +97,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Remove `features/group_active_tab.feature`. !5554 (@blackst0ne) - Remove `features/group_active_tab.feature`. !5554 (@blackst0ne)
## 10.7.5 (2018-05-28)
### Security (3 changes)
- Fixed XSS in protected branches & tags access dropdown.
- Escape name in merge request approvers dropdown.
- Fixes include directive to not allow SSRF requests.
## 10.7.4 (2018-05-21) ## 10.7.4 (2018-05-21)
### Fixed (2 changes) ### Fixed (2 changes)
...@@ -197,6 +215,15 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -197,6 +215,15 @@ Please view this file on the master branch, on stable branches it's out of date.
- Breaks utils function to parse codeclimate and sast into separate functions. - Breaks utils function to parse codeclimate and sast into separate functions.
## 10.6.6 (2018-05-28)
### Security (3 changes)
- Fixed XSS in protected branches & tags access dropdown.
- Escape name in merge request approvers dropdown.
- Fixes include directive to not allow SSRF requests.
## 10.6.5 (2018-04-24) ## 10.6.5 (2018-04-24)
- No changes. - No changes.
......
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.8.2 (2018-05-28)
### Security (3 changes)
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.8.1 (2018-05-23) ## 10.8.1 (2018-05-23)
### Fixed (9 changes) ### Fixed (9 changes)
...@@ -193,6 +202,15 @@ entry. ...@@ -193,6 +202,15 @@ entry.
- Gitaly handles repository forks by default. - Gitaly handles repository forks by default.
## 10.7.5 (2018-05-28)
### Security (3 changes)
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.7.4 (2018-05-21) ## 10.7.4 (2018-05-21)
### Fixed (1 change) ### Fixed (1 change)
...@@ -457,6 +475,16 @@ entry. ...@@ -457,6 +475,16 @@ entry.
- Upgrade Gitaly to upgrade its charlock_holmes. - Upgrade Gitaly to upgrade its charlock_holmes.
## 10.6.6 (2018-05-28)
### Security (4 changes)
- Do not allow non-members to create MRs via forked projects when MRs are private.
- Prevent user passwords from being changed without providing the previous password.
- Fix API to remove deploy key from project instead of deleting it entirely.
- Fixed bug that allowed importing arbitrary project attributes.
## 10.6.5 (2018-04-24) ## 10.6.5 (2018-04-24)
### Security (1 change) ### Security (1 change)
......
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore';
import Api from '~/api'; import Api from '~/api';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Flash from '~/flash'; import Flash from '~/flash';
...@@ -29,10 +30,9 @@ export default class ApproversSelect { ...@@ -29,10 +30,9 @@ export default class ApproversSelect {
static getApprovers(fieldName, approverList) { static getApprovers(fieldName, approverList) {
const input = $(`[name="${fieldName}"]`); const input = $(`[name="${fieldName}"]`);
const existingApprovers = $(approverList).map((i, el) => const existingApprovers = $(approverList).map((i, el) => parseInt($(el).data('id'), 10));
parseInt($(el).data('id'), 10), const selectedApprovers = input
); .val()
const selectedApprovers = input.val()
.split(',') .split(',')
.filter(val => val !== ''); .filter(val => val !== '');
return [...existingApprovers, ...selectedApprovers]; return [...existingApprovers, ...selectedApprovers];
...@@ -76,40 +76,41 @@ export default class ApproversSelect { ...@@ -76,40 +76,41 @@ export default class ApproversSelect {
} }
initSelect2() { initSelect2() {
this.$approverSelect.select2({ this.$approverSelect
placeholder: 'Search for users or groups', .select2({
multiple: true, placeholder: 'Search for users or groups',
minimumInputLength: 0, multiple: true,
query: (query) => { minimumInputLength: 0,
const fetchGroups = this.fetchGroups(query.term); query: query => {
const fetchUsers = this.fetchUsers(query.term); const fetchGroups = this.fetchGroups(query.term);
return Promise.all([fetchGroups, fetchUsers]).then(([groups, users]) => { const fetchUsers = this.fetchUsers(query.term);
const data = { return Promise.all([fetchGroups, fetchUsers]).then(([groups, users]) => {
results: groups.concat(users), const data = {
results: groups.concat(users),
};
return query.callback(data);
});
},
formatResult: ApproversSelect.formatResult,
formatSelection: ApproversSelect.formatSelection,
dropdownCss() {
const $input = $('.js-select-user-and-group .select2-input');
const offset = $input.offset();
const inputRightPosition = offset.left + $input.outerWidth();
const $dropdown = $('.select2-drop-active');
let left = offset.left;
if ($dropdown.outerWidth() > $input.outerWidth()) {
left = `${inputRightPosition - $dropdown.width()}px`;
}
return {
left,
right: 'auto',
width: 'auto',
}; };
return query.callback(data); },
}); })
}, .on('change', this.handleSelectChange);
formatResult: ApproversSelect.formatResult,
formatSelection: ApproversSelect.formatSelection,
dropdownCss() {
const $input = $('.js-select-user-and-group .select2-input');
const offset = $input.offset();
const inputRightPosition = offset.left + $input.outerWidth();
const $dropdown = $('.select2-drop-active');
let left = offset.left;
if ($dropdown.outerWidth() > $input.outerWidth()) {
left = `${inputRightPosition - $dropdown.width()}px`;
}
return {
left,
right: 'auto',
width: 'auto',
};
},
})
.on('change', this.handleSelectChange);
} }
static formatSelection(group) { static formatSelection(group) {
...@@ -131,8 +132,8 @@ export default class ApproversSelect { ...@@ -131,8 +132,8 @@ export default class ApproversSelect {
<img class="avatar s40" src="${avatar}"> <img class="avatar s40" src="${avatar}">
</div> </div>
<div class="user-info"> <div class="user-info">
<div class="user-name">${name}</div> <div class="user-name">${_.escape(name)}</div>
<div class="user-username">@${username}</div> <div class="user-username">@${_.escape(username)}</div>
</div> </div>
</div> </div>
`; `;
...@@ -140,8 +141,8 @@ export default class ApproversSelect { ...@@ -140,8 +141,8 @@ export default class ApproversSelect {
return ` return `
<div class="group-result"> <div class="group-result">
<div class="group-name">${fullName}</div> <div class="group-name">${_.escape(fullName)}</div>
<div class="group-path">${fullPath}</div> <div class="group-path">${_.escape(fullPath)}</div>
</div> </div>
`; `;
} }
...@@ -163,17 +164,20 @@ export default class ApproversSelect { ...@@ -163,17 +164,20 @@ export default class ApproversSelect {
const $form = $('.js-add-approvers').closest('form'); const $form = $('.js-add-approvers').closest('form');
$loadWrapper.removeClass('hidden'); $loadWrapper.removeClass('hidden');
axios.post($form.attr('action'), `_method=PATCH&${[encodeURIComponent(fieldName)]}=${newValue}`, { axios
headers: { .post($form.attr('action'), `_method=PATCH&${[encodeURIComponent(fieldName)]}=${newValue}`, {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', headers: {
}, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}).then(({ data }) => { },
ApproversSelect.updateApproverList(data); })
ApproversSelect.saveApproversComplete($input, $approverSelect, $loadWrapper); .then(({ data }) => {
}).catch(() => { ApproversSelect.updateApproverList(data);
Flash(__('An error occurred while adding approver')); ApproversSelect.saveApproversComplete($input, $approverSelect, $loadWrapper);
ApproversSelect.saveApproversComplete($input, $approverSelect, $loadWrapper); })
}); .catch(() => {
Flash(__('An error occurred while adding approver'));
ApproversSelect.saveApproversComplete($input, $approverSelect, $loadWrapper);
});
} }
static saveApproversComplete($input, $approverSelect, $loadWrapper) { static saveApproversComplete($input, $approverSelect, $loadWrapper) {
...@@ -188,20 +192,27 @@ export default class ApproversSelect { ...@@ -188,20 +192,27 @@ export default class ApproversSelect {
const $loadWrapper = $('.load-wrapper'); const $loadWrapper = $('.load-wrapper');
$loadWrapper.removeClass('hidden'); $loadWrapper.removeClass('hidden');
axios.post(target.getAttribute('href'), '_method=DELETE', { axios
headers: { .post(target.getAttribute('href'), '_method=DELETE', {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', headers: {
}, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}).then(({ data }) => { },
ApproversSelect.updateApproverList(data); })
$loadWrapper.addClass('hidden'); .then(({ data }) => {
}).catch(() => { ApproversSelect.updateApproverList(data);
Flash(__('An error occurred while removing approver')); $loadWrapper.addClass('hidden');
$loadWrapper.addClass('hidden'); })
}); .catch(() => {
Flash(__('An error occurred while removing approver'));
$loadWrapper.addClass('hidden');
});
} }
static updateApproverList(html) { static updateApproverList(html) {
$('.js-current-approvers').html($(html).find('.js-current-approvers').html()); $('.js-current-approvers').html(
$(html)
.find('.js-current-approvers')
.html(),
);
} }
} }
...@@ -458,7 +458,7 @@ export default class AccessDropdown { ...@@ -458,7 +458,7 @@ export default class AccessDropdown {
<li> <li>
<a href="#" class="${isActiveClass}"> <a href="#" class="${isActiveClass}">
<img src="${user.avatar_url}" class="avatar avatar-inline" width="30"> <img src="${user.avatar_url}" class="avatar avatar-inline" width="30">
<strong class="dropdown-menu-user-full-name">${user.name}</strong> <strong class="dropdown-menu-user-full-name">${_.escape(user.name)}</strong>
<span class="dropdown-menu-user-username">${user.username}</span> <span class="dropdown-menu-user-username">${user.username}</span>
</a> </a>
</li> </li>
......
---
title: Escape name in merge request approvers dropdown
merge_request:
author:
type: security
---
title: Fixes include directive to not allow SSRF requests
merge_request:
author:
type: security
...@@ -11,8 +11,8 @@ module Gitlab ...@@ -11,8 +11,8 @@ module Gitlab
@content = strong_memoize(:content) do @content = strong_memoize(:content) do
begin begin
Gitlab::HTTP.get(location, allow_local_requests: true) Gitlab::HTTP.get(location)
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError
nil nil
end end
end end
......
...@@ -50,6 +50,14 @@ describe Gitlab::Ci::External::File::Remote do ...@@ -50,6 +50,14 @@ describe Gitlab::Ci::External::File::Remote do
expect(remote_file.valid?).to be_falsy expect(remote_file.valid?).to be_falsy
end end
end end
context 'with an internal url' do
let(:location) { 'http://localhost:8080' }
it 'should be falsy' do
expect(remote_file.valid?).to be_falsy
end
end
end end
describe "#content" do describe "#content" do
...@@ -84,6 +92,14 @@ describe Gitlab::Ci::External::File::Remote do ...@@ -84,6 +92,14 @@ describe Gitlab::Ci::External::File::Remote do
expect(remote_file.content).to be_nil expect(remote_file.content).to be_nil
end end
end end
context 'with an internal url' do
let(:location) { 'http://localhost:8080' }
it 'should be nil' do
expect(remote_file.content).to be_nil
end
end
end end
describe "#error_message" do describe "#error_message" do
......
...@@ -34,4 +34,29 @@ describe('ApproversSelect', () => { ...@@ -34,4 +34,29 @@ describe('ApproversSelect', () => {
expect($loadWrapper.addClass).toHaveBeenCalledWith('hidden'); expect($loadWrapper.addClass).toHaveBeenCalledWith('hidden');
}); });
}); });
describe('formatResult', () => {
it('escapes name', () => {
const output = ApproversSelect.formatResult({
name: '<script>alert("testing")</script>',
username: 'testing',
avatar_url: gl.TEST_HOST,
full_name: '<script>alert("testing")</script>',
full_path: 'testing',
});
expect(output).not.toContain('<script>alert("testing")</script>');
});
it('escapes full name', () => {
const output = ApproversSelect.formatResult({
username: 'testing',
avatar_url: gl.TEST_HOST,
full_name: '<script>alert("testing")</script>',
full_path: 'testing',
});
expect(output).not.toContain('<script>alert("testing")</script>');
});
});
}); });
...@@ -123,4 +123,17 @@ describe('AccessDropdown', () => { ...@@ -123,4 +123,17 @@ describe('AccessDropdown', () => {
}); });
}); });
}); });
describe('userRowHtml', () => {
it('escapes users name', () => {
const user = {
avatar_url: '',
name: '<img src=x onerror=alert(document.domain)>',
username: 'test',
};
const template = dropdown.userRowHtml(user);
expect(template).not.toContain(user.name);
});
});
}); });
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