Skip to content
Snippets Groups Projects
Commit 36f255b2 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez
Browse files

Merge remote-tracking branch 'ce/master' into ce-to-ee

parents add80eb9 66cddf34
No related branches found
No related tags found
No related merge requests found
Showing
with 461 additions and 69 deletions
Loading
Loading
@@ -6,6 +6,12 @@ module Banzai
def references_relation
Label
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_label, ref_project)
end
end
end
end
Loading
Loading
@@ -6,6 +6,12 @@ module Banzai
def references_relation
MergeRequest.includes(:author, :assignee, :target_project)
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_merge_request, ref_project)
end
end
end
end
Loading
Loading
@@ -6,6 +6,12 @@ module Banzai
def references_relation
Milestone
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_milestone, ref_project)
end
end
end
end
Loading
Loading
@@ -6,6 +6,12 @@ module Banzai
def references_relation
Snippet
end
private
def can_read_reference?(user, ref_project)
can?(user, :read_project_snippet, ref_project)
end
end
end
end
Loading
Loading
@@ -30,22 +30,36 @@ module Banzai
 
nodes.each do |node|
if node.has_attribute?(group_attr)
node_group = groups[node.attr(group_attr).to_i]
if node_group &&
can?(user, :read_group, node_group)
visible << node
end
# Remaining nodes will be processed by the parent class'
# implementation of this method.
next unless can_read_group_reference?(node, user, groups)
visible << node
elsif can_read_project_reference?(node)
visible << node
else
remaining << node
end
end
 
# If project does not belong to a group
# and does not have the same project id as the current project
# base class will check if user can read the project that contains
# the user reference.
visible + super(current_user, remaining)
end
 
# Check if project belongs to a group which
# user can read.
def can_read_group_reference?(node, user, groups)
node_group = groups[node.attr('data-group').to_i]
node_group && can?(user, :read_group, node_group)
end
def can_read_project_reference?(node)
node_id = node.attr('data-project').to_i
project && project.id == node_id
end
def nodes_user_can_reference(current_user, nodes)
project_attr = 'data-project'
author_attr = 'data-author'
Loading
Loading
@@ -88,6 +102,10 @@ module Banzai
collection_objects_for_ids(Project, ids).
flat_map { |p| p.team.members.to_a }
end
def can_read_reference?(user, ref_project)
can?(user, :read_project, ref_project)
end
end
end
end
module Gitlab
class ContributionsCalendar
attr_reader :activity_dates, :projects, :user
attr_reader :contributor
attr_reader :current_user
attr_reader :projects
 
def initialize(projects, user)
@projects = projects
@user = user
def initialize(contributor, current_user = nil)
@contributor = contributor
@current_user = current_user
@projects = ContributedProjectsFinder.new(contributor).execute(current_user)
end
 
def activity_dates
return @activity_dates if @activity_dates.present?
 
@activity_dates = {}
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 3 different contribution types
date_from = 1.year.ago
repo_events = event_counts(date_from, :repository).
having(action: Event::PUSHED)
issue_events = event_counts(date_from, :issues).
having(action: [Event::CREATED, Event::CLOSED], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests).
having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
 
events = Event.reorder(nil).contributions.where(author_id: user.id).
where("created_at > ?", date_from).where(project_id: projects).
group('date(created_at)').
select('date(created_at) as date, count(id) as total_amount').
map(&:attributes)
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes)
 
activity_dates = (1.year.ago.to_date..Date.today).to_a
activity_dates.each do |date|
day_events = events.find { |day_events| day_events["date"] == date }
if day_events
@activity_dates[date] = day_events["total_amount"]
end
@activity_events = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
activities[event["date"]] += event["total_amount"]
end
@activity_dates
end
 
def events_by_date(date)
events = Event.contributions.where(author_id: user.id).
where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day).
events = Event.contributions.where(author_id: contributor.id).
where(created_at: date.beginning_of_day..date.end_of_day).
where(project_id: projects)
 
events.select do |event|
event.push? || event.issue? || event.merge_request?
end
# Use visible_to_user? instead of the complicated logic in activity_dates
# because we're only viewing the events for a single day.
events.select {|event| event.visible_to_user?(current_user) }
end
 
def starting_year
Loading
Loading
@@ -49,5 +48,30 @@ module Gitlab
def starting_month
Date.today.month
end
private
def event_counts(date_from, feature)
t = Event.arel_table
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
@contributed_project_ids ||= projects.uniq.pluck(:id)
authed_projects = Project.where(id: @contributed_project_ids).
with_feature_available_for_user(feature, current_user).
reorder(nil).
select(:id)
conditions = t[:created_at].gteq(date_from.beginning_of_day).
and(t[:created_at].lteq(Date.today.end_of_day)).
and(t[:author_id].eq(contributor.id))
Event.reorder(nil).
select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount').
group(t[:project_id], t[:target_type], t[:action], 'date(created_at)').
where(conditions).
having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
end
end
end
Loading
Loading
@@ -2,6 +2,7 @@
# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
<<<<<<< HEAD
include PathLocksHelper
UnauthorizedError = Class.new(StandardError)
 
Loading
Loading
@@ -16,6 +17,20 @@ module Gitlab
deploy_key: 'Deploy keys are not allowed to push code.',
no_repo: 'A repository for this project does not exist yet.'
}
=======
UnauthorizedError = Class.new(StandardError)
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
deploy_key: 'Deploy keys are not allowed to push code.',
no_repo: 'A repository for this project does not exist yet.'
}
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
PUSH_COMMANDS = %w{ git-receive-pack }
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
>>>>>>> ce/master
 
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
 
Loading
Loading
@@ -32,16 +47,22 @@ module Gitlab
check_active_user!
check_project_accessibility!
check_command_existence!(cmd)
<<<<<<< HEAD
 
check_geo_license!
=======
>>>>>>> ce/master
 
case cmd
when *DOWNLOAD_COMMANDS
download_access_check
when *PUSH_COMMANDS
push_access_check(changes)
<<<<<<< HEAD
when *GIT_ANNEX_COMMANDS
git_annex_access_check(project, changes)
=======
>>>>>>> ce/master
end
 
build_status_object(true)
Loading
Loading
@@ -52,7 +73,11 @@ module Gitlab
def download_access_check
if user
user_download_access_check
<<<<<<< HEAD
elsif deploy_key.nil? && geo_node_key.nil? && !Guest.can?(:download_code, project)
=======
elsif deploy_key.nil? && !Guest.can?(:download_code, project)
>>>>>>> ce/master
raise UnauthorizedError, ERROR_MESSAGES[:download]
end
end
Loading
Loading
@@ -100,6 +125,7 @@ module Gitlab
 
unless project.repository.exists?
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
<<<<<<< HEAD
end
 
if project.above_size_limit?
Loading
Loading
@@ -109,6 +135,8 @@ module Gitlab
if ::License.block_changes?
message = ::LicenseHelper.license_message(signed_in: true, is_admin: (user && user.is_admin?))
raise UnauthorizedError, message
=======
>>>>>>> ce/master
end
 
changes_list = Gitlab::ChangesList.new(changes)
Loading
Loading
@@ -121,6 +149,7 @@ module Gitlab
unless status.allowed?
# If user does not have access to make at least one change - cancel all push
raise UnauthorizedError, status.message
<<<<<<< HEAD
end
 
if project.size_limit_enabled?
Loading
Loading
@@ -131,6 +160,10 @@ module Gitlab
if project.changes_will_exceed_size_limit?(push_size_in_bytes.to_mb)
raise UnauthorizedError, Gitlab::RepositorySizeError.new(project).new_changes_error
end
=======
end
end
>>>>>>> ce/master
end
 
def change_access_check(change)
Loading
Loading
@@ -167,12 +200,15 @@ module Gitlab
end
end
 
<<<<<<< HEAD
def check_geo_license!
if Gitlab::Geo.secondary? && !Gitlab::Geo.license_allows?
raise UnauthorizedError, 'Your current license does not have GitLab Geo add-on enabled.'
end
end
 
=======
>>>>>>> ce/master
def matching_merge_request?(newrev, branch_name)
Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
Loading
Loading
@@ -219,8 +255,11 @@ module Gitlab
user_access.can_read_project?
elsif deploy_key
deploy_key_can_read_project?
<<<<<<< HEAD
elsif geo_node_key
true
=======
>>>>>>> ce/master
else
Guest.can?(:read_project, project)
end
Loading
Loading
Loading
Loading
@@ -101,8 +101,13 @@ module Gitlab
options['timeout'].to_i
end
 
<<<<<<< HEAD
def external_groups
options['external_groups']
=======
def has_auth?
options['password'] || options['bind_dn']
>>>>>>> ce/master
end
 
protected
Loading
Loading
@@ -135,10 +140,6 @@ module Gitlab
}
}
end
def has_auth?
options['password'] || options['bind_dn']
end
end
end
end
Loading
Loading
@@ -761,7 +761,7 @@ namespace :gitlab do
end
 
namespace :ldap do
task :check, [:limit] => :environment do |t, args|
task :check, [:limit] => :environment do |_, args|
# Only show up to 100 results because LDAP directories can be very big.
# This setting only affects the `rake gitlab:check` script.
args.with_defaults(limit: 100)
Loading
Loading
@@ -769,7 +769,7 @@ namespace :gitlab do
start_checking "LDAP"
 
if Gitlab::LDAP::Config.enabled?
print_users(args.limit)
check_ldap(args.limit)
else
puts 'LDAP is disabled in config/gitlab.yml'
end
Loading
Loading
@@ -777,21 +777,42 @@ namespace :gitlab do
finished_checking "LDAP"
end
 
def print_users(limit)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
def check_ldap(limit)
servers = Gitlab::LDAP::Config.providers
 
servers.each do |server|
puts "Server: #{server}"
Gitlab::LDAP::Adapter.open(server) do |adapter|
users = adapter.users(adapter.config.uid, '*', limit)
users.each do |user|
puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
begin
Gitlab::LDAP::Adapter.open(server) do |adapter|
check_ldap_auth(adapter)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
users = adapter.users(adapter.config.uid, '*', limit)
users.each do |user|
puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
end
end
rescue Net::LDAP::ConnectionRefusedError, Errno::ECONNREFUSED => e
puts "Could not connect to the LDAP server: #{e.message}".color(:red)
end
end
end
def check_ldap_auth(adapter)
auth = adapter.config.has_auth?
if auth && adapter.ldap.bind
message = 'Success'.color(:green)
elsif auth
message = 'Failed. Check `bind_dn` and `password` configuration values'.color(:red)
else
message = 'Anonymous. No `bind_dn` or `password` configured'.color(:yellow)
end
puts "LDAP authentication... #{message}"
end
end
 
namespace :repo do
Loading
Loading
Loading
Loading
@@ -9,6 +9,7 @@
"eslint-config-airbnb": "^12.0.0",
"eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jasmine": "^1.8.1",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1"
}
Loading
Loading
require 'spec_helper'
feature 'Group issues page', feature: true do
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', Issue
end
require 'spec_helper'
feature 'Group merge requests page', feature: true do
let(:path) { merge_requests_group_path(group) }
let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', MergeRequest
end
Loading
Loading
@@ -22,6 +22,7 @@ feature 'Start new branch from an issue', feature: true do
create(:note, :on_issue, :system, project: project, noteable: issue,
note: "Mentioned in !#{referenced_mr.iid}")
end
let(:referenced_mr) do
create(:merge_request, :simple, source_project: project, target_project: project,
description: "Fixes ##{issue.iid}", author: user)
Loading
Loading
Loading
Loading
@@ -218,42 +218,24 @@ describe ApplicationHelper do
end
 
it 'includes a default js-timeago class' do
expect(element.attr('class')).to eq 'js-timeago js-timeago-pending'
expect(element.attr('class')).to eq 'js-timeago'
end
 
it 'accepts a custom html_class' do
expect(element(html_class: 'custom_class').attr('class')).
to eq 'js-timeago custom_class js-timeago-pending'
to eq 'js-timeago custom_class'
end
 
it 'accepts a custom tooltip placement' do
expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom'
end
 
it 're-initializes timeago Javascript' do
el = element.next_element
expect(el.name).to eq 'script'
expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
end
it 'allows the script tag to be excluded' do
expect(element(skip_js: true)).not_to include 'script'
end
it 'converts to Time' do
expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
end
 
it 'add class for the short format and includes inline script' do
it 'add class for the short format' do
timeago_element = element(short_format: 'short')
expect(timeago_element.attr('class')).to eq 'js-short-timeago js-timeago-pending'
script_element = timeago_element.next_element
expect(script_element.name).to eq 'script'
end
it 'add class for the short format and does not include inline script' do
timeago_element = element(short_format: 'short', skip_js: true)
expect(timeago_element.attr('class')).to eq 'js-short-timeago'
expect(timeago_element.next_element).to eq nil
end
Loading
Loading
Loading
Loading
@@ -61,7 +61,7 @@ describe DiffHelper do
 
describe '#diff_line_content' do
it 'returns non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;')
expect(diff_line_content(nil)).to eq('&nbsp;')
end
 
it 'returns the line itself' do
Loading
Loading
{
"plugins": ["jasmine"],
"env": {
"jasmine": true
},
"extends": "plugin:jasmine/recommended",
"rules": {
"prefer-arrow-callback": 0,
"func-names": 0
}
}
/* global Build */
/* eslint-disable no-new */
//= require build
//= require breakpoints
//= require jquery.nicescroll
//= require turbolinks
(() => {
describe('Build', () => {
fixture.preload('build.html');
beforeEach(function () {
fixture.load('build.html');
spyOn($, 'ajax');
});
describe('constructor', () => {
beforeEach(function () {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
describe('setup', function () {
beforeEach(function () {
this.build = new Build();
});
it('copies build options', function () {
expect(this.build.pageUrl).toBe('http://example.com/root/test-build/builds/2');
expect(this.build.buildUrl).toBe('http://example.com/root/test-build/builds/2.json');
expect(this.build.buildStatus).toBe('passed');
expect(this.build.buildStage).toBe('test');
expect(this.build.state).toBe('buildstate');
});
it('only shows the jobs matching the current stage', function () {
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
it('selects the current stage in the build dropdown menu', function () {
expect($('.stage-selection').text()).toBe('test');
});
it('updates the jobs when the build dropdown changes', function () {
$('.stage-item:contains("build")').click();
expect($('.stage-selection').text()).toBe('build');
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
});
describe('initial build trace', function () {
beforeEach(function () {
new Build();
});
it('displays the initial build trace', function () {
expect($.ajax.calls.count()).toBe(1);
const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0);
expect(url).toBe('http://example.com/root/test-build/builds/2.json');
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, { trace_html: '<span>Example</span>', status: 'running' });
expect($('#build-trace .js-build-output').text()).toMatch(/Example/);
});
it('removes the spinner', function () {
const [{ success, context }] = $.ajax.calls.argsFor(0);
success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
expect($('.js-build-refresh').length).toBe(0);
});
});
describe('running build', function () {
beforeEach(function () {
$('.js-build-options').data('buildStatus', 'running');
this.build = new Build();
spyOn(this.build, 'location')
.and.returnValue('http://example.com/root/test-build/builds/2');
});
it('updates the build trace on an interval', function () {
jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(2);
let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
expect(url).toBe(
'http://example.com/root/test-build/builds/2/trace.json?state=buildstate'
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
expect(this.build.state).toBe('newstate');
jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(3);
[{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
expect(url).toBe(
'http://example.com/root/test-build/builds/2/trace.json?state=newstate'
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
expect(this.build.state).toBe('finalstate');
});
it('replaces the entire build trace', function () {
jasmine.clock().tick(4001);
let [{ success, context }] = $.ajax.calls.argsFor(1);
success.call(context, {
html: '<span>Update</span>',
status: 'running',
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
jasmine.clock().tick(4001);
[{ success, context }] = $.ajax.calls.argsFor(2);
success.call(context, {
html: '<span>Different</span>',
status: 'running',
append: false,
});
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
it('reloads the page when the build is done', function () {
spyOn(Turbolinks, 'visit');
jasmine.clock().tick(4001);
const [{ success, context }] = $.ajax.calls.argsFor(1);
success.call(context, {
html: '<span>Final</span>',
status: 'passed',
append: true,
});
expect(Turbolinks.visit).toHaveBeenCalledWith(
'http://example.com/root/test-build/builds/2'
);
});
});
});
});
})();
.build-page
.prepend-top-default
.autoscroll-container
%button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
#js-build-scroll.scroll-controls
%a.btn{href: '#build-trace'}
%i.fa.fa-angle-up
%a.btn{href: '#down-build-trace'}
%i.fa.fa-angle-down
%pre.build-trace#build-trace
%code.bash.js-build-output
%i.fa.fa-refresh.fa-spin.js-build-refresh
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
Build
%strong #1
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
%i.fa.fa-angle-double-right
.blocks-container
.dropdown.build-dropdown
.title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
%span.stage-selection More
%i.fa.fa-caret-down
%ul.dropdown-menu
%li
%a.stage-item build
%li
%a.stage-item test
%li
%a.stage-item deploy
.builds-container
.build-job{data: {stage: 'build'}}
%a{href: 'http://example.com/root/test-build/builds/1'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Setup
.build-job{data: {stage: 'test'}}
%a{href: 'http://example.com/root/test-build/builds/2'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Tests
.build-job{data: {stage: 'deploy'}}
%a{href: 'http://example.com/root/test-build/builds/3'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Deploy
.js-build-options{ data: { page_url: 'http://example.com/root/test-build/builds/2',
build_url: 'http://example.com/root/test-build/builds/2.json',
build_status: 'passed',
build_stage: 'test',
state1: 'buildstate' }}
/* eslint-disable */
/*= require merge_request_widget */
/*= require jquery.timeago.js */
/*= require lib/utils/timeago.js */
 
(function() {
describe('MergeRequestWidget', function() {
Loading
Loading
Loading
Loading
@@ -99,6 +99,28 @@ describe Banzai::Filter::AutolinkFilter, lib: true do
expect(doc.at_css('a')['href']).to eq link
end
 
it 'autolinks rdar' do
link = 'rdar://localhost.com/blah'
doc = filter("See #{link}")
expect(doc.at_css('a').text).to eq link
expect(doc.at_css('a')['href']).to eq link
end
it 'does not autolink javascript' do
link = 'javascript://alert(document.cookie);'
doc = filter("See #{link}")
expect(doc.at_css('a')).to be_nil
end
it 'does not autolink bad URLs' do
link = 'foo://23423:::asdf'
doc = filter("See #{link}")
expect(doc.to_s).to eq("See #{link}")
end
it 'does not include trailing punctuation' do
doc = filter("See #{link}.")
expect(doc.at_css('a').text).to eq link
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment