Commit 10528341 authored by Patricio Cano's avatar Patricio Cano

Merge branch 'master' into feature_password_strength_indicator

parents 91c96b37 c6efa56e
......@@ -39,3 +39,4 @@ public/assets/
.envrc
dump.rdb
tags
.gitlab_shell_secret
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal)
v 7.4.0
- Refactored membership logic
- Improve error reporting on users API (Julien Bianchi)
......@@ -20,6 +24,21 @@ v 7.4.0
- Add select field type for services options (Sullivan Senechal)
- Add cross-project references to the Markdown parser (Vinnie Okada)
- Add task lists to issue and merge request descriptions (Vinnie Okada)
- Snippets can be public, internal or private
- Improve danger zone: ask project path to confirm data-loss action
- Raise exception on forgery
- Show build coverage in Merge Requests (requires GitLab CI v5.1)
- New milestone and label links on issue edit form
- Improved repository graphs
- Improve event note display in dashboard and project activity views (Vinnie Okada)
- Add users sorting to admin area
- UI improvements
- Fix ambiguous sha problem with mentioned commit
- Fixed bug with apostrophe when at mentioning users
- Add active directory ldap option
- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
- Faster rev list
- Fix branch removal
v 7.3.2
- Fix creating new file via web editor
......
......@@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge
1. The change is as small as possible (see the above paragraph for details)
1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality
......@@ -100,7 +101,11 @@ For examples of feedback on merge requests please look at already [closed merge
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
1. It conforms to the following style guides
1. It conforms to the following style guides.
If your change touches a line that does not follow the style,
modify the entire line to follow it. This prevents linting tools from generating warnings.
Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
may leave style non-compliant.
## Style guides
......
......@@ -168,7 +168,7 @@ GEM
multi_json
gitlab-grack (2.0.0.pre)
rack (~> 1.5.1)
gitlab-grit (2.6.11)
gitlab-grit (2.6.12)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
......@@ -241,7 +241,8 @@ GEM
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
html-pipeline-gitlab (0.1.3)
html-pipeline-gitlab (0.1.5)
actionpack (~> 4)
gitlab_emoji (~> 0.0.1)
html-pipeline (~> 1.11.0)
sanitize (~> 2.1)
......
......@@ -51,7 +51,7 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Installation
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend fast and reliable Omnibus package installation (deb/rpm) on that page.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Third-party applications
......
7.4.0-pre
7.5.0.pre
class Activities
class @Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
......@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
@Activities = Activities
class Admin
class @Admin
constructor: ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
......@@ -51,5 +51,3 @@ class Admin
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
@Admin = Admin
class BlobView
class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
......@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView
class Commit
class @Commit
constructor: ->
$('.files .diff-file').each ->
new CommitFile(this)
@Commit = Commit
class CommitFile
class @CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
@CommitFile = CommitFile
class ImageFile
class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
......@@ -124,5 +124,3 @@ class ImageFile
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
@ImageFile = ImageFile
class CommitsList
class @CommitsList
@data =
ref: null
limit: 0
......@@ -53,5 +53,3 @@ class CommitsList
@disable
callback: =>
this.getOld()
this.CommitsList = CommitsList
class ConfirmDangerModal
class @ConfirmDangerModal
constructor: (form, text) ->
@form = form
$('.js-confirm-text').text(text || '')
......@@ -16,5 +16,3 @@ class ConfirmDangerModal
$('.js-confirm-danger-submit').on 'click', =>
@form.submit()
@ConfirmDangerModal = ConfirmDangerModal
class Dashboard
class @Dashboard
constructor: ->
@initSidebarTab()
......@@ -28,6 +28,3 @@ class Dashboard
# show tab from cookie
sidebar_filter = $.cookie(key)
$("#" + sidebar_filter).tab('show') if sidebar_filter
@Dashboard = Dashboard
class Diff
class @Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
......@@ -41,6 +41,3 @@ class Diff
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
@Diff = Diff
class Flash
class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
......@@ -10,5 +10,3 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
@Flash = Flash
class GroupMembers
class @GroupMembers
constructor: ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
@GroupMembers = GroupMembers
$ ->
# avatar
$('.js-choose-group-avatar-button').bind "click", ->
......
class Issue
class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", ->
......@@ -15,5 +15,3 @@ class Issue
"issue"
updateTaskState
)
@Issue = Issue
class Labels
class @Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
......@@ -31,5 +31,3 @@ class Labels
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
@Labels = Labels
class MergeRequest
class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
this.$el = $('.merge-request')
......@@ -132,5 +132,3 @@ class MergeRequest
this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show()
this.MergeRequest = MergeRequest
class Milestone
class @Milestone
@updateIssue: (li, issue_url, data) ->
$.ajax
type: "PUT"
......@@ -115,5 +115,3 @@ class Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection()
@Milestone = Milestone
class Notes
class @Notes
@interval: null
constructor: (notes_url, note_ids, last_fetched_at) ->
......@@ -514,7 +514,3 @@ class Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
@Notes = Notes
class NotesVotes
class @NotesVotes
updateVotes: ->
votes = $("#votes .votes")
notes = $("#notes-list .note .vote")
......@@ -18,5 +18,3 @@ class NotesVotes
# replace vote numbers
votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
@NotesVotes = NotesVotes
class Project
class @Project
constructor: ->
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
......@@ -24,9 +24,6 @@ class Project
else
$('#project_issues_tracker_id').removeAttr('disabled')
@Project = Project
$ ->
# Git clone panel switcher
scope = $ '.git-clone-holder'
......
class ProjectImport
class @ProjectImport
constructor: ->
setTimeout ->
Turbolinks.visit(location.href)
, 5000
@ProjectImport = ProjectImport
class SearchAutocomplete
class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id
project_ref = '' unless project_ref
......@@ -9,5 +9,3 @@ class SearchAutocomplete
minLength: 1
select: (event, ui) ->
location.href = ui.item.url
@SearchAutocomplete = SearchAutocomplete
class window.StatGraph
class @StatGraph
@log: {}
@get_log: ->
@log
......
class window.ContributorsStatGraph
class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
......
class window.ContributorsGraph
class @ContributorsGraph
MARGIN:
top: 20
right: 20
......@@ -44,7 +44,7 @@ class window.ContributorsGraph
set_data: (data) ->
@data = data
class window.ContributorsMasterGraph extends ContributorsGraph
class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width() - 70
@height = 200
......@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
class window.ContributorsAuthorGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width()/2 - 100
@height = 200
......
class TeamMembers
class @TeamMembers
constructor: ->
$('.team-members .project-access-select').on "change", ->
$(this.form).submit()
@TeamMembers = TeamMembers
class TreeView
class @TreeView
constructor: ->
@initKeyNav()
......@@ -39,5 +39,3 @@ class TreeView
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
Turbolinks.visit(path)
@TreeView = TreeView
class Wikis
class @Wikis
constructor: ->
$('.build-new-wiki').bind "click", ->
field = $('#new_wiki_path')
......@@ -7,6 +7,3 @@ class Wikis
if(slug.length > 0)
location.href = path + "/" + slug
@Wikis = Wikis
/** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
......@@ -75,7 +75,7 @@
}
.participants {
margin-bottom: 10px;
margin-bottom: 20px;
}
.issues_bulk_update {
......
......@@ -111,31 +111,38 @@
.ci_widget {
padding: 10px 15px;
font-size: 15px;
border-bottom: 1px dashed #AAA;
border-bottom: 1px solid #BBB;
color: #777;
background-color: #F5F5F5;
&.ci-success {
color: $bg_success;
border-color: $border_success;
background-color: #F1FAF1;
}
&.ci-pending {
color: #548;
border-color: #548;
background-color: #F4F1FA;
}
&.ci-running {
color: $bg_warning;
border-color: $border_warning;
background-color: #FAF5F1;
}
&.ci-failed {
color: $bg_danger;
border-color: $border_danger;
background-color: #FAF1F1;
}
&.ci-error {
color: $bg_danger;
border-color: $border_danger;
background-color: #FAF1F1;
}
}
......@@ -143,7 +150,8 @@
padding: 10px 15px;
h4 {
margin-top: 0px;
font-size: 20px;
font-weight: normal;
}
p:last-child {
......
......@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController
protected
def project
id = params[:project_id] || params[:id]
@project = Project.find_with_namespace(id)
@project = Project.find_with_namespace(params[:id])
@project || render_404
end
def group
@group ||= project.group
end
def repository
@repository ||= project.repository
@group ||= @project.group
end
end
......@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index
@users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page])
end
......
......@@ -7,7 +7,6 @@ class ApplicationController < ActionController::Base
before_filter :check_password_expiration
before_filter :add_abilities
before_filter :ldap_security_check
before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
......@@ -81,6 +80,7 @@ class ApplicationController < ActionController::Base
end
def project
unless @project
id = params[:project_id] || params[:id]
# Redirect from
......@@ -104,6 +104,8 @@ class ApplicationController < ActionController::Base
render_404 and return
end
end
@project
end
def repository
@repository ||= project.repository
......@@ -119,14 +121,6 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, action, project)
end
def authorize_code_access!
return access_denied! unless can?(current_user, :download_code, project)
end
def authorize_push!
return access_denied! unless can?(current_user, :push_code, project)
end
def authorize_labels!
# Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request!
......@@ -170,9 +164,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def dev_tools
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
......
......@@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController
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
respond_to do |format|
......
......@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error.to_s.humanize if error
end
def ldap
# We only find ourselves here
# if the authentication to LDAP was successful.
@user = Gitlab::LDAP::User.find_or_create(oauth)
@user.remember_me = true if @user.persisted?
def ldap
@user = Gitlab::LDAP::User.new(oauth)
@user.save if @user.changed? # will also save new users
gl_user = @user.gl_user
gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
if Gitlab::LDAP::Access.allowed?(@user)
sign_in_and_redirect(@user)
if @user.allowed?
sign_in_and_redirect(gl_user)
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
......@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
current_user.save
redirect_to profile_path
else
@user = Gitlab::OAuth::User.find(oauth)
# Create user if does not exist
# and allow_single_sign_on is true
if Gitlab.config.omniauth['allow_single_sign_on'] && !@user
@user, errors = Gitlab::OAuth::User.create(oauth)
end
@user = Gitlab::OAuth::User.new(oauth)
@user.save
if @user && !errors
sign_in_and_redirect(@user)
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
sign_in_and_redirect(@user.gl_user)
else
if errors
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
error_message =
if @user.gl_user.errors.any?
@user.gl_user.errors.map do |attribute, message|
"#{attribute} #{message}"
end.join(", ")
else
flash[:notice] = "There's no such user!"
''
end
redirect_to new_user_session_path
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
rescue StandardError
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end
def oauth
......
......@@ -2,7 +2,7 @@ class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
......@@ -4,7 +4,7 @@ class Projects::BlameController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -4,9 +4,9 @@ class Projects::BlobController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :authorize_push!, only: [:destroy]
before_filter :authorize_push_code!, only: [:destroy]
before_filter :blob
......@@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref)
else
flash[:alert] = result[:error]
flash[:alert] = result[:message]
render :show
end
end
......
......@@ -3,8 +3,8 @@ class Projects::BranchesController < Projects::ApplicationController
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :authorize_code_access!
before_filter :authorize_push!, only: [:create, :destroy]
before_filter :authorize_download_code!
before_filter :authorize_push_code!, only: [:create, :destroy]
def index
@sort = params[:sort] || 'name'
......
......@@ -4,19 +4,19 @@
class Projects::CommitController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :commit
def show
return git_not_found! unless @commit
@line_notes = project.notes.for_commit_id(commit.id).inline
@branches = project.repository.branch_names_contains(commit.id)
@line_notes = @project.notes.for_commit_id(commit.id).inline
@branches = @project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs
@note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
@note = @project.build_commit_note(commit)
@notes_count = @project.notes.for_commit_id(commit.id).count
@notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = {
......@@ -32,6 +32,6 @@ class Projects::CommitController < Projects::ApplicationController
end
def commit
@commit ||= project.repository.commit(params[:id])
@commit ||= @project.repository.commit(params[:id])
end
end
......@@ -5,7 +5,7 @@ class Projects::CommitsController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
class Projects::CompareController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def index
......
......@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def enable
project.deploy_keys << available_keys.find(params[:id])
@project.deploy_keys << available_keys.find(params[:id])
redirect_to project_deploy_keys_path(@project)
end
......
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
before_filter :authorize_push!
before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
......@@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
redirect_to after_edit_path
else
flash[:alert] = result[:error]
flash[:alert] = result[:message]
render :show
end
end
......
class Projects::GraphsController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -4,7 +4,7 @@ class Projects::NetworkController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :authorize_push!
before_filter :authorize_push_code!
def show
end
......
......@@ -4,7 +4,7 @@ class Projects::RawController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
......
......@@ -3,7 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def switch
......
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def archive
......
......@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html
def index
@snippets = @project.snippets.fresh.non_expired
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_project,
project: @project
})
end
def new
......@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def snippet_params
params.require(:project_snippet).permit(:title, :content, :file_name, :private)
params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
end
......@@ -3,8 +3,8 @@ class Projects::TagsController < Projects::ApplicationController
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :authorize_code_access!
before_filter :authorize_push!, only: [:create]
before_filter :authorize_download_code!
before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy]
def index
......
......@@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def new
@user_project_relation = project.project_members.new
@user_project_relation = @project.project_members.new
end
def create
......@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
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)
unless @user_project_relation.valid?
......@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
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
respond_to do |format|
......@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
project.project_members.find_by(user_id: current_user).destroy
@project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
......
......@@ -6,7 +6,6 @@ class ProjectsController < ApplicationController
# Authorize
before_filter :authorize_read_project!, except: [:index, :new, :create]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create]
......@@ -76,7 +75,7 @@ class ProjectsController < ApplicationController
end
def import
if project.import_finished?
if @project.import_finished?
redirect_to @project
return
end
......@@ -98,7 +97,7 @@ class ProjectsController < ApplicationController
end
def destroy
return access_denied! unless can?(current_user, :remove_project, project)
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
......@@ -148,8 +147,8 @@ class ProjectsController < ApplicationController
end
def archive
return access_denied! unless can?(current_user, :archive_project, project)
project.archive!
return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive!
respond_to do |format|
format.html { redirect_to @project }
......@@ -157,8 +156,8 @@ class ProjectsController < ApplicationController
end
def unarchive
return access_denied! unless can?(current_user, :archive_project, project)
project.unarchive!
return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive!
respond_to do |format|
format.html { redirect_to @project }
......
......@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path)
end
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
end
super
end
......
......@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title
skip_before_filter :authenticate_user!, only: [:index, :user_index]
respond_to :html
layout 'navless'
layout :determine_layout
def index
@snippets = Snippet.are_internal.fresh.non_expired.page(params[:page]).per(20)
@snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
end
def user_index
......@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController
render_404 and return unless @user
@snippets = @user.snippets.fresh.non_expired
if @user == current_user
@snippets = case params[:scope]
when 'are_internal' then
@snippets.are_internal
when 'are_private' then
@snippets.are_private
else
@snippets
end
else
@snippets = @snippets.are_internal
end
@snippets = @snippets.page(params[:page]).per(20)
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
scope: params[:scope]}).
page(params[:page]).per(20)
if @user == current_user
render 'current_user_index'
......@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController
protected
def snippet
@snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id])
@snippet ||= if current_user
PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
current_user.id,
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.are_public.find(params[:id])
end
end
def authorize_modify_snippet!
......@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController
end
def snippet_params
params.require(:personal_snippet).permit(:title, :content, :file_name, :private)
params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
def determine_layout
current_user ? 'navless' : 'public_users'
end
end
class SnippetsFinder
def execute(current_user, params = {})
filter = params[:filter]
case filter
when :all then
snippets(current_user).fresh.non_expired
when :by_user then
by_user(current_user, params[:user], params[:scope])
when :by_project
by_project(current_user, params[:project])
end
end
private
def snippets(current_user)
if current_user
Snippet.public_and_internal
else
# Not authenticated
#
# Return only:
# public snippets
Snippet.are_public
end
end
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
if user == current_user
case scope
when 'are_internal' then
snippets.are_internal
when 'are_private' then
snippets.are_private
when 'are_public' then
snippets.are_public
else
snippets
end
else
snippets.public_and_internal
end
end
def by_project(current_user, project)
snippets = project.snippets.fresh.non_expired
if current_user
if project.team.member?(current_user.id)
snippets
else
snippets.public_and_internal
end
else
snippets.are_public
end
end
end
......@@ -120,4 +120,8 @@ module CommitsHelper
class: 'commit-short-id')
end
end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
end
......@@ -136,9 +136,8 @@ module EventsHelper
end
def event_note(text)
text = first_line_in_markdown(text)
text = truncate(text, length: 150)
sanitize(markdown(text), tags: %w(a img b pre p))
text = first_line_in_markdown(text, 150)
sanitize(text, tags: %w(a img b pre code p))
end
def event_commit_title(message)
......
......@@ -51,12 +51,14 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe
end
def first_line_in_markdown(text)
line = text.split("\n").detect do |i|
i.present? && markdown(i).present?
end
line += '...' unless line.nil?
line
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(text, max_chars = nil)
md = markdown(text).strip
truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
......@@ -204,4 +206,52 @@ module GitlabMarkdownHelper
def correct_ref
@ref ? @ref : "master"
end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
end
module OauthHelper
def ldap_enabled?
Devise.omniauth_providers.include?(:ldap)
Gitlab.config.ldap.enabled
end
def default_providers
......
......@@ -28,6 +28,23 @@ module VisibilityLevelHelper
end
end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me"
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
......
......@@ -19,13 +19,24 @@ class Commit
class << self
def decorate(commits)
commits.map { |c| self.new(c) }
commits.map do |commit|
if commit.kind_of?(Commit)
commit
else
self.new(commit)
end
end
end
# Calculate number of lines to render for diffs
def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
end
attr_accessor :raw
......@@ -111,7 +122,7 @@ class Commit
# Mentionable override.
def gfm_reference
"commit #{sha[0..5]}"
"commit #{id}"
end
def method_missing(m, *args, &block)
......@@ -124,6 +135,11 @@ class Commit
super
end
# Truncate sha to 8 characters
def short_id
@raw.short_id(7)
end
def parents
@parents ||= Commit.decorate(super)
end
......
......@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base
end
def note_short_commit_id
note_commit_id[0..8]
Commit.truncate_sha(note_commit_id)
end
def note_commit?
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
......
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
#
class ProjectMember < Member
SOURCE_TYPE = 'Project'
......
......@@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.sha[0..10]
@last_commit_short_sha ||= last_commit.short_id
end
private
......
......@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base
note_options = {
project: project,
author: author,
note: "_mentioned in #{gfm_reference}_",
note: cross_reference_note_content(gfm_reference),
system: true
}
......@@ -174,7 +174,7 @@ class Note < ActiveRecord::Base
where(noteable_id: noteable.id)
end
notes.where('note like ?', "_mentioned in #{gfm_reference}_").
notes.where('note like ?', cross_reference_note_content(gfm_reference)).
system.any?
end
......@@ -182,8 +182,16 @@ class Note < ActiveRecord::Base
where("note like :query", query: "%#{query}%")
end
def cross_reference_note_prefix
'_mentioned in '
end
private
def cross_reference_note_content(gfm_reference)
cross_reference_note_prefix + "#{gfm_reference}_"
end
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
......@@ -249,6 +257,10 @@ class Note < ActiveRecord::Base
nil
end
def cross_reference?
note.start_with?(self.class.cross_reference_note_prefix)
end
def find_diff
return nil unless noteable && noteable.diffs.present?
......
......@@ -11,8 +11,8 @@
# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255)
# visibility_level :integer default(0), not null
#
class PersonalSnippet < Snippet
......
......@@ -173,7 +173,7 @@ class Project < ActiveRecord::Base
end
def with_push
includes(:events).where('events.action = ?', Event::PUSHED)
joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
......
......@@ -9,7 +9,7 @@
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# property :text
# properties :text
#
class BuildboxService < CiService
......
......@@ -9,7 +9,7 @@
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# property :text
# properties :text
#
class GitlabCiService < CiService
......
......@@ -40,7 +40,8 @@ class SlackService < Service
project_name: project_name
))
credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/)
credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/)
if credentials.present?
subdomain = credentials[1]
token = credentials[2].split("token=").last
......
......@@ -11,8 +11,8 @@
# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255)
# visibility_level :integer default(0), not null
#
class ProjectSnippet < Snippet
......
......@@ -133,6 +133,10 @@ class ProjectTeam
max_tm_access(user.id) == Gitlab::Access::MASTER
end
def member?(user_id)
!!find_tm(user_id)
end
def max_tm_access(user_id)
access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
......
......@@ -30,6 +30,8 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
rescue Rugged::OdbError => ex
nil
end
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
......
......@@ -10,6 +10,7 @@
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
......
......@@ -11,14 +11,15 @@
# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255)
# visibility_level :integer default(0), not null
#
class Snippet < ActiveRecord::Base
include Linguist::BlobHelper
include Gitlab::VisibilityLevel
default_value_for :private, true
default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: "User"
......@@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
# Scopes
scope :are_internal, -> { where(private: false) }
scope :are_private, -> { where(private: true) }
scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
......@@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base
expires_at && expires_at < Time.current
end
def visibility_level_field
visibility_level
end
class << self
def search(query)
where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%")
......@@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base
end
def accessible_to(user)
where('private = ? OR author_id = ?', false, user)
where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user)
end
end
end
......@@ -178,8 +178,7 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :ldap, -> { where(provider: 'ldap') }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
......@@ -196,6 +195,16 @@ class User < ActiveRecord::Base
end
end
def sort(method)
case method.to_s
when 'recent_sign_in' then reorder('users.last_sign_in_at DESC')
when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC')
when 'recently_created' then reorder('users.created_at DESC')
when 'late_created' then reorder('users.created_at ASC')
else reorder("users.name ASC")
end
end
def find_for_commit(email, name)
# Prefer email match over name match
User.where(email: email).first ||
......@@ -397,7 +406,7 @@ class User < ActiveRecord::Base
end
def ldap_user?
extern_uid && provider == 'ldap'
extern_uid && provider.start_with?('ldap')
end
def accessible_deploy_keys
......
......@@ -10,12 +10,6 @@ module Files
private
def success
out = super()
out[:error] = ''
out
end
def repository
project.repository
end
......
......@@ -33,12 +33,5 @@ module Issues
issue
end
private
def update_task(issue, params, checked)
issue.update_nth_task(params[:task_num].to_i, checked)
params.except!(:task_num)
end
end
end
......@@ -119,7 +119,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/
return true if note.note =~ /\A_mentioned in / && note.system == true
return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
......
......@@ -2,39 +2,20 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
.form-group.group_name_holder
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Example Group", class: "form-control"
.form-group.group-description-holder
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
= render 'shared/group_form', f: f
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
= render 'shared/choose_group_avatar_button', f: f
- if @group.new_record?
.form-group
.col-sm-2
.col-sm-10
.bs-callout.bs-callout-info
%ul
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
= render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
......
......@@ -74,13 +74,13 @@
%ul.well-list.group-users-list
- @members.each do |member|
- user = member.user
%li{class: dom_class(user)}
%li{class: dom_class(member), id: dom_id(user)}
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
= link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
%ul.nav.nav-tabs.log-tabs
%li.active
= link_to "githost.log", "#githost", 'data-toggle' => 'tab'
%li
= link_to "application.log", "#application", 'data-toggle' => 'tab'
%li
= link_to "production.log", "#production", 'data-toggle' => 'tab'
%li
= link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
- loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
= link_to klass::file_name, "##{klass::file_name_noext}",
'data-toggle' => 'tab'
%p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content
.tab-pane.active#githost
.file-holder#README
.file-title
%i.fa.fa-file
githost.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::GitLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#application
.file-holder#README
.file-title
%i.fa.fa-file
application.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#production
.file-holder#README
.file-title
%i.fa.fa-file
production.log
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::Logger.read_latest_for('production.log').each do |line|
%li
%p= line
.tab-pane#sidekiq
- loggers.each do |klass|
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
id: klass::file_name_noext }
.file-holder#README
.file-title
%i.fa.fa-file
sidekiq.log
= klass::file_name
.pull-right
= link_to '#', class: 'log-bottom' do
%i.fa.fa-arrow-down
Scroll down
.file-content.logs
%ol
- Gitlab::Logger.read_latest_for('sidekiq.log').each do |line|
- klass.read_latest.each do |line|
%li
%p= line
......@@ -32,6 +32,26 @@
.panel-heading
Users (#{@users.total_count})
.panel-head-actions
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= @sort.humanize
- else
Name
%b.caret
%ul.dropdown-menu
%li
= link_to admin_users_path(sort: nil) do
Name
= link_to admin_users_path(sort: 'recent_sign_in') do
Recent sign in
= link_to admin_users_path(sort: 'oldest_sign_in') do
Oldest sign in
= link_to admin_users_path(sort: 'recently_created') do
Recently created
= link_to admin_users_path(sort: 'late_created') do
Late created
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list
- @users.each do |user|
......
......@@ -46,5 +46,5 @@
%br
Public projects are an easy way to allow everyone to have read-only access.
.link_holder
= link_to explore_projects_path, class: "btn btn-new" do
= link_to trending_explore_projects_path, class: "btn btn-new" do
Browse public projects »
= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
= form_tag(user_omniauth_callback_path(provider), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
......
......@@ -2,22 +2,24 @@
.login-heading
%h3 Sign in
.login-body
- if ldap_enabled? && gitlab_config.signin_enabled
- if ldap_enabled?
%ul.nav.nav-tabs
%li.active
= link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
- @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 gitlab_config.signin_enabled
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
%div#tab-ldap.tab-pane.active
= render partial: 'devise/sessions/new_ldap'
- @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 gitlab_config.signin_enabled
%div#tab-signin.tab-pane
= render partial: 'devise/sessions/new_base'
= render 'devise/sessions/new_base'
- elsif ldap_enabled?
= render partial: 'devise/sessions/new_ldap'
- elsif gitlab_config.signin_enabled
= render partial: 'devise/sessions/new_base'
= render 'devise/sessions/new_base'
- else
%div
No authentication methods configured.
......@@ -36,7 +38,6 @@
%span.light Did not receive confirmation email?
= link_to "Send again", new_confirmation_path(resource_name)
- if extra_config.has_key?('sign_in_text')
%hr
= markdown(extra_config.sign_in_text)
%li.commit
.commit-row-title
= link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
= link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
&nbsp;
= gfm event_commit_title(commit[:message]), project
......@@ -2,7 +2,7 @@
- event.commits.first(15).each do |commit|
%p
%strong= commit[:author][:name]
= link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id])
= link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
%i
at
= commit[:timestamp].to_time.to_s(:short)
......
......@@ -22,4 +22,4 @@
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
= link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do
%strong Compare &rarr; #{event.commit_from[0..7]}...#{event.commit_to[0..7]}
%strong Compare &rarr; #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)}
......@@ -6,6 +6,7 @@
- if current_page?(starred_explore_projects_path)
%strong.pull-right
%i.fa.fa-star
= pluralize project.star_count, 'star'
.project-info
......
.explore-trending-block
%p.lead
%i.fa.fa-comments-o
%i.fa.fa-star
See most starred projects
%hr
.public-projects
......
......@@ -11,16 +11,7 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
.form-group
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
.form-group.group-description-holder
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
= render 'shared/group_form', f: f
.form-group
.col-sm-2
......@@ -31,13 +22,7 @@
You can change your group avatar here
- else
You can upload a group avatar here
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
= render 'shared/choose_group_avatar_button', f: f
- if @group.avatar?
%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"
......
......@@ -2,37 +2,18 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
.form-group
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
.form-group.group-description-holder
= f.label :description, "Details", class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
= render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-group-avatar-input hidden"
.light The maximum file size allowed is 100KB.
= render 'shared/choose_group_avatar_button', f: f
.form-group
.col-sm-2
.col-sm-10
%ul
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
= render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
......@@ -31,12 +31,12 @@
.clearfix
%hr
%p
You can also specify notification level per group or per project
%br
By default all projects and groups uses notification level set above
.row.all-notifications
.col-md-6
%p
You can also specify notification level per group or per project.
%br
By default all projects and groups uses notification level set above.
%h4 Groups:
%ul.bordered-list
- @group_members.each do |users_group|
......@@ -44,6 +44,10 @@
= render 'settings', type: 'group', membership: users_group, notification: notification
.col-md-6
%p
To specify notification level per project of a group you belong to,
%br
you need to be a member of the project itself, not only its group.
%h4 Projects:
%ul.bordered-list
- @project_members.each do |project_member|
......
.form-actions
.commit-button-annotation
= button_tag 'Commit Changes',
class: 'btn commit-btn js-commit-button btn-create'
.message
to branch
%strong= ref
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
......@@ -15,7 +15,7 @@
%tr
%td.blame-commit
%span.commit
= link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
= link_to commit.short_id, project_commit_path(@project, commit), class: "commit_short_id"
&nbsp;
= commit_author_link(commit, avatar: true, size: 16)
&nbsp;
......
......@@ -35,7 +35,7 @@
.commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
= link_to parent.id[0...10], project_commit_path(@project, parent)
= link_to parent.short_id, project_commit_path(@project, parent)
- if @branches.any?
.commit-info-row
......
%li.commit.js-toggle-container
.commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
= link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
......
%li.commit.inline-commit
.commit-row-title
= link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
= link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id"
&nbsp;
%span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
......
......@@ -23,16 +23,11 @@
%i.fa.fa-spinner.fa-spin
= render 'shared/commit_message_container', params: params,
placeholder: "Update #{@blob.name}"
.form-actions
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
.commit-button-annotation
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
.message
to branch
%strong= @ref
= link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
= render 'projects/commit_button', ref: @ref,
cancel_path: @after_edit_path
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
......
......@@ -19,12 +19,13 @@
= form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing repo
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Retry import', class: "btn btn-create", tabindex: 4
......@@ -19,6 +19,7 @@
= hidden_field_tag :issue_context
= f.submit class: 'btn'
- elsif issue.milestone
= link_to issue.milestone.title, project_milestone_path
= link_to project_milestone_path(@project, @issue.milestone) do
= @issue.milestone.title
- else
None
......@@ -21,7 +21,7 @@
- content_for :note_actions do
- if can?(current_user, :modify_merge_request, @merge_request)
- unless @merge_request.closed? || @merge_request.merged?
- if @merge_request.open?
= link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- if @merge_request.closed?
= link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
......
......@@ -16,15 +16,6 @@
%h4
You can accept this request automatically.
.accept-merge-holder.clearfix
.js-toggle-container
%p
You can
%strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
before accepting merge request
.js-toggle-content.hide
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
.accept-group
.pull-left
= f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
......@@ -33,6 +24,14 @@
= label_tag :should_remove_source_branch, class: "checkbox" do
= check_box_tag :should_remove_source_branch
Remove source-branch
.js-toggle-container
%label
%i.fa.fa-edit
= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
.js-toggle-content.hide
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
%hr
.light
......
......@@ -21,6 +21,12 @@
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
- if @merge_request.locked?
%h4
Merge in progress...
%p
GitLab tries to merge it right now. During this time merge request is locked and can not be closed.
- unless @commits.any?
%h4 Nothing to merge
%p
......
......@@ -44,13 +44,14 @@
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing repo
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
%hr
.form-group
......
......@@ -27,14 +27,9 @@
.file-content.code
%pre#editor= params[:content]
.form-actions
= hidden_field_tag 'content', '', id: "file-content"
.commit-button-annotation
= button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
.message
to branch
%strong= @ref
= link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
:javascript
ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
......
......@@ -21,7 +21,7 @@
- else
%td.old_line= raw(line.type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line= raw(line.type == "old" ? "&nbsp;" : line.new_pos)
%td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw "#{line.text} &nbsp;"
%td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
%h3.page-title Protected branches
%p.light This ability keeps stable branches secure and forces developers to use code reviews
%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
.bs-callout.bs-callout-info
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
%li prevents anyone from force pushing to the branch
%li prevents anyone from deleting the branch
%li prevent anyone from force pushing to the branch
%li prevent anyone from deleting the branch
%p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
......
......@@ -7,8 +7,8 @@
@
%span.monospace
- if commit.nil?
#{submodule_item.id[0..10]}
#{truncate_sha(submodule_item.id)}
- else
= link_to "#{submodule_item.id[0..10]}", commit
= link_to "#{truncate_sha(submodule_item.id)}", commit
%td
%td.hidden-xs
......@@ -17,7 +17,7 @@
%tr
%td
= link_to project_wiki_path(@project, @page, version_id: commit.id) do
= commit.id[0..10]
= truncate_sha(commit.id)
%td
= commit.author.name
%td
......
......@@ -10,7 +10,7 @@
= project.name_with_namespace
&middot;
= link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do
Commit #{note.commit_id[0..8]}
Commit #{truncate_sha(note.commit_id)}
- else
= link_to project do
= project.name_with_namespace
......
%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: 'js-group-avatar-input hidden'
.light The maximum file size allowed is 100KB.
.form-group
= f.label :name, class: 'control-label' do
Group name
.col-sm-10
= f.text_field :name, placeholder: 'Example Group', class: 'form-control',
autofocus: local_assigns[:autofocus] || false
.form-group.group-description-holder
= f.label :description, 'Details', class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
%ul
%li A group is a collection of several projects
%li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
......@@ -10,21 +10,7 @@
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
- unless @snippet.respond_to?(:project)
.form-group
= f.label "Access", class: 'control-label'
.col-sm-10
= f.label :private_true, class: 'radio-label' do
= f.radio_button :private, true
%span
%strong Private
(only you can see this snippet)
%br
= f.label :private_false, class: 'radio-label' do
= f.radio_button :private, false
%span
%strong Internal
(GitLab users can see this snippet)
= render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
.form-group
.file-editor
......
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do
Visibility Level
= link_to "(?)", help_page_path("public_access", "public_access")
.col-sm-10
- if can_change_visibility_level
- Gitlab::VisibilityLevel.values.each do |level|
.radio
- restricted = restricted_visibility_levels.include?(level)
= f.radio_button :visibility_level, level, disabled: restricted
= label "#{dom_class(@snippet)}_visibility_level", level do
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
.option-descr
= snippet_visibility_level_description(level)
- unless restricted_visibility_levels.empty?
.col-sm-10
%span.info
Some visibility level settings have been restricted by the administrator.
- else
.col-sm-10
%span.info
= visibility_level_icon(visibility_level)
%strong
= visibility_level_label(visibility_level)
.light= visibility_level_description(visibility_level)
......@@ -28,6 +28,11 @@
Internal
%span.pull-right
= @user.snippets.are_internal.count
= nav_tab :scope, 'are_public' do
= link_to user_snippets_path(@user, scope: 'are_public') do
Public
%span.pull-right
= @user.snippets.are_public.count
.col-md-9.my-snippets
= render 'snippets'
......
......@@ -2,6 +2,8 @@
Public snippets
.pull-right
- if current_user
= link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do
Add new snippet
= link_to user_snippets_path(current_user), class: "btn btn-grouped" do
......
......@@ -4,6 +4,7 @@
%span
\/
Snippets
- if current_user
= link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
Add new snippet
......
......@@ -13,7 +13,6 @@ module Gitlab
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib
#{config.root}/app/finders
#{config.root}/app/models/hooks
#{config.root}/app/models/concerns
#{config.root}/app/models/project_services
......@@ -25,6 +24,7 @@ module Gitlab
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# NOTE: Please prefer set time zone on config/gitlab.yml configuration file.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
......
......@@ -33,6 +33,11 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
## Date & Time settings
# Uncomment and customize if you want to change the default time zone of GitLab application.
# To see all available zones, run `bundle exec rake time:zones:all`
# time_zone: 'UTC'
## Email settings
# Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com
......@@ -119,6 +124,7 @@ production: &base
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
......@@ -134,6 +140,16 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
servers:
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
host: '_your_ldap_server'
port: 636
uid: 'sAMAccountName'
......@@ -172,6 +188,14 @@ production: &base
#
user_filter: ''
# GitLab EE only: add more LDAP servers
# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
# so that GitLab can remember which LDAP server a user belongs to.
# uswest2:
# label:
# host:
# ....
## OmniAuth settings
omniauth:
......@@ -299,6 +323,20 @@ test:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
ldap:
enabled: false
servers:
main:
label: ldap
host: 127.0.0.1
port: 3890
uid: 'uid'
method: 'plain' # "tls" or "ssl" or "plain"
base: 'dc=example,dc=com'
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
sync_ssh_keys: false
staging:
<<: *base
......@@ -56,9 +56,25 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
Settings.ldap['active_directory'] = true if Settings.ldap['active_directory'].nil?
# backwards compatibility, we only have one host
if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present?
server = Settings.ldap.except('sync_time')
server['provider_name'] = 'ldap'
Settings.ldap['servers'] = {
'ldap' => server
}
end
Settings.ldap['servers'].each do |key, server|
server['label'] ||= 'LDAP'
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
server['active_directory'] = true if server['active_directory'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
end
end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
......@@ -87,6 +103,7 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
......
if Gitlab::LDAP::Config.enabled?
module OmniAuth::Strategies
server = Gitlab.config.ldap.servers.values.first
klass = server['provider_class']
const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
end
OmniauthCallbacksController.class_eval do
server = Gitlab.config.ldap.servers.values.first
alias_method server['provider_name'], :ldap
end
end
\ No newline at end of file
......@@ -204,23 +204,25 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
if Gitlab.config.ldap.enabled
if Gitlab.config.ldap.allow_username_or_email_login
if Gitlab::LDAP::Config.enabled?
Gitlab.config.ldap.servers.values.each do |server|
if server['allow_username_or_email_login']
email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
else
email_stripping_proc = ->(name) {name}
end
config.omniauth :ldap,
host: Gitlab.config.ldap['host'],
base: Gitlab.config.ldap['base'],
uid: Gitlab.config.ldap['uid'],
port: Gitlab.config.ldap['port'],
method: Gitlab.config.ldap['method'],
bind_dn: Gitlab.config.ldap['bind_dn'],
password: Gitlab.config.ldap['password'],
config.omniauth server['provider_name'],
host: server['host'],
base: server['base'],
uid: server['uid'],
port: server['port'],
method: server['method'],
bind_dn: server['bind_dn'],
password: server['password'],
name_proc: email_stripping_proc
end
end
Gitlab.config.omniauth.providers.each do |provider|
provider_arguments = []
......
# Be sure to restart your server when you modify this file.
require 'securerandom'
# Your secret key for verifying the gitlab_shell.
secret_file = Rails.root.join('.gitlab_shell_secret')
gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
unless File.exist? secret_file
# Generate a new token of 16 random hexadecimal characters and store it in secret_file.
token = SecureRandom.hex(16)
File.write(secret_file, token)
end
if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
FileUtils.symlink(secret_file, gitlab_shell_symlink)
end
\ No newline at end of file
Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
Gitlab::Seeder.quiet do
contents = [
`curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
`curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`,
`curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
]
content =<<eos
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
delegate :name, :username, :email, to: :user, prefix: true
end
eos
(1..50).each do |i|
user = User.all.sample
......@@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do
id: i,
author_id: user.id,
title: Faker::Lorem.sentence(3),
file_name: Faker::Internet.domain_word + '.sh',
private: [true, false].sample,
content: contents.sample,
file_name: Faker::Internet.domain_word + '.rb',
visibility_level: Gitlab::VisibilityLevel.values.sample,
content: content,
}])
print('.')
end
end
......
class AddVisibilityLevelToSnippet < ActiveRecord::Migration
def up
add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
add_index :snippets, :visibility_level
remove_column :snippets, :private
end
def down
add_column :snippets, :private, :boolean, :default => false, :null => false
Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false)
Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true)
remove_column :snippets, :visibility_level
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141006143943) do
ActiveRecord::Schema.define(version: 20141007100818) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -299,14 +299,15 @@ ActiveRecord::Schema.define(version: 20141006143943) do
t.datetime "updated_at"
t.string "file_name"
t.datetime "expires_at"
t.boolean "private", default: true, null: false
t.string "type"
t.integer "visibility_level", default: 0, null: false
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
create_table "taggings", force: true do |t|
t.integer "tag_id"
......
......@@ -20,6 +20,7 @@
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
## Contributor documentation
......
# Services
## GitLab CI
### Edit GitLab CI service
Set GitLab CI service for a project.
```
PUT /projects/:id/services/gitlab-ci
```
Parameters:
- `token` (required) - CI project token
- `project_url` (required) - CI project url
### Delete GitLab CI service
Delete GitLab CI service settings for a project.
```
DELETE /projects/:id/services/gitlab-ci
```
## Hipchat
### Edit Hipchat service
Set Hipchat service for project.
```
PUT /projects/:id/services/hipchat
```
Parameters:
- `token` (required) - Hipchat token
- `room` (required) - Hipchat room name
### Delete Hipchat service
Delete Hipchat service for a project.
```
DELETE /projects/:id/services/hipchat
```
# Use Libravatar service with GitLab
GitLab by default supports [Gravatar](gravatar.com) avatar service.
Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
[heavily based on gravatar](http://wiki.libravatar.org/api/).
This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
# Configuration
In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set
the configuration options as follows:
## For HTTP
```yml
gravatar:
enabled: true
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
## For HTTPS
```yml
gravatar:
enabled: true
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
## Self-hosted
If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the url will be different in the configuration
but the important part is to provide the same placeholders so GitLab can parse the url correctly.
For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
## Omnibus-gitlab example
In `/etc/gitlab/gitlab.rb`:
#### For http
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
#### For https
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
Run `sudo gitlab-ctl reconfigure` for changes to take effect.
## Default URL for missing images
[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
In order to use a different set other than `identicon`, replace `&d=identicon` portion of the url with another supported set.
For example, you can use `retro` set in which case url would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
......@@ -4,14 +4,14 @@ This document describes what services we use for testing GitLab and GitLab CI.
We currently use three CI services to test GitLab:
1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
|---------------------------------------|---------------------------|------------------------|-----------|
| GitLab CE @ MySQL | ✓ | ✓ | |
| GitLab CE @ PostgreSQL | | | ✓ |
|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | |
| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) |
| GitLab EE @ MySQL | ✓ | | |
| GitLab CI @ MySQL | ✓ | | |
| GitLab CI @ PostgreSQL | | | ✓ |
......@@ -19,13 +19,15 @@ We currently use three CI services to test GitLab:
| GitLab Shell | ✓ | | ✓ |
| GitLab Shell | ✓ | | ✓ |
Core team has access to trigger builds if needed for GitLab CE.
We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI.
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
Language: Ruby
Ruby verion: 2.1.2
database.yml: pg
- Language: Ruby
- Ruby verion: 2.1.2
- database.yml: pg
Build commands
......
# Installation
## Consider the Omnibus package installation
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Select Version to Install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
......@@ -70,8 +74,8 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz
cd git-2.0.0/
curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
cd git-2.1.2/
make prefix=/usr/local all
# Install into /usr/local/bin
......@@ -161,9 +165,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-4-stable gitlab
**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
**Note:** You can change `7-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
......
......@@ -6,6 +6,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
## Configuring GitLab for LDAP integration
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
The old LDAP integration syntax still works in GitLab 7.4.
If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
```ruby
# For omnibus packages
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
host: '_your_ldap_server'
port: 636
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
# This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false.
active_directory: true
# If allow_username_or_email_login is enabled, GitLab will ignore everything
# after the first '@' in the LDAP username submitted by the user on login.
#
# Example:
# - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
# - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
#
# If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
# disable this setting, because the userPrincipalName contains an '@'.
allow_username_or_email_login: false
# Base where we can search for users
#
# Ex. ou=People,dc=gitlab,dc=example
#
base: ''
# Filter LDAP users
#
# Format: RFC 4515 http://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
user_filter: ''
# GitLab EE only: add more LDAP servers
# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
# so that GitLab can remember which LDAP server a user belongs to.
# uswest2:
# label:
# host:
# ....
EOS
```
If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
```
production:
# snip...
ldap:
enabled: false
servers:
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
# snip...
```
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
......@@ -24,14 +113,21 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
```ruby
# For omnibus-gitlab
gitlab_rails['ldap_user_filter'] = '(employeeType=developer)'
# For omnibus packages; new LDAP server syntax
gitlab_rails['ldap_servers'] = YAML.load <<-EOS
main:
# snip...
user_filter: '(employeeType=developer)'
EOS
```
```yaml
# For installations from source
# For installations from source; new LDAP server syntax
production:
ldap:
servers:
main:
# snip...
user_filter: '(employeeType=developer)'
```
......
......@@ -510,6 +510,10 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
**Note**
The row of dashes between the table header and body must have at least three dashes in each column.
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
......
......@@ -191,6 +191,7 @@ It is important to do this as soon as possible, so we can catch any errors befor
- Ask Dmitriy to add screenshots to the WIP MR.
- Decide with team who will be the MVP user.
- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
......
......@@ -195,6 +195,12 @@ sudo rm -R tmp
sudo -u git -H mkdir tmp
sudo chmod -R u+rwX tmp/
# create directory for pids, make sure GitLab can write to it
sudo -u git -H mkdir tmp/pids/
sudo chmod -R u+rwX tmp/pids/
# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create
# reboot system
sudo reboot
......
# From 6.x or 7.x to 7.3
# From 6.x or 7.x to 7.4
This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3.
This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4.
## Global issue numbers
......@@ -64,13 +64,13 @@ sudo gem install bundler --no-ri --no-rdoc
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable
sudo -u git -H git checkout 7-4-stable
```
OR
......@@ -78,8 +78,7 @@ OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable-ee
sudo -u git -H git checkout 7-4-stable-ee
```
## 4. Install additional packages
......@@ -153,14 +152,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example
git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example
```
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings.
* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings.
* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings.
* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Copy rack attack middleware config
```bash
......
......@@ -18,12 +18,12 @@ sudo service gitlab stop
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable
```
......@@ -32,7 +32,6 @@ OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable-ee
```
......@@ -75,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
......
# From 7.3 to 7.4
### 0. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 1. Stop server
```bash
sudo service gitlab stop
```
### 2. Get latest code
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-4-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-4-stable-ee
```
### 3. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 4. Configure Redis to use sockets
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
# Disable Redis listening on TCP by setting 'port' to 0
sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
sudo usermod -aG redis git
# Configure Redis connection settings
sudo -u git -H cp config/resque.yml.example config/resque.yml
# Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
sudo -u git -H editor config/resque.yml
# Configure gitlab-shell to use Redis sockets
sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml
### 5. Update config files
#### New configuration options for gitlab.yml
There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
```
git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example
```
#### Change timeout for unicorn
```
# config/unicorn.rb
timeout 60
```
#### Change nginx https settings
* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
#### Update database.yml config file(for mysql only) if needed (basically it is required for old gitlab installations)
* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
### 6. Start application
sudo service gitlab start
sudo service nginx restart
### 7. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete!
### 8. Update OmniAuth configuration
When using Google omniauth login, changes of the Google account required.
Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
More details can be found at the [integration documentation](../integration/google.md).
### 9. Optional optimizations for GitLab setups with MySQL databases
Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
```
# Secure your MySQL installation (added in GitLab 6.2)
sudo mysql_secure_installation
# Login to MySQL
mysql -u root -p
# do not type the 'mysql>', this is part of the prompt
# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
# If previous query returned results, copy & run all outputed SQL statements
# Convert all tables to correct character set
SET foreign_key_checks = 0;
SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
# If previous query returned results, copy & run all outputed SQL statements
# turn foreign key checks back on
SET foreign_key_checks = 1;
# Find MySQL users
mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
# If git user exists and gitlab user does not exist
# you are done with the database cleanup tasks
mysql> \q
# If both users exist skip to Delete gitlab user
# Create new user for GitLab (changed in GitLab 6.4)
# change $password in the command below to a real password you pick
mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
# Grant the git user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
# Delete the old gitlab user
mysql> DELETE FROM mysql.user WHERE user='gitlab';
# Quit the database session
mysql> \q
# Try connecting to the new database with the new user
sudo -u git -H mysql -u git -p -D gitlabhq_production
# Type the password you replaced $password with earlier
# You should now see a 'mysql>' prompt
# Quit the database session
mysql> \q
# Update database configuration details
# See config/database.yml.mysql for latest recommended configuration details
# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
# Set production -> pool: 10 (updated in GitLab 5.3)
# Set production -> username: git
# Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml
```
## Things went south? Revert to previous version (7.3)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
......@@ -4,3 +4,5 @@
- [Groups](groups.md)
- [Labels](labels.md)
- [GitLab Flow](gitlab_flow.md)
- [Notifications](notifications.md)
- [Migrating from SVN to GitLab](migrating_from_svn.md)
......@@ -26,7 +26,7 @@ After getting used to these three steps the branching model becomes the challeng
Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
The biggest problem they run into is that many long running branches that each contain part of the changes are around.
People have a hard time figuring out which branch they should develop on or deploy to production.
Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](https://guides.github.com/introduction/flow/index.html)
Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
We think there is still room for improvement and will detail a set of practices we call GitLab flow.
# Git flow and its problems
......
# Migrating from SVN to GitLab
SVN stands for Subversion and is a version control system (VCS).
Git is a distributed version control system.
There are some major differences between the two, for more information consult your favourite search engine.
Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
user created step by step guide for migrating from SVN to GitLab.
[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
## Contribute to this guide
We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
# GitLab Notifications
GitLab has notifications system in place to notify a user of events important for the workflow.
## Notification settings
Under user profile page you can find the notification settings.
![notification settings](notifications/settings.png)
Notification settings are divided into three groups:
* Global Settings
* Group Settings
* Project Settings
Each of these settings have levels of notification:
* Disabled - turns off notifications
* Participating - receive notifications from related resources
* Watch - receive notifications from projects or groups user is a member of
* Global - notifications as set at the global settings
#### Global Settings
Global Settings are at the bottom of the hierarchy.
Any setting set here will be overriden by a setting at the group or a project level.
Group or Project settings can use `global` notification setting which will then use
anything that is set at Global Settings.
#### Group Settings
Group Settings are taking presedence over Global Settings but are on a level below Project Settings.
This means that you can set a different level of notifications per group while still being able
to have a finer level setting per project.
Organization like this is suitable for users that belong to different groups but don't have the
same need for being notified for every group they are member of.
#### Project Settings
Project Settings are at the top level and any setting placed at this level will take presedence of any
other setting.
This is suitable for users that have different needs for notifications per project basis.
## Notification events
Below is the table of events users can be notified of:
| Event | Sent to | Settings level |
|------------------------------|-------------------------------------------------------------------|------------------------------|
| New SSH key added | User | Security email, always sent. |
| New email added | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for omniauth (LDAP)|
| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating |
| User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed |
| User added to group | User | Sent when user is added to group |
| Project moved | Project members [1] | [1] not disabled |
| Group access level changed | User | Sent when user group access level is changed |
| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled |
| Reopen issue | Project members [1] | [1] higher than participating |
| New merge request | MR assignee [1] | [1] not disabled |
| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled |
| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
| Reopen merge request | Project members [1] | [1] higher than participating |
| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
......@@ -20,3 +20,10 @@ Feature: Admin Groups
When I visit admin group page
When I select user "John Doe" from user list as "Reporter"
Then I should see "John Doe" in team list in every project as "Reporter"
@javascript
Scenario: Remove user from group
Given we have user "John Doe" in group
When I visit admin group page
And I remove user "John Doe" from group
Then I should not see "John Doe" in team list
......@@ -30,10 +30,20 @@ Feature: Project Source Browse Files
And I edit code
And I fill the new file name
And I fill the commit message
And I click on "Commit changes"
And I click on "Commit Changes"
Then I am redirected to the new file
And I should see its new content
@javascript
Scenario: If I enter an illegal file name I see an error message
Given I click on "new file" link in repo
And I fill the new file name with an illegal name
And I edit code
And I fill the commit message
And I click on "Commit changes"
Then I am on the new file page
And I see a commit error message
@javascript
Scenario: I can edit file
Given I click on ".gitignore" file in repo
......@@ -46,10 +56,20 @@ Feature: Project Source Browse Files
And I click button "Edit"
And I edit code
And I fill the commit message
And I click on "Commit changes"
And I click on "Commit Changes"
Then I am redirected to the ".gitignore"
And I should see its new content
@javascript @wip
Scenario: If I don't change the content of the file I see an error message
Given I click on ".gitignore" file in repo
And I click button "edit"
And I fill the commit message
And I click on "Commit changes"
# Test fails because carriage returns are added to the file.
Then I am on the ".gitignore" edit file page
And I see a commit error message
@javascript
Scenario: I can see editing preview
Given I click on ".gitignore" file in repo
......
......@@ -4,8 +4,10 @@ Feature: Snippets Discover
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
And I have internal "Personal snippet internal" snippet
Scenario: I should see snippets
Given I visit snippets page
Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets
......@@ -4,20 +4,31 @@ Feature: Snippets User
Given I sign in as a user
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
And I have internal "Personal snippet internal" snippet
Scenario: I should see all my snippets
Given I visit my snippets page
Then I should see "Personal snippet one" in snippets
And I should see "Personal snippet private" in snippets
And I should see "Personal snippet internal" in snippets
Scenario: I can see only my private snippets
Given I visit my snippets page
And I click "Private" filter
Then I should not see "Personal snippet one" in snippets
And I should not see "Personal snippet internal" in snippets
And I should see "Personal snippet private" in snippets
Scenario: I can see only my public snippets
Given I visit my snippets page
And I click "Internal" filter
And I click "Public" filter
Then I should see "Personal snippet one" in snippets
And I should not see "Personal snippet private" in snippets
And I should not see "Personal snippet internal" in snippets
Scenario: I can see only my internal snippets
Given I visit my snippets page
And I click "Internal" filter
Then I should see "Personal snippet internal" in snippets
And I should not see "Personal snippet private" in snippets
And I should not see "Personal snippet one" in snippets
......@@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
When 'I select user "John Doe" from user list as "Reporter"' do
user = User.find_by(name: "John Doe")
select2(user.id, from: "#user_ids", multiple: true)
select2(user_john.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
select "Reporter", from: "access_level"
end
......@@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
end
step 'we have user "John Doe" in group' do
current_group.add_user(user_john, Gitlab::Access::REPORTER)
end
step 'I remove user "John Doe" from group' do
within "#user_#{user_john.id}" do
click_link 'Remove user from group'
end
end
step 'I should not see "John Doe" in team list' do
within ".group-users-list" do
page.should_not have_content "John Doe"
end
end
protected
def current_group
@group ||= Group.first
end
def user_john
@user_john ||= User.find_by(name: "John Doe")
end
end
......@@ -8,7 +8,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
commit = @project.repository.commit
page.should have_content(@project.name)
page.should have_content(commit.message[0..20])
page.should have_content(commit.id.to_s[0..5])
page.should have_content(commit.short_id)
end
step 'I click atom feed link' do
......
......@@ -111,7 +111,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click on the commit in the merge request' do
within '.mr-commits' do
click_link sample_commit.id[0..8]
click_link Commit.truncate_sha(sample_commit.id)
end
end
......
......@@ -61,6 +61,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
fill_in :file_name, with: new_file_name
end
step 'I fill the new file name with an illegal name' do
fill_in :file_name, with: '.git'
end
step 'I fill the commit message' do
fill_in :commit_message, with: 'Not yet a commit message.'
end
......@@ -69,8 +73,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_link 'Diff'
end
step 'I click on "Commit changes"' do
click_button 'Commit changes'
step 'I click on "Commit Changes"' do
click_button 'Commit Changes'
end
step 'I click on "Remove"' do
......@@ -151,6 +155,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).not_to have_link('permalink')
end
step 'I see a commit error message' do
expect(page).to have_content('Your changes could not be committed')
end
private
def set_new_content
......
......@@ -265,6 +265,15 @@ module SharedPaths
visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
end
step 'I am on the new file page' do
current_path.should eq(project_new_tree_path(@project, root_ref))
end
step 'I am on the ".gitignore" edit file page' do
current_path.should eq(project_edit_tree_path(
@project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
visit project_tree_path(@project, "6d39438")
end
......
......@@ -6,7 +6,7 @@ module SharedSnippet
title: "Personal snippet one",
content: "Test content",
file_name: "snippet.rb",
private: false,
visibility_level: Snippet::PUBLIC,
author: current_user)
end
......@@ -15,9 +15,19 @@ module SharedSnippet
title: "Personal snippet private",
content: "Provate content",
file_name: "private_snippet.rb",
private: true,
visibility_level: Snippet::PRIVATE,
author: current_user)
end
step 'I have internal "Personal snippet internal" snippet' do
create(:personal_snippet,
title: "Personal snippet internal",
content: "Provate content",
file_name: "internal_snippet.rb",
visibility_level: Snippet::INTERNAL,
author: current_user)
end
step 'I have a public many lined snippet' do
create(:personal_snippet,
title: 'Many lined snippet',
......@@ -38,7 +48,7 @@ module SharedSnippet
|line fourteen
END
file_name: 'many_lined_snippet.rb',
private: true,
visibility_level: Snippet::PUBLIC,
author: current_user)
end
end
......@@ -7,6 +7,10 @@ class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps
page.should have_content "Personal snippet one"
end
step 'I should see "Personal snippet internal" in snippets' do
page.should have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet private" in snippets' do
page.should_not have_content "Personal snippet private"
end
......
......@@ -15,6 +15,10 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
page.should have_content "Personal snippet private"
end
step 'I should see "Personal snippet internal" in snippets' do
page.should have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet one" in snippets' do
page.should_not have_content "Personal snippet one"
end
......@@ -23,6 +27,10 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
page.should_not have_content "Personal snippet private"
end
step 'I should not see "Personal snippet internal" in snippets' do
page.should_not have_content "Personal snippet internal"
end
step 'I click "Internal" filter' do
within('.nav-stacked') do
click_link "Internal"
......@@ -35,6 +43,12 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
end
end
step 'I click "Public" filter' do
within('.nav-stacked') do
click_link "Public"
end
end
def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
......
......@@ -85,7 +85,7 @@ module API
branch_name: branch_name
}
else
render_api_error!(result[:error], 400)
render_api_error!(result[:message], 400)
end
end
......@@ -117,7 +117,7 @@ module API
branch_name: branch_name
}
else
render_api_error!(result[:error], 400)
render_api_error!(result[:message], 400)
end
end
......@@ -149,7 +149,7 @@ module API
branch_name: branch_name
}
else
render_api_error!(result[:error], 400)
render_api_error!(result[:message], 400)
end
end
end
......
......@@ -67,6 +67,10 @@ module API
unauthorized! unless current_user
end
def authenticate_by_gitlab_shell_token!
unauthorized! unless secret_token == params['secret_token']
end
def authenticated_as_admin!
forbidden! unless current_user.is_admin?
end
......@@ -193,5 +197,9 @@ module API
abilities
end
end
def secret_token
File.read(Rails.root.join('.gitlab_shell_secret'))
end
end
end
module API
# Internal access API
class Internal < Grape::API
before {
authenticate_by_gitlab_shell_token!
}
namespace 'internal' do
# Check if git command is allowed to project
#
......
......@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings
#
# Example Request:
# DELETE /projects/:id/keys/:id
# DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
......@@ -38,7 +38,41 @@ module API
)
end
end
# Set Hipchat service for project
#
# Parameters:
# token (required) - Hipchat token
# room (required) - Hipchat room name
#
# Example Request:
# PUT /projects/:id/services/hipchat
put ':id/services/hipchat' do
required_attributes! [:token, :room]
attrs = attributes_for_keys [:token, :room]
user_project.build_missing_services
if user_project.hipchat_service.update_attributes(
attrs.merge(active: true))
true
else
not_found!
end
end
end
# Delete Hipchat service settings
#
# Example Request:
# DELETE /projects/:id/services/hipchat
delete ':id/services/hipchat' do
if user_project.hipchat_service
user_project.hipchat_service.update_attributes(
active: false,
token: nil,
room: nil
)
end
end
end
end
end
......@@ -30,7 +30,7 @@ module Backup
if File.exists?(path_to_repo(wiki))
print " * #{wiki.path_with_namespace} ... "
if wiki.empty?
if wiki.repository.empty?
puts " [SKIPPED]".cyan
else
output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all))
......
module Gitlab
class AppLogger < Gitlab::Logger
def self.file_name
'application.log'
def self.file_name_noext
'application'
end
def format_message(severity, timestamp, progname, msg)
......
......@@ -3,22 +3,16 @@ module Gitlab
def find(login, password)
user = User.find_by(email: login) || User.find_by(username: login)
# If no user is found, or it's an LDAP server, try LDAP.
# LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
return nil unless ldap_conf.enabled
return nil unless Gitlab::LDAP::Config.enabled?
Gitlab::LDAP::User.authenticate(login, password)
Gitlab::LDAP::Authentication.login(login, password)
else
user if user.valid_password?(password)
end
end
def log
Gitlab::AppLogger
end
def ldap_conf
@ldap_conf ||= Gitlab.config.ldap
end
end
end
......@@ -90,7 +90,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
# It will be serverd by update hook in repository
# It will be done by the pre-receive hook in the repository.
true
else
false
......
module Gitlab
class GitLogger < Gitlab::Logger
def self.file_name
'githost.log'
def self.file_name_noext
'githost'
end
def format_message(severity, timestamp, progname, msg)
......
# LDAP authorization model
#
# * Check if we are allowed access (not blocked)
#
module Gitlab
module LDAP
class Access
attr_reader :adapter
attr_reader :adapter, :provider, :user
def self.open(&block)
Gitlab::LDAP::Adapter.open do |adapter|
block.call(self.new(adapter))
def self.open(user, &block)
Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
self.open do |access|
if access.allowed?(user)
# GitLab EE LDAP code goes here
self.open(user) do |access|
if access.allowed?
user.last_credential_check_at = Time.now
user.save
true
......@@ -22,21 +25,30 @@ module Gitlab
end
end
def initialize(adapter=nil)
def initialize(user, adapter=nil)
@adapter = adapter
@user = user
@provider = user.provider
end
def allowed?(user)
def allowed?
if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
if Gitlab.config.ldap.active_directory
return true unless ldap_config.active_directory
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
end
else
false
end
rescue
false
end
def adapter
@adapter ||= Gitlab::LDAP::Adapter.new(provider)
end
def ldap_config
Gitlab::LDAP::Config.new(provider)
end
end
end
end
module Gitlab
module LDAP
class Adapter
attr_reader :ldap
attr_reader :provider, :ldap
def self.open(&block)
Net::LDAP.open(adapter_options) do |ldap|
block.call(self.new(ldap))
def self.open(provider, &block)
Net::LDAP.open(config(provider).adapter_options) do |ldap|
block.call(self.new(provider, ldap))
end
end
def self.config
Gitlab.config.ldap
def self.config(provider)
Gitlab::LDAP::Config.new(provider)
end
def self.adapter_options
encryption =
case config['method'].to_s
when 'ssl'
:simple_tls
when 'tls'
:start_tls
else
nil
end
options = {
host: config['host'],
port: config['port'],
encryption: encryption
}
auth_options = {
auth: {
method: :simple,
username: config['bind_dn'],
password: config['password']
}
}
if config['password'] || config['bind_dn']
options.merge!(auth_options)
def initialize(provider, ldap=nil)
@provider = provider
@ldap = ldap || Net::LDAP.new(config.adapter_options)
end
options
end
def initialize(ldap=nil)
@ldap = ldap || Net::LDAP.new(self.class.adapter_options)
def config
Gitlab::LDAP::Config.new(provider)
end
def users(field, value)
def users(field, value, limit = nil)
if field.to_sym == :dn
options = {
base: value,
......@@ -57,13 +30,13 @@ module Gitlab
}
else
options = {
base: config['base'],
base: config.base,
filter: Net::LDAP::Filter.eq(field, value)
}
end
if config['user_filter'].present?
user_filter = Net::LDAP::Filter.construct(config['user_filter'])
if config.user_filter.present?
user_filter = Net::LDAP::Filter.construct(config.user_filter)
options[:filter] = if options[:filter]
Net::LDAP::Filter.join(options[:filter], user_filter)
......@@ -72,12 +45,16 @@ module Gitlab
end
end
if limit.present?
options.merge!(size: limit)
end
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
Gitlab::LDAP::Person.new(entry)
Gitlab::LDAP::Person.new(entry, provider)
end
end
......@@ -105,12 +82,6 @@ module Gitlab
results
end
end
private
def config
@config ||= self.class.config
end
end
end
end
# This calls helps to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
# until a valid bind is found
#
module Gitlab
module LDAP
class Authentication
def self.login(login, password)
return unless Gitlab::LDAP::Config.enabled?
return unless login.present? && password.present?
auth = nil
# loop through providers until valid bind
providers.find do |provider|
auth = new(provider)
auth.login(login, password) # true will exit the loop
end
# If (login, password) was invalid for all providers, the value of auth is now the last
# Gitlab::LDAP::Authentication instance we tried.
auth.user
end
def self.providers
Gitlab::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
def login(login, password)
@ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
end
def adapter
OmniAuth::LDAP::Adaptor.new(config.options)
end
def config
Gitlab::LDAP::Config.new(provider)
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(config.uid, login)
# Apply LDAP user filter if present
if config.user_filter.present?
filter = Net::LDAP::Filter.join(
filter,
Net::LDAP::Filter.construct(config.user_filter)
)
end
filter
end
def user
return nil unless ldap_user
Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
end
end
\ No newline at end of file
# Load a specific server configuration
module Gitlab
module LDAP
class Config
attr_accessor :provider, :options
def self.enabled?
Gitlab.config.ldap.enabled
end
def self.servers
Gitlab.config.ldap.servers.values
end
def self.providers
servers.map {|server| server['provider_name'] }
end
def initialize(provider)
@provider = provider
invalid_provider unless valid_provider?
@options = config_for(provider)
end
def enabled?
base_config.enabled
end
def adapter_options
{
host: options['host'],
port: options['port'],
encryption: encryption
}.tap do |options|
options.merge!(auth_options) if has_auth?
end
end
def base
options['base']
end
def uid
options['uid']
end
def sync_ssh_keys?
sync_ssh_keys.present?
end
# The LDAP attribute in which the ssh keys are stored
def sync_ssh_keys
options['sync_ssh_keys']
end
def user_filter
options['user_filter']
end
def group_base
options['group_base']
end
def admin_group
options['admin_group']
end
def active_directory
options['active_directory']
end
protected
def base_config
Gitlab.config.ldap
end
def config_for(provider)
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
def encryption
case options['method'].to_s
when 'ssl'
:simple_tls
when 'tls'
:start_tls
else
nil
end
end
def valid_provider?
self.class.providers.include?(provider)
end
def invalid_provider
raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}"
end
def auth_options
{
auth: {
method: :simple,
username: options['bind_dn'],
password: options['password']
}
}
end
def has_auth?
options['password'] || options['bind_dn']
end
end
end
end
......@@ -6,24 +6,24 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
def self.find_by_uid(uid, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
adapter.user(config.uid, uid)
attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter)
adapter.user(adapter.config.uid, uid)
end
def self.find_by_dn(dn, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
def self.find_by_dn(dn, adapter)
adapter.user('dn', dn)
end
def self.disabled_via_active_directory?(dn, adapter=nil)
adapter ||= Gitlab::LDAP::Adapter.new
def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
def initialize(entry)
def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
@provider = provider
end
def name
......@@ -38,6 +38,10 @@ module Gitlab
uid
end
def email
entry.try(:mail)
end
def dn
entry.dn
end
......@@ -48,12 +52,8 @@ module Gitlab
@entry
end
def adapter
@adapter ||= Gitlab::LDAP::Adapter.new
end
def config
@config ||= Gitlab.config.ldap
@config ||= Gitlab::LDAP::Config.new(provider)
end
end
end
......
......@@ -10,77 +10,52 @@ module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
def find_or_create(auth_hash)
self.auth_hash = auth_hash
find(auth_hash) || find_and_connect_by_email(auth_hash) || create(auth_hash)
end
def find_and_connect_by_email(auth_hash)
self.auth_hash = auth_hash
user = model.find_by(email: self.auth_hash.email)
if user
user.update_attributes(extern_uid: auth_hash.uid, provider: auth_hash.provider)
Gitlab::AppLogger.info("(LDAP) Updating legacy LDAP user #{self.auth_hash.email} with extern_uid => #{auth_hash.uid}")
return user
def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
::User.
where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last
end
end
def authenticate(login, password)
# Check user against LDAP backend if user is not authenticated
# Only check with valid login and password to prevent anonymous bind results
return nil unless ldap_conf.enabled && login.present? && password.present?
ldap_user = adapter.bind_as(
filter: user_filter(login),
size: 1,
password: password
)
find_by_uid(ldap_user.dn) if ldap_user
def initialize(auth_hash)
super
update_user_attributes
end
def adapter
@adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf)
# instance methods
def gl_user
@gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
end
protected
def find_by_uid_and_provider
find_by_uid(auth_hash.uid)
end
def find_by_uid(uid)
# LDAP distinguished name is case-insensitive
model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
end
def provider
'ldap'
self.class.find_by_uid_and_provider(
auth_hash.uid.downcase, auth_hash.provider)
end
def raise_error(message)
raise OmniAuth::Error, "(LDAP) " + message
def find_by_email
model.find_by(email: auth_hash.email)
end
def ldap_conf
Gitlab.config.ldap
def update_user_attributes
gl_user.attributes = {
extern_uid: auth_hash.uid,
provider: auth_hash.provider,
email: auth_hash.email
}
end
def user_filter(login)
filter = Net::LDAP::Filter.eq(adapter.uid, login)
# Apply LDAP user filter if present
if ldap_conf['user_filter'].present?
user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
filter = Net::LDAP::Filter.join(filter, user_filter)
end
filter
end
def changed?
gl_user.changed?
end
def needs_blocking?
false
end
def allowed?
Gitlab::LDAP::Access.allowed?(gl_user)
end
end
end
end
module Gitlab
class Logger < ::Logger
def self.file_name
file_name_noext + '.log'
end
def self.error(message)
build.error(message)
end
......
......@@ -70,14 +70,22 @@ module Gitlab
insert_piece($1)
end
# Context passed to the markdoqwn pipeline
# Used markdown pipelines in GitLab:
# GitlabEmojiFilter - performs emoji replacement.
#
# see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
filters = [
HTML::Pipeline::Gitlab::GitlabEmojiFilter
]
markdown_context = {
asset_root: File.join(root_url,
Gitlab::Application.config.assets.prefix)
asset_root: Gitlab.config.gitlab.url,
asset_host: Gitlab::Application.config.asset_host
}
result = HTML::Pipeline::Gitlab::MarkdownPipeline.call(text,
markdown_context)
markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
result = markdown_pipeline.call(text, markdown_context)
text = result[:output].to_html(save_with: 0)
allowed_attributes = ActionView::Base.sanitized_allowed_attributes
......
......@@ -21,7 +21,7 @@ module Gitlab
end
def name
(info.name || full_name).to_s.force_encoding('utf-8')
(info.try(:name) || full_name).to_s.force_encoding('utf-8')
end
def full_name
......
......@@ -6,55 +6,77 @@
module Gitlab
module OAuth
class User
class << self
attr_reader :auth_hash
attr_accessor :auth_hash, :gl_user
def find(auth_hash)
def initialize(auth_hash)
self.auth_hash = auth_hash
find_by_uid_and_provider
end
def create(auth_hash)
user = new(auth_hash)
user.save_and_trigger_callbacks
def persisted?
gl_user.try(:persisted?)
end
def model
::User
def new?
!persisted?
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
def valid?
gl_user.try(:valid?)
end
protected
def find_by_uid_and_provider
model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
def save
unauthorized_to_create unless gl_user
if needs_blocking?
gl_user.save!
gl_user.block
else
gl_user.save!
end
log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
return self, e.record.errors
end
# Instance methods
attr_accessor :auth_hash, :user
def gl_user
@user ||= find_by_uid_and_provider
def initialize(auth_hash)
self.auth_hash = auth_hash
self.user = self.class.model.new(user_attributes)
user.skip_confirmation!
if signup_enabled?
@user ||= build_new_user
end
@user
end
protected
def needs_blocking?
new? && block_after_signup?
end
def signup_enabled?
Gitlab.config.omniauth.allow_single_sign_on
end
def block_after_signup?
Gitlab.config.omniauth.block_auto_created_users
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
def save_and_trigger_callbacks
user.save!
log.info "(OAuth) Creating user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
user.block if needs_blocking?
def find_by_uid_and_provider
model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
end
user
rescue ActiveRecord::RecordInvalid => e
log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}"
return nil, e.record.errors
def build_new_user
model.new(user_attributes).tap do |user|
user.skip_confirmation!
end
end
def user_attributes
......@@ -73,12 +95,12 @@ module Gitlab
Gitlab::AppLogger
end
def raise_error(message)
raise OmniAuth::Error, "(OAuth) " + message
def model
::User
end
def needs_blocking?
Gitlab.config.omniauth['block_auto_created_users']
def raise_unauthorized_to_create
raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
......
module Gitlab
class ProductionLogger < Gitlab::Logger
def self.file_name_noext
'production'
end
end
end
module Gitlab
class SidekiqLogger < Gitlab::Logger
def self.file_name_noext
'sidekiq'
end
end
end
......@@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
super options
end
# If project has issue number 39, apostrophe will be linked in
# regular text to the issue as Redcarpet will convert apostrophe to
# #39;
# We replace apostrophe with right single quote before Redcarpet
# does the processing and put the apostrophe back in postprocessing.
# This only influences regular text, code blocks are untouched.
def normal_text(text)
return text unless text.present?
text.gsub("'", "&rsquo;")
end
def block_code(code, language)
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
......@@ -44,6 +55,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def postprocess(full_document)
full_document.gsub!("&rsquo;", "'")
unless @template.instance_variable_get("@project_wiki") || @project.nil?
full_document = h.create_relative_links(full_document)
end
......
......@@ -60,17 +60,16 @@ server {
client_max_body_size 20m;
## Strong SSL Security
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
ssl_ciphers 'AES256+EECDH:AES256+EDH';
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
## [WARNING] The following header states that the browser should only communicate
## with your server over a secure connection for the next 24 months.
......@@ -87,11 +86,10 @@ server {
# ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
# resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired
# resolver_timeout 10s;
# resolver_timeout 5s;
## [Optional] Generate a stronger DHE parameter:
## cd /etc/ssl/certs
## sudo openssl dhparam -out dhparam.pem 4096
## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
......
......@@ -664,7 +664,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "LDAP"
if ldap_config.enabled
if Gitlab::LDAP::Config.enabled?
print_users(args.limit)
else
puts 'LDAP is disabled in config/gitlab.yml'
......@@ -675,38 +675,18 @@ namespace :gitlab do
def print_users(limit)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry|
puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}"
end
end
def attributes
[ldap_config.uid]
end
def filter
uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
if user_filter
Net::LDAP::Filter.join(uid_filter, user_filter)
else
uid_filter
end
end
servers = Gitlab::LDAP::Config.providers
def user_filter
if ldap_config['user_filter'] && ldap_config.user_filter.present?
Net::LDAP::Filter.construct(ldap_config.user_filter)
else
nil
servers.each do |server|
puts "Server: #{server}"
Gitlab::LDAP::Adapter.open(server) do |adapter|
users = adapter.users(adapter.config.uid, '*', 100)
users.each do |user|
puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
end
end
def ldap
@ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection
end
def ldap_config
@ldap_config ||= Gitlab.config.ldap
end
end
......
......@@ -34,7 +34,7 @@ namespace :gitlab do
puts "Processing #{repo_path}".yellow
if path =~ /.wiki\Z/
if path =~ /\.wiki\Z/
puts " * Skipping wiki repo"
next
end
......
......@@ -11,7 +11,7 @@ namespace :gitlab do
home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home
gitlab_url = Settings.gitlab.url
# gitlab-shell requires a / at the end of the url
gitlab_url += "/" unless gitlab_url.match(/\/$/)
gitlab_url += '/' unless gitlab_url.end_with?('/')
repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path
......
......@@ -24,6 +24,11 @@ FactoryGirl.define do
admin true
end
trait :ldap do
provider 'ldapmain'
extern_uid 'my-ldap-id'
end
factory :admin, traits: [:admin]
end
......
require 'spec_helper'
describe SnippetsFinder do
let(:user) { create :user }
let(:user1) { create :user }
let(:group) { create :group }
let(:project1) { create(:empty_project, :public, group: group) }
let(:project2) { create(:empty_project, :private, group: group) }
context ':all filter' do
before do
@snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
@snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
@snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
end
it "returns all private and internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :all)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns all public snippets" do
snippets = SnippetsFinder.new.execute(nil, filter: :all)
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
end
context ':by_user filter' do
before do
@snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
@snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
@snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
end
it "returns all public and internal snippets" do
snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
snippets.should include(@snippet2)
snippets.should_not include(@snippet1, @snippet3)
end
it "returns private snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private")
snippets.should include(@snippet1)
snippets.should_not include(@snippet2, @snippet3)
end
it "returns public snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public")
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
it "returns all snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
snippets.should include(@snippet1, @snippet2, @snippet3)
end
end
context 'by_project filter' do
before do
@snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
@snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
@snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
end
it "returns public snippets for unauthorized user" do
snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1)
snippets.should include(@snippet3)
snippets.should_not include(@snippet1, @snippet2)
end
it "returns public and internal snippets for none project members" do
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
snippets.should include(@snippet2, @snippet3)
snippets.should_not include(@snippet1)
end
it "returns all snippets for project members" do
project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
snippets.should include(@snippet1, @snippet2, @snippet3)
end
end
end
require 'spec_helper'
describe EventsHelper do
include ApplicationHelper
include GitlabMarkdownHelper
it 'should display one line of plain text without alteration' do
input = 'A short, plain note'
expect(event_note(input)).to match(input)
expect(event_note(input)).not_to match(/\.\.\.\z/)
end
it 'should display inline code' do
input = 'A note with `inline code`'
expected = 'A note with <code>inline code</code>'
expect(event_note(input)).to match(expected)
end
it 'should truncate a note with multiple paragraphs' do
input = "Paragraph 1\n\nParagraph 2"
expected = 'Paragraph 1...'
expect(event_note(input)).to match(expected)
end
it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
expected = '<pre><code class="">Code block...</code></pre>'
expect(event_note(input)).to match(expected)
end
it 'should truncate a single long line of text' do
text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
input = "#{text}#{text}#{text}#{text}" # 200 chars
expected = "#{text}#{text}".sub(/.{3}/, '...')
expect(event_note(input)).to match(expected)
end
it 'should preserve a link href when link text is truncated' do
text = 'The quick brown fox jumped over the lazy dog' # 44 chars
input = "#{text}#{text}#{text} " # 133 chars
link_url = 'http://example.com/foo/bar/baz' # 30 chars
input << link_url
expected_link_text = 'http://example...</a>'
expect(event_note(input)).to match(link_url)
expect(event_note(input)).to match(expected_link_text)
end
end
......@@ -60,7 +60,7 @@ describe GitlabMarkdownHelper do
end
it "should link using a short id" do
actual = "Backported from #{commit.short_id(6)}"
actual = "Backported from #{commit.short_id}"
gfm(actual).should match(expected)
end
......@@ -530,6 +530,24 @@ describe GitlabMarkdownHelper do
markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
end
it "should not link the apostrophe to issue 39" do
project.team << [user, :master]
project.issues.stub(:where).with(iid: '39').and_return([issue])
actual = "Yes, it is @#{member.user.username}'s task."
expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
markdown(actual).should match(expected)
end
it "should not link the apostrophe to issue 39 in code blocks" do
project.team << [user, :master]
project.issues.stub(:where).with(iid: '39').and_return([issue])
actual = "Yes, `it is @#{member.user.username}'s task.`"
expected = /Yes, <code>it is @gfm\'s task.<\/code>/
markdown(actual).should match(expected)
end
it "should handle references in <em>" do
actual = "Apply _!#{merge_request.iid}_ ASAP"
......@@ -576,9 +594,21 @@ describe GitlabMarkdownHelper do
end
it "should generate absolute urls for emoji" do
markdown(":smile:").should include("src=\"#{url_helper('emoji/smile')}")
markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png")
end
it "should generate absolute urls for emoji if relative url is present" do
Gitlab.config.gitlab.stub(:url).and_return('http://localhost/gitlab/root')
markdown(":smile:").should include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png")
end
it "should generate absolute urls for emoji if asset_host is present" do
Gitlab::Application.config.stub(:asset_host).and_return("https://cdn.example.com")
ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
markdown(":smile:").should include("src=\"https://cdn.example.com/assets/emoji/smile.png")
end
it "should handle relative urls for a file in master" do
actual = "[GitLab API doc](doc/api/README.md)\n"
expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
......
......@@ -28,17 +28,16 @@ describe Gitlab::Auth do
end
context "with ldap enabled" do
before { Gitlab.config.ldap['enabled'] = true }
after { Gitlab.config.ldap['enabled'] = false }
before { Gitlab::LDAP::Config.stub(enabled?: true) }
it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::User).not_to receive(:authenticate)
expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find(username, password)
end
it "uses ldap as fallback to for authentication" do
expect(Gitlab::LDAP::User).to receive(:authenticate)
expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find('ldap_user', 'password')
end
......
require 'spec_helper'
describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new }
let(:user) { create(:user) }
let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:user, :ldap) }
describe :allowed? do
subject { access.allowed?(user) }
subject { access.allowed? }
context 'when the user cannot be found' do
before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
......@@ -28,19 +28,13 @@ describe Gitlab::LDAP::Access do
it { should be_true }
end
context 'and has no disabled flag in active diretory' do
before {
Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
Gitlab.config.ldap['enabled'] = true
Gitlab.config.ldap['active_directory'] = false
}
after {
Gitlab.config.ldap['enabled'] = false
Gitlab.config.ldap['active_directory'] = true
}
context 'without ActiveDirectory enabled' do
before do
Gitlab::LDAP::Config.stub(enabled?: true)
Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
end
it { should be_false }
it { should be_true }
end
end
end
......
require 'spec_helper'
describe Gitlab::LDAP::Adapter do
let(:adapter) { Gitlab::LDAP::Adapter.new }
let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
describe :dn_matches_filter? do
let(:ldap) { double(:ldap) }
......
require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
let(:user) { create(:user, :ldap, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
describe :login do
let(:adapter) { double :adapter }
before do
Gitlab::LDAP::Config.stub(enabled?: true)
end
it "finds the user if authentication is successful" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_true
end
it "is false if the user does not exist" do
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter,
bind_as: double(:ldap_user, dn: dn)
))
expect(klass.login(login, password)).to be_false
end
it "is false if authentication fails" do
user
# try only to fake the LDAP call
klass.any_instance.stub(adapter: double(:adapter, bind_as: nil))
expect(klass.login(login, password)).to be_false
end
it "fails if ldap is disabled" do
Gitlab::LDAP::Config.stub(enabled?: false)
expect(klass.login(login, password)).to be_false
end
it "fails if no login is supplied" do
expect(klass.login('', password)).to be_false
end
it "fails if no password is supplied" do
expect(klass.login(login, '')).to be_false
end
end
end
\ No newline at end of file
require 'spec_helper'
describe Gitlab::LDAP::Config do
let(:config) { Gitlab::LDAP::Config.new provider }
let(:provider) { 'ldapmain' }
describe :initalize do
it 'requires a provider' do
expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
end
it "works" do
expect(config).to be_a described_class
end
it "raises an error if a unknow provider is used" do
expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
end
end
end
\ No newline at end of file
require 'spec_helper'
describe Gitlab::LDAP::User do
let(:gl_user) { Gitlab::LDAP::User }
let(:gl_user) { Gitlab::LDAP::User.new(auth_hash) }
let(:info) do
double(
{
name: 'John',
email: 'john@example.com',
nickname: 'john'
)
}
end
before { Gitlab.config.stub(omniauth: {}) }
describe :find_or_create do
let(:auth) do
double(info: info, provider: 'ldap', uid: 'my-uid')
let(:auth_hash) do
double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end
describe :find_or_create do
it "finds the user if already existing" do
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap')
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:user, email: 'john@example.com')
expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
expect(existing_user.extern_uid).to eql 'my-uid'
expect(existing_user.provider).to eql 'ldap'
expect(existing_user.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
expect{ gl_user.find_or_create(auth) }.to change{ User.count }.by(1)
end
end
describe "authenticate" do
let(:login) { 'john' }
let(:password) { 'my-secret' }
before {
Gitlab.config.ldap['enabled'] = true
Gitlab.config.ldap['user_filter'] = 'employeeType=developer'
}
after { Gitlab.config.ldap['enabled'] = false }
it "send an authentication request to ldap" do
expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as)
Gitlab::LDAP::User.authenticate(login, password)
expect{ gl_user.save }.to change{ User.count }.by(1)
end
end
end
require 'spec_helper'
describe Gitlab::OAuth::AuthHash do
let(:auth_hash) do
Gitlab::OAuth::AuthHash.new(double({
provider: 'twitter',
uid: uid,
info: double(info_hash)
}))
end
let(:uid) { 'my-uid' }
let(:email) { 'my-email@example.com' }
let(:nickname) { 'my-nickname' }
let(:info_hash) {
{
email: email,
nickname: nickname,
name: 'John',
first_name: "John",
last_name: "Who"
}
}
context "defaults" do
it { expect(auth_hash.provider).to eql 'twitter' }
it { expect(auth_hash.uid).to eql uid }
it { expect(auth_hash.email).to eql email }
it { expect(auth_hash.username).to eql nickname }
it { expect(auth_hash.name).to eql "John" }
it { expect(auth_hash.password).to_not be_empty }
end
context "email not provided" do
before { info_hash.delete(:email) }
it "generates a temp email" do
expect( auth_hash.email).to start_with('temp-email-for-oauth')
end
end
context "username not provided" do
before { info_hash.delete(:nickname) }
it "takes the first part of the email as username" do
expect( auth_hash.username ).to eql "my-email"
end
end
context "name not provided" do
before { info_hash.delete(:name) }
it "concats first and lastname as the name" do
expect( auth_hash.name ).to eql "John Who"
end
end
end
\ No newline at end of file
require 'spec_helper'
describe Gitlab::OAuth::User do
let(:gl_auth) { Gitlab::OAuth::User }
let(:info) do
double(
let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) }
let(:gl_user) { oauth_user.gl_user }
let(:uid) { 'my-uid' }
let(:provider) { 'my-provider' }
let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) }
let(:info_hash) do
{
nickname: 'john',
name: 'John',
email: 'john@mail.com'
)
}
end
before do
Gitlab.config.stub(omniauth: {})
end
describe :find do
describe :persisted? do
let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
assert gl_auth.find(auth)
expect( oauth_user.persisted? ).to be_true
end
it "finds an existing user based on nested uid and provider" do
auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
assert gl_auth.find(auth)
it "returns false if use is not found in database" do
auth_hash.stub(uid: 'non-existing')
expect( oauth_user.persisted? ).to be_false
end
end
describe :create do
it "should create user from LDAP" do
auth = double(info: info, uid: 'my-uid', provider: 'ldap')
user = gl_auth.create(auth)
describe :save do
let(:provider) { 'twitter' }
describe 'signup' do
context "with allow_single_sign_on enabled" do
before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
it "creates a user from Omniauth" do
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.extern_uid).to eql uid
expect(gl_user.provider).to eql 'twitter'
end
end
user.should be_valid
user.extern_uid.should == auth.uid
user.provider.should == 'ldap'
context "with allow_single_sign_on disabled (Default)" do
it "throws an error" do
expect{ oauth_user.save }.to raise_error StandardError
end
end
end
describe 'blocking' do
let(:provider) { 'twitter' }
before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
it "should create user from Omniauth" do
auth = double(info: info, uid: 'my-uid', provider: 'twitter')
user = gl_auth.create(auth)
context 'signup' do
context 'dont block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: false }
user.should be_valid
user.extern_uid.should == auth.uid
user.provider.should == 'twitter'
it do
oauth_user.save
gl_user.should be_valid
gl_user.should_not be_blocked
end
end
it "should apply defaults to user" do
auth = double(info: info, uid: 'my-uid', provider: 'ldap')
user = gl_auth.create(auth)
context 'block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: true }
user.should be_valid
user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
it do
oauth_user.save
gl_user.should be_valid
gl_user.should be_blocked
end
end
end
it "Set a temp email address if not provided (like twitter does)" do
info = double(
uid: 'my-uid',
nickname: 'john',
name: 'John'
)
auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
context 'sign-in' do
before do
oauth_user.save
oauth_user.gl_user.activate
end
context 'dont block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: false }
user = gl_auth.create(auth)
expect(user.email).to_not be_empty
it do
oauth_user.save
gl_user.should be_valid
gl_user.should_not be_blocked
end
end
it 'generates a username if non provided (google)' do
info = double(
uid: 'my-uid',
name: 'John',
email: 'john@example.com'
)
auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
context 'block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: true }
user = gl_auth.create(auth)
expect(user.username).to eql 'john'
it do
oauth_user.save
gl_user.should be_valid
gl_user.should_not be_blocked
end
end
end
end
end
end
......@@ -5,16 +5,11 @@
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# properties :text
#
require 'spec_helper'
......
......@@ -75,7 +75,7 @@ eos
it_behaves_like 'a mentionable' do
let(:subject) { commit }
let(:mauthor) { create :user, email: commit.author_email }
let(:backref_text) { "commit #{subject.sha[0..5]}" }
let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
# Include the subject in the repository stub.
......
# == Schema Information
#
# Table name: group_members
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# group_id :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
# notification_level :integer default(3), not null
#
require 'spec_helper'
......
......@@ -228,7 +228,7 @@ describe Note do
it { should be_valid }
its(:noteable) { should == issue }
its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" }
its(:note) { should == "_mentioned in commit #{commit.sha}_" }
end
context 'merge request from an issue' do
......@@ -267,7 +267,7 @@ describe Note do
its(:noteable_type) { should == "Commit" }
its(:noteable_id) { should be_nil }
its(:commit_id) { should == commit.id }
its(:note) { should == "_mentioned in commit #{parent_commit.id[0...6]}_" }
its(:note) { should == "_mentioned in commit #{parent_commit.id}_" }
end
end
......
# == Schema Information
#
# Table name: project_members
# Table name: members
#
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
# source_type :string(255) not null
# user_id :integer not null
# project_id :integer not null
# notification_level :integer not null
# type :string(255)
# created_at :datetime
# updated_at :datetime
# project_access :integer default(0), not null
# notification_level :integer default(3), not null
#
require 'spec_helper'
......
......@@ -11,8 +11,8 @@
# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255)
# visibility_level :integer default(0), not null
#
require 'spec_helper'
......
......@@ -27,6 +27,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_false }
it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false }
it { project.team.member?(nonmember).should be_false }
it { project.team.member?(guest).should be_true }
end
end
......@@ -60,6 +62,8 @@ describe ProjectTeam do
it { project.team.master?(guest).should be_true }
it { project.team.master?(reporter).should be_false }
it { project.team.master?(nonmember).should be_false }
it { project.team.member?(nonmember).should be_false }
it { project.team.member?(guest).should be_true }
end
end
end
......
......@@ -77,5 +77,25 @@ describe SlackService do
WebMock.should have_requested(:post, api_url).once
end
end
context 'with new webhook syntax with slack allowed team name' do
before do
@allowed_webhook = 'https://gitlab-hq-123.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI'
slack_service.stub(
project: project,
project_id: project.id,
service_hook: true,
webhook: @allowed_webhook
)
WebMock.stub_request(:post, @allowed_webhook)
end
it "should call Slack API" do
slack_service.execute(sample_data)
WebMock.should have_requested(:post, @allowed_webhook).once
end
end
end
end
......@@ -11,8 +11,8 @@
# updated_at :datetime
# file_name :string(255)
# expires_at :datetime
# private :boolean default(TRUE), not null
# type :string(255)
# visibility_level :integer default(0), not null
#
require 'spec_helper'
......
......@@ -346,6 +346,25 @@ describe User do
end
end
describe :ldap_user? do
let(:user) { build(:user, :ldap) }
it "is true if provider name starts with ldap" do
user.provider = 'ldapmain'
expect( user.ldap_user? ).to be_true
end
it "is false for other providers" do
user.provider = 'other-provider'
expect( user.ldap_user? ).to be_false
end
it "is false if no extern_uid is provided" do
user.extern_uid = nil
expect( user.ldap_user? ).to be_false
end
end
describe '#full_website_url' do
let(:user) { create(:user) }
......@@ -429,4 +448,32 @@ describe User do
expect(user.starred?(project)).to be_false
end
end
describe "#sort" do
before do
User.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
end
it "sorts users as recently_signed_in" do
User.sort('recent_sign_in').first.should == @user
end
it "sorts users as late_signed_in" do
User.sort('oldest_sign_in').first.should == @user1
end
it "sorts users as recently_created" do
User.sort('recently_created').first.should == @user
end
it "sorts users as late_created" do
User.sort('late_created').first.should == @user1
end
it "sorts users by name when nil is passed" do
User.sort(nil).first.should == @user
end
end
end
......@@ -5,10 +5,11 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
let(:project) { create(:project) }
let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') }
describe "GET /internal/check", no_db: true do
it do
get api("/internal/check")
get api("/internal/check"), secret_token: secret_token
response.status.should == 200
json_response['api_version'].should == API::API.version
......@@ -17,7 +18,7 @@ describe API::API, api: true do
describe "GET /internal/discover" do
it do
get(api("/internal/discover"), key_id: key.id)
get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
response.status.should == 200
......@@ -159,7 +160,8 @@ describe API::API, api: true do
api("/internal/allowed"),
key_id: key.id,
project: project.path_with_namespace,
action: 'git-upload-pack'
action: 'git-upload-pack',
secret_token: secret_token
)
end
......@@ -169,7 +171,8 @@ describe API::API, api: true do
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
key_id: key.id,
project: project.path_with_namespace,
action: 'git-receive-pack'
action: 'git-receive-pack',
secret_token: secret_token
)
end
......@@ -179,7 +182,8 @@ describe API::API, api: true do
ref: 'master',
key_id: key.id,
project: project.path_with_namespace,
action: 'git-upload-archive'
action: 'git-upload-archive',
secret_token: secret_token
)
end
end
......@@ -27,4 +27,30 @@ describe API::API, api: true do
project.gitlab_ci_service.should be_nil
end
end
describe 'PUT /projects/:id/services/hipchat' do
it 'should update hipchat settings' do
put api("/projects/#{project.id}/services/hipchat", user),
token: 'secret-token', room: 'test'
response.status.should == 200
project.hipchat_service.should_not be_nil
end
it 'should return if required fields missing' do
put api("/projects/#{project.id}/services/gitlab-ci", user),
token: 'secret-token', active: true
response.status.should == 400
end
end
describe 'DELETE /projects/:id/services/hipchat' do
it 'should delete hipchat settings' do
delete api("/projects/#{project.id}/services/hipchat", user)
response.status.should == 200
project.hipchat_service.should be_nil
end
end
end
......@@ -30,15 +30,15 @@ def common_mentionable_setup
"!#{mentioned_mr.iid}, " +
"#{ext_proj.path_with_namespace}##{ext_issue.iid}, " +
"#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " +
"#{ext_proj.path_with_namespace}@#{ext_commit.id[0..5]}, " +
"#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
"#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " +
"#{mentioned_commit.sha[0..10]} and itself as #{backref_text}"
end
before do
# Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits.
commitmap = { '123456' => mentioned_commit }
extra_commits.each { |c| commitmap[c.sha[0..5]] = c }
commitmap = { '1234567890a' => mentioned_commit }
extra_commits.each { |c| commitmap[c.short_id] = c }
mproject.repository.stub(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string)
end
......@@ -54,7 +54,6 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do
# De-duplicate and omit itself
refs = subject.references(mproject)
refs.should have(6).items
refs.should include(mentioned_issue)
refs.should include(mentioned_mr)
......@@ -90,7 +89,7 @@ shared_examples 'an editable mentionable' do
it 'creates new cross-reference notes when the mentionable text is edited' do
new_text = "still mentions ##{mentioned_issue.iid}, " +
"#{mentioned_commit.sha[0..5]}, " +
"#{mentioned_commit.sha[0..10]}, " +
"#{ext_issue.iid}, " +
"new refs: ##{other_issue.iid}, " +
"#{ext_proj.path_with_namespace}##{other_ext_issue.iid}"
......
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