Read about our upcoming Code of Conduct on this issue

Commit cc41381b authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets
Browse files

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

Please view this file on the master branch, on stable branches it's out of date.
v 7.10.0 (unreleased)
- Allow users to be invited by email to join a group or project.
- Don't crash when project repository doesn't exist.
- Add config var to block auto-created LDAP users.
- Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
- Set EmailsOnPush reply-to address to committer email when enabled.
- Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
- Fix persistent XSS vulnerability around profile website URLs.
- Fix project import URL regex to prevent arbitary local repos from being imported.
......@@ -16,6 +21,9 @@ v 7.10.0 (unreleased)
- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
- Allow HTML tags in Markdown input
- Fix code unfold not working on Compare commits page (Stan Hu)
- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu)
- Make maximum attachment size configurable via Application Settings (Stan Hu)
- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
......
......@@ -8,6 +8,7 @@ class @UsersSelect
@groupId = $(select).data('group-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
firstUser = $(select).data('first-user')
$(select).select2
......@@ -19,20 +20,6 @@ class @UsersSelect
data = { results: users }
if query.term.length == 0
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
id: null
}
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
if firstUser
# Move current user to the front of the list
for obj, index in data.results
......@@ -40,11 +27,34 @@ class @UsersSelect
data.results.splice(index, 1)
data.results.unshift(obj)
break
if showNullUser
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
id: null
}
data.results.unshift(anyUser)
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
avatar: null,
username: query.term,
id: query.term
}
data.results.unshift(emailUser)
query.callback(data)
initSelection: (element, callback) =>
......
......@@ -41,7 +41,7 @@ def update
end
def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level])
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
......
......@@ -126,7 +126,7 @@ def project
def repository
@repository ||= project.repository
rescue Grit::NoSuchPathError(e)
rescue Grit::NoSuchPathError => e
log_exception(e)
nil
end
......
......@@ -4,11 +4,11 @@ class ConfirmationsController < Devise::ConfirmationsController
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
after_sign_in_path_for(resource)
else
sign_in(resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
......
......@@ -13,7 +13,7 @@ def authorize_read_group!
end
def authorize_admin_group!
unless can?(current_user, :manage_group, group)
unless can?(current_user, :admin_group, group)
return render_404
end
end
......
......@@ -11,6 +11,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
......@@ -22,7 +23,7 @@ def index
end
def create
@group.add_users(params[:user_ids].split(','), params[:access_level])
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
end
......@@ -38,7 +39,7 @@ def destroy
if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner.
@group_member.destroy
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
else
......@@ -46,12 +47,26 @@ def destroy
end
end
def resend_invite
redirect_path = group_group_members_path(@group)
@group_member = @group.group_members.find(params[:id])
if @group_member.invite?
@group_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
def leave
@group_member = @group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
else
return render_403
end
......
......@@ -51,6 +51,6 @@ def state(state = nil)
end
def authorize_group_milestone!
return render_404 unless can?(current_user, :manage_group, group)
return render_404 unless can?(current_user, :admin_group, group)
end
end
class InvitesController < ApplicationController
before_filter :member
skip_before_filter :authenticate_user!, only: :decline
respond_to :html
layout 'navless'
def show
end
def accept
if member.accept_invite!(current_user)
label, path = source_info(member.source)
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else
redirect_to :back, alert: "The invitation could not be accepted."
end
end
def decline
if member.decline_invite!
label, _ = source_info(member.source)
path =
if current_user
dashboard_path
else
new_user_session_path
end
redirect_to path, notice: "You have declined the invitation to join #{label}."
else
redirect_to :back, alert: "The invitation could not be declined."
end
end
private
def member
return @member if defined?(@member)
@token = params[:id]
@member = Member.find_by_invite_token(@token)
unless @member
render_404 and return
end
@member
end
def authenticate_user!
return if current_user
notice = "To accept this invitation, sign in"
notice << " or create an account" if current_application_settings.signup_enabled?
notice << "."
store_location_for :user, request.fullpath
redirect_to new_user_session_path, notice: notice
end
def source_info(source)
case source
when Project
project = member.source
label = "project #{project.name_with_namespace}"
path = namespace_project_path(project.namespace, project)
when Group
group = member.source
label = "group #{group.name}"
path = group_path(group)
else
label = "who knows what"
path = dashboard_path
end
[label, path]
end
end
......@@ -6,6 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
if params[:search].present?
users = @project.users.search(params[:search]).to_a
......@@ -17,6 +18,7 @@ def index
@group = @project.group
if @group
@group_members = @group.group_members
@group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
......@@ -34,30 +36,42 @@ def new
end
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:access_level]]
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
def update
@project_member = @project.project_members.find_by(user_id: member)
@project_member = @project.project_members.find(params[:id])
@project_member.update_attributes(member_params)
end
def destroy
@project_member = @project.project_members.find_by(user_id: member)
@project_member = @project.project_members.find(params[:id])
@project_member.destroy
respond_to do |format|
format.html do
redirect_to namespace_project_project_members_path(@project.namespace,
@project)
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
format.js { render nothing: true }
end
end
def resend_invite
redirect_path = namespace_project_project_members_path(@project.namespace, @project)
@project_member = @project.project_members.find(params[:id])
if @project_member.invite?
@project_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
def leave
@project.project_members.find_by(user_id: current_user).destroy
......@@ -69,7 +83,7 @@ def leave
def apply_import
giver = Project.find(params[:source_project_id])
status = @project.team.import(giver)
status = @project.team.import(giver, current_user)
notice = status ? "Successfully imported" : "Import failed"
redirect_to(namespace_project_project_members_path(project.namespace, project),
......@@ -78,10 +92,6 @@ def apply_import
protected
def member
@member ||= User.find_by(username: params[:id])
end
def member_params
params.require(:project_member).permit(:user_id, :access_level)
end
......
module GroupsHelper
def remove_user_from_group_message(group, user)
"Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?"
def remove_user_from_group_message(group, member)
if member.user
"Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?"
else
"Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?"
end
end
def leave_group_message(group)
......
module ProjectsHelper
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
def remove_from_project_team_message(project, member)
if member.user
"You are going to remove #{member.user.name} from #{project.name} project team. Are you sure?"
else
"You are going to revoke the invitation for #{member.invite_email} to join #{project.name} project team. Are you sure?"
end
end
def link_to_project(project)
......
......@@ -8,6 +8,7 @@ def users_select_tag(id, opts = {})
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
html = {
......@@ -15,6 +16,7 @@ def users_select_tag(id, opts = {})
'data-placeholder' => placeholder,
'data-null-user' => null_user,
'data-any-user' => any_user,
'data-email-user' => email_user,
'data-first-user' => first_user
}
......
......@@ -3,10 +3,50 @@ module Groups
def group_access_granted_email(group_member_id)
@group_member = GroupMember.find(group_member_id)
@group = @group_member.group
@target_url = group_url(@group)
@current_user = @group_member.user
mail(to: @group_member.user.email,
mail(to: @group_member.user.notification_email,
subject: subject("Access to group was granted"))
end
def group_member_invited_email(group_member_id, token)
@group_member = GroupMember.find group_member_id
@group = @group_member.group
@token = token
@target_url = group_url(@group)
@current_user = @group_member.user
mail(to: @group_member.invite_email,
subject: "Invitation to join group #{@group.name}")
end
def group_invite_accepted_email(group_member_id)
@group_member = GroupMember.find group_member_id
return if @group_member.created_by.nil?
@group = @group_member.group
@target_url = group_url(@group)
@current_user = @group_member.created_by
mail(to: @group_member.created_by.notification_email,
subject: subject("Invitation accepted"))
end
def group_invite_declined_email(group_id, invite_email, access_level, created_by_id)
return if created_by_id.nil?
@group = Group.find(group_id)
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = group_url(@group)
mail(to: @created_by.notification_email,
subject: subject("Invitation declined"))
end
end
end
module Emails
module Projects
def project_access_granted_email(user_project_id)
@project_member = ProjectMember.find user_project_id
def project_access_granted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.user
mail(to: @project_member.user.email,
mail(to: @project_member.user.notification_email,
subject: subject("Access to project was granted"))
end
def project_member_invited_email(project_member_id, token)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@token = token
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.user
mail(to: @project_member.invite_email,
subject: "Invitation to join project #{@project.name_with_namespace}")
end
def project_invite_accepted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
return if @project_member.created_by.nil?
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.created_by
mail(to: @project_member.created_by.notification_email,
subject: subject("Invitation accepted"))
end
def project_invite_declined_email(project_id, invite_email, access_level, created_by_id)
return if created_by_id.nil?
@project = Project.find(project_id)
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = namespace_project_url(@project.namespace, @project)
mail(to: @created_by.notification_email,
subject: subject("Invitation declined"))
end
def project_was_moved_email(project_id, user_id)
@current_user = @user = User.find user_id
@project = Project.find project_id
......@@ -84,9 +125,17 @@ def repository_push_email(project_id, recipient, author_id: nil,
@disable_footer = true
mail(from: sender(author_id, send_from_committer_email),
to: recipient,
subject: @subject)
reply_to =
if send_from_committer_email && can_send_from_user_email?(@author)
@author.email
else
Gitlab.config.gitlab.email_reply_to
end
mail(from: sender(author_id, send_from_committer_email),
reply_to: reply_to,
to: recipient,
subject: @subject)
end
end
end
......@@ -60,20 +60,24 @@ def default_sender_address
address
end
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
end
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false)
if sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
return unless sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
sender_domain = sender.email.split("@").last
if send_from_user_email && self.class.allowed_email_domains.include?(sender_domain)
address.address = sender.email
end
address.format
if send_from_user_email && can_send_from_user_email?(sender)
address.address = sender.email
end
address.format
end
# Look up a User by their ID and return their email address
......
......@@ -198,11 +198,11 @@ def group_abilities(user, group)
])
end
# Only group owner and administrators can manage group
# Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin?
rules.push(*[
:manage_group,
:manage_namespace
:admin_group,
:admin_namespace
])
end
......@@ -212,11 +212,11 @@ def group_abilities(user, group)
def namespace_abilities(user, namespace)
rules = []
# Only namespace owner and administrators can manage it
# Only namespace owner and administrators can admin it
if namespace.owner == user || user.admin?
rules.push(*[
:create_projects,
:manage_namespace
:admin_namespace
])
end
......@@ -254,7 +254,7 @@ def group_member_abilities(user, subject)
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:manage_group)
can_manage = group_abilities(user, group).include?(:admin_group)
if can_manage && (user != target_user)
rules << :modify_group_member
rules << :destroy_group_member
......
......@@ -77,7 +77,7 @@ def title