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 ...@@ -392,6 +392,9 @@ v 6.9.0
- Labels for merge requests (Drew Blessing) - Labels for merge requests (Drew Blessing)
- Threaded emails by setting a Message-ID (Philip Blatter) - 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 v 6.8.0
- Ability to at mention users that are participating in issue and merge req. discussion - 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) - 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' ...@@ -45,6 +45,7 @@ gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
gem 'net-ldap'
# Git Wiki # Git Wiki
gem 'gollum-lib', '~> 4.0.0' gem 'gollum-lib', '~> 4.0.0'
...@@ -119,6 +120,7 @@ gem "acts-as-taggable-on" ...@@ -119,6 +120,7 @@ gem "acts-as-taggable-on"
# Background jobs # Background jobs
gem 'slim' gem 'slim'
gem 'sinatra', require: nil gem 'sinatra', require: nil
gem 'sidetiq', '0.6.3'
gem 'sidekiq', '~> 3.3' gem 'sidekiq', '~> 3.3'
# HTTP requests # HTTP requests
......
...@@ -261,7 +261,8 @@ GEM ...@@ -261,7 +261,8 @@ GEM
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpauth (0.2.1) httpauth (0.2.1)
httpclient (2.5.3.3) httpclient (2.5.3.3)
i18n (0.7.0) i18n (0.6.11)
ice_cube (0.11.1)
ice_nine (0.10.0) ice_nine (0.10.0)
jasmine (2.0.2) jasmine (2.0.2)
jasmine-core (~> 2.0.0) jasmine-core (~> 2.0.0)
...@@ -280,7 +281,7 @@ GEM ...@@ -280,7 +281,7 @@ GEM
turbolinks turbolinks
jquery-ui-rails (4.2.1) jquery-ui-rails (4.2.1)
railties (>= 3.2.16) railties (>= 3.2.16)
json (1.8.2) json (1.8.1)
jwt (0.1.13) jwt (0.1.13)
multi_json (>= 1.5) multi_json (>= 1.5)
kaminari (0.15.1) kaminari (0.15.1)
...@@ -291,7 +292,7 @@ GEM ...@@ -291,7 +292,7 @@ GEM
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.1.2)
launchy (~> 2.2) launchy (~> 2.2)
libv8 (3.16.14.7) libv8 (3.16.14.3)
listen (2.3.1) listen (2.3.1)
celluloid (>= 0.15.2) celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
...@@ -508,6 +509,10 @@ GEM ...@@ -508,6 +509,10 @@ GEM
json json
redis (>= 3.0.6) redis (>= 3.0.6)
redis-namespace (>= 1.3.1) 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) simple_oauth (0.1.9)
simplecov (0.9.0) simplecov (0.9.0)
docile (~> 1.1.0) docile (~> 1.1.0)
...@@ -523,7 +528,7 @@ GEM ...@@ -523,7 +528,7 @@ GEM
slim (2.0.2) slim (2.0.2)
temple (~> 0.6.6) temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1) tilt (>= 1.3.3, < 2.1)
slop (3.6.0) slop (3.4.7)
spinach (0.8.7) spinach (0.8.7)
colorize (= 0.5.8) colorize (= 0.5.8)
gherkin-ruby (>= 0.3.1) gherkin-ruby (>= 0.3.1)
...@@ -564,7 +569,7 @@ GEM ...@@ -564,7 +569,7 @@ GEM
tilt (1.4.1) tilt (1.4.1)
timers (4.0.1) timers (4.0.1)
hitimes hitimes
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8)
tinder (1.9.3) tinder (1.9.3)
eventmachine (~> 1.0) eventmachine (~> 1.0)
faraday (~> 0.8) faraday (~> 0.8)
...@@ -687,6 +692,7 @@ DEPENDENCIES ...@@ -687,6 +692,7 @@ DEPENDENCIES
minitest (~> 5.3.0) minitest (~> 5.3.0)
mousetrap-rails mousetrap-rails
mysql2 mysql2
net-ldap
newrelic_rpm newrelic_rpm
nprogress-rails nprogress-rails
octokit (= 3.7.0) octokit (= 3.7.0)
...@@ -727,6 +733,7 @@ DEPENDENCIES ...@@ -727,6 +733,7 @@ DEPENDENCIES
settingslogic settingslogic
shoulda-matchers (~> 2.7.0) shoulda-matchers (~> 2.7.0)
sidekiq (~> 3.3) sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov simplecov
sinatra sinatra
six 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 Copyright (c) 2013-2014 GitLab B.V.
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:
The above copyright notice and this permission notice shall be included in This software and associated documentation files (the "Software") can only be
all copies or substantial portions of the Software. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
......
# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab # ![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 ## Open source software to collaborate on code
![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif) ![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 ...@@ -21,7 +45,7 @@ To get access to the EE and support please [become a subscriber](https://about.g
## Canonical source ## 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 ## Code status
...@@ -87,7 +111,7 @@ Instructions on how to start Gitlab and how to run the tests can be found in the ...@@ -87,7 +111,7 @@ Instructions on how to start Gitlab and how to run the tests can be found in the
## Documentation ## 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 ## 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 @@ ...@@ -4,8 +4,10 @@
users_path: "/api/:version/users.json" users_path: "/api/:version/users.json"
user_path: "/api/:version/users/:id.json" user_path: "/api/:version/users/:id.json"
notes_path: "/api/:version/projects/:id/notes.json" notes_path: "/api/:version/projects/:id/notes.json"
ldap_groups_path: "/api/:version/ldap/:provider/groups.json"
namespaces_path: "/api/:version/namespaces.json" namespaces_path: "/api/:version/namespaces.json"
project_users_path: "/api/:version/projects/:id/users.json" project_users_path: "/api/:version/projects/:id/users.json"
projects_path: "/api/:version/projects.json"
# Get 20 (depends on api) recent notes # Get 20 (depends on api) recent notes
# and sort the ascending from oldest to newest # and sort the ascending from oldest to newest
...@@ -39,7 +41,7 @@ ...@@ -39,7 +41,7 @@
# Return users list. Filtered by query # Return users list. Filtered by query
# Only active users retrieved # Only active users retrieved
users: (query, callback) -> users: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.users_path) url = Api.buildUrl(Api.users_path)
$.ajax( $.ajax(
...@@ -49,6 +51,7 @@ ...@@ -49,6 +51,7 @@
search: query search: query
per_page: 20 per_page: 20
active: true active: true
skip_ldap: skip_ldap
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
...@@ -114,3 +117,33 @@ ...@@ -114,3 +117,33 @@
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) 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 ...@@ -81,6 +81,10 @@ class Dispatcher
new ProjectFork() new ProjectFork()
when 'users:show' when 'users:show'
new User() new User()
when 'projects:group_links:index'
new GroupsSelect()
when 'admin:emails:show'
new AdminEmailSelect()
switch path.first() switch path.first()
when 'admin' 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
...@@ -9,3 +9,8 @@ class @ProjectNew ...@@ -9,3 +9,8 @@ class @ProjectNew
initEvents: -> initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit' 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 class @UsersSelect
constructor: -> constructor: ->
$('.ajax-users-select').each (i, select) => $('.ajax-users-select').each (i, select) =>
skip_ldap = $(select).hasClass('skip_ldap')
$(select).select2 $(select).select2
placeholder: "Search for a user" placeholder: "Search for a user"
multiple: $(select).hasClass('multiselect') multiple: $(select).hasClass('multiselect')
minimumInputLength: 0 minimumInputLength: 0
query: (query) -> query: (query) ->
Api.users query.term, (users) -> Api.users query.term, skip_ldap, (users) ->
data = { results: users } data = { results: users }
query.callback(data) query.callback(data)
......
...@@ -284,6 +284,15 @@ img.emoji { ...@@ -284,6 +284,15 @@ img.emoji {
margin-bottom: 10px; margin-bottom: 10px;
} }
.group-name {
font-size: 14px;
line-height: 24px;
}
.available-groups form {
margin: 5px 0;
}
table { table {
td.permission-x { td.permission-x {
background: #D9EDF7 !important; background: #D9EDF7 !important;
......
...@@ -128,6 +128,15 @@ select { ...@@ -128,6 +128,15 @@ select {
} }
} }
.project-result {
.project-name {
font-weight: bold;
}
.project-path {
color: #999;
}
}
.user-result { .user-result {
.user-image { .user-image {
float: left; 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 ...@@ -253,6 +253,12 @@ class ApplicationController < ActionController::Base
end end
end end
def require_ldap_enabled
unless Gitlab.config.ldap.enabled
render_404 and return
end
end
def set_filters_params def set_filters_params
params[:sort] ||= 'newest' params[:sort] ||= 'newest'
params[:scope] = 'all' if params[:scope].blank? 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 ...@@ -7,21 +7,59 @@ class Groups::GroupMembersController < ApplicationController
layout 'group' layout 'group'
def create 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.' redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end end
def update def update
@member = @group.group_members.find(params[:id]) @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 end
def destroy def destroy
@users_group = @group.group_members.find(params[:id]) @users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner. 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| respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true } 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 ...@@ -37,6 +37,8 @@ class GroupsController < ApplicationController
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@shared_projects = @group.shared_projects
respond_to do |format| respond_to do |format|
format.html format.html
format.json { pager_json("events/_events", @events.count) } format.json { pager_json("events/_events", @events.count) }
...@@ -147,6 +149,6 @@ class GroupsController < ApplicationController ...@@ -147,6 +149,6 @@ class GroupsController < ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar) params.require(:group).permit(:name, :description, :path, :avatar, :membership_lock)
end end
end end
...@@ -5,8 +5,12 @@ class HelpController < ApplicationController ...@@ -5,8 +5,12 @@ class HelpController < ApplicationController
def show def show
@category = params[:category] @category = params[:category]
@file = params[:file] @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' render 'show'
else else
not_found! not_found!
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery except: :kerberos
Gitlab.config.omniauth.providers.each do |provider| Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do define_method provider['name'] do
handle_omniauth handle_omniauth
......
...@@ -26,7 +26,7 @@ class Profiles::KeysController < ApplicationController ...@@ -26,7 +26,7 @@ class Profiles::KeysController < ApplicationController
def destroy def destroy
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
@key.destroy @key.destroy unless @key.is_a? LDAPKey
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_keys_url } 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 ...@@ -7,6 +7,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index def index
@group = @project.group @group = @project.group
@project_members = @project.project_members.order('access_level DESC') @project_members = @project.project_members.order('access_level DESC')
@project_group_links = @project.project_group_links
end end
def new def new
...@@ -15,8 +16,19 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -15,8 +16,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def create def create
users = User.where(id: params[:user_ids].split(',')) users = User.where(id: params[:user_ids].split(','))
access_level = params[:access_level]
@project.team << [users, 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] if params[:redirect_to]
redirect_to params[:redirect_to] redirect_to params[:redirect_to]
...@@ -27,7 +39,19 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -27,7 +39,19 @@ class Projects::TeamMembersController < Projects::ApplicationController
def update def update
@user_project_relation = @project.project_members.find_by(user_id: member) @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? unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role" flash[:alert] = "User should have at least one role"
...@@ -37,7 +61,18 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -37,7 +61,18 @@ class Projects::TeamMembersController < Projects::ApplicationController
def destroy def destroy
@user_project_relation = @project.project_members.find_by(user_id: member) @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| respond_to do |format|
format.html { redirect_to project_team_index_path(@project) } format.html { redirect_to project_team_index_path(@project) }
......
...@@ -179,7 +179,8 @@ class ProjectsController < ApplicationController ...@@ -179,7 +179,8 @@ class ProjectsController < ApplicationController
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :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 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 ...@@ -37,12 +37,24 @@ class ProjectsFinder
) )
else else
# User has no access to group or group projects # User has no access to group or group projects
# or has access through shared project
# #
# Return only: # Return only:
# public projects # public projects
# internal projects # internal projects
# # shared projects
group.projects.public_and_internal_only 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
end end
else else
......
module AppearancesHelper module AppearancesHelper
def brand_item
nil
end
def brand_title def brand_title
'GitLab Community Edition' if brand_item
brand_item.title
else
'GitLab Enterprise Edition'
end
end end
def brand_image def brand_image
if brand_item.logo?
image_tag brand_item.logo
else
nil nil
end 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 def brand_text
nil markdown(brand_item.description)
end
def brand_item
@appearance ||= Appearance.first
end end
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 ...@@ -14,4 +14,14 @@ module BranchesHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, branch_name) ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, branch_name)
end 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 end
...@@ -36,6 +36,8 @@ module GroupsHelper ...@@ -36,6 +36,8 @@ module GroupsHelper
def group_settings_page? def group_settings_page?
if current_controller?('groups') if current_controller?('groups')
current_action?('edit') || current_action?('projects') current_action?('edit') || current_action?('projects')
elsif current_controller?('ldap_group_links') || current_controller?('audit_events')
true
else else
false false
end end
......
...@@ -32,8 +32,15 @@ module MergeRequestsHelper ...@@ -32,8 +32,15 @@ module MergeRequestsHelper
classes classes
end end
def ci_build_details_path(merge_request) def ci_build_details_path merge_request
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha) 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 end
def merge_path_description(merge_request, separator) def merge_path_description(merge_request, separator)
......
...@@ -3,6 +3,14 @@ module OauthHelper ...@@ -3,6 +3,14 @@ module OauthHelper
Gitlab.config.ldap.enabled Gitlab.config.ldap.enabled
end end
def kerberos_enabled?
enabled_oauth_providers.include?(:kerberos)
end
def standard_login_form_only?
ldap_enabled? || kerberos_enabled?
end
def default_providers def default_providers
[:twitter, :github, :gitlab, :google_oauth2, :ldap] [:twitter, :github, :gitlab, :google_oauth2, :ldap]
end end
...@@ -13,11 +21,13 @@ module OauthHelper ...@@ -13,11 +21,13 @@ module OauthHelper
def enabled_social_providers def enabled_social_providers
enabled_oauth_providers.select do |name| 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
end end
def additional_providers 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
end end
...@@ -257,4 +257,12 @@ module ProjectsHelper ...@@ -257,4 +257,12 @@ module ProjectsHelper
def gitlab_import_enabled? def gitlab_import_enabled?
enabled_oauth_providers.include?(:gitlab) enabled_oauth_providers.include?(:gitlab)
end end
def membership_locked?
if @project.group && @project.group.membership_lock
true
else
false
end
end
end end
...@@ -2,6 +2,7 @@ module SelectsHelper ...@@ -2,6 +2,7 @@ module SelectsHelper
def users_select_tag(id, opts = {}) def users_select_tag(id, opts = {})
css_class = "ajax-users-select " css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple] css_class << "multiselect " if opts[:multiple]
css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '') css_class << (opts[:class] || '')
value = opts[:selected] || '' value = opts[:selected] || ''
...@@ -18,6 +19,14 @@ module SelectsHelper ...@@ -18,6 +19,14 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id) hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
end 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 = {}) def groups_select_tag(id, opts = {})
css_class = "ajax-groups-select " css_class = "ajax-groups-select "
css_class << "multiselect " if opts[:multiple] css_class << "multiselect " if opts[:multiple]
...@@ -26,4 +35,13 @@ module SelectsHelper ...@@ -26,4 +35,13 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class) hidden_field_tag(id, value, class: css_class)
end 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 end
...@@ -89,7 +89,7 @@ module TabHelper ...@@ -89,7 +89,7 @@ module TabHelper
def project_tab_class def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project) 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" "active"
end end
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 class Notify < ActionMailer::Base
include ActionDispatch::Routing::PolymorphicRoutes include ActionDispatch::Routing::PolymorphicRoutes
include Emails::AdminNotification
include Emails::Issues include Emails::Issues
include Emails::MergeRequests include Emails::MergeRequests
include Emails::Notes 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' ...@@ -19,6 +19,9 @@ require 'file_size_validator'
class Group < Namespace class Group < Namespace
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members 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? } validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
...@@ -73,10 +76,27 @@ class Group < Namespace ...@@ -73,10 +76,27 @@ class Group < Namespace
end end
end end
def human_ldap_access
Gitlab::Access.options_with_owner.key ldap_access
end
def public_profile? def public_profile?
projects.public_only.any? projects.public_only.any?
end 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 def post_create_hook
system_hook_service.execute_hooks_for(self, :create) system_hook_service.execute_hooks_for(self, :create)
end 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 ...@@ -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 :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
scope :ldap, -> { where(type: 'LDAPKey') }
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
after_create :add_to_shell 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 ...@@ -26,6 +26,7 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) } scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.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_create :post_create_hook
after_update :notify_update after_update :notify_update
......
...@@ -41,6 +41,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -41,6 +41,9 @@ class MergeRequest < ActiveRecord::Base
attr_accessor :should_remove_source_branch 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 # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
attr_accessor :allow_broken attr_accessor :allow_broken
......
...@@ -57,6 +57,7 @@ class Project < ActiveRecord::Base ...@@ -57,6 +57,7 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace 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' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
# Project services # Project services
...@@ -70,6 +71,8 @@ class Project < ActiveRecord::Base ...@@ -70,6 +71,8 @@ class Project < ActiveRecord::Base
has_one :assembla_service, dependent: :destroy has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy
has_one :slack_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 :buildbox_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy has_one :teamcity_service, dependent: :destroy
...@@ -79,6 +82,7 @@ class Project < ActiveRecord::Base ...@@ -79,6 +82,7 @@ class Project < ActiveRecord::Base
has_one :custom_issue_tracker_service, dependent: :destroy has_one :custom_issue_tracker_service, dependent: :destroy
has_one :gitlab_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_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
...@@ -102,6 +106,9 @@ class Project < ActiveRecord::Base ...@@ -102,6 +106,9 @@ class Project < ActiveRecord::Base
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user 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 :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
...@@ -363,7 +370,8 @@ class Project < ActiveRecord::Base ...@@ -363,7 +370,8 @@ class Project < ActiveRecord::Base
def available_services_names def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla %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 end
def gitlab_ci? def gitlab_ci?
...@@ -378,6 +386,14 @@ class Project < ActiveRecord::Base ...@@ -378,6 +386,14 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first @ci_service ||= ci_services.select(&:activated?).first
end end
def jira_tracker?
issues_tracker.to_param == 'jira'
end
def redmine_tracker?
issues_tracker.to_param == 'redmine'
end
def avatar_type def avatar_type
unless self.avatar.image? unless self.avatar.image?
self.errors.add :avatar, 'only images allowed' self.errors.add :avatar, 'only images allowed'
...@@ -648,6 +664,14 @@ class Project < ActiveRecord::Base ...@@ -648,6 +664,14 @@ class Project < ActiveRecord::Base
merge_requests.where(source_project_id: self.id) merge_requests.where(source_project_id: self.id)
end end
def group_ldap_synced?
if group
group.ldap_synced?
else
false
end
end
def create_repository def create_repository
if gitlab_shell.add_repository(path_with_namespace) if gitlab_shell.add_repository(path_with_namespace)
true 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 @@ ...@@ -13,8 +13,12 @@
# #
class JiraService < IssueTrackerService 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 def title
if self.properties && self.properties['title'].present? if self.properties && self.properties['title'].present?
...@@ -35,4 +39,63 @@ class JiraService < IssueTrackerService ...@@ -35,4 +39,63 @@ class JiraService < IssueTrackerService
def to_param def to_param
'jira' 'jira'
end 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 end
...@@ -52,6 +52,7 @@ class ProjectTeam ...@@ -52,6 +52,7 @@ class ProjectTeam
end end
def add_users_ids(user_ids, access) def add_users_ids(user_ids, access)
return false if group_member_lock
ProjectMember.add_users_into_projects( ProjectMember.add_users_into_projects(
[project.id], [project.id],
user_ids, user_ids,
...@@ -145,14 +146,63 @@ class ProjectTeam ...@@ -145,14 +146,63 @@ class ProjectTeam
access << group.group_members.find_by(user_id: user_id).try(:access_field) access << group.group_members.find_by(user_id: user_id).try(:access_field)
end end
if project.invited_groups.any?
access << max_invited_level(user_id)
end
access.compact.max access.compact.max
end 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 private
def fetch_members(level = nil) def fetch_members(level = nil)
project_members = project.project_members project_members = project.project_members
group_members = group ? group.group_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 if level
project_members = project_members.send(level) project_members = project_members.send(level)
...@@ -160,6 +210,7 @@ class ProjectTeam ...@@ -160,6 +210,7 @@ class ProjectTeam
end end
user_ids = project_members.pluck(:user_id) 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_ids.push(*group_members.pluck(:user_id)) if group
User.where(id: user_ids) User.where(id: user_ids)
...@@ -168,4 +219,8 @@ class ProjectTeam ...@@ -168,4 +219,8 @@ class ProjectTeam
def group def group
project.group project.group
end end
def group_member_lock
group && group.membership_lock
end
end end
class SecurityEvent < AuditEvent
end
...@@ -181,6 +181,8 @@ class User < ActiveRecord::Base ...@@ -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_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 :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 :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 } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
...@@ -214,6 +216,10 @@ class User < ActiveRecord::Base ...@@ -214,6 +216,10 @@ class User < ActiveRecord::Base
User.where(name: name).first User.where(name: name).first
end end
def existing_member?(email)
User.where(email: email).any? || Email.where(email: email).any?
end
def filter(filter_name) def filter(filter_name)
case filter_name case filter_name
when "admins"; self.admins when "admins"; self.admins
...@@ -301,6 +307,7 @@ class User < ActiveRecord::Base ...@@ -301,6 +307,7 @@ class User < ActiveRecord::Base
project_ids = personal_projects.pluck(:id) project_ids = personal_projects.pluck(:id)
project_ids.push(*groups_projects.pluck(:id)) project_ids.push(*groups_projects.pluck(:id))
project_ids.push(*projects.pluck(:id).uniq) 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') Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
end end
end end
...@@ -435,7 +442,7 @@ class User < ActiveRecord::Base ...@@ -435,7 +442,7 @@ class User < ActiveRecord::Base
if !Gitlab.config.ldap.enabled if !Gitlab.config.ldap.enabled
false false
elsif ldap_user? 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 else
false false
end end
...@@ -544,6 +551,10 @@ class User < ActiveRecord::Base ...@@ -544,6 +551,10 @@ class User < ActiveRecord::Base
SystemHooksService.new SystemHooksService.new
end end
def admin_unsubscribe!
update_column :admin_email_unsubscribed_at, Time.now
end
def starred?(project) def starred?(project)
starred_projects.exists?(project) starred_projects.exists?(project)
end 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 ...@@ -13,5 +13,9 @@ module Files
def repository def repository
project.repository project.repository
end end
def git_hook
project.git_hook
end
end end
end end
...@@ -9,6 +9,10 @@ module Files ...@@ -9,6 +9,10 @@ module Files
return error("You are not allowed to create file in this branch") return error("You are not allowed to create file in this branch")
end 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_name = File.basename(path)
file_path = path file_path = path
......
...@@ -13,6 +13,10 @@ module Files ...@@ -13,6 +13,10 @@ module Files
return error("You can only create files if you are on top of a branch") return error("You can only create files if you are on top of a branch")
end 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) blob = repository.blob_at_branch(ref, path)
unless blob unless blob
......
...@@ -13,6 +13,10 @@ module Files ...@@ -13,6 +13,10 @@ module Files
return error("You can only create files if you are on top of a branch") return error("You can only create files if you are on top of a branch")
end 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) blob = repository.blob_at_branch(ref, path)
unless blob unless blob
......
...@@ -88,9 +88,13 @@ class GitPushService ...@@ -88,9 +88,13 @@ class GitPushService
author ||= commit_user(commit) author ||= commit_user(commit)
issues_to_close.each do |issue| issues_to_close.each do |issue|
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(push_data, issue)
else
Issues::CloseService.new(project, author, {}).execute(issue, commit) Issues::CloseService.new(project, author, {}).execute(issue, commit)
end end
end end
end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an # Create cross-reference notes for any other references. Omit any issues that were referenced in an
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit # issue-closing phrase, or have already been mentioned from this commit (probably from this commit
......
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 ...@@ -19,6 +19,9 @@ module MergeRequests
# Generate suggested MR title based on source branch name # Generate suggested MR title based on source branch name
merge_request.title = merge_request.source_branch.titleize.humanize 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( compare_result = CompareService.new.execute(
current_user, current_user,
merge_request.source_project, 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 @@ ...@@ -25,3 +25,7 @@
= f.submit 'Save changes', class: "btn btn-primary" = f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" = 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 @@ ...@@ -31,6 +31,17 @@
%strong %strong
= @group.created_at.stamp("March 1, 1999") = @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.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
...@@ -49,6 +60,22 @@ ...@@ -49,6 +60,22 @@
.panel-footer .panel-footer
= paginate @projects, param_name: 'projects_page', theme: 'gitlab' = 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 .col-md-6
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
...@@ -60,7 +87,7 @@ ...@@ -60,7 +87,7 @@
= form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
%div %div
= users_select_tag(:user_ids, multiple: true) = users_select_tag(:user_ids, { multiple: true, skip_ldap: @group.ldap_synced? })
%div.prepend-top-10 %div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr %hr
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
.panel-heading .panel-heading
Users (#{@users.total_count}) Users (#{@users.total_count})
.panel-head-actions .panel-head-actions
= link_to 'Send email to users', admin_email_path, class: 'btn btn-info'
.dropdown.inline .dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort: %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 @@ ...@@ -7,6 +7,9 @@
- @ldap_servers.each_with_index do |server, i| - @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)} %li{class: (:active if i.zero?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' = 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? - if signin_enabled?
%li %li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
...@@ -14,6 +17,9 @@ ...@@ -14,6 +17,9 @@
- @ldap_servers.each_with_index do |server, i| - @ldap_servers.each_with_index do |server, i|
%div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
= render 'devise/sessions/new_ldap', provider: server['provider_name'] = 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? - if signin_enabled?
%div#tab-signin.tab-pane %div#tab-signin.tab-pane
= render 'devise/sessions/new_base' = 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_for @users_group, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
.form-group .form-group
= f.label :user_ids, "People", class: 'control-label' = 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 .form-group
= f.label :access_level, "Group Access", class: 'control-label' = f.label :access_level, "Group Access", class: 'control-label'
......
...@@ -9,3 +9,14 @@ ...@@ -9,3 +9,14 @@
%i.fa.fa-folder %i.fa.fa-folder
%span %span
Projects 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 @@ ...@@ -23,6 +23,15 @@
%hr %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" = 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 .form-actions
= f.submit 'Save group', class: "btn btn-save" = f.submit 'Save group', class: "btn btn-save"
...@@ -33,5 +42,4 @@ ...@@ -33,5 +42,4 @@
Removing group will cause all child projects and resources to be removed. Removing group will cause all child projects and resources to be removed.
%br %br
%strong Removed group can not be restored! %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" = 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 @@ ...@@ -17,6 +17,10 @@
- if current_user && current_user.can?(:manage_group, @group) - if current_user && current_user.can?(:manage_group, @group)
.pull-right .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 = link_to '#', class: 'btn btn-new js-toggle-button' do
Add members Add members
%i.fa.fa-chevron-down %i.fa.fa-chevron-down
...@@ -24,6 +28,19 @@ ...@@ -24,6 +28,19 @@
.js-toggle-content.hide.new-group-member-holder .js-toggle-content.hide.new-group-member-holder
= render "new_group_member" = 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.panel-default.prepend-top-20
.panel-heading .panel-heading
%strong #{@group.name} %strong #{@group.name}
......
...@@ -20,3 +20,5 @@ ...@@ -20,3 +20,5 @@
= spinner = spinner
%aside.side.col-md-4 %aside.side.col-md-4
= render "projects", projects: @projects = render "projects", projects: @projects
%br
= render "shared_projects", projects: @shared_projects
%div %div
%h1 %h1
GitLab GitLab
%span.light Enterprise Edition
%span= Gitlab::VERSION %span= Gitlab::VERSION
%small= Gitlab::REVISION %small= Gitlab::REVISION
%p.slead %p.slead
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
-# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555 -# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555
- if controller_name == 'projects' && action_name == 'show' - if controller_name == 'projects' && action_name == 'show'
%meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} %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
= "#{title} | " if defined?(title) = "#{title} | " if defined?(title)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
.navbar-inner .navbar-inner
.container .container
%div.app_logo %div.app_logo
- brand_header_logo if brand_item
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
%h1 GITLAB %h1 GITLAB
%h1.title= title %h1.title= title
......
...@@ -39,6 +39,16 @@ ...@@ -39,6 +39,16 @@
%i.fa.fa-cog %i.fa.fa-cog
%span %span
Background Jobs 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 = nav_link(controller: :applications) do
= link_to admin_applications_path, title: '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