diff options
Diffstat (limited to 'lib/gitlab')
-rw-r--r-- | lib/gitlab/backend/grack_auth.rb | 106 | ||||
-rw-r--r-- | lib/gitlab/backend/grack_helpers.rb | 28 | ||||
-rw-r--r-- | lib/gitlab/backend/shell.rb | 16 | ||||
-rw-r--r-- | lib/gitlab/git_access.rb | 78 | ||||
-rw-r--r-- | lib/gitlab/ldap/access.rb | 23 | ||||
-rw-r--r-- | lib/gitlab/ldap/adapter.rb | 86 | ||||
-rw-r--r-- | lib/gitlab/ldap/person.rb | 50 | ||||
-rw-r--r-- | lib/gitlab/ldap/user.rb | 35 | ||||
-rw-r--r-- | lib/gitlab/logger.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/markdown.rb | 8 | ||||
-rw-r--r-- | lib/gitlab/oauth/user.rb | 13 | ||||
-rw-r--r-- | lib/gitlab/popen.rb | 14 | ||||
-rw-r--r-- | lib/gitlab/regex.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/satellite/satellite.rb | 19 | ||||
-rw-r--r-- | lib/gitlab/seeder.rb | 19 | ||||
-rw-r--r-- | lib/gitlab/upgrader.rb | 28 |
16 files changed, 399 insertions, 134 deletions
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..eefdb1833fc --- /dev/null +++ b/lib/gitlab/git_access.rb @@ -0,0 +1,78 @@ +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, forced_push = false) + 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, forced_push) + 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, forced_push) + 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, forced_push) + if user && user_allowed?(user) + action = if project.protected_branch?(ref) + if forced_push.to_s == 'true' + :force_push_code_to_protected_branches + else + :push_code_to_protected_branches + end + 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..01d86430f02 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,20 +79,19 @@ 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_and_provider + find_by_uid(uid) + end + def find_by_uid(uid) - model.where(provider: provider, extern_uid: uid).last + # LDAP distinguished name is case-insensitive + model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last + end + + def username + auth.info.nickname.to_s.force_encoding("utf-8") end def provider diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 389eef3395f..64cf3303ea3 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -11,12 +11,14 @@ module Gitlab def self.read_latest path = Rails.root.join("log", file_name) self.build unless File.exist?(path) - logs = `tail -n 2000 #{path}`.split("\n") + tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path})) + tail_output.split("\n") end def self.read_latest_for filename path = Rails.root.join("log", filename) - logs = `tail -n 2000 #{path}`.split("\n") + tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path})) + tail_output.split("\n") end def self.build diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index dc9c0e0ab2c..de14a3eca27 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 @@ -166,8 +166,8 @@ module Gitlab end def reference_user(identifier) - if member = @project.team_members.find { |user| user.username == identifier } - link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member + if user = User.find_by_username(identifier) + link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) end end diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 529753c4019..1bac93378ef 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -29,6 +29,17 @@ module Gitlab user = model.build_user(opts, as: :admin) user.skip_confirmation! + + # Services like twitter and github does not return email via oauth + # In this case we generate temporary email and force user to fill it later + if user.email.blank? + user.generate_tmp_oauth_email + else + # Google oauth returns email but dont return nickname + # So we use part of email as username for new user + user.username = email.match(/^[^@]*/)[0] + end + user.save! log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" @@ -58,7 +69,7 @@ module Gitlab end def username - email.match(/^[^@]*/)[0] + auth.info.nickname.to_s.force_encoding("utf-8") end def provider diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index 5283cf0b821..e2fbafb3899 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -1,8 +1,16 @@ require 'fileutils' +require 'open3' module Gitlab module Popen - def popen(cmd, path) + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise "System commands must be given as an array of strings" + end + + path ||= Dir.pwd vars = { "PWD" => path } options = { chdir: path } @@ -12,10 +20,10 @@ module Gitlab @cmd_output = "" @cmd_status = 0 - Open3.popen3(vars, cmd, options) do |stdin, stdout, stderr, wait_thr| - @cmd_status = wait_thr.value.exitstatus + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| @cmd_output << stdout.read @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus end return @cmd_output, @cmd_status 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 353c3024aad..8c62778d90d 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? + project.ensure_satellite_exists - File.exists? path @repo = nil clear_working_dir! delete_heads! @@ -33,7 +32,7 @@ module Gitlab end def create - output, status = popen("git clone #{project.repository.path_to_repo} #{path}", + output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}), Gitlab.config.satellites.path) log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}") @@ -55,7 +54,7 @@ module Gitlab # * Changes the current directory to the satellite's working dir # * Yields def lock - raise_no_satellite unless exists? + project.ensure_satellite_exists File.open(lock_file, "w+") do |f| begin @@ -77,7 +76,7 @@ module Gitlab end def repo - raise_no_satellite unless exists? + project.ensure_satellite_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 f46685e4bbe..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 @@ -42,28 +43,33 @@ module Gitlab end def latest_version_raw - git_tags = `git ls-remote --tags origin | grep tags\/v#{current_version.major}` - git_tags = git_tags.lines.to_a.select { |version| version =~ /v\d\.\d\.\d\Z/ } + remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags origin)) + git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) + git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ } last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s end def update_commands { - "Stash changed files" => "git stash", - "Get latest code" => "git fetch", - "Switch to new version" => "git checkout v#{latest_version}", - "Install gems" => "bundle", - "Migrate DB" => "bundle exec rake db:migrate RAILS_ENV=production", - "Recompile assets" => "bundle exec rake assets:clean assets:precompile RAILS_ENV=production", - "Clear cache" => "bundle exec rake cache:clear RAILS_ENV=production" + "Stash changed files" => %W(git stash), + "Get latest code" => %W(git fetch), + "Switch to new version" => %W(git checkout v#{latest_version}), + "Install gems" => %W(bundle), + "Migrate DB" => %W(bundle exec rake db:migrate), + "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile), + "Clear cache" => %W(bundle exec rake cache:clear) } end + def env + {'RAILS_ENV' => 'production'} + end + def upgrade update_commands.each do |title, cmd| puts title - puts " -> #{cmd}" - if system(cmd) + puts " -> #{cmd.join(' ')}" + if system(env, *cmd) puts " -> OK" else puts " -> FAILED" |