Integrate grack into rails stack. Implement basic http auth for grack. Allow only downcase for uname and unixname. Refactor and code cleanup. Refs #2243

This commit is contained in:
Pavel Chipiga 2011-11-15 23:58:27 +02:00
parent b36cc24a64
commit b2be92e4d1
12 changed files with 87 additions and 8 deletions

View File

@ -21,6 +21,8 @@ gem "will_paginate", "~> 3.0.2"
gem 'meta-tags', '~> 1.2.4', :require => 'meta_tags'
gem "russian"
# gem 'ghoul_grack', '~> 0.0.1'
gem 'grack', :git => 'git://github.com/rdblue/grack.git', :require => 'git_http'
gem "grit"
gem 'gitolito', :git => 'git@github.com:warpc/gitolito.git'
gem 'whenever', :require => false

View File

@ -12,6 +12,12 @@ GIT
specs:
rails-xmlrpc (0.3.1)
GIT
remote: git://github.com/rdblue/grack.git
revision: 020be3fef3fb308b9d214252522aa5945bf6584a
specs:
grack (0.2.0)
GIT
remote: git@github.com:warpc/gitolito.git
revision: 120992e79d079800f6efb07f100ab80437b20b43
@ -281,6 +287,7 @@ DEPENDENCIES
devise (~> 1.4.8)
factory_girl_rails (~> 1.3.0)
gitolito!
grack!
grit
haml-rails (~> 0.3.4)
hirb

View File

@ -16,9 +16,8 @@ class Group < ActiveRecord::Base
has_many :platforms, :through => :targets, :source => :target, :source_type => 'Platform', :autosave => true
has_many :repositories, :through => :targets, :source => :target, :source_type => 'Repository', :autosave => true
validates :name, :uname, :owner_id, :presence => true
validates :name, :uname, :uniqueness => true
validates :uname, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validates :name, :owner, :presence => true
validates :uname, :presence => true, :uniqueness => {:case_sensitive => false}, :format => { :with => /^[a-z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validate { errors.add(:uname, :taken) if User.where('uname LIKE ?', uname).present? }
attr_readonly :uname

View File

@ -16,7 +16,7 @@ class Platform < ActiveRecord::Base
has_many :groups, :through => :objects, :source => :object, :source_type => 'Group'
validates :name, :presence => true, :uniqueness => true
validates :unixname, :uniqueness => true, :presence => true, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validates :unixname, :uniqueness => true, :presence => true, :format => { :with => /^[a-z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validates :distrib_type, :presence => true, :allow_nil => :false, :allow_blank => false, :inclusion => {:in => APP_CONFIG['distr_types']}
after_create :make_owner_rel

View File

@ -15,10 +15,9 @@ class Project < ActiveRecord::Base
has_many :relations, :as => :target, :dependent => :destroy
has_many :collaborators, :through => :relations, :source => :object, :source_type => 'User'
has_many :groups, :through => :relations, :source => :object, :source_type => 'Group'
has_many :auto_build_lists, :dependent => :destroy
validates :name, :uniqueness => {:scope => [:owner_id, :owner_type]}, :presence => true, :allow_nil => false, :allow_blank => false
validates :unixname, :uniqueness => {:scope => [:owner_id, :owner_type]}, :presence => true, :format => { :with => /^[a-zA-Z0-9_\-\+\.]+$/ }, :allow_nil => false, :allow_blank => false
validates :unixname, :uniqueness => {:scope => [:owner_id, :owner_type]}, :presence => true, :format => { :with => /^[a-z0-9_\-\+\.]+$/ }, :allow_nil => false, :allow_blank => false
validates :owner, :presence => true
validate {errors.add(:base, I18n.t('flash.project.save_warning_ssh_key')) if owner.ssh_key.blank?}

View File

@ -12,7 +12,7 @@ class Repository < ActiveRecord::Base
has_many :groups, :through => :objects, :source => :object, :source_type => 'Group'
validates :name, :uniqueness => {:scope => :platform_id}, :presence => true
validates :unixname, :uniqueness => {:scope => :platform_id}, :presence => true, :format => { :with => /^[a-zA-Z0-9\-.]+$/ }
validates :unixname, :uniqueness => {:scope => :platform_id}, :presence => true, :format => { :with => /^[a-z0-9\-.]+$/ }
# validates :platform_id, :presence => true # if you uncomment this platform clone will not work
scope :recent, order("name ASC")

View File

@ -24,7 +24,7 @@ class User < ActiveRecord::Base
include PersonalRepository
validates :uname, :presence => true, :uniqueness => {:case_sensitive => false}, :format => { :with => /^[a-zA-Z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validates :uname, :presence => true, :uniqueness => {:case_sensitive => false}, :format => { :with => /^[a-z0-9_]+$/ }, :allow_nil => false, :allow_blank => false
validates :ssh_key, :uniqueness => true, :allow_blank => true
validate { errors.add(:uname, :taken) if Group.where('uname LIKE ?', uname).present? }
#TODO: Replace this simple cross-table uniq validation by more progressive analog

View File

@ -1 +1,8 @@
APP_CONFIG = YAML.load_file("#{Rails.root}/config/application.yml")[Rails.env]
# Setup Smart HTTP GRack
require 'grack'
config = {:project_root => File.join(APP_CONFIG['root_path'], 'git_projects'), :git_path => '/opt/local/bin/git', :upload_pack => true, :receive_pack => true}
# Rosa::Application.config.middleware.insert_before ::ActionDispatch::Static, ::Grack::Handler, config
Rosa::Application.config.middleware.insert_after ::Rails::Rack::Logger, ::Grack::Handler, config
Rosa::Application.config.middleware.insert_before ::Grack::Handler, ::Grack::Auth

7
lib/grack.rb Normal file
View File

@ -0,0 +1,7 @@
module Grack
extend ActiveSupport::Autoload
autoload :Base
autoload :Auth
autoload :Handler
end

31
lib/grack/auth.rb Normal file
View File

@ -0,0 +1,31 @@
module Grack
class Auth < Base
def initialize(app)
@app = app
end
def call(env)
if git?(env)
uname, unixname = env['PATH_INFO'].split('/')[1,2]
unixname.gsub! /\.git$/, ''
owner = User.find_by_uname(uname) || Group.find_by_uname(uname)
project = Project.where(:owner_id => owner.id, :owner_type => owner.class).find_by_unixname(unixname)
return [404, {}, []] if project.blank?
# TODO r/rw ?
unless project.public? # need auth
::Rack::Auth::Basic.new(@app) do |user, password|
user = User.find_for_database_authentication(:login => user) and user.valid_password?(password) and
users = project.collaborators << project.groups.map(&:members).flatten and users.include?(user) # TODO ACL ?
# env['REQUEST_METHOD'] == 'GET' # read
# env['REQUEST_METHOD'] == 'POST' # write
end.call(env)
else
@app.call(env)
end
else
@app.call(env)
end
end
end
end

11
lib/grack/base.rb Normal file
View File

@ -0,0 +1,11 @@
module Grack
class Base # abstract
def git?(env)
env['HTTP_USER_AGENT'] =~ /git\//
end
end
end
# ({"HTTP_ACCEPT"=>"*/*", "HTTP_HOST"=>"localhost:3000", "SERVER_NAME"=>"localhost", "rack.url_scheme"=>"http", "PASSENGER_CONNECT_PASSWORD"=>"xbRC6murG5bIDTsaed8ksaZhjf8yFsadlX4QL0qWNbS", "HTTP_USER_AGENT"=>"git/1.7.7.2", "PASSENGER_SPAWN_METHOD"=>"smart-lv2", "PASSENGER_FRIENDLY_ERROR_PAGES"=>"true", "CONTENT_LENGTH"=>"0", "rack.errors"=>#<IO:0x108494a90>, "SERVER_PROTOCOL"=>"HTTP/1.1", "action_dispatch.secret_token"=>"df2fb72d477491cf15ef0f93449bcb59c3412c255c2386e07772935565c1b6ad23539ed804b8f12e3221e47abb78f5b679693c391acb33477be0e633e7a2e2a4", "rack.run_once"=>false, "rack.version"=>[1, 0], "REMOTE_ADDR"=>"127.0.0.1", "SERVER_SOFTWARE"=>"nginx/1.0.6", "PASSENGER_MIN_INSTANCES"=>"1", "PATH_INFO"=>"/codefoundry.git/info/refs", "SERVER_ADDR"=>"127.0.0.1", "SCRIPT_NAME"=>"", "action_dispatch.parameter_filter"=>[:password], "action_dispatch.show_exceptions"=>true, "rack.multithread"=>false, "PASSENGER_USER"=>"", "PASSENGER_ENVIRONMENT"=>"development", "PASSENGER_SHOW_VERSION_IN_HEADER"=>"true", "rack.multiprocess"=>true, "REMOTE_PORT"=>"49387", "REQUEST_URI"=>"/codefoundry.git/info/refs", "SERVER_PORT"=>"3000", "SCGI"=>"1", "PASSENGER_APP_TYPE"=>"rack", "PASSENGER_USE_GLOBAL_QUEUE"=>"true", "REQUEST_METHOD"=>"GET", "PASSENGER_GROUP"=>"", "PASSENGER_DEBUGGER"=>"false", "DOCUMENT_ROOT"=>"/Users/pasha/Sites/rosa-build/public", "_"=>"_", "PASSENGER_FRAMEWORK_SPAWNER_IDLE_TIME"=>"-1", "UNION_STATION_SUPPORT"=>"false", "rack.input"=>#<PhusionPassenger::Utils::RewindableInput:0x10bb55a20 @rewindable_io=nil, @io=#<PhusionPassenger::Utils::UnseekableSocket:0x10bb56c90 @socket=#<UNIXSocket:0x10bb56b28>>, @unlinked=false>, "HTTP_PRAGMA"=>"no-cache", "QUERY_STRING"=>"", "PASSENGER_APP_SPAWNER_IDLE_TIME"=>"-1"}) (process 41940, thread #<Thread:0x1084a1268>)
# {"rack.session"=>{}, "HTTP_ACCEPT"=>"*/*", "HTTP_HOST"=>"localhost:3000", "SERVER_NAME"=>"localhost", "action_dispatch.remote_ip"=>#<ActionDispatch::RemoteIp::RemoteIpGetter:0x10b621338 @check_ip_spoofing=true, @env={...}, @trusted_proxies=/(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)/i>, "rack.url_scheme"=>"http", "PASSENGER_CONNECT_PASSWORD"=>"dljpLA91qGH4v2gwaccoAxFysOmSkEFbRtPyPOe9953", "HTTP_USER_AGENT"=>"git/1.7.7.2", "PASSENGER_SPAWN_METHOD"=>"smart-lv2", "PASSENGER_FRIENDLY_ERROR_PAGES"=>"true", "CONTENT_LENGTH"=>"0", "action_dispatch.request.unsigned_session_cookie"=>{}, "rack.errors"=>#<IO:0x10724baa0>, "SERVER_PROTOCOL"=>"HTTP/1.1", "action_dispatch.secret_token"=>"df2fb72d477491cf15ef0f93449bcb59c3412c255c2386e07772935565c1b6ad23539ed804b8f12e3221e47abb78f5b679693c391acb33477be0e633e7a2e2a4", "rack.run_once"=>false, "rack.version"=>[1, 0], "REMOTE_ADDR"=>"127.0.0.1", "SERVER_SOFTWARE"=>"nginx/1.0.6", "PASSENGER_MIN_INSTANCES"=>"1", "PATH_INFO"=>"/pasha/mc.git/info/refs", "SERVER_ADDR"=>"127.0.0.1", "SCRIPT_NAME"=>"", "action_dispatch.parameter_filter"=>[:password], "action_dispatch.show_exceptions"=>true, "rack.multithread"=>false, "PASSENGER_USER"=>"", "PASSENGER_ENVIRONMENT"=>"development", "PASSENGER_SHOW_VERSION_IN_HEADER"=>"true", "action_dispatch.cookies"=>{}, "rack.multiprocess"=>true, "REMOTE_PORT"=>"49643", "REQUEST_URI"=>"/pasha/mc.git/info/refs", "SERVER_PORT"=>"3000", "SCGI"=>"1", "PASSENGER_APP_TYPE"=>"rack", "PASSENGER_USE_GLOBAL_QUEUE"=>"true", "rack.session.options"=>{:httponly=>true, :expire_after=>nil, :domain=>nil, :path=>"/", :secure=>false, :id=>nil}, "REQUEST_METHOD"=>"GET", "PASSENGER_GROUP"=>"", "PASSENGER_DEBUGGER"=>"false", "DOCUMENT_ROOT"=>"/Users/pasha/Sites/rosa-build/public", "warden"=>Warden::Proxy:2242130160 @config={:failure_app=>Devise::FailureApp, :default_scope=>:user, :intercept_401=>false, :scope_defaults=>{}, :default_strategies=>{:user=>[:rememberable, :database_authenticatable]}}, "_"=>"_", "PASSENGER_FRAMEWORK_SPAWNER_IDLE_TIME"=>"-1", "UNION_STATION_SUPPORT"=>"false", "rack.input"=>#<PhusionPassenger::Utils::RewindableInput:0x10b6225f8 @rewindable_io=nil, @io=#<PhusionPassenger::Utils::UnseekableSocket:0x10a8f5a10 @socket=#<UNIXSocket:0x10b623700>>, @unlinked=false>, "HTTP_PRAGMA"=>"no-cache", "QUERY_STRING"=>"", "PASSENGER_APP_SPAWNER_IDLE_TIME"=>"-1"}
#<Grack::Handler:0x10fe1c1d8 @app=#<ActionDispatch::ShowExceptions:0x10fe1c278 @app=#<ActionDispatch::RemoteIp:0x10fe1c390 @check_ip_spoofing=true, @app=#<Rack::Sendfile:0x10fe1c430 @variation="", @app=#<ActionDispatch::Callbacks:0x10fe1c728 @app=#<ActiveRecord::ConnectionAdapters::ConnectionManagement:0x10fe1c958 @app=#<ActiveRecord::QueryCache:0x10fe1cc00 @app=#<ActionDispatch::Cookies:0x10fe1cd68 @app=#<ActionDispatch::Session::CookieStore:0x10fe1ce58 @app=#<ActionDispatch::Flash:0x10fe1cef8 @app=#<ActionDispatch::ParamsParser:0x10fe1d8f8 @app=#<Rack::MethodOverride:0x10fe1d998 @app=#<ActionDispatch::Head:0x10fe1da38 @app=#<ActionDispatch::BestStandardsSupport:0x10fe1db00 @app=#<Warden::Manager:0x10fe1dd30 @app=#<Sass::Plugin::Rack:0x10fe1de20 @app=#<OmniAuth::Strategies::OpenID>, @check_after=1321397135.42431, @dwell=1.0>, @config={:failure_app=>Devise::FailureApp, :intercept_401=>false, :default_scope=>:user, :scope_defaults=>{}, :default_strategies=>{:user=>[:rememberable, :database_authenticatable]}}>, @header="IE=Edge">>>, @parsers={#<Mime::Type:0x10e0ebc58 @string="application/xml", @synonyms=["text/xml", "application/x-xml"], @symbol=:xml>=>:xml_simple, #<Mime::Type:0x10e0eace0 @string="application/json", @synonyms=["text/x-json", "application/jsonrequest"], @symbol=:json>=>:json}>>, @default_options={:expire_after=>nil, :httponly=>true, :path=>"/", :domain=>nil, :secure=>false}, @key="_rosa_session", @cookie_only=true>>>>, @prepare_each_request=true>>, @trusted_proxies=/(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)/i>, @consider_all_requests_local=true>, @config={:receive_pack=>true, :git_path=>"/opt/local/bin/git", :upload_pack=>true, :project_root=>"/var/rosa/git_projects"}>

16
lib/grack/handler.rb Normal file
View File

@ -0,0 +1,16 @@
module Grack
class Handler < Base
def initialize(app, config)
@app = app
@config = config
end
def call(env)
if git?(env)
::GitHttp::App.new(@config).call(env)
else
@app.call(env)
end
end
end
end