Commit 45ec0d25 authored by Valery Sizov's avatar Valery Sizov

Merge remote-tracking branch 'ee/master' into ce_non_conflict

Conflicts:
	app/helpers/oauth_helper.rb
	app/helpers/projects_helper.rb
parents bc57ff0e cb8b5c32
......@@ -392,6 +392,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.7.0
- Added custom header logo support (Drew Blessing)
- Fixed preview appearance bug
- Improve performance for selectboxes: project share page, admin email users page
v 7.6.2
- Fix failing migrations for MySQL, LDAP
v 7.6.1
- No changes
v 7.6.0
- Added Audit events related to membership changes for groups and projects
- Added option to attempt a rebase before merging merge request
- Dont show LDAP groups settings if LDAP disabled
- Added member lock for groups to disallow membership additions on project level
- Rebase on merge request. Introduced merge request option to rebase before merging
- Better message for failed pushes because of git hooks
- Kerberos support for web interface and git HTTP
v 7.5.3
- Only set up Sidetiq from a Sidekiq server process (fixes Redis::InheritedError)
v 7.5.0
- Added an ability to check each author commit's email by regex
- Added an ability to restrict commit authors to existing Gitlab users
- Add an option for automatic daily LDAP user sync
- Added git hook for preventing tag removal to API
- Added git hook for setting commit message regex to API
- Added an ability to block commits with certain filenames by regex expression
- Improved a jenkins parser
v 7.4.4
- Fix broken ldap migration
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
......@@ -45,6 +45,7 @@ gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
gem 'net-ldap'
# Git Wiki
gem 'gollum-lib', '~> 4.0.0'
......@@ -119,6 +120,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
gem 'sidetiq', '0.6.3'
gem 'sidekiq', '~> 3.3'
# HTTP requests
......
......@@ -261,7 +261,8 @@ GEM
multi_xml (>= 0.5.2)
httpauth (0.2.1)
httpclient (2.5.3.3)
i18n (0.7.0)
i18n (0.6.11)
ice_cube (0.11.1)
ice_nine (0.10.0)
jasmine (2.0.2)
jasmine-core (~> 2.0.0)
......@@ -280,7 +281,7 @@ GEM
turbolinks
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (1.8.2)
json (1.8.1)
jwt (0.1.13)
multi_json (>= 1.5)
kaminari (0.15.1)
......@@ -291,7 +292,7 @@ GEM
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
libv8 (3.16.14.7)
libv8 (3.16.14.3)
listen (2.3.1)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
......@@ -508,6 +509,10 @@ GEM
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidetiq (0.6.3)
celluloid (>= 0.14.1)
ice_cube (= 0.11.1)
sidekiq (>= 3.0.0)
simple_oauth (0.1.9)
simplecov (0.9.0)
docile (~> 1.1.0)
......@@ -523,7 +528,7 @@ GEM
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
slop (3.6.0)
slop (3.4.7)
spinach (0.8.7)
colorize (= 0.5.8)
gherkin-ruby (>= 0.3.1)
......@@ -564,7 +569,7 @@ GEM
tilt (1.4.1)
timers (4.0.1)
hitimes
timfel-krb5-auth (0.8.3)
timfel-krb5-auth (0.8)
tinder (1.9.3)
eventmachine (~> 1.0)
faraday (~> 0.8)
......@@ -687,6 +692,7 @@ DEPENDENCIES
minitest (~> 5.3.0)
mousetrap-rails
mysql2
net-ldap
newrelic_rpm
nprogress-rails
octokit (= 3.7.0)
......@@ -727,6 +733,7 @@ DEPENDENCIES
settingslogic
shoulda-matchers (~> 2.7.0)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov
sinatra
six
......
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
## Subscriber onboarding information
Thank you for purchasing a GitLab subscription!
For standard subscribers, please see **emergency contact info and other useful information** in [the Standard subscribers README](https://gitlab.com/standard/standard-subscriber-information/tree/master#README).
If you would like to receive access to GitLab Enterprise Edition please create an account on https://gitlab.com/users/sign_up and send us the username. Note that your user will be visible to other Support Subscribers.
Once your username is added, you can find the repository here:
https://gitlab.com/subscribers/gitlab-ee
Packages:
https://gitlab.com/subscribers/gitlab-ee/blob/master/doc/install/packages.md
Documentation:
http://doc.gitlab.com/ee/
To upgrade from CE, just perform a normal upgrade, but make use of an EE package:
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md#updating-from-gitlab-6-6-x-and-higher-to-the-latest-version
If you need help with your GitLab installation and for any technical questions please contact us at subscribers@gitlab.com.
For all other questions, contact us at sales@gitlab.com
## Open source software to collaborate on code
![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
......@@ -21,7 +45,7 @@ To get access to the EE and support please [become a subscriber](https://about.g
## 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
......@@ -87,7 +111,7 @@ Instructions on how to start Gitlab and how to run the tests can be found in the
## 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
......
7.8.0.pre
7.8.0.pre-ee
class @AdminEmailSelect
constructor: ->
$('.ajax-admin-email-select').each (i, select) =>
skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2
placeholder: "Select group or project"
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) ->
group_result = Api.groups query.term, skip_ldap, (groups) ->
groups
project_result = Api.projects query.term, (projects) ->
projects
$.when(project_result, group_result).done (projects, groups) ->
all = {id: "all"}
data = $.merge([all], groups[0], projects[0])
query.callback({ results: data})
id: (object) ->
if object.path_with_namespace
"project-#{object.id}"
else if object.path
"group-#{object.id}"
else
"all"
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@formatSelection(args...)
dropdownCssClass: "ajax-admin-email-dropdown"
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
formatResult: (object) ->
if object.path_with_namespace
"<div class='project-result'>
<div class='project-name'>#{object.name}</div>
<div class='project-path'>#{object.path_with_namespace}</div>
</div>"
else if object.path
"<div class='group-result'>
<div class='group-name'>#{object.name}</div>
<div class='group-path'>#{object.path}</div>
</div>"
else
"<div class='group-result'>
<div class='group-name'>All</div>
<div class='group-path'>All groups and projects</div>
</div>"
formatSelection: (object) ->
if object.path_with_namespace
"Project: #{object.name}"
else if object.path
"Group: #{object.name}"
else
"All groups and projects"
......@@ -4,8 +4,10 @@
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"
projects_path: "/api/:version/projects.json"
# Get 20 (depends on api) recent notes
# and sort the ascending from oldest to newest
......@@ -39,7 +41,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(
......@@ -49,6 +51,7 @@
search: query
per_page: 20
active: true
skip_ldap: skip_ldap
dataType: "json"
).done (users) ->
callback(users)
......@@ -114,3 +117,33 @@
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)
# Return projects list. Filtered by query
projects: (query, callback) ->
project_url = Api.buildUrl(Api.projects_path)
project_query = $.ajax(
url: project_url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
# 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/
......@@ -81,6 +81,10 @@ class Dispatcher
new ProjectFork()
when 'users:show'
new User()
when 'projects:group_links:index'
new GroupsSelect()
when 'admin:emails:show'
new AdminEmailSelect()
switch path.first()
when 'admin'
......
$ ->
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
......@@ -17,4 +17,4 @@ class @Project
path = '/'
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
e.preventDefault()
\ No newline at end of file
......@@ -9,3 +9,8 @@ class @ProjectNew
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
$("#project_merge_requests_enabled").change ->
checked = $(this).prop("checked")
$("#project_merge_requests_template").prop "disabled", not checked
$("#project_merge_requests_rebase_enabled").prop "disabled", not checked
class @UsersSelect
constructor: ->
$('.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)
......@@ -37,4 +39,4 @@ class @UsersSelect
</div>"
formatSelection: (user) ->
user.name
user.name
\ No newline at end of file
......@@ -284,6 +284,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;
......
......@@ -128,6 +128,15 @@ select {
}
}
.project-result {
.project-name {
font-weight: bold;
}
.project-path {
color: #999;
}
}
.user-result {
.user-image {
float: left;
......
.appearance-logo-preview {
max-width: 400px;
margin-bottom: 20px;
}
.appearance-dark-logo-preview {
background-color: #F1F1F1;
}
.appearance-light-logo-preview {
background-color: #373737;
}
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
def header_logos
appearance = Appearance.last
appearance.remove_light_logo!
appearance.remove_dark_logo!
appearance.save
redirect_to admin_appearances_path, notice: 'Header logos were 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,
:dark_logo, :light_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
......@@ -253,6 +253,12 @@ class ApplicationController < ActionController::Base
end
end
def require_ldap_enabled
unless Gitlab.config.ldap.enabled
render_404 and return
end
end
def set_filters_params
params[:sort] ||= 'newest'
params[:scope] = 'all' if params[:scope].blank?
......
class AuditEventsController < ApplicationController
# Authorize
before_filter :repository, only: :project_log
before_filter :authorize_admin_project!, only: :project_log
before_filter :group, only: :group_log
before_filter :authorize_admin_group!, only: :group_log
layout :determine_layout
def project_log
@events = AuditEvent.where(entity_type: "Project", entity_id: project.id).page(params[:page]).per(20)
end
def group_log
@events = AuditEvent.where(entity_type: "Group", entity_id: group.id).page(params[:page]).per(20)
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 determine_layout
if @project
'project_settings'
elsif @group
'group'
end
end
def audit_events_params
params.permit(:project_id, :group_id)
end
end
......@@ -7,21 +7,59 @@ class Groups::GroupMembersController < ApplicationController
layout 'group'
def create
@group.add_users(params[:user_ids].split(','), params[:access_level])
access_level = params[:access_level]
user_ids = params[:user_ids].split(',')
@group.add_users(user_ids, access_level)
users = User.where(id: user_ids).pluck(:id, :name)
users.each do |user|
details = {
add: "user_access",
as: Gitlab::Access.options_with_owner.key(access_level.to_i),
target_id: user[0],
target_type: "User",
target_details: user[1],
}
AuditEventService.new(current_user, @group, details).security_event
end
redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end
def update
@member = @group.group_members.find(params[:id])
@member.update_attributes(member_params)
old_access_level = @member.human_access
if @member.update_attributes(member_params)
details = {
change: "access_level",
from: old_access_level,
to: @member.human_access,
target_id: @member.user_id,
target_type: "User",
target_details: @member.user.name,
}
AuditEventService.new(current_user, @group, details).security_event
end
end
def destroy
@users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
user_id = @users_group.id
user_name = @users_group.user.name
if @users_group.destroy
details = {
remove: "user_access",
target_id: user_id,
target_type: "User",
target_details: user_name,
}
AuditEventService.new(current_user, @group, details).security_event
end
respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
......
class Groups::LdapGroupLinksController < ApplicationController
before_action :group
before_action :require_ldap_enabled
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
......@@ -37,6 +37,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) }
......@@ -147,6 +149,6 @@ class GroupsController < ApplicationController
end
def group_params
params.require(:group).permit(:name, :description, :path, :avatar)
params.require(:group).permit(:name, :description, :path, :avatar, :membership_lock)
end
end
......@@ -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!
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery except: :kerberos
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
handle_omniauth
......
......@@ -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, :author_email_regex, :member_check, :file_name_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
end
def create
link = project.project_group_links.new
link.group_id = params[:link_group_id]
link.group_access = params[:link_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
......@@ -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
......@@ -15,8 +16,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:access_level]]
access_level = params[:access_level]
@project.team << [users, access_level]
users.each do |user|
details = {
add: "user_access",
as: Gitlab::Access.options_with_owner.key(access_level.to_i),
target_id: user.id,
target_type: "User",
target_details: user.name,
}
AuditEventService.new(current_user, @project, details).security_event
end
if params[:redirect_to]
redirect_to params[:redirect_to]
......@@ -27,7 +39,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def update
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
old_access_level = @user_project_relation.human_access
if @user_project_relation.update_attributes(member_params)
details = {
change: "access_level",
from: old_access_level,
to: @user_project_relation.human_access,
target_id: @user_project_relation.user_id,
target_type: "User",
target_details: @user_project_relation.user.name,
}
AuditEventService.new(current_user, @project, details).security_event
end
unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role"
......@@ -37,7 +61,18 @@ class Projects::TeamMembersController < Projects::ApplicationController
def destroy
@user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy
user_id = @user_project_relation.user_id
user_name = @user_project_relation.user.name
if @user_project_relation.destroy
details = {
remove: "user_access",
target_id: user_id,
target_type: "User",
target_details: user_name,
}
AuditEventService.new(current_user, @project, details).security_event
end
respond_to do |format|
format.html { redirect_to project_team_index_path(@project) }
......
......@@ -179,7 +179,8 @@ 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, :avatar
:wiki_enabled, :merge_requests_template, :visibility_level, :merge_requests_rebase_enabled,
:import_url, :last_activity_at, :namespace_id, :avatar
)
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 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_header_logo
if brand_item.header_logos?
haml_tag(:style) do
# Dark theme/light logo
haml_concat ".dark_theme .app_logo a h1 {" \
"background: url('#{brand_item.light_logo}') " \
"no-repeat center center !important; }"
# Light theme/dark logo
haml_concat ".light_theme .app_logo a h1 {" \
"background: url('#{brand_item.dark_logo}') " \
"no-repeat center center !important; }"
end
end
end
def brand_text
nil
markdown(brand_item.description)
end
def brand_item
@appearance ||= Appearance.first
end
end
module AuditEventsHelper
def human_text(details)
details.map{ |key, value| select_keys(key, value) }.join(" ").humanize
end
def select_keys(key, value)
if key.match(/^target_.*/)
""
else
"#{key.to_s} <strong>#{value}</strong>"
end
end
end
......@@ -14,4 +14,14 @@ module BranchesHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, branch_name)
end
def can_rebase?(project, branch_name)
if project.protected_branch? branch_name
can?(current_user, :push_code_to_protected_branches, project)
elsif can?(current_user, :push_code, project)
true
else
false
end
end
end
......@@ -36,6 +36,8 @@ module GroupsHelper
def group_settings_page?
if current_controller?('groups')
current_action?('edit') || current_action?('projects')
elsif current_controller?('ldap_group_links') || current_controller?('audit_events')
true
else
false
end
......
......@@ -32,8 +32,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)
......
......@@ -3,6 +3,14 @@ module OauthHelper
Gitlab.config.ldap.enabled
end
def kerberos_enabled?
enabled_oauth_providers.include?(:kerberos)
end
def standard_login_form_only?
ldap_enabled? || kerberos_enabled?
end
def default_providers
[:twitter, :github, :gitlab, :google_oauth2, :ldap]
end
......@@ -13,11 +21,13 @@ module OauthHelper
def enabled_social_providers
enabled_oauth_providers.select do |name|
[:twitter, :gitlab, :github, :google_oauth2].include?(name.to_sym)
[:twitter, :gitlab, :github, :google_oauth2, :kerberos,].include?(name.to_sym)
end
end
def additional_providers
enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
enabled_oauth_providers.reject do |provider|
provider.to_s.starts_with?('ldap') || provider == :kerberos
end
end
end
......@@ -257,4 +257,12 @@ module ProjectsHelper
def gitlab_import_enabled?
enabled_oauth_providers.include?(:gitlab)
end
def membership_locked?
if @project.group && @project.group.membership_lock
true
else
false
end
end
end
......@@ -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] || ''
......@@ -18,6 +19,14 @@ module SelectsHelper
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
def groups_select_tag(id, opts = {})
css_class = "ajax-groups-select "
css_class << "multiselect " if opts[:multiple]
......@@ -26,4 +35,13 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class)
end
def admin_email_select_tag(id, opts = {})
css_class = "ajax-admin-email-select "
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
hidden_field_tag(id, value, class: css_class)
end
end
......@@ -89,7 +89,7 @@ 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
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 }
validates :dark_logo,
file_size: { maximum: 1000.kilobytes.to_i },
presence: true, if: :light_logo?
validates :light_logo,
file_size: { maximum: 1000.kilobytes.to_i },
presence: true, if: :dark_logo?
mount_uploader :logo, AttachmentUploader
mount_uploader :dark_logo, AttachmentUploader
mount_uploader :light_logo, AttachmentUploader
def header_logos?
dark_logo? && light_logo?
end
end
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
belongs_to :user, foreign_key: :author_id
validates :author_id, presence: true
validates :entity_id, presence: true
validates :entity_type, presence: true
after_initialize :initialize_details
def initialize_details
self.details = {} if details.nil?
end
def author_name
self.user.name
end
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
def commit_validation?
commit_message_regex.present? || author_email_regex.present? || member_check || file_name_regex.present?
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: 200.kilobytes.to_i }
......@@ -73,10 +76,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
def post_create_hook
system_hook_service.execute_hooks_for(self, :create)
end
......
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)
rescue Gitlab::LDAP::Config::InvalidProvider
nil
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,7 @@ 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, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') }
after_create :post_create_hook
after_update :notify_update
......
......@@ -41,6 +41,9 @@ class MergeRequest < ActiveRecord::Base
attr_accessor :should_remove_source_branch
# If true, merge request should rebase before merging
attr_accessor :should_rebase
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
......
......@@ -57,6 +57,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
......@@ -70,6 +71,8 @@ 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 :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
......@@ -79,6 +82,7 @@ class Project < ActiveRecord::Base
has_one :custom_issue_tracker_service, dependent: :destroy
has_one :gitlab_issue_tracker_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
......@@ -102,6 +106,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
......@@ -363,7 +370,8 @@ class Project < ActiveRecord::Base
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jenkins jira redmine custom_issue_tracker
)
end
def gitlab_ci?
......@@ -378,6 +386,14 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
def jira_tracker?
issues_tracker.to_param == 'jira'
end
def redmine_tracker?
issues_tracker.to_param == 'redmine'
end
def avatar_type
unless self.avatar.image?
self.errors.add :avatar, 'only images allowed'
......@@ -648,6 +664,14 @@ class Project < ActiveRecord::Base
merge_requests.where(source_project_id: self.id)
end
def group_ldap_synced?
if group
group.ldap_synced?
else
false
end
end
def create_repository
if gitlab_shell.add_repository(path_with_namespace)
true
......
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
# img.build-caption-status-icon for old jenkins version
src = Nokogiri.parse(response).css('img.build-caption-status-icon,.build-caption>img').first.attributes['src'].value
if src =~ /blue\.png$/
'success'
elsif src =~ /(red\.png|aborted\.png)$/
'failed'
elsif src =~ /anime\.gif$/
'running'
else
'pending'
end
else
:error
end
end
end
......@@ -13,8 +13,12 @@
#
class JiraService < IssueTrackerService
include HTTParty
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
prop_accessor :username, :password, :api_version, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
before_validation :set_api_version
def title
if self.properties && self.properties['title'].present?
......@@ -35,4 +39,63 @@ class JiraService < IssueTrackerService
def to_param
'jira'
end
def fields
super.push(
{ 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
......@@ -52,6 +52,7 @@ class ProjectTeam
end
def add_users_ids(user_ids, access)
return false if group_member_lock
ProjectMember.add_users_into_projects(
[project.id],
user_ids,
......@@ -145,14 +146,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 +210,7 @@ class ProjectTeam
end
user_ids = project_members.pluck(:user_id)
user_ids.push(*invited_members.map(&:user_id)) if invited_members.any?
user_ids.push(*group_members.pluck(:user_id)) if group
User.where(id: user_ids)
......@@ -168,4 +219,8 @@ class ProjectTeam
def group
project.group
end
def group_member_lock
group && group.membership_lock
end
end
class SecurityEvent < AuditEvent
end
......@@ -181,6 +181,8 @@ 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, -> { joins(:identities).where('identities.provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
......@@ -214,6 +216,10 @@ class User < ActiveRecord::Base
User.where(name: name).first
end
def existing_member?(email)
User.where(email: email).any? || Email.where(email: email).any?
end
def filter(filter_name)
case filter_name
when "admins"; self.admins
......@@ -301,6 +307,7 @@ class User < ActiveRecord::Base
project_ids = personal_projects.pluck(:id)
project_ids.push(*groups_projects.pluck(:id))
project_ids.push(*projects.pluck(:id).uniq)
project_ids.push(*groups.joins(:shared_projects).pluck(:project_id))
Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
end
end
......@@ -435,7 +442,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
......@@ -544,6 +551,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
......
class AuditEventService
def initialize(author, entity, details = {})
@author, @entity, @details = author, entity, details
end
def security_event
SecurityEvent.create(
author_id: @author.id,
entity_id: @entity.id,
entity_type: @entity.class.name,
details: @details
)
end
end
......@@ -13,5 +13,9 @@ module Files
def repository
project.repository
end
def git_hook
project.git_hook
end
end
end
......@@ -9,6 +9,10 @@ module Files
return error("You are not allowed to create file in this 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
......
......@@ -13,6 +13,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
......
......@@ -13,6 +13,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
......
......@@ -88,7 +88,11 @@ class GitPushService
author ||= commit_user(commit)
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
%fieldset.sign-in
%legend
Sign in/Sign up pages:
.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
%fieldset.app_logo
%legend
Navigation bar:
.form-group
= f.label :dark_logo, class: 'control-label'
.col-sm-10
- if @appearance.dark_logo?
= image_tag @appearance.dark_logo, class: 'appearance-dark-logo-preview'
= f.file_field :dark_logo, class: ""
.hint
Maximum size is 1MB, page optimized for logo size 40x40px
= f.label :light_logo, class: 'control-label'
.col-sm-10
- if @appearance.light_logo?
= image_tag @appearance.light_logo, class: 'appearance-light-logo-preview'
= f.file_field :light_logo, class: ""
.hint
Maximum size is 1MB, page optimized for logo size 41x41px
-if @appearance.light_logo? || @appearance.dark_logo?
%br
= link_to 'Remove header logos', header_logos_admin_appearances_path, data: { confirm: "Header logos will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo"
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- if @appearance.persisted?
= link_to 'Preview last save', 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 the look and feel of GitLab here
= 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
= admin_email_select_tag(:recipients)
.form-actions
= submit_tag 'Send message', class: 'btn btn-create'
......@@ -25,3 +25,7 @@
= 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:
......
- if defined?(events)
%table.table#audits
%thead
%tr
%th Author
%th Action
%th Target
%th At
%tbody
- events.each do |event|
%tr
%td #{event.author_name}
%td
%span
#{raw human_text(event.details)}
%td #{event.details[:target_details]}
%td #{event.created_at}
= paginate events, theme: "gitlab"
%h3.page-title Group Audit Events
%p.light Events in #{@group.name}
= render 'event_table', events: @events
%h3.page-title Project Audit Events
%p.light Events in #{@project.path_with_namespace}
= render 'event_table', events: @events
= form_tag(user_omniauth_callback_path(provider), id: 'new_kerberos_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "Kerberos Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
= button_tag "Kerberos Sign in", class: "btn-save btn"
\ No newline at end of file
......@@ -7,6 +7,9 @@
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- if kerberos_enabled?
%li{class: (:active unless ldap_enabled?)}
= link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab'
- if signin_enabled?
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
......@@ -14,6 +17,9 @@
- @ldap_servers.each_with_index do |server, i|
%div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
= render 'devise/sessions/new_ldap', provider: server['provider_name']
- if kerberos_enabled?
%div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)}
= render 'devise/sessions/new_kerberos', provider: :kerberos
- if signin_enabled?
%div#tab-signin.tab-pane
= render 'devise/sessions/new_base'
......
= 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'
......
......@@ -9,3 +9,14 @@
%i.fa.fa-folder
%span
Projects
- if ldap_enabled?
= nav_link(controller: :ldap_group_links) do
= link_to group_ldap_group_links_path(@group) do
%i.fa.fa-exchange
%span
LDAP Groups
= nav_link(controller: :audit_events) do
= link_to group_audit_events_path(@group) do
%i.fa.fa-file-text-o
%span
Audit Events
- 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
......@@ -23,6 +23,15 @@
%hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
.form-group
%hr
= f.label :name, class: 'control-label' do
Member lock
.col-sm-10
.checkbox
= f.check_box :membership_lock
%span.descr Prevent adding new members to project membership within this group
.form-actions
= f.submit 'Save group', class: "btn btn-save"
......@@ -33,5 +42,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"
%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}
......
......@@ -20,3 +20,5 @@
= spinner
%aside.side.col-md-4
= render "projects", projects: @projects
%br
= render "shared_projects", projects: @shared_projects
%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)
......
......@@ -2,6 +2,7 @@
.navbar-inner
.container
%div.app_logo
- brand_header_logo if brand_item
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
%h1 GITLAB
%h1.title= title
......
......@@ -39,6 +39,16 @@
%i.fa.fa-cog
%span
Background Jobs
= nav_link(controller: :appearances) do
= link_to admin_appearances_path do
%i.fa.fa-image
%span
Appearance
= nav_link(controller: :application_settings) do
= link_to admin_application_settings_path do
%i.fa.fa-cogs
%span
Settings
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do
......
%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
%h4= ldap_group_link.cn
.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')
- if ldap_group_link.config
%p.light
As
%strong #{ldap_group_link.human_access}
on
%strong #{ldap_group_link.provider_label}
server
- else
%p.cred
%i.fa.fa-warning
Config for
%code #{ldap_group_link.provider}
does not present in GitLab
.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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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