Commit dd559674 authored by Valery Sizov's avatar Valery Sizov

Merge branch '7-4-stable-ee' into ce_stable

Conflicts:
	VERSION
	lib/gitlab/ldap/adapter.rb
parents 9712fbcd aae4e3f8
......@@ -210,6 +210,9 @@ v 6.9.0
- Labels for merge requests (Drew Blessing)
- Threaded emails by setting a Message-ID (Philip Blatter)
v 6.8.1
- Bump required gitlab-shell version to 1.9.3
v 6.8.0
- Ability to at mention users that are participating in issue and merge req. discussion
- Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu)
......
v 7.4.0
- Support for multiple LDAP servers
- Skip AD specific LDAP checks
- Do not show ldap users in dropdowns for groups with enabled ldap-sync
- Update the JIRA integration documentation
- Reset the homepage to show the GitLab logo by deleting the custom logo.
v 7.3.0
- Add an option to change the LDAP sync time from default 1 hour
- User will receive an email when unsubscribed from admin notifications
- Show group sharing members on /my/project/team
- Improve explanation of the LDAP permission reset
- Fix some navigation issues
- Added support for multiple LDAP groups per Gitlab group
v 7.2.0
- Improve Redmine integration
- Better logging for the JIRA issue closing service
- Administrators can now send email to all users through the admin interface
- JIRA issue transition ID is now customizable
- LDAP group settings are now visible in admin group show page and group members page
v 7.1.0
- Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader)
- Synchronize SSH keys with LDAP (Oleg Girko (Jolla) and Marvin Frick (SinnerSchrader))
- Support Jenkins jobs with multiple modules (Marvin Frick, sponsored by SinnerSchrader)
v 7.0.0
- Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader)
v 6.9.4
- Fix bug in JIRA Issue closing triggered by commit messages
- Fix JIRA issue reference bug
v 6.9.3
- Fix check CI status only when CI service is enabled(Daniel Aquino)
v 6.9.2
- Merge community edition changes for version 6.9.2
v 6.9.1
- Merge community edition changes for version 6.9.1
v 6.9.0
- Add support for closing Jira tickets with commits and MR
- Template for Merge Request description can be added in project settings
- Jenkins CI service
- Fix LDAP email upper case bug
v 6.8.0
- Customise sign-in page with custom text and logo
v 6.7.1
- Handle LDAP errors in Adapter#dn_matches_filter?
v 6.7.0
- Improve LDAP sign-in speed by reusing connections
- Add support for Active Directory nested LDAP groups
- Git hooks: Commit message regex
- Git hooks: Deny git tag removal
- Fix group edit in admin area
v 6.5.0
- Add reset permissions button to Group#members page
v 6.4.0
- Respect existing group permissions during sync with LDAP group (d3844662ec7ce816b0a85c8b40f66ee6c5ae90a1)
v 6.3.0
- When looking up a user by DN, use single scope (bc8a875df1609728f1c7674abef46c01168a0d20)
- Try sAMAccountName if omniauth nickname is nil (9b7174c333fa07c44cc53b80459a115ef1856e38)
v 6.2.0
- API: expose ldap_cn and ldap_access group attributes
- Use omniauth-ldap nickname attribute as GitLab username
- Improve group sharing UI for installation with many groups
- Fix empty LDAP group raises exception
- Respect LDAP user filter for git access
......@@ -38,6 +38,7 @@ gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
gem 'net-ldap'
# Git Wiki
gem 'gollum-lib', '~> 3.0.0'
......
......@@ -650,6 +650,7 @@ DEPENDENCIES
minitest (~> 5.3.0)
mousetrap-rails
mysql2
net-ldap
newrelic_rpm
nprogress-rails
omniauth (~> 1.1.3)
......
Copyright (c) 2011-2014 GitLab B.V.
The GitLab Enterprise Edition (EE) license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Copyright (c) 2013-2014 GitLab B.V.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
This software and associated documentation files (the "Software") can only be
used with a valid GitLab subscription for the correct number of users. You are
free to modify this Software and publish patches. It is forbidden to copy,
merge, publish, distribute, sublicense, and/or sell copies of the Software.
The above copyright notice applies only to the part of this Software that is
not distributed as part of GitLab Community Edition (CE). Any part of this
Software distributed as part of GitLab CE is copyrighted under the MIT Expat
license. The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
......
# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
For upgrading from GitLab CE to GitLab EE there is [upgrade guide](doc/update/7.4-ce-to-ee.md)
## Open source software to collaborate on code
![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
......@@ -13,7 +15,7 @@
## Canonical source
- The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
- The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://dev.gitlab.org/gitlab/gitlab-ee/) and acessible only to [subscribers](https://about.gitlab.com/subscription/).
## Code status
......@@ -124,7 +126,7 @@ And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5i
## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
All documentation can be found on [doc.gitlab.com/ee/](http://doc.gitlab.com/ee/).
## Getting help
......
......@@ -2,6 +2,7 @@
users_path: "/api/:version/users.json"
user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json"
ldap_groups_path: "/api/:version/ldap/:provider/groups.json"
namespaces_path: "/api/:version/namespaces.json"
project_users_path: "/api/:version/projects/:id/users.json"
......@@ -37,7 +38,7 @@
# Return users list. Filtered by query
# Only active users retrieved
users: (query, callback) ->
users: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.users_path)
$.ajax(
......@@ -47,6 +48,7 @@
search: query
per_page: 20
active: true
skip_ldap: skip_ldap
dataType: "json"
).done (users) ->
callback(users)
......@@ -85,3 +87,19 @@
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
# Return LDAP groups list. Filtered by query
ldap_groups: (query, provider, callback) ->
url = Api.buildUrl(Api.ldap_groups_path)
url = url.replace(':provider', provider);
$.ajax(
url: url
data:
private_token: gon.api_token
search: query
per_page: 20
active: true
dataType: "json"
).done (groups) ->
callback(groups)
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$ ->
ldapGroupResult = (group) ->
group.cn
groupFormatSelection = (group) ->
group.cn
$('.ajax-ldap-groups-select').each (i, select) ->
$(select).select2
id: (group) ->
group.cn
placeholder: "Search for a LDAP group"
minimumInputLength: 1
query: (query) ->
provider = $('#ldap_group_link_provider').val();
Api.ldap_groups query.term, provider, (groups) ->
data = { results: groups }
query.callback(data)
initSelection: (element, callback) ->
id = $(element).val()
if id isnt ""
callback(cn: id)
formatResult: ldapGroupResult
formatSelection: groupFormatSelection
dropdownCssClass: "ajax-groups-dropdown"
formatNoMatches: (nomatch) ->
"Match not found; try refining your search query."
$('#ldap_group_link_provider').on 'change', ->
$('.ajax-ldap-groups-select').select2('data', null)
\ No newline at end of file
......@@ -24,6 +24,14 @@ class Project
else
$('#project_issues_tracker_id').removeAttr('disabled')
$('#project_merge_requests_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_merge_requests_template').removeAttr('disabled')
else
$('#project_merge_requests_template').attr('disabled', 'disabled')
$('#project_merge_requests_template').change()
@Project = Project
......
......@@ -15,12 +15,14 @@ $ ->
user.name
$('.ajax-users-select').each (i, select) ->
skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2
placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
Api.users query.term, (users) ->
Api.users query.term, skip_ldap, (users) ->
data = { results: users }
query.callback(data)
......
......@@ -291,6 +291,15 @@ img.emoji {
margin-bottom: 10px;
}
.group-name {
font-size: 14px;
line-height: 24px;
}
.available-groups form {
margin: 5px 0;
}
table {
td.permission-x {
background: #D9EDF7 !important;
......
.appearance-logo-preview {
max-width: 400px;
margin-bottom: 20px;
}
class Admin::AppearancesController < Admin::ApplicationController
before_filter :set_appearance, except: :create
def show
end
def preview
end
def create
@appearance = Appearance.new(appearance_params)
if @appearance.save
redirect_to admin_appearances_path, notice: 'Appearance was successfully created.'
else
render action: 'show'
end
end
def update
if @appearance.update(appearance_params)
redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.'
else
render action: 'show'
end
end
def logo
appearance = Appearance.last
appearance.remove_logo!
appearance.save
redirect_to admin_appearances_path, notice: 'Logo was succesfully removed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_appearance
@appearance = Appearance.last || Appearance.new
end
# Only allow a trusted parameter "white list" through.
def appearance_params
params.require(:appearance).permit(:title, :description, :logo, :updated_by)
end
end
class Admin::EmailsController < Admin::ApplicationController
def show
end
def create
AdminEmailsWorker.perform_async(params[:recipients], params[:subject], params[:body])
redirect_to admin_email_path, notice: 'Email sent'
end
end
class Groups::LdapGroupLinksController < ApplicationController
before_action :group
before_action :authorize_admin_group!
layout 'group'
def index
end
def create
ldap_group_link = @group.ldap_group_links.build(ldap_group_link_params)
if ldap_group_link.save
if request.referer && request.referer.include?('admin')
redirect_to [:admin, @group], notice: 'New LDAP link saved'
else
redirect_to :back, notice: 'New LDAP link saved'
end
else
redirect_to :back, alert: "Could not create new LDAP link: #{ldap_group_link.errors.full_messages * ', '}"
end
end
def destroy
@group.ldap_group_links.where(id: params[:id]).destroy_all
redirect_to :back, notice: 'LDAP link removed'
end
private
def group
@group ||= Group.find_by(path: params[:group_id])
end
def authorize_admin_group!
render_404 unless can?(current_user, :manage_group, group)
end
def ldap_group_link_params
params.require(:ldap_group_link).permit(:cn, :group_access, :provider)
end
end
class Groups::LdapsController < ApplicationController
before_filter :group
before_filter :authorize_admin_group!
def reset_access
LdapGroupResetService.new.execute(group, current_user)
redirect_to members_group_path(@group), notice: 'Access reset complete'
end
private
def group
@group ||= Group.find_by(path: params[:group_id])
end
def authorize_admin_group!
unless can?(current_user, :manage_group, group)
return render_404
end
end
end
......@@ -39,6 +39,8 @@ class GroupsController < ApplicationController
@events = @events.limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push if current_user
@shared_projects = @group.shared_projects
respond_to do |format|
format.html
format.json { pager_json("events/_events", @events.count) }
......
......@@ -5,8 +5,12 @@ class HelpController < ApplicationController
def show
@category = params[:category]
@file = params[:file]
format = params[:format] || 'md'
file_path = Rails.root.join('doc', @category, @file + ".#{format}")
if File.exists?(Rails.root.join('doc', @category, @file + '.md'))
if %w(png jpg jpeg gif).include?(format)
send_file file_path, disposition: 'inline'
elsif File.exists?(file_path)
render 'show'
else
not_found!
......
......@@ -26,7 +26,7 @@ class Profiles::KeysController < ApplicationController
def destroy
@key = current_user.keys.find(params[:id])
@key.destroy
@key.destroy unless @key.is_a? LDAPKey
respond_to do |format|
format.html { redirect_to profile_keys_url }
......
class Projects::GitHooksController < Projects::ApplicationController
# Authorize
before_filter :authorize_admin_project!
respond_to :html
layout "project_settings"
def index
project.create_git_hook unless project.git_hook
@pre_receive_hook = project.git_hook
end
def update
@pre_receive_hook = project.git_hook
@pre_receive_hook.update_attributes(git_hook_params)
if @pre_receive_hook.valid?
redirect_to project_git_hooks_path(@project)
else
render :index
end
end
private
# Only allow a trusted parameter "white list" through.
def git_hook_params
params.require(:git_hook).permit(:deny_delete_tag, :delete_branch_regex, :commit_message_regex, :force_push_regex)
end
end
class Projects::GroupLinksController < Projects::ApplicationController
layout 'project_settings'
before_filter :authorize_admin_project!
def index
@group_links = project.project_group_links.all
@available_groups = Group.all
@available_groups -= project.invited_groups
@available_groups -= [project.group]
end
def create
link = project.project_group_links.new
link.group_id = params[:group_id]
link.group_access = params[:group_access]
link.save
redirect_to project_group_links_path(project)
end
def destroy
project.project_group_links.find(params[:id]).destroy
redirect_to project_group_links_path(project)
end
end
......@@ -40,7 +40,7 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params
params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:room, :recipients, :project_url, :webhook, :username, :password, :api_version,
:user_key, :device, :priority, :sound
)
end
......
......@@ -7,6 +7,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index
@group = @project.group
@project_members = @project.project_members.order('access_level DESC')
@project_group_links = @project.project_group_links
end
def new
......
......@@ -206,7 +206,7 @@ class ProjectsController < ApplicationController
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template
)
end
end
class UnsubscribesController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers,
:add_abilities
layout 'public_users'
def show
@user = get_user
end
def create
@user = get_user
if @user
@user.admin_unsubscribe!
Notify.send_unsubscribed_notification(@user).deliver
end
redirect_to new_user_session_path, notice: 'You have been unsubscribed'
end
protected
def get_user
@email = Base64.urlsafe_decode64(params[:email])
User.where(email: @email).first
end
end
......@@ -37,12 +37,24 @@ class ProjectsFinder
)
else
# User has no access to group or group projects
# or has access through shared project
#
# Return only:
# public projects
# internal projects
#
group.projects.public_and_internal_only
# shared projects
projects_ids = []
ProjectGroupLink.where(project_id: group.projects).each do |shared_project|
if shared_project.group.users.include?(current_user) || shared_project.project.users.include?(current_user)
projects_ids << shared_project.project.id
end
end
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_ids,
Project.public_and_internal_levels
)
end
end
else
......
module AdminEmailHelper
def admin_email_grouped_recipient_options
options_for_select([['All GitLab users', 'all']]) +
grouped_options_for_select(
'Groups' => Group.pluck(:name, :id).map{ |name, id| [name, "group-#{id}"] },
'Projects' => grouped_project_list
)
end
protected
def grouped_project_list
Group.includes(:projects).flat_map do |group|
group.human_name
group.projects.map do |project|
["#{group.human_name} / #{project.name}", "project-#{project.id}"]
end
end
end
end
\ No newline at end of file
module AppearancesHelper
def brand_item
nil
end
def brand_title
'GitLab Community Edition'
if brand_item
brand_item.title
else
'GitLab Enterprise Edition'
end
end
def brand_image
nil
if brand_item.logo?
image_tag brand_item.logo
else
nil
end
end
def brand_text
nil
markdown(brand_item.description)
end
def brand_item
@appearance ||= Appearance.first
end
end
......@@ -6,7 +6,7 @@ module GroupsHelper
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
......
......@@ -30,8 +30,15 @@ module MergeRequestsHelper
classes
end
def ci_build_details_path(merge_request)
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha)
def ci_build_details_path merge_request
build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha)
parsed_url = URI.parse(build_url)
unless parsed_url.userinfo.blank?
parsed_url.userinfo = ''
end
parsed_url.to_s
end
def merge_path_description(merge_request, separator)
......
......@@ -2,6 +2,7 @@ module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
......@@ -17,4 +18,12 @@ module SelectsHelper
project_id = opts[:project_id] || @project.id
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
end
def ldap_server_select_options
options_from_collection_for_select(
Gitlab::LDAP::Config.servers,
'provider_name',
'label'
)
end
end
......@@ -75,8 +75,8 @@ module TabHelper
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
if ['services', 'hooks', 'deploy_keys', 'team_members', 'protected_branches'].include? controller.controller_name
"active"
if ['services', 'git_hooks', 'hooks', 'deploy_keys', 'team_members', 'protected_branches'].include? controller.controller_name
"active"
end
end
......
module Emails
module AdminNotification
def send_admin_notification(user_id, subject, body)
email = recipient(user_id)
@unsubscribe_url = unsubscribe_url(email: Base64.urlsafe_encode64(email))
@body = body
mail to: email, subject: subject
end
def send_unsubscribed_notification(user_id)
email = recipient(user_id)
mail to: email, subject: "Unsubscribed from GitLab administrator notifications"
end
end
end
class Notify < ActionMailer::Base
include ActionDispatch::Routing::PolymorphicRoutes
include Emails::AdminNotification
include Emails::Issues
include Emails::MergeRequests
include Emails::Notes
......
class Appearance < ActiveRecord::Base
validates :title, presence: true
validates :description, presence: true
validates :logo, file_size: { maximum: 1000.kilobytes.to_i }
mount_uploader :logo, AttachmentUploader
end
class GitHook < ActiveRecord::Base
belongs_to :project
validates :project, presence: true
def commit_message_allowed?(message)
if commit_message_regex.present?
if message =~ Regexp.new(commit_message_regex)
true
else
false
end
else
true
end
end
end
......@@ -19,6 +19,9 @@ require 'file_size_validator'
class Group < Namespace
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
......@@ -70,10 +73,27 @@ class Group < Namespace
end
end
def human_ldap_access
Gitlab::Access.options_with_owner.key ldap_access
end
def public_profile?
projects.public_only.any?
end
# NOTE: Backwards compatibility with old ldap situation
def ldap_cn
ldap_group_links.first.try(:cn)
end
def ldap_access
ldap_group_links.first.try(:group_access)
end
def ldap_synced?
ldap_cn.present?
end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
......
class JiraIssue
def initialize(issue_identifier)
@issue_identifier = issue_identifier
end
def to_s
@issue_identifier.to_s
end
def id
@issue_identifier.to_s
end
def iid
@issue_identifier.to_s
end
def ==(other)
other.is_a?(self.class) && (to_s == other.to_s)
end
end
......@@ -25,6 +25,8 @@ class Key < ActiveRecord::Base
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
scope :ldap, -> { where(type: 'LDAPKey') }
delegate :name, :email, to: :user, prefix: true
after_create :add_to_shell
......
class LdapGroupLink < ActiveRecord::Base
include Gitlab::Access
belongs_to :group
validates :cn, :group_access, :group_id, presence: true
validates :cn, uniqueness: { scope: [:group_id, :provider] }
validates :group_access, inclusion: { in: Gitlab::Access.all_values }
scope :with_provider, ->(provider) { where(provider: provider) }
def access_field
group_access
end
def config
Gitlab::LDAP::Config.new(provider)
end
# default to the first LDAP server
def provider
read_attribute(:provider) || Gitlab::LDAP::Config.providers.first
end
def provider_label
config.label
end
end
# == Schema Information
#
# Table name: keys
#
# id :integer not null, primary key
# user_id :integer
# created_at :datetime
# updated_at :datetime
# key :text
# title :string(255)
# identifier :string(255)
# type :string(255)
#
class LDAPKey < Key
end
......@@ -26,6 +26,8 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
scope :with_ldap_dn, -> { references(:user).includes(:user).
where(users: { provider: 'ldap' }) }
after_create :notify_create
after_update :notify_update
......
......@@ -51,6 +51,7 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id"
belongs_to :namespace
has_one :git_hook, dependent: :destroy
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
# Project services
......@@ -64,8 +65,11 @@ class Project < ActiveRecord::Base
has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :jira_service, dependent: :destroy
has_one :jenkins_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
......@@ -88,6 +92,9 @@ class Project < ActiveRecord::Base
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
......@@ -313,7 +320,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox)
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira jenkins pushover buildbox)
end
def gitlab_ci?
......@@ -328,6 +335,14 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
def jira_tracker?
self.issues_tracker == "jira"
end
def redmine_tracker?
self.issues_tracker == "redmine"
end
# For compatibility with old code
def code
path
......@@ -620,4 +635,12 @@ class Project < ActiveRecord::Base
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
def group_ldap_synced?
if group
group.ldap_synced?
else
false
end
end
end
class ProjectGroupLink < ActiveRecord::Base
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MASTER = 40
belongs_to :project
belongs_to :group
validates :project_id, presence: true
validates :group_id, presence: true
validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
def self.access_options
Gitlab::Access.options
end
def self.default_access
DEVELOPER
end
def human_access
self.class.access_options.key(self.group_access)
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class JenkinsService < CiService
prop_accessor :project_url
validates :project_url, presence: true, if: :activated?
delegate :execute, to: :service_hook, prefix: nil
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
jenkins_url = project_url.sub(/job\/.*/, '')
hook.url = jenkins_url + "/gitlab/build_now"
hook.save
end
def title
'Jenkins CI'
end
def description
'An extendable open source continuous integration server'
end
def help
'You must have installed GitLab Hook plugin into Jenkins.'
end
def to_param
'jenkins'
end
def fields
[
{ type: 'text', name: 'project_url', placeholder: 'Jenkins project URL like http://jenkins.example.com/job/my-project/' }
]
end
def build_page sha
project_url + "/scm/bySHA1/#{sha}"
end
def commit_status sha
parsed_url = URI.parse(build_page(sha))
if parsed_url.userinfo.blank?
response = HTTParty.get(build_page(sha), verify: false)
else
get_url = build_page(sha).gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
response = HTTParty.get(get_url, verify: false, basic_auth: auth)
end
if response.code == 200
status = Nokogiri.parse(response).xpath('//img[@class="build-caption-status-icon"]').first.attributes['alt'].value
if status.include?('Success')
'success'
elsif status.include?('Failed') || status.include?('Aborted')
'failed'
elsif status.include?('In progress')
'running'
else
'pending'
end
else
:error
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class JiraService < Service
include HTTParty
prop_accessor :project_url, :username, :password,
:api_version, :jira_issue_transition_id
validates :username, :password, presence: true, if: :activated?
before_validation :set_api_version
def title
'JIRA'
end
def description
'Bug, issue tracking, and project management system'
end
def to_param
'jira'
end
def fields
[
{ type: 'text', name: 'project_url', placeholder: 'Url to JIRA, http://jira.example' },
{ type: 'text', name: 'username', placeholder: '' },
{ type: 'password', name: 'password', placeholder: '' },
{ type: 'text', name: 'api_version', placeholder: '2' },
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
]
end
def set_api_version
self.api_version = "2"
end
def execute(push, issue = nil)
close_issue(push, issue) if issue
end
private
def close_issue(push_data, issue_name)
url = close_issue_url(issue_name)
commit_url = push_data[:commits].first[:url]
message = {
'update' => {
'comment' => [{
'add' => {
'body' => "Issue solved with #{commit_url}"
}
}]
},
'transition' => {
'id' => jira_issue_transition_id
}
}
json_body = message.to_json
Rails.logger.info("#{self.class.name}: sending POST with body #{json_body} to #{url}")
JiraService.post(
url,
body: json_body,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}"
}
)
end
def close_issue_url(issue_name)
"#{self.project_url.chomp("/")}/rest/api/#{self.api_version}/issue/#{issue_name}/transitions"
end
def auth
require 'base64'
Base64.urlsafe_encode64("#{self.username}:#{self.password}")
end
end
......@@ -145,14 +145,63 @@ class ProjectTeam
access << group.group_members.find_by(user_id: user_id).try(:access_field)
end
if project.invited_groups.any?
access << max_invited_level(user_id)
end
access.compact.max
end
def max_invited_level(user_id)
project.project_group_links.map do |group_link|
invited_group = group_link.group
access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
# If group member has higher access level we should restrict it
# to max allowed access level
if access && access > group_link.group_access
access = group_link.group_access
end
access
end.compact.max
end
private
def fetch_members(level = nil)
project_members = project.project_members
group_members = group ? group.group_members : []
invited_members = []
if project.invited_groups.any?
project.project_group_links.each do |group_link|
invited_group = group_link.group
im = invited_group.group_members
if level
int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
# Skip group members if we ask for masters
# but max group access is developers
next if int_level > group_link.group_access
# If we ask for developers and max
# group access is developers we need to provide
# both group master, developers as devs
if int_level == group_link.group_access
im.where("access_level >= ?)", group_link.group_access)
else
im.send(level)
end
end
invited_members << im
end
invited_members = invited_members.flatten.compact
end
if level
project_members = project_members.send(level)
......@@ -160,6 +209,7 @@ class ProjectTeam
end
user_ids = project_members.pluck(:user_id)
user_ids += invited_members.map(&:user_id) if invited_members.any?
user_ids += group_members.pluck(:user_id) if group
User.where(id: user_ids)
......
......@@ -178,6 +178,7 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
......@@ -293,7 +294,8 @@ class User < ActiveRecord::Base
@authorized_projects ||= begin
project_ids = personal_projects.pluck(:id)
project_ids += groups_projects.pluck(:id)
project_ids += projects.pluck(:id).uniq
project_ids += projects.pluck(:id)
project_ids += groups.joins(:shared_projects).pluck(:project_id)
Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
end
end
......@@ -428,7 +430,7 @@ class User < ActiveRecord::Base
if !Gitlab.config.ldap.enabled
false
elsif ldap_user?
!last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
!last_credential_check_at || (last_credential_check_at + Gitlab.config.ldap['sync_time']) < Time.now
else
false
end
......@@ -529,6 +531,10 @@ class User < ActiveRecord::Base
SystemHooksService.new
end
def admin_unsubscribe!
update_column :admin_email_unsubscribed_at, Time.now
end
def starred?(project)
starred_projects.exists?(project)
end
......
......@@ -13,5 +13,9 @@ module Files
def repository
project.repository
end
def git_hook
project.git_hook
end
end
end
......@@ -17,6 +17,10 @@ module Files
return error("You can only create files if you are on top of a branch")
end
if git_hook && !git_hook.commit_message_allowed?(params[:commit_message])
return error("Commit message must match next format: #{git_hook.commit_message_regex}")
end
file_name = File.basename(path)
file_path = path
......
......@@ -17,6 +17,10 @@ module Files
return error("You can only create files if you are on top of a branch")
end
if git_hook && !git_hook.commit_message_allowed?(params[:commit_message])
return error("Commit message must match next format: #{git_hook.commit_message_regex}")
end
blob = repository.blob_at_branch(ref, path)
unless blob
......
......@@ -17,6 +17,10 @@ module Files
return error("You can only create files if you are on top of a branch")
end
if git_hook && !git_hook.commit_message_allowed?(params[:commit_message])
return error("Commit message must match next format: #{git_hook.commit_message_regex}")
end
blob = repository.blob_at_branch(ref, path)
unless blob
......
......@@ -87,7 +87,11 @@ class GitPushService
if !issues_to_close.empty? && is_default_branch
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(push_data, issue)
else
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
end
end
......
class LdapGroupResetService
def execute(group, current_user)
# Only for ldap connected users
# reset last_credential_check_at to force LDAP::Access::update_permissions
# set Gitlab::Access::Guest to later on upgrade the access of a user
# trigger the lowest access possible for all LDAP connected users
a = group.members.with_ldap_dn.map do |member|
# don't unauthorize the current user
next if current_user == member.user
member.update_attribute :access_level, Gitlab::Access::GUEST
end
group.users.ldap.update_all last_credential_check_at: nil
end
end
......@@ -19,6 +19,9 @@ module MergeRequests
# Generate suggested MR title based on source branch name
merge_request.title = merge_request.source_branch.titleize.humanize
# Set MR description based on project template
merge_request.description = merge_request.target_project.merge_requests_template
compare_result = CompareService.new.execute(
current_user,
merge_request.source_project,
......
= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f|
- if @appearance.errors.any?
.alert.alert-danger
- @appearance.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, class: "form-control"
.form-group
= f.label :description, class: 'control-label'
.col-sm-10
= f.text_area :description, class: "form-control", rows: 10
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}.
.form-group
= f.label :logo, class: 'control-label'
.col-sm-10
- if @appearance.logo?
= image_tag @appearance.logo, class: 'appearance-logo-preview'
%br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo"
%hr
= f.file_field :logo, class: ""
.hint
Maximum logo size is 1MB, page optimized for logo size 640x360px
.form-actions
= f.submit 'Save', class: 'btn btn-save'
= link_to 'Preview', preview_admin_appearances_path, class: 'btn', target: '_blank'
- if @appearance.updated_at
%span.pull-right
Last edit #{time_ago_with_tooltip(@appearance.updated_at)}
%h3.page-title
Appearance settings - Preview
%hr
.ui-box
.title
Sign-in page
%div
.login-page
.container
.content
.login-title
%h1= brand_title
%hr
.container
.content
.row
.col-sm-7
.brand-image
= brand_image
.brand_text
= brand_text
.col-sm-4
.login-box
%h3.page-title Sign in
= text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
= password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
= button_tag "Sign in", class: "btn-create btn"
%h3.page-title
Appearance settings
%p.light
You can modify look of sign-in and sign-up pages here
%hr
= render 'form'
%h3.page-title
Send email notication
%p.light
You can notify the app / group or a project by sending them an email notification
= form_tag admin_email_path, class: 'form-horizontal', id: 'new-admin-email' do
.form-group
%label.control-label{for: :subject} Subject
.col-sm-10
= text_field_tag :subject, '', class: 'form-control', required: true
.form-group
%label.control-label{for: :body} Body
.col-sm-10
= text_area_tag :body, '', class: 'form-control', rows: 15, required: true
.form-group
%label.control-label{for: :recipients} Recipient group
.col-sm-10
= select_tag :recipients, admin_email_grouped_recipient_options, class: :select2, required: true
.form-actions
= submit_tag 'Send message', class: 'btn btn-create'
......@@ -51,7 +51,12 @@
%li Renaming group path will rename directory for all related projects
%li It will change web url for access group and group projects.
%li It will change the git path to repositories under this group.
.form-actions
= f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
- if @group.persisted?
%h3.page-title Linked LDAP groups
= render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group
......@@ -31,6 +31,17 @@
%strong
= @group.created_at.stamp("March 1, 1999")
.panel.panel-default
.panel-heading Linked LDAP groups
%ul.well-list
- if @group.ldap_group_links.any?
- @group.ldap_group_links.each do |ldap_group_link|
%li
cn:
%strong= ldap_group_link.cn
as
%strong= ldap_group_link.human_access
.panel.panel-default
.panel-heading
%h3.panel-title
......@@ -49,6 +60,22 @@
.panel-footer
= paginate @projects, param_name: 'projects_page', theme: 'gitlab'
- if @group.shared_projects.any?
.panel.panel-default
.panel-heading
Projects shared with #{@group.name}
%span.badge
#{@group.shared_projects.count}
%ul.well-list
- @group.shared_projects.sort_by(&:name).each do |project|
%li
%strong
= link_to project.name_with_namespace, [:admin, project]
%span.label.label-gray
= repository_size(project)
%span.pull-right.light
%span.monospace= project.path_with_namespace + ".git"
.col-md-6
.panel.panel-default
.panel-heading
......@@ -60,7 +87,7 @@
= form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true)
= users_select_tag(:user_ids, { multiple: true, skip_ldap: @group.ldap_synced? })
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
......
......@@ -32,6 +32,7 @@
.panel-heading
Users (#{@users.total_count})
.panel-head-actions
= link_to 'Send email to users', admin_email_path, class: 'btn btn-info'
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
......
= form_for @users_group, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
.form-group
= f.label :user_ids, "People", class: 'control-label'
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
.col-sm-10= users_select_tag(:user_ids, { multiple: true, skip_ldap: @group.ldap_synced? , class: 'input-large' })
.form-group
= f.label :access_level, "Group Access", class: 'control-label'
......
......@@ -7,4 +7,8 @@
= link_to projects_group_path(@group) do
%i.fa.fa-folder
Projects
= nav_link(controller: :ldap_group_links) do
= link_to group_ldap_group_links_path(@group) do
%i.icon-exchange
LDAP Groups
- if projects.present?
.panel.panel-default
.panel-heading
Projects shared with
%strong #{@group.name}
(#{projects.count})
%ul.well-list
- projects.each do |project|
%li.project-row
= link_to project_path(project), class: dom_class(project) do
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name
= truncate(project.name, length: 25)
%span.arrow
%i.icon-angle-right
......@@ -21,7 +21,6 @@
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
.form-group
.col-sm-2
.col-sm-10
......@@ -52,5 +51,4 @@
Removing group will cause all child projects and resources to be removed.
%br
%strong Removed group can not be restored!
= link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
.row
.col-md-2= render 'groups/settings_nav'
.col-md-10
%h3.page-title Linked LDAP groups
= render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group
......@@ -17,6 +17,10 @@
- if current_user && current_user.can?(:manage_group, @group)
.pull-right
- if ldap_enabled? && @group.ldap_group_links.any?
= link_to reset_access_group_ldap_path(@group), class: 'btn btn-grouped', data: { confirm: "Force GitLab to do LDAP permission checks for all group members? All members besides yourself will be reduced to 'Guest' access until their next interaction with GitLab." }, method: :put do
Clear LDAP permission cache
= link_to '#', class: 'btn btn-new js-toggle-button' do
Add members
%i.fa.fa-chevron-down
......@@ -24,6 +28,19 @@
.js-toggle-content.hide.new-group-member-holder
= render "new_group_member"
- if ldap_enabled? && @group.ldap_group_links.any?
.bs-callout.bs-callout-info
The members of this group are sync with LDAP.
Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below.
%ul
- @group.ldap_group_links.each do |ldap_group_link|
%li
People in cn
%code= ldap_group_link.cn
are given
%code= ldap_group_link.human_access
access.
.panel.panel-default.prepend-top-20
.panel-heading
%strong #{@group.name}
......
......@@ -26,6 +26,9 @@
%p
= escaped_autolink(@group.description)
= render "projects", projects: @projects
%br
= render "shared_projects", projects: @shared_projects
- if current_user
.prepend-top-20
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
......
%div
%h1
GitLab
%span.light Enterprise Edition
%span= Gitlab::VERSION
%small= Gitlab::REVISION
%p.slead
......
......@@ -7,7 +7,7 @@
-# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555
- if controller_name == 'projects' && action_name == 'show'
%meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
%meta{content: "GitLab Community Edition", name: "description"}
%meta{content: "GitLab Enterprise Edition", name: "description"}
%title
= "#{title} | " if defined?(title)
......
......@@ -16,4 +16,5 @@
= link_to "Hooks", admin_hooks_path
= nav_link(controller: :background_jobs) do
= link_to "Background Jobs", admin_background_jobs_path
= nav_link(controller: :appearances) do
= link_to "Appearance", admin_appearances_path
%section.ldap-group-links
= form_for [group, LdapGroupLink.new] do |f|
%fieldset
%legend
%div.form-holder
.form-group.clearfix
= f.label :cn, class: 'control-label' do
LDAP Server
.col-sm-10
= f.select :provider, ldap_server_select_options
.form-group.clearfix
= f.label :cn, class: 'control-label' do
LDAP Group cn
.col-sm-10
= f.hidden_field :cn, placeholder: "Ex. QA group", class: "xxlarge ajax-ldap-groups-select input-mn-300"
.help-block
Synchronize #{group.name}'s members with this LDAP group.
%br
If you select an LDAP group you do not belong to you will lose ownership of #{group.name}.
.form-group.clearfix
= f.label :group_access, class: 'control-label' do
LDAP Access
.col-sm-10
= f.select :group_access, options_for_select(GroupMember.access_level_roles)
.help-block
Default, minimum permission level for LDAP group members of #{group.name}.
%br
You can manage permission levels for individual group members in the Members tab.
.form-actions
= f.submit 'Add synchronization', class: "btn btn-create"
%li
= ldap_group_link.cn
%small.light== as #{ldap_group_link.human_access} on #{ldap_group_link.provider_label}
.pull-right
= link_to group_ldap_group_link_path(group, ldap_group_link), method: :delete, class: 'btn btn-danger btn-small' do
= fa_icon('unlink', text: 'unlink')
.panel.panel-default
.panel-heading
%h4.panel-title
Linked LDAP groups
== (#{group.ldap_group_links.count})
- if group.ldap_group_links.any?
%ul.well-list
= render collection: group.ldap_group_links, partial: 'ldap_group_links/ldap_group_link', locals: { group: group }
- else
.panel-body
No linked LDAP groups
= simple_format @body
\----
%p
Don't want to receive updates from GitLab administrators?
= link_to 'Unsubscribe', @unsubscribe_url
\ No newline at end of file
= h @body
\-----
Don't want to receive updates from GitLab administrators?
== Unsubscribe here: #{@unsubscribe_url}
\ No newline at end of file
%p
You have been unsubscribed from receiving GitLab administrator notifications.
You have been unsubscribed from receiving GitLab administrator notifications.
......@@ -6,4 +6,5 @@
%span.cgray
added #{time_ago_with_tooltip(key.created_at)}
= link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
- unless key.is_a? LDAPKey
= link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
......@@ -14,7 +14,7 @@
.panel-heading
SSH Keys (#{@keys.count})
%ul.well-list#keys-table
= render @keys
= render partial: "key", collection: @keys
- if @keys.blank?
%li
.nothing-here-block There are no SSH keys with access to your account.
......
......@@ -19,4 +19,5 @@
= @key.key
.pull-right
= link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
- unless @key.is_a? LDAPKey
= link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
......@@ -7,6 +7,10 @@
= link_to project_team_index_path(@project), class: "team-tab tab" do
%i.fa.fa-users
Members
= nav_link(controller: :group_links) do
= link_to project_group_links_path(@project) do
%i.icon-share
Groups
= nav_link(controller: :deploy_keys) do
= link_to project_deploy_keys_path(@project) do
%i.fa.fa-key
......@@ -15,6 +19,10 @@
= link_to project_hooks_path(@project) do
%i.fa.fa-link
Web Hooks
= nav_link(controller: :git_hooks) do
= link_to project_git_hooks_path(@project) do
%i.icon-upload
Git Hooks
= nav_link(controller: :services) do
= link_to project_services_path(@project) do
%i.fa.fa-cogs
......
......@@ -66,6 +66,13 @@
= f.check_box :merge_requests_enabled
%span.descr Submit changes to be merged upstream.
.form-group
= f.label :merge_requests_template, class: 'control-label' do
Merge request template
%span.light (optional)
.col-sm-10
= f.text_area :merge_requests_template, placeholder: "This MR should have: *", disabled: !@project.merge_requests_enabled, class: "form-control", rows: 3
.form-group
= f.label :wiki_enabled, "Wiki", class: 'control-label'
.col-sm-10
......
%h3.page-title
Git hooks
%p.light
Rules that define what git pushes are accepted for this project
%hr.clearfix
= form_for [@project, @pre_receive_hook], html: { class: 'form-horizontal' } do |f|
-if @pre_receive_hook.errors.any?
.alert.alert-danger
- @pre_receive_hook.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :deny_delete_tag, "Prevent tag removal", class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :deny_delete_tag
%span.descr
Do not allow users to remove git tags with
= succeed '.' do
%code git push
Tags can still be deleted through the web UI.
-#.form-group
= f.label :force_push_regex, "Force push", class: 'control-label'
.col-sm-10
= f.text_field :force_push_regex, class: "form-control"
%p.hint Regular expression that finds branches that you can force push to. If this field is empty it allows force pushes to any branch.
-#.form-group
= f.label :delete_branch_regex, "Branch removal", class: 'control-label'
.col-sm-10
= f.text_field :delete_branch_regex, class: "form-control"
%p.hint Regular expression that finds branches that can be removed. If this field is empty it allows removal of all branches.
.form-group
= f.label :commit_message_regex, "Commit message", class: 'control-label'
.col-sm-10
= f.text_field :commit_message_regex, class: "form-control", placeholder: 'Example: Fixes \d+\..*'
%p.hint
All commit messages must match this
= link_to 'Ruby regular expression', 'http://www.ruby-doc.org/core-2.1.1/Regexp.html'
to be pushed.
If this field is empty it allows any commit message.
For example you can require that an issue number is always mentioned in the commit message.
.form-actions
= f.submit "Save Git hooks", class: "btn btn-create"
%h3.page_title Share project with other groups
%p.light
Projects can be stored in only one group at once. However you can share a project with other groups here.
%hr
- if @group_links.present?
.enabled-groups.append-bottom-20
%h4
Already shared with
%ul.bordered-list
- @group_links.each do |group_link|
- group = group_link.group
%li
%h4
= link_to group do
%i.icon-folder-open
= group.name
%small.light up to #{group_link.human_access}
.pull-right
= link_to project_group_link_path(@project, group_link), method: :delete, class: 'btn btn-small append-right-10' do
%i.icon-remove
disable sharing
- if @available_groups.present?
.available-groups
%h4
Can be shared with
%div
= form_tag project_group_links_path(@project), method: :post, class: 'form-horizontal' do
.form-group
= label_tag :group_id, 'Group', class: 'control-label'
.col-sm-10
= select_tag :group_id, options_from_collection_for_select(@available_groups, :id, :name), class: 'select2'
.form-group
= label_tag :group_access, 'Max access level', class: 'control-label'
.col-sm-10
= select_tag :group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control"
.form-actions
= submit_tag "Share", class: "btn btn-create"
......@@ -21,6 +21,10 @@
.bs-callout
= @service.help
- if @service.help.present?
.bs-callout
= @service.help
.form-group
= f.label :active, "Active", class: "control-label"
.col-sm-10
......@@ -43,6 +47,8 @@
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= f.check_box name
- elsif type == 'password'
= f.password_field name, class: "form-control"
- elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
......
......@@ -12,7 +12,7 @@
.form-group
= f.label :user_ids, "People", class: 'control-label'
.col-sm-10
= users_select_tag(:user_ids, multiple: true)
= users_select_tag(:user_ids, { multiple: true, skip_ldap: @project.group_ldap_synced? })
%p 2. Set access level for them
.form-group
......
- @project_group_links.each do |group_links|
- shared_group = group_links.group
- shared_group_users_count = group_links.group.group_members.count
.panel.panel-default
.panel-heading
Shared with
%strong #{shared_group.name}
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
.pull-right
= link_to members_group_path(shared_group), class: 'btn btn-small' do
%i.icon-edit
%ul.well-list
- shared_group.group_members.order('access_level DESC').limit(20).each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false, show_roles: false
- if shared_group_users_count > 20
%li
and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(shared_group)}
......@@ -14,3 +14,5 @@
= render "team", members: @project_members
- if @group
= render "group_members"
- if @project_group_links.any?
= render "shared_group_members"
%h3.page-title Unsubscribe from Admin notifications
%hr
= form_tag unsubscribe_path(Base64.urlsafe_encode64(@email)) do
%p
Yes, I want to unsubscribe
%strong= @email
from any further admin emails.
.form-actions
= submit_tag 'Unsubscribe', class: 'btn btn-create'
class AdminEmailsWorker
include Sidekiq::Worker
def perform(recipient_id, subject, body)
recipient_list(recipient_id).pluck(:id).each do |user_id|
Notify.send_admin_notification(user_id, subject, body).deliver
end
end
private
def recipient_list(recipient_id)
case recipient_id
when 'all'
User.subscribed_for_admin_email
when /group-(\d+)\z/
Group.find($1).users.subscribed_for_admin_email
when /project-(\d+)\z/
Project.find($1).team.users.subscribed_for_admin_email
end
end
end
\ No newline at end of file
......@@ -70,7 +70,7 @@ production: &base
# This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used.
# Tip: you can test your closing pattern at http://rubular.com
# issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)'
# issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) (#\d+|([A-Z\-]+-)\d+)'
## Default project features settings
default_projects_features:
......@@ -183,6 +183,33 @@ production: &base
#
user_filter: ''
# This setting controls the amount of time between LDAP permission checks for each user.
# After this time has expired for a given user, their next interaction with GitLab (a click in the web UI, a git pull etc.) will be slower because the LDAP permission check is being performed.
# How much slower depends on your LDAP setup, but it is not uncommon for this check to add seconds of waiting time.
# The default value is to have a 'slow click' once every 3600 seconds, i.e. once per hour.
#
# Warning: if you set this value too low, every click in GitLab will be a 'slow click' for all of your LDAP users.
# sync_time: 3600
# Base where we can search for groups
#
# Ex. ou=Groups,dc=gitlab,dc=example
#
group_base: ''
# LDAP group of users who should be admins in GitLab
#
# Ex. GLAdmins
#
admin_group: ''
# Name of attribute which holds a ssh public key of the user object.
# If false or nil, SSH key syncronisation will be disabled.
#
# Ex. sshpublickey
#
sync_ssh_keys: false
# GitLab EE only: add more LDAP servers
# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
# so that GitLab can remember which LDAP server a user belongs to.
......@@ -191,7 +218,6 @@ production: &base
# host:
# ....
## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
......
......@@ -52,15 +52,17 @@ class Settings < Settingslogic
end
end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
Settings.ldap['sync_time'] = 3600 if Settings.ldap['sync_time'].nil?
# backwards compatibility, we only have one host
if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present?
server = Settings.ldap.except('sync_time')
server = Settingslogic.new(server)
server['label'] = 'LDAP'
server['provider_name'] = 'ldap'
Settings.ldap['servers'] = {
'ldap' => server
......@@ -68,11 +70,14 @@ if Settings.ldap['enabled'] || Rails.env.test?
end
Settings.ldap['servers'].each do |key, server|
server = Settingslogic.new(server)
server['label'] ||= 'LDAP'
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
server['active_directory'] = true if server['active_directory'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['sync_time'] = 3600 if server['sync_time'].nil?
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
Settings.ldap['servers'][key] = server
end
end
......@@ -107,7 +112,7 @@ Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) (#\d+|([A-Z\-]+-)\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
......
if Gitlab::LDAP::Config.enabled?
module OmniAuth::Strategies
server = Gitlab.config.ldap.servers.values.first
klass = server['provider_class']
const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
Gitlab::LDAP::Config.servers.each do |server|
# do not redeclare LDAP
next if server['provider_name'] == 'ldap'
const_set(server['provider_class'], Class.new(LDAP))
end
end
OmniauthCallbacksController.class_eval do
server = Gitlab.config.ldap.servers.values.first
alias_method server['provider_name'], :ldap
Gitlab::LDAP::Config.servers.each do |server|
alias_method server['provider_name'], :ldap
end
end
end
\ No newline at end of file
end
......@@ -205,7 +205,7 @@ Devise.setup do |config|
# end
if Gitlab::LDAP::Config.enabled?
Gitlab.config.ldap.servers.values.each do |server|
Gitlab::LDAP::Config.servers.each do |server|
if server['allow_username_or_email_login']
email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
else
......
......@@ -34,6 +34,7 @@ Gitlab::Application.routes.draw do
get 'help' => 'help#index'
get 'help/:category/:file' => 'help#show', as: :help_page
get 'help/:category/*file' => 'help#show'
get 'help/shortcuts'
#
......@@ -96,6 +97,7 @@ Gitlab::Application.routes.draw do
resources :broadcast_messages, only: [:index, :create, :destroy]
resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :email, only: [:show, :create]
resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
member do
......@@ -103,6 +105,13 @@ Gitlab::Application.routes.draw do
end
end
resource :appearances, path: 'appearance' do
member do
get :preview
delete :logo
end
end
root to: "dashboard#index"
end
......@@ -162,12 +171,23 @@ Gitlab::Application.routes.draw do
end
scope module: :groups do
resource :ldap, only: [] do
member do
put :reset_access
end
end
end
scope module: :groups do
resources :ldap_group_links, only: [:index, :create, :destroy]
resources :group_members, only: [:create, :update, :destroy]
resource :avatar, only: [:destroy]
resources :milestones
end
end
get 'unsubscribes/:email', to: 'unsubscribes#show', as: :unsubscribe
post 'unsubscribes/:email', to: 'unsubscribes#create'
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations }
......@@ -287,6 +307,8 @@ Gitlab::Application.routes.draw do
end
end
resources :git_hooks, constraints: {id: /\d+/}
resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do
member do
get :test
......@@ -324,6 +346,8 @@ Gitlab::Application.routes.draw do
end
end
resources :group_links, only: [:index, :create, :destroy], constraints: {id: /\d+/}
resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do
member do
delete :delete_attachment
......
class CreateProjectGroupLinks < ActiveRecord::Migration
def change
create_table :project_group_links do |t|
t.integer :project_id, null: false
t.integer :group_id, null: false
t.timestamps
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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