Skip to content

Commit

Permalink
Add HTTP Basic Auth support for requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
copiousfreetime committed Feb 13, 2012
1 parent 16396bf commit c3580d4
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 33 deletions.
15 changes: 15 additions & 0 deletions examples/auth_repo.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#-----------------------------------------------------------------------
# Example rackup file for an entire stickler stack with authorization
#
# -*- vim: set ft=ruby: -*-
#-----------------------------------------------------------------------
$:.unshift File.expand_path( File.join( File.dirname(__FILE__), "..", "lib" ) )

require 'stickler'

tmp = File.expand_path( File.join( File.dirname( __FILE__ ), "..", "spec", "data" ) )

use Rack::Auth::Basic, 'Secure Stickler' do |u,p|
(u == "stickler") and (p == "secret")
end
run Stickler::Server.new( tmp ).app
22 changes: 22 additions & 0 deletions lib/stickler/repository/basic_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'addressable/uri'

module Stickler::Repository
#
# Generate the authentication for basic auth request
#
class BasicAuthenticator
def self.handles?( uri )
%w[ http https ].include?( uri.scheme ) and uri.user and uri.password
end

def initialize( uri )
@user = uri.user
@password = uri.password
@cred = ["#{@user}:#{@password}"].pack('m').tr("\n", '')
end

def credentials
"Basic #{@cred}"
end
end
end
23 changes: 18 additions & 5 deletions lib/stickler/repository/remote.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'stickler/version'
require 'stickler/repository/api'
require 'stickler/repository/rubygems_authenticator'
require 'stickler/repository/basic_authenticator'
require 'stringio'

module ::Stickler::Repository
Expand All @@ -16,8 +17,8 @@ class Remote
attr_reader :authenticator

def initialize( repo_uri, options = {} )
@authenticator = options[:authenticator] || Stickler::Repository::RubygemsAuthenticator.new
@uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
@authenticator = load_authenticator( @uri )
@specs_list = nil
end

Expand Down Expand Up @@ -139,6 +140,17 @@ def ensure_http( uri )
return uri
end

def authenticator_class( uri )
[ RubygemsAuthenticator, BasicAuthenticator ].find { |a| a.handles?( uri ) }
end

def load_authenticator( uri )
if klass = authenticator_class( uri ) then
return klass.new( uri )
end
return nil
end

def full_uri_to_gem( spec )
gems_uri.join( spec.file_name )
end
Expand Down Expand Up @@ -200,7 +212,7 @@ def download_uri( uri )
def download_resource( resource )
resource_request( resource, :method => :get, :expects => [200] ).body
rescue Excon::Errors::Error => e
puts e.inspect
$stderr.puts e.inspect
return false
end

Expand Down Expand Up @@ -234,19 +246,20 @@ def resource_request( resource, params = {} )
begin
resource.connection[:headers]['User-Agent'] = "Stickler Client v#{Stickler::VERSION}"
resource.connection[:headers].delete('Authorization')
if authenticator.handles?( resource.connection[:scheme], resource.connection[:host] ) then
if authenticator then
resource.connection[:headers]['Authorization'] = authenticator.credentials
end
trys += 1
#puts "Making request #{resource.connection.inspect} with extra params #{params.inspect}"
resource.request( params )
rescue Excon::Errors::Unauthorized => unauth
uri = "#{unauth.request[:scheme]}://#{unauth.request[:host]}:#{unauth.request[:port]}#{unauth.request[:path]}"
raise Stickler::Repository::Error, "Not authorized to access #{uri}. Authorization needed for: #{unauth.response.headers['WWW-Authenticate']}"
rescue Excon::Errors::MovedPermanently, Excon::Errors::Found,
Excon::Errors::SeeOther, Excon::Errors::TemporaryRedirect => redirect
# follow a redirect, it is only allowable to follow redirects from a GET or
# HEAD request. Only follow a few times though.
raise redirect unless [ :get, :head ].include?( redirect.request[:method] )
raise redirect if trys > 5
#puts "Redirecting to #{redirect.response.headers['Location']}"
resource = Excon::Connection.new( redirect.response.headers['Location'],
{ :headers => resource.connection[:headers],
:query => resource.connection[:headers],
Expand Down
13 changes: 9 additions & 4 deletions lib/stickler/repository/rubygems_authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ def self.rubygems_uri
@rubygems_uri ||= Addressable::URI.parse( "https://rubygems.org" )
end

def self.handles?( uri )
return ( uri.scheme == rubygems_uri.scheme ) &&
( uri.host == rubygems_uri.host )
end

def initialize( uri )
# do nothing
end

def credentials
Gem.configuration.rubygems_api_key
end
Expand All @@ -19,9 +28,5 @@ def rubygems_uri
self.class.rubygems_uri
end

def handles?( scheme, host )
return ( scheme == rubygems_uri.scheme ) &&
( host == rubygems_uri.host )
end
end
end
88 changes: 64 additions & 24 deletions spec/repository/remote_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,41 @@

require 'stickler/repository/remote'

class SticklerTestServer
def initialize( spec_dir, ru_file )
@spec_dir = spec_dir
@repo_uri = "http://localhost:6789/"
@tmp_dir = File.join( @spec_dir, "tmp" )
FileUtils.mkdir_p( @tmp_dir )

@pid_file = File.join( @tmp_dir , "rack.pid" )
@ru_file = File.expand_path( File.join( @spec_dir, "..", "examples", ru_file ) )
@cmd = "rackup --port 6789 --pid #{@pid_file} --daemonize #{@ru_file}"
end

def start
system @cmd
tries = 0
loop do
begin
Excon.get( @repo_uri + "specs.#{Gem.marshal_version}.gz" )
#puts "rackup started with pid #{IO.read( @pid_file )}"
break
rescue => e
tries += 1
sleep tries * 0.1
end
end
end

def stop
pid = IO.read( @pid_file ).to_i
Process.kill( 'KILL', pid )
#FileUtils.rm_rf( @tmp_dir, :verbose => true )
FileUtils.rm_rf( @tmp_dir )
end
end

describe Stickler::Repository::Remote do
before do
@repo_uri = "http://localhost:6789/"
Expand All @@ -13,36 +48,41 @@

describe "Using a live server" do
before do
@tmp_dir = File.join( @spec_dir, "tmp" )
FileUtils.mkdir_p( @tmp_dir )

@pid_file = File.join( @tmp_dir , "rack.pid" )
@ru_file = File.expand_path( File.join( @spec_dir, "..", "examples", "gemcutter_repo.ru" ) )
cmd = "rackup --port 6789 --pid #{@pid_file} --daemonize #{@ru_file}"
#puts cmd
system cmd

tries = 0
loop do
begin
Excon.get( @repo_uri + "specs.#{Gem.marshal_version}.gz" )
#puts "rackup started with pid #{IO.read( @pid_file )}"
break
rescue => e
tries += 1
sleep tries * 0.1
end
end
@server = SticklerTestServer.new( @spec_dir, "gemcutter_repo.ru" )
@server.start
end

after do
pid = IO.read( @pid_file ).to_i
Process.kill( 'KILL', pid )
#FileUtils.rm_rf( @tmp_dir, :verbose => true )
FileUtils.rm_rf( @tmp_dir )
@server.stop
end

it_should_behave_like 'implements Repository::Api'
end

describe "Using a live authenticated server" do
before do
@server = SticklerTestServer.new( @spec_dir, "auth_repo.ru" )
@server.start
@foo_gem_local_path = File.join( @gems_dir, "foo-1.0.0.gem" )
@foo_spec = Stickler::SpecLite.new( 'foo', '1.0.0' )
@foo_digest = Digest::SHA1.hexdigest( IO.read( @foo_gem_local_path ) )
end

after do
@server.stop
end

it "should raise an an authentication denied error" do
repo = ::Stickler::Repository::Remote.new( "http://localhost:6789/")
lambda { repo.get( @foo_spec ) }.should raise_error( ::Stickler::Repository::Error, /Not authorized/ )
end

it "should connect with proper authentication" do
repo = ::Stickler::Repository::Remote.new( "http://stickler:secret@localhost:6789/")
data = repo.get( @foo_spec )
sha1 = Digest::SHA1.hexdigest( data )
sha1.should == @foo_digest
end
end
end

0 comments on commit c3580d4

Please sign in to comment.