diff options
Diffstat (limited to 'lib')
35 files changed, 632 insertions, 301 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 6bec8368b12..7c4cdad7f0d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -38,6 +38,7 @@ module API mount Internal mount SystemHooks mount ProjectSnippets + mount ProjectMembers mount DeployKeys mount ProjectHooks mount Services diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8557fa074d4..9fa8506926c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -15,7 +15,7 @@ module API end class UserSafe < Grape::Entity - expose :name + expose :name, :username end class UserBasic < Grape::Entity @@ -44,7 +44,7 @@ module API expose :id, :description, :default_branch expose :public?, as: :public expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url - expose :owner, using: Entities::UserBasic + expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at @@ -58,18 +58,6 @@ module API end end - class TeamMember < UserBasic - expose :permission, as: :access_level do |user, options| - options[:user_team].user_team_user_relationships.find_by(user_id: user.id).permission - end - end - - class TeamProject < Project - expose :greatest_access, as: :greatest_access_level do |project, options| - options[:user_team].user_team_project_relationships.find_by(project_id: project.id).greatest_access - end - end - class Group < Grape::Entity expose :id, :name, :path, :owner_id end @@ -144,7 +132,7 @@ module API end class MergeRequest < ProjectEntity - expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes + expose :target_branch, :source_branch, :title, :state, :upvotes, :downvotes, :description expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id end @@ -175,5 +163,33 @@ module API class Namespace < Grape::Entity expose :id, :path, :kind end + + class ProjectAccess < Grape::Entity + expose :project_access, as: :access_level + expose :notification_level + end + + class GroupAccess < Grape::Entity + expose :group_access, as: :access_level + expose :notification_level + end + + class ProjectWithAccess < Project + expose :permissions do + expose :project_access, using: Entities::ProjectAccess do |project, options| + project.users_projects.find_by(user_id: options[:user].id) + end + + expose :group_access, using: Entities::GroupAccess do |project, options| + if project.group + project.group.users_groups.find_by(user_id: options[:user].id) + end + end + end + end + + class Label < Grape::Entity + expose :name + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index f8c48e2f3b2..fc309f65a56 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -47,7 +47,7 @@ module API end def find_project(id) - project = Project.find_by(id: id) || Project.find_with_namespace(id) + project = Project.find_with_namespace(id) || Project.find_by(id: id) if project && can?(current_user, :read_project, project) project diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ebc9fef07b4..bcf97574673 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,16 +1,12 @@ module API # Internal access API class Internal < Grape::API - - DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } - PUSH_COMMANDS = %w{ git-receive-pack } - namespace 'internal' do - # - # Check if ssh key has access to project code + # Check if git command is allowed to project # # Params: - # key_id - SSH Key id + # key_id - ssh key id for Git over SSH + # user_id - user id for Git over HTTP # project - project path with namespace # action - git action (git-upload-pack or git-receive-pack) # ref - branch name @@ -22,37 +18,25 @@ module API # the wiki repository as well. project_path = params[:project] project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ - - key = Key.find(params[:key_id]) project = Project.find_with_namespace(project_path) - git_cmd = params[:action] return false unless project - - if key.is_a? DeployKey - key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd) - else - user = key.user - - return false if user.blocked? - if Gitlab.config.ldap.enabled - return false if user.ldap_user? && Gitlab::LDAP::User.blocked?(user.extern_uid) - end - - action = case git_cmd - when *DOWNLOAD_COMMANDS - then :download_code - when *PUSH_COMMANDS - then - if project.protected_branch?(params[:ref]) - :push_code_to_protected_branches - else - :push_code - end - end - - user.can?(action, project) - end + actor = if params[:key_id] + Key.find(params[:key_id]) + elsif params[:user_id] + User.find(params[:user_id]) + end + + return false unless actor + + Gitlab::GitAccess.new.allowed?( + actor, + params[:action], + project, + params[:ref], + params[:oldrev], + params[:newrev] + ) end # diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 58d2f79faff..e2458198411 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -64,6 +64,7 @@ module API # target_project - The target project of the merge request defaults to the :id of the project # assignee_id - Assignee user ID # title (required) - Title of MR + # description - Description of MR # # Example: # POST /projects/:id/merge_requests @@ -72,7 +73,7 @@ module API set_current_user_for_thread do authorize! :write_merge_request, user_project required_attributes! [:source_branch, :target_branch, :title] - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] merge_request = user_project.merge_requests.new(attrs) merge_request.author = current_user merge_request.source_project = user_project @@ -105,12 +106,13 @@ module API # assignee_id - Assignee user ID # title - Title of MR # state_event - Status of MR. (close|reopen|merge) + # description - Description of MR # Example: # PUT /projects/:id/merge_request/:merge_request_id # put ":id/merge_request/:merge_request_id" do set_current_user_for_thread do - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :modify_merge_request, merge_request diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index c271dd8b61b..79c3d122d32 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -5,15 +5,6 @@ module API before { authorize_admin_project } resource :projects do - helpers do - def handle_project_member_errors(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - end - not_found! - end - end - # Get project hooks # # Parameters: diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb new file mode 100644 index 00000000000..47c4ddce163 --- /dev/null +++ b/lib/api/project_members.rb @@ -0,0 +1,114 @@ +module API + # Projects members API + class ProjectMembers < Grape::API + before { authenticate! } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + # Get a project team members + # + # Parameters: + # id (required) - The ID of a project + # query - Query string + # Example Request: + # GET /projects/:id/members + get ":id/members" do + if params[:query].present? + @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%") + else + @members = paginate user_project.users + end + present @members, with: Entities::ProjectMember, project: user_project + end + + # Get a project team members + # + # Parameters: + # id (required) - The ID of a project + # user_id (required) - The ID of a user + # Example Request: + # GET /projects/:id/members/:user_id + get ":id/members/:user_id" do + @member = user_project.users.find params[:user_id] + present @member, with: Entities::ProjectMember, project: user_project + end + + # Add a new project team member + # + # Parameters: + # id (required) - The ID of a project + # user_id (required) - The ID of a user + # access_level (required) - Project access level + # Example Request: + # POST /projects/:id/members + post ":id/members" do + authorize! :admin_project, user_project + required_attributes! [:user_id, :access_level] + + # either the user is already a team member or a new one + team_member = user_project.team_member_by_id(params[:user_id]) + if team_member.nil? + team_member = user_project.users_projects.new( + user_id: params[:user_id], + project_access: params[:access_level] + ) + end + + if team_member.save + @member = team_member.user + present @member, with: Entities::ProjectMember, project: user_project + else + handle_project_member_errors team_member.errors + end + end + + # Update project team member + # + # Parameters: + # id (required) - The ID of a project + # user_id (required) - The ID of a team member + # access_level (required) - Project access level + # Example Request: + # PUT /projects/:id/members/:user_id + put ":id/members/:user_id" do + authorize! :admin_project, user_project + required_attributes! [:access_level] + + team_member = user_project.users_projects.find_by(user_id: params[:user_id]) + not_found!("User can not be found") if team_member.nil? + + if team_member.update_attributes(project_access: params[:access_level]) + @member = team_member.user + present @member, with: Entities::ProjectMember, project: user_project + else + handle_project_member_errors team_member.errors + end + end + + # Remove a team member from project + # + # Parameters: + # id (required) - The ID of a project + # user_id (required) - The ID of a team member + # Example Request: + # DELETE /projects/:id/members/:user_id + delete ":id/members/:user_id" do + authorize! :admin_project, user_project + team_member = user_project.users_projects.find_by(user_id: params[:user_id]) + unless team_member.nil? + team_member.destroy + else + {message: "Access revoked", id: params[:user_id].to_i} + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index bcca69ff49a..9d290c75ba9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -5,13 +5,6 @@ module API resource :projects do helpers do - def handle_project_member_errors(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - end - not_found! - end - def map_public_to_visibility_level(attrs) publik = attrs.delete(:public) publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik) @@ -55,7 +48,7 @@ module API # Example Request: # GET /projects/:id get ":id" do - present user_project, with: Entities::Project + present user_project, with: Entities::ProjectWithAccess, user: current_user end # Get a single project events @@ -196,104 +189,6 @@ module API user_project.forked_project_link.destroy end end - - # Get a project team members - # - # Parameters: - # id (required) - The ID of a project - # query - Query string - # Example Request: - # GET /projects/:id/members - get ":id/members" do - if params[:query].present? - @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%") - else - @members = paginate user_project.users - end - present @members, with: Entities::ProjectMember, project: user_project - end - - # Get a project team members - # - # Parameters: - # id (required) - The ID of a project - # user_id (required) - The ID of a user - # Example Request: - # GET /projects/:id/members/:user_id - get ":id/members/:user_id" do - @member = user_project.users.find params[:user_id] - present @member, with: Entities::ProjectMember, project: user_project - end - - # Add a new project team member - # - # Parameters: - # id (required) - The ID of a project - # user_id (required) - The ID of a user - # access_level (required) - Project access level - # Example Request: - # POST /projects/:id/members - post ":id/members" do - authorize! :admin_project, user_project - required_attributes! [:user_id, :access_level] - - # either the user is already a team member or a new one - team_member = user_project.team_member_by_id(params[:user_id]) - if team_member.nil? - team_member = user_project.users_projects.new( - user_id: params[:user_id], - project_access: params[:access_level] - ) - end - - if team_member.save - @member = team_member.user - present @member, with: Entities::ProjectMember, project: user_project - else - handle_project_member_errors team_member.errors - end - end - - # Update project team member - # - # Parameters: - # id (required) - The ID of a project - # user_id (required) - The ID of a team member - # access_level (required) - Project access level - # Example Request: - # PUT /projects/:id/members/:user_id - put ":id/members/:user_id" do - authorize! :admin_project, user_project - required_attributes! [:access_level] - - team_member = user_project.users_projects.find_by(user_id: params[:user_id]) - not_found!("User can not be found") if team_member.nil? - - if team_member.update_attributes(project_access: params[:access_level]) - @member = team_member.user - present @member, with: Entities::ProjectMember, project: user_project - else - handle_project_member_errors team_member.errors - end - end - - # Remove a team member from project - # - # Parameters: - # id (required) - The ID of a project - # user_id (required) - The ID of a team member - # Example Request: - # DELETE /projects/:id/members/:user_id - delete ":id/members/:user_id" do - authorize! :admin_project, user_project - team_member = user_project.users_projects.find_by(user_id: params[:user_id]) - unless team_member.nil? - team_member.destroy - else - {message: "Access revoked", id: params[:user_id].to_i} - end - end - # search for projects current_user has access to # # Parameters: @@ -320,6 +215,17 @@ module API @users = paginate @users present @users, with: Entities::User end + + # Get a project labels + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/labels + get ':id/labels' do + @labels = user_project.issues_labels + present @labels, with: Entities::Label + end end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 60c03ce1c04..c2f3b851c07 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,11 +1,9 @@ require_relative 'shell_env' -require_relative 'grack_helpers' module Grack class Auth < Rack::Auth::Basic - include Helpers - attr_accessor :user, :project, :ref, :env + attr_accessor :user, :project, :env def call(env) @env = env @@ -24,14 +22,16 @@ module Grack @env['SCRIPT_NAME'] = "" - auth! + if project + auth! + else + render_not_found + end end private def auth! - return render_not_found unless project - if @auth.provided? return bad_request unless @auth.basic? @@ -40,12 +40,8 @@ module Grack # Allow authentication for GitLab CI service # if valid token passed - if login == "gitlab-ci-token" && project.gitlab_ci? - token = project.gitlab_ci_service.token - - if token.present? && token == password && service_name == 'git-upload-pack' - return @app.call(env) - end + if gitlab_ci_request?(login, password) + return @app.call(env) end @user = authenticate_user(login, password) @@ -53,23 +49,26 @@ module Grack if @user Gitlab::ShellEnv.set_env(@user) @env['REMOTE_USER'] = @auth.username - else - return unauthorized end - - else - return unauthorized unless project.public? end - if authorized_git_request? + if authorized_request? @app.call(env) else unauthorized end end - def authorized_git_request? - authorize_request(service_name) + def gitlab_ci_request?(login, password) + if login == "gitlab-ci-token" && project.gitlab_ci? + token = project.gitlab_ci_service.token + + if token.present? && token == password && git_cmd == 'git-upload-pack' + return true + end + end + + false end def authenticate_user(login, password) @@ -77,31 +76,31 @@ module Grack auth.find(login, password) end - def authorize_request(service) - case service - when 'git-upload-pack' - can?(user, :download_code, project) - when'git-receive-pack' - refs.each do |ref| - action = if project.protected_branch?(ref) - :push_code_to_protected_branches - else - :push_code - end - - return false unless can?(user, action, project) + def authorized_request? + case git_cmd + when *Gitlab::GitAccess::DOWNLOAD_COMMANDS + if user + Gitlab::GitAccess.new.download_allowed?(user, project) + elsif project.public? + # Allow clone/fetch for public projects + true + else + false + end + when *Gitlab::GitAccess::PUSH_COMMANDS + if user + # Skip user authorization on upload request. + # It will be serverd by update hook in repository + true + else + false end - - # Never let git-receive-pack trough unauthenticated; it's - # harmless but git < 1.8 doesn't like it - return false if user.nil? - true else false end end - def service_name + def git_cmd if @request.get? @request.params['service'] elsif @request.post? @@ -115,28 +114,17 @@ module Grack @project ||= project_by_path(@request.path_info) end - def refs - @refs ||= parse_refs - end - - def parse_refs - input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/ - Zlib::GzipReader.new(@request.body).read - else - @request.body.read - end - - # Need to reset seek point - @request.body.rewind - - # Parse refs - refs = input.force_encoding('ascii-8bit').scan(/refs\/heads\/([\/\w\.-]+)/n).flatten.compact + def project_by_path(path) + if m = /^([\w\.\/-]+)\.git/.match(path).to_a + path_with_namespace = m.last + path_with_namespace.gsub!(/\.wiki$/, '') - # Cleanup grabare from refs - # if push to multiple branches - refs.map do |ref| - ref.gsub(/00.*/, "") + Project.find_with_namespace(path_with_namespace) end end + + def render_not_found + [404, {"Content-Type" => "text/plain"}, ["Not Found"]] + end end end diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb deleted file mode 100644 index cb747fe0137..00000000000 --- a/lib/gitlab/backend/grack_helpers.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Grack - module Helpers - def project_by_path(path) - if m = /^([\w\.\/-]+)\.git/.match(path).to_a - path_with_namespace = m.last - path_with_namespace.gsub!(/\.wiki$/, '') - - Project.find_with_namespace(path_with_namespace) - end - end - - def render_not_found - [404, {"Content-Type" => "text/plain"}, ["Not Found"]] - end - - def can?(object, action, subject) - abilities.allowed?(object, action, subject) - end - - def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end - end - end -end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 7121c8e40d2..b93800e235f 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -2,6 +2,12 @@ module Gitlab class Shell class AccessDenied < StandardError; end + class KeyAdder < Struct.new(:io) + def add_key(id, key) + io.puts("#{id}\t#{key.strip}") + end + end + # Init new repository # # name - project path with namespace @@ -130,6 +136,16 @@ module Gitlab system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content end + # Batch-add keys to authorized_keys + # + # Ex. + # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") } + def batch_add_keys(&block) + IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io| + block.call(KeyAdder.new(io)) + end + end + # Remove ssh key from gitlab shell # # Ex. diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb new file mode 100644 index 00000000000..1ab8f9213a3 --- /dev/null +++ b/lib/gitlab/git_access.rb @@ -0,0 +1,74 @@ +module Gitlab + class GitAccess + DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } + PUSH_COMMANDS = %w{ git-receive-pack } + + attr_reader :params, :project, :git_cmd, :user + + def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil) + case cmd + when *DOWNLOAD_COMMANDS + if actor.is_a? User + download_allowed?(actor, project) + elsif actor.is_a? DeployKey + actor.projects.include?(project) + elsif actor.is_a? Key + download_allowed?(actor.user, project) + else + raise 'Wrong actor' + end + when *PUSH_COMMANDS + if actor.is_a? User + push_allowed?(actor, project, ref, oldrev, newrev) + elsif actor.is_a? DeployKey + # Deploy key not allowed to push + return false + elsif actor.is_a? Key + push_allowed?(actor.user, project, ref, oldrev, newrev) + else + raise 'Wrong actor' + end + else + false + end + end + + def download_allowed?(user, project) + if user && user_allowed?(user) + user.can?(:download_code, project) + else + false + end + end + + def push_allowed?(user, project, ref, oldrev, newrev) + if user && user_allowed?(user) + action = if project.protected_branch?(ref) + :push_code_to_protected_branches + else + :push_code + end + user.can?(action, project) + else + false + end + end + + private + + def user_allowed?(user) + return false if user.blocked? + + if Gitlab.config.ldap.enabled + if user.ldap_user? + # Check if LDAP user exists and match LDAP user_filter + unless Gitlab::LDAP::Access.new.allowed?(user) + return false + end + end + end + + true + end + end +end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb new file mode 100644 index 00000000000..8f492e5c012 --- /dev/null +++ b/lib/gitlab/ldap/access.rb @@ -0,0 +1,23 @@ +module Gitlab + module LDAP + class Access + attr_reader :adapter + + def self.open(&block) + Gitlab::LDAP::Adapter.open do |adapter| + block.call(self.new(adapter)) + end + end + + def initialize(adapter=nil) + @adapter = adapter + end + + def allowed?(user) + !!Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) + rescue + false + end + end + end +end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb new file mode 100644 index 00000000000..983a2956a35 --- /dev/null +++ b/lib/gitlab/ldap/adapter.rb @@ -0,0 +1,86 @@ +module Gitlab + module LDAP + class Adapter + attr_reader :ldap + + def self.open(&block) + Net::LDAP.open(adapter_options) do |ldap| + block.call(self.new(ldap)) + end + end + + def self.config + Gitlab.config.ldap + end + + def self.adapter_options + encryption = config['method'].to_s == 'ssl' ? :simple_tls : nil + + 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) + end + options + end + + + def initialize(ldap=nil) + @ldap = ldap || Net::LDAP.new(self.class.adapter_options) + end + + def users(field, value) + if field.to_sym == :dn + options = { + base: value + } + else + options = { + 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']) + + options[:filter] = if options[:filter] + Net::LDAP::Filter.join(options[:filter], user_filter) + else + user_filter + end + end + + entries = ldap.search(options).select do |entry| + entry.respond_to? config.uid + end + + entries.map do |entry| + Gitlab::LDAP::Person.new(entry) + end + end + + def user(*args) + users(*args).first + end + + private + + def config + @config ||= self.class.config + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb new file mode 100644 index 00000000000..06b17c58f8c --- /dev/null +++ b/lib/gitlab/ldap/person.rb @@ -0,0 +1,50 @@ +module Gitlab + module LDAP + class Person + def self.find_by_uid(uid, adapter=nil) + adapter ||= Gitlab::LDAP::Adapter.new + adapter.user(config.uid, uid) + end + + def self.find_by_dn(dn, adapter=nil) + adapter ||= Gitlab::LDAP::Adapter.new + adapter.user('dn', dn) + end + + def initialize(entry) + Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } + @entry = entry + end + + def name + entry.cn.first + end + + def uid + entry.send(config.uid).first + end + + def username + uid + end + + def dn + entry.dn + end + + private + + def entry + @entry + end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new + end + + def config + @config ||= Gitlab.config.ldap + end + end + end +end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index fd36dda7d22..456a61b9e43 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -13,8 +13,8 @@ module Gitlab def find_or_create(auth) @auth = auth - if uid.blank? || email.blank? - raise_error("Account must provide an uid and email address") + if uid.blank? || email.blank? || username.blank? + raise_error("Account must provide a dn, uid and email address") end user = find(auth) @@ -62,8 +62,16 @@ module Gitlab return nil unless ldap_conf.enabled && login.present? && password.present? ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf) + filter = Net::LDAP::Filter.eq(ldap.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 + ldap_user = ldap.bind_as( - filter: Net::LDAP::Filter.eq(ldap.uid, login), + filter: filter, size: 1, password: password ) @@ -71,22 +79,20 @@ module Gitlab find_by_uid(ldap_user.dn) if ldap_user end - # Check LDAP user existance by dn. User in git over ssh check - # - # It covers 2 cases: - # * when ldap account was removed - # * when ldap account was deactivated by change of OU membership in 'dn' - def blocked?(dn) - ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf) - ldap.connection.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject, size: 1).blank? - end - private def find_by_uid(uid) model.where(provider: provider, extern_uid: uid).last end + def username + (auth.info.nickname || samaccountname).to_s.force_encoding("utf-8") + end + + def samaccountname + (auth.extra[:raw_info][:samaccountname] || []).first + end + def provider 'ldap' end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index dc9c0e0ab2c..80bb00821f7 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -98,7 +98,7 @@ module Gitlab (?<prefix>\W)? # Prefix ( # Reference @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name - |\#(?<issue>([a-zA-Z]+-)?\d+) # Issue ID + |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID |!(?<merge_request>\d+) # MR ID |\$(?<snippet>\d+) # Snippet ID |(?<commit>[\h]{6,40}) # Commit ID @@ -152,7 +152,7 @@ module Gitlab # # Returns boolean def valid_emoji?(emoji) - Emoji.names.include? emoji + Emoji.find_by_name emoji end # Private: Dispatches to a dedicated processing method based on reference diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index d18fc8bf2ce..e932b64f4f0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -7,7 +7,7 @@ module Gitlab end def project_name_regex - /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/ + /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/ end def name_regex @@ -49,7 +49,7 @@ module Gitlab protected def default_regex - /\A[.?]?[a-zA-Z0-9][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ + /\A[.?]?[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ end end end diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index bcf3012bd92..bdfcf254e9e 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -1,5 +1,9 @@ module Gitlab - class SatelliteNotExistError < StandardError; end + class SatelliteNotExistError < StandardError + def initialize(msg = "Satellite doesn't exist") + super + end + end module Satellite class Satellite @@ -17,14 +21,9 @@ module Gitlab Gitlab::Satellite::Logger.error(message) end - def raise_no_satellite - raise SatelliteNotExistError.new("Satellite doesn't exist") - end - def clear_and_update! - raise_no_satellite unless exists? + raise SatelliteNotExistError unless exists? - File.exists? path @repo = nil clear_working_dir! delete_heads! @@ -55,7 +54,7 @@ module Gitlab # * Changes the current directory to the satellite's working dir # * Yields def lock - raise_no_satellite unless exists? + raise SatelliteNotExistError unless exists? File.open(lock_file, "w+") do |f| begin @@ -77,7 +76,7 @@ module Gitlab end def repo - raise_no_satellite unless exists? + raise SatelliteNotExistError unless exists? @repo ||= Grit::Repo.new(path) end diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 3aa3b2ba1e9..39de1223b18 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -1,10 +1,29 @@ module Gitlab class Seeder def self.quiet + mute_mailer SeedFu.quiet = true yield SeedFu.quiet = false puts "\nOK".green end + + def self.by_user(user) + begin + Thread.current[:current_user] = user + yield + ensure + Thread.current[:current_user] = nil + end + end + + def self.mute_mailer + code = <<-eos +def Notify.delay + self +end + eos + eval(code) + end end end diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 0fe4888665d..0846359f9b1 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -1,3 +1,4 @@ +require_relative "popen" require_relative "version_info" module Gitlab diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 2e18b0592b5..86d8b69b0ef 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -46,8 +46,10 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end def preprocess(full_document) - if @project - h.create_relative_links(full_document, @project, @ref, @request_path, is_wiki?) + if is_wiki? + full_document + elsif @project + h.create_relative_links(full_document, @project, @ref, @request_path) else full_document end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index c6e570784e0..ff584e69058 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -40,7 +40,7 @@ test -f /etc/default/gitlab && . /etc/default/gitlab # Switch to the app_user if it is not he/she who is running the script. if [ "$USER" != "$app_user" ]; then - sudo -u "$app_user" -H -i $0 "$@"; exit; + eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit; fi # Switch to the gitlab path, exit on failure. diff --git a/lib/support/logrotate/gitlab b/lib/support/logrotate/gitlab index df9398d0795..d9b07b61ec3 100644 --- a/lib/support/logrotate/gitlab +++ b/lib/support/logrotate/gitlab @@ -2,21 +2,19 @@ # based on: http://stackoverflow.com/a/4883967 /home/git/gitlab/log/*.log { - weekly + daily missingok - rotate 52 + rotate 90 compress - delaycompress notifempty copytruncate } /home/git/gitlab-shell/gitlab-shell.log { - weekly + daily missingok - rotate 52 + rotate 90 compress - delaycompress notifempty copytruncate } diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 7a0f3efbb53..5bff362da0e 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -54,6 +54,14 @@ server { proxy_pass http://gitlab; } + # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression + location ~ ^/(assets)/ { + root /home/git/gitlab/public; + gzip_static on; # to serve pre-gzipped version + expires max; + add_header Cache-Control public; + } + error_page 502 /502.html; } diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index 7d3602211c1..058c7417040 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -1,10 +1,10 @@ +task dev: ["dev:setup"] + namespace :dev do desc "GITLAB | Setup developer environment (db, fixtures)" task :setup => :environment do ENV['force'] = 'yes' - Rake::Task["db:setup"].invoke - Rake::Task["db:seed_fu"].invoke + Rake::Task["gitlab:setup"].invoke Rake::Task["gitlab:shell:setup"].invoke end end - diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 767674e1e84..071760c0c36 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -17,6 +17,7 @@ namespace :gitlab do check_database_config_exists check_database_is_not_sqlite check_migrations_are_up + check_orphaned_users_groups check_gitlab_config_exists check_gitlab_config_not_outdated check_log_writable @@ -65,6 +66,7 @@ namespace :gitlab do puts "no".green else puts "yes".red + puts "Please fix this by removing the SQLite entry from the database.yml".blue for_more_information( "https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL", see_database_guide @@ -181,6 +183,19 @@ namespace :gitlab do end end + def check_orphaned_users_groups + print "Database contains orphaned UsersGroups? ... " + if UsersGroup.where("user_id not in (select id from users)").count > 0 + puts "yes".red + try_fixing_it( + "You can delete the orphaned records using something along the lines of:", + sudo_gitlab("bundle exec rails runner -e production 'UsersGroup.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'") + ) + else + puts "no".green + end + end + def check_satellites_exist print "Projects have satellites? ... " @@ -727,7 +742,7 @@ namespace :gitlab do end def check_gitlab_shell - required_version = Gitlab::VersionInfo.new(1, 7, 9) + required_version = Gitlab::VersionInfo.new(1, 9, 1) current_version = Gitlab::VersionInfo.parse(gitlab_shell_version) print "GitLab Shell version >= #{required_version} ? ... " diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake index 2b730774e06..853994dd67d 100644 --- a/lib/tasks/gitlab/setup.rake +++ b/lib/tasks/gitlab/setup.rake @@ -15,6 +15,14 @@ namespace :gitlab do end Rake::Task["db:setup"].invoke + + config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env] + success = case config["adapter"] + when /^mysql/ then + Rake::Task["add_limits_mysql"].invoke + when "postgresql" then + end + Rake::Task["db:seed_fu"].invoke rescue Gitlab::TaskAbortedByUserError puts "Quitting...".red diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 0d7a390bc92..08de0f2dd5d 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -34,14 +34,18 @@ namespace :gitlab do Gitlab::Shell.new.remove_all_keys - Key.find_each(batch_size: 1000) do |key| - if Gitlab::Shell.new.add_key(key.shell_id, key.key) + Gitlab::Shell.new.batch_add_keys do |adder| + Key.find_each(batch_size: 1000) do |key| + adder.add_key(key.shell_id, key.key) print '.' - else - print 'F' end end + unless $?.success? + puts "Failed to add keys...".red + exit 1 + end + rescue Gitlab::TaskAbortedByUserError puts "Quitting...".red exit 1 diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index d36b9682850..da61c6e007f 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -82,6 +82,8 @@ namespace :gitlab do def run(command) output, _ = Gitlab::Popen.popen(command) output + rescue Errno::ENOENT + '' # if the command does not exist, return an empty string end def uid_for(user_name) diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index f52af0c3ded..2c9b9978933 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -2,15 +2,13 @@ namespace :gitlab do desc "GITLAB | Run all tests" task :test do cmds = [ - %W(rake db:setup), - %W(rake db:seed_fu), %W(rake spinach), %W(rake spec), %W(rake jasmine:ci) ] cmds.each do |cmd| - system({'RAILS_ENV' => 'test'}, *cmd) + system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) raise "#{cmd} failed!" unless $?.exitstatus.zero? end diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake new file mode 100644 index 00000000000..46b6451752b --- /dev/null +++ b/lib/tasks/migrate/add_limits_mysql.rake @@ -0,0 +1,14 @@ +desc "GITLAB | Add limits to strings in mysql database" +task add_limits_mysql: :environment do + puts "Adding limits to schema.rb for mysql" + LimitsToMysql.new.up +end + +class LimitsToMysql < ActiveRecord::Migration + def up + change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647 + change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 + change_column :snippets, :content, :text, limit: 2147483647 + change_column :notes, :st_diff, :text, limit: 2147483647 + end +end diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake new file mode 100644 index 00000000000..90a1809914b --- /dev/null +++ b/lib/tasks/spec.rake @@ -0,0 +1,14 @@ +Rake::Task["spec"].clear if Rake::Task.task_defined?('spec') + +desc "GITLAB | Run specs" +task :spec do + cmds = [ + %W(rake gitlab:setup), + %W(rspec spec), + ] + + cmds.each do |cmd| + system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) + raise "#{cmd} failed!" unless $?.exitstatus.zero? + end +end diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake new file mode 100644 index 00000000000..c23d0e0e188 --- /dev/null +++ b/lib/tasks/spinach.rake @@ -0,0 +1,14 @@ +Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach') + +desc "GITLAB | Run spinach" +task :spinach do + cmds = [ + %W(rake gitlab:setup), + %W(spinach), + ] + + cmds.each do |cmd| + system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) + raise "#{cmd} failed!" unless $?.exitstatus.zero? + end +end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake new file mode 100644 index 00000000000..f19da1bb437 --- /dev/null +++ b/lib/tasks/test.rake @@ -0,0 +1,6 @@ +Rake::Task["test"].clear + +desc "GITLAB | Run all tests" +task :test do + Rake::Task["gitlab:test"].invoke +end |
