rack-auth-krb 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ vendor/
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gemspec
3
+
data/Gemfile.lock ADDED
@@ -0,0 +1,68 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-auth-krb (0.0.2)
5
+ gssapi
6
+ rkerberos
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ async-rack (0.5.1)
12
+ rack (~> 1.1)
13
+ diff-lcs (1.1.3)
14
+ em-synchrony (1.0.1)
15
+ eventmachine (>= 1.0.0.beta.1)
16
+ eventmachine (1.0.0.beta.4)
17
+ ffi (1.0.11)
18
+ goliath (0.9.4)
19
+ async-rack
20
+ em-synchrony (>= 1.0.0)
21
+ eventmachine (>= 1.0.0.beta.3)
22
+ http_parser.rb
23
+ http_router (~> 0.9.0)
24
+ log4r
25
+ multi_json
26
+ rack (>= 1.2.2)
27
+ rack-contrib
28
+ rack-respond_to
29
+ gssapi (1.1.1)
30
+ ffi (>= 1.0.1)
31
+ http_parser.rb (0.5.3)
32
+ http_router (0.9.7)
33
+ rack (>= 1.0.0)
34
+ url_mount (~> 0.2.1)
35
+ log4r (1.1.10)
36
+ multi_json (1.3.4)
37
+ puma (1.3.0)
38
+ rack (~> 1.2)
39
+ rack (1.4.1)
40
+ rack-accept-media-types (0.9)
41
+ rack-contrib (1.1.0)
42
+ rack (>= 0.9.1)
43
+ rack-respond_to (0.9.8)
44
+ rack-accept-media-types (>= 0.6)
45
+ rake (0.9.2.2)
46
+ rake-compiler (0.8.1)
47
+ rake
48
+ rkerberos (0.1.0)
49
+ rake-compiler
50
+ rspec (2.9.0)
51
+ rspec-core (~> 2.9.0)
52
+ rspec-expectations (~> 2.9.0)
53
+ rspec-mocks (~> 2.9.0)
54
+ rspec-core (2.9.0)
55
+ rspec-expectations (2.9.1)
56
+ diff-lcs (~> 1.1.3)
57
+ rspec-mocks (2.9.0)
58
+ url_mount (0.2.1)
59
+ rack
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ goliath
66
+ puma
67
+ rack-auth-krb!
68
+ rspec (~> 2.0)
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ rack-auth-krb
2
+ =============
3
+
4
+ Kerberos/GSSAPI authentication (Basic and Negotiate) rack middleware.
5
+
6
+ Actually this middleware should (hopefully) work for standard Rack
7
+ application and as a Goliath middleware.
8
+
9
+ Dependencies
10
+ ============
11
+ Kerberos should be installed and configured on the server.
12
+
13
+ If you do want to share the authentication through your application,
14
+ you'll need to have a Rack::Session middleware inserted before you in
15
+ the loop.
16
+
17
+ Rack applications
18
+ =================
19
+
20
+ ```ruby
21
+ require 'rack/auth/krb/basic_and_nego'
22
+
23
+ infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello #{env['REMOTE_USER']}"]]}
24
+
25
+ use Rack::Session::Cookie
26
+ use Rack::Logger, ::Logger::DEBUG
27
+ use Rack::Auth::Krb::BasicAndNego, 'my realm', 'my keytab'
28
+
29
+ map '/' do
30
+ run infinity
31
+ end
32
+ ```
33
+
34
+
35
+ Goliath applications
36
+ ====================
37
+
38
+ ```ruby
39
+ require 'rack/session/cookie'
40
+ require 'goliath'
41
+ require 'goliath/rack/auth/krb/basic_and_nego'
42
+
43
+ class DumpHeaders < Goliath::API
44
+ # Must be placed *before* BasicAndNego if we want it to use sessions !
45
+ use Rack::Session::Cookie
46
+ use Goliath::Rack::Auth::Krb::BasicAndNego, 'my realm', 'my keytab'
47
+
48
+ def on_headers(env, headers)
49
+ env.logger.info 'received headers: ' + headers.inspect
50
+ end
51
+
52
+ def response(env)
53
+ [200, {}, "Hello #{env['REMOTE_USER']}"]
54
+ end
55
+ end
56
+ ```
57
+
@@ -0,0 +1,20 @@
1
+ require 'basic_and_nego/auth/responses'
2
+
3
+ module BasicAndNego
4
+ module Auth
5
+ class Base
6
+ include Responses
7
+
8
+ attr_reader :response, :client_name, :headers
9
+
10
+ def initialize(request, logger, realm, keytab, service)
11
+ @request = request
12
+ @logger = logger
13
+ @realm = realm
14
+ @keytab = keytab
15
+ @service = service
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ require 'basic_and_nego/auth/base'
2
+ require 'basic_and_nego/auth/krb'
3
+
4
+ module BasicAndNego
5
+ module Auth
6
+ class Basic < Base
7
+
8
+ def initialize(request, logger, realm, keytab, service)
9
+ super
10
+ @krb = BasicAndNego::Auth::Krb.new(@logger, @realm, @keytab)
11
+ end
12
+
13
+ def process
14
+ @logger.debug "Basic scheme proposed by client"
15
+ user, password = @request.credentials
16
+ authenticate(user, password)
17
+ @client_name = user unless @response
18
+ end
19
+
20
+ private
21
+
22
+ def authenticate(user, password)
23
+ unless @krb.authenticate(user, password)
24
+ @logger.debug "Unable to authenticate (401)"
25
+ @response = unauthorized
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ require 'gssapi'
2
+
3
+ module BasicAndNego
4
+ module Auth
5
+ class GSS
6
+ attr_reader :gssapi, :logger
7
+
8
+ #
9
+ # Can raise GSSAPI::GssApiError
10
+ #
11
+ def initialize(logger, service, realm, keytab)
12
+ @logger = logger
13
+ @service = service
14
+ @realm = realm
15
+ @keytab = keytab
16
+ @gssapi = GSSAPI::Simple.new(@realm, @service, @keytab)
17
+
18
+ gssapi.acquire_credentials
19
+ end
20
+
21
+ #
22
+ # Attempt to authenticate the furnished token against gssapi
23
+ #
24
+ # It return nil (in case of error) or the token sent back
25
+ # by the gssapi if the authentication is successfull
26
+ #
27
+ def authenticate(token)
28
+ return gssapi.accept_context(token)
29
+ end
30
+
31
+ def display_name
32
+ return gssapi.display_name
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ require "rkerberos"
2
+
3
+ module BasicAndNego
4
+ module Auth
5
+ class Krb
6
+ attr_reader :realm, :keytab, :logger
7
+
8
+ def initialize(logger, realm, keytab)
9
+ @logger = logger
10
+ @realm = realm
11
+ @keytab = keytab
12
+ end
13
+
14
+ def authenticate(user, passwd)
15
+ successfull = false
16
+ Kerberos::Krb5.new do |krb5|
17
+ begin
18
+ krb5.get_init_creds_password(user, passwd)
19
+ successfull = true
20
+ rescue Kerberos::Krb5::Exception => e
21
+ logger.error "Failed to authenticate user '#{user}': #{e.message}"
22
+ end
23
+ end
24
+ successfull
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ require 'basic_and_nego/auth/base'
2
+ require 'basic_and_nego/auth/gss'
3
+ require 'base64'
4
+
5
+ module BasicAndNego
6
+ module Auth
7
+ class Negotiate < Base
8
+
9
+ def initialize(request, logger, realm, keytab, service)
10
+ super
11
+ setup_gss
12
+ end
13
+
14
+ def process
15
+ @logger.debug "Negotiate scheme proposed by client"
16
+ authenticate unless @response
17
+ verify_token unless @response
18
+ set_headers unless @response
19
+ end
20
+
21
+ private
22
+
23
+ def setup_gss
24
+ @gss = BasicAndNego::Auth::GSS.new(@logger, @service, @realm, @keytab)
25
+ rescue GSSAPI::GssApiError => e
26
+ @logger.error "Unable to setup GSSAPI: #{e.message}"
27
+ @response = error
28
+ end
29
+
30
+ def authenticate
31
+ token = ::Base64.strict_decode64(@request.params)
32
+ @out_tok = @gss.authenticate(token)
33
+ rescue GSSAPI::GssApiError => e
34
+ @logger.error "Unable to authenticate: #{e.message}"
35
+ @response = unauthorized
36
+ end
37
+
38
+ def verify_token
39
+ if !@out_tok
40
+ @logger.debug "Unable to authenticate (401)"
41
+ @response = unauthorized
42
+ end
43
+ end
44
+
45
+ def set_headers
46
+ tok_b64 = ::Base64.strict_encode64(@out_tok)
47
+ @headers = {'WWW-Authenticate' => "Negotiate #{tok_b64}"}
48
+ @client_name = @gss.display_name
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ require 'basic_and_nego/auth/base'
2
+
3
+ module BasicAndNego
4
+ module Auth
5
+ class None < Base
6
+
7
+ def process
8
+ @logger.debug "No authorization key provided: asking for one (401)"
9
+ @response = unauthorized
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ module BasicAndNego
2
+ module Auth
3
+ module Responses
4
+
5
+ def challenge
6
+ ["Negotiate", "Basic"]
7
+ end
8
+
9
+ def unauthorized
10
+ [ 401,
11
+ { 'Content-Type' => 'text/plain',
12
+ 'Content-Length' => '0',
13
+ 'WWW-Authenticate' => challenge },
14
+ []
15
+ ]
16
+ end
17
+
18
+ def bad_request
19
+ [ 400,
20
+ { 'Content-Type' => 'text/plain',
21
+ 'Content-Length' => '0' },
22
+ []
23
+ ]
24
+ end
25
+
26
+ def error
27
+ [ 500,
28
+ { 'Content-Type' => 'text/plain',
29
+ 'Content-Length' => '0' },
30
+ []
31
+ ]
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ require 'basic_and_nego/auth/base'
2
+
3
+ module BasicAndNego
4
+ module Auth
5
+ class Unsupported < Base
6
+
7
+ def process
8
+ @logger.debug "Unsupported authentication type"
9
+ @response = bad_request
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ require 'basic_and_nego/auth/none'
2
+ require 'basic_and_nego/auth/basic'
3
+ require 'basic_and_nego/auth/negotiate'
4
+ require 'basic_and_nego/auth/unsupported'
@@ -0,0 +1,10 @@
1
+ module BasicAndNego
2
+ class NullLogger
3
+ def info(progname = nil, &block); end
4
+ def debug(progname = nil, &block); end
5
+ def warn(progname = nil, &block); end
6
+ def error(progname = nil, &block); end
7
+ def fatal(progname = nil, &block); end
8
+ end
9
+ end
10
+
@@ -0,0 +1,59 @@
1
+ require 'socket'
2
+ require 'basic_and_nego/request'
3
+ require 'basic_and_nego/auth'
4
+
5
+ module BasicAndNego
6
+ class Processor
7
+ DEFAULT_SERVICE = "http@#{Socket::gethostname}"
8
+
9
+ def initialize(env, logger, realm, keytab, service)
10
+ @env = env
11
+ @logger = logger
12
+ @realm = realm
13
+ @keytab = keytab
14
+ @service = service || DEFAULT_SERVICE
15
+
16
+ @request = BasicAndNego::Request.new(@env)
17
+ @authenticator = @request.authenticator.new(@request, @logger, @realm, @keytab, @service)
18
+ @session = @env['rack.session']
19
+ end
20
+
21
+ def process_request
22
+ if authenticated
23
+ @logger.debug "User #{@session['REMOTE_USER']} already authenticated"
24
+ @env['REMOTE_USER'] = @session['REMOTE_USER']
25
+ else
26
+ @logger.debug "User not authenticated : delegate to authenticator"
27
+
28
+ if authenticate
29
+ @env['REMOTE_USER'] = client_name
30
+ @session['REMOTE_USER'] = client_name if @session
31
+ end
32
+ end
33
+ end
34
+
35
+ def client_name
36
+ @authenticator.client_name
37
+ end
38
+
39
+ def headers
40
+ @authenticator.headers || {}
41
+ end
42
+
43
+ def response
44
+ @authenticator.response
45
+ end
46
+
47
+ private
48
+
49
+ def authenticated
50
+ @session && @session['REMOTE_USER']
51
+ end
52
+
53
+ def authenticate
54
+ @authenticator.process
55
+ @authenticator.response.nil?
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,30 @@
1
+ require 'rack/auth/abstract/request'
2
+ module BasicAndNego
3
+ class Request < Rack::Auth::AbstractRequest
4
+ attr_reader :credentials
5
+
6
+ def authenticator
7
+ if !provided?
8
+ BasicAndNego::Auth::None
9
+ elsif supported_auth?
10
+ BasicAndNego::Auth.const_get(scheme.to_s.capitalize)
11
+ else
12
+ BasicAndNego::Auth::Unsupported
13
+ end
14
+ end
15
+
16
+ def credentials
17
+ @credentials ||= params.unpack("m*").first.split(/:/, 2)
18
+ end
19
+
20
+ def username
21
+ credentials.first
22
+ end
23
+
24
+ private
25
+
26
+ def supported_auth?
27
+ [:basic, :negotiate].include? scheme
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ require 'goliath'
2
+ require 'basic_and_nego/request'
3
+ require 'basic_and_nego/processor'
4
+
5
+ module Goliath
6
+ module Rack
7
+ module Auth
8
+ module Krb
9
+ class BasicAndNego
10
+ include Goliath::Rack::AsyncMiddleware
11
+
12
+ def initialize(app, realm, keytab, service=nil)
13
+ @app = app
14
+ @realm = realm
15
+ @keytab = keytab
16
+ @service = service
17
+ end
18
+
19
+ def call(env)
20
+ a = ::BasicAndNego::Processor.new(env, env.logger, @realm, @keytab, @service)
21
+ a.process_request
22
+
23
+ return a.response if a.response
24
+
25
+ super(env, a.headers)
26
+ end
27
+
28
+ def post_process(env, status, headers, body, additional_headers)
29
+ [status, headers.merge(additional_headers), body]
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ require 'basic_and_nego/request'
2
+ require 'basic_and_nego/processor'
3
+
4
+ module Rack
5
+ module Auth
6
+ module Krb
7
+ class BasicAndNego
8
+
9
+ def initialize(app, realm, keytab, service=nil)
10
+ @app = app
11
+ @realm = realm
12
+ @keytab = keytab
13
+ @service = service
14
+ end
15
+
16
+ def call(env)
17
+ a = ::BasicAndNego::Processor.new(env, env['rack.logger'], @realm, @keytab, @service)
18
+ a.process_request
19
+
20
+ return a.response if a.response
21
+
22
+ status, headers, body = @app.call(env)
23
+
24
+ [status, headers.merge(a.headers), body]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rack/session/cookie'
5
+ require 'goliath'
6
+ require 'goliath/rack/auth/krb/basic_and_nego'
7
+
8
+ class DumpHeaders < Goliath::API
9
+ # Must be placed *before* BasicAndNego if we want it to use sessions !
10
+ use Rack::Session::Cookie
11
+ use Goliath::Rack::Auth::Krb::BasicAndNego, 'my realm', 'my keytab'
12
+
13
+ def on_headers(env, headers)
14
+ env.logger.info 'received headers: ' + headers.inspect
15
+ end
16
+
17
+ def response(env)
18
+ [200, {}, "Hello #{env['REMOTE_USER']}"]
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require 'rack/auth/krb/basic_and_nego'
2
+
3
+ infinity = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Hello #{env['REMOTE_USER']}"]]}
4
+
5
+ use Rack::Session::Cookie
6
+ use Rack::Logger, ::Logger::DEBUG
7
+ use Rack::Auth::Krb::BasicAndNego, 'my realm', 'my keytab'
8
+
9
+ map '/' do
10
+ run infinity
11
+ end
12
+
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ LD_LIBRARY_PATH=/usr/lib ruby -Ilib misc/goliath_dump_headers.rb -s -v -p 9090
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ LD_LIBRARY_PATH=/usr/lib bundle exec puma -Ilib misc/rack_dump_headers.ru
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'rack-auth-krb'
5
+ gem.version = '0.0.8'
6
+ gem.authors = ["Frederick Ros"]
7
+ gem.email = 'frederick.ros@gmail.com'
8
+ gem.homepage = 'https://github.com/sleeper/rack-auth-krb'
9
+ gem.summary = 'Kerberos/GSSAPI authentication (Basic and Negotiate) Rack library'
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
12
+
13
+ gem.add_dependency "rack"
14
+ gem.add_dependency "gssapi"
15
+ gem.add_dependency "rkerberos"
16
+
17
+ gem.add_development_dependency "rspec", "~> 2.0"
18
+ gem.add_development_dependency "goliath"
19
+ gem.add_development_dependency "puma"
20
+
21
+ gem.extra_rdoc_files = ['README.md']
22
+
23
+ gem.description = <<-EOF
24
+ This library allows Kerberos/GSSAPI authentication using either Basic method
25
+ or Negotiate (i.e. authentication without the need of user/password combo).
26
+ EOF
27
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/auth/basic'
3
+ require 'basic_and_nego/nulllogger'
4
+ require 'basic_and_nego/request'
5
+ require 'base64'
6
+
7
+ describe BasicAndNego::Auth::Basic do
8
+
9
+ before(:each) do
10
+ env = {'HTTP_AUTHORIZATION' => "Basic #{::Base64.encode64('fred:pass')}"}
11
+ @realm = "my realm"
12
+ @keytab = "my keytab"
13
+ @service = "http/hostname"
14
+ @logger = BasicAndNego::NullLogger.new
15
+ @request = BasicAndNego::Request.new(env)
16
+ @request.should_receive(:credentials).and_return(['fred', 'pass'])
17
+ @krb = double('kerberos').as_null_object
18
+ BasicAndNego::Auth::Krb.should_receive(:new).with(@logger, @realm, @keytab).and_return(@krb)
19
+ @a = BasicAndNego::Auth::Basic.new(@request, @logger, @realm, @keytab, @service)
20
+ end
21
+
22
+ it "should try authentication against Kerberos in case of Basic" do
23
+ @krb.should_receive(:authenticate).with("fred", "pass").and_return(true)
24
+ @a.process
25
+ end
26
+
27
+ it "should return 'unauthorized' if authentication fails" do
28
+ @krb.should_receive(:authenticate).and_return(false)
29
+ @a.process
30
+ @a.response.should_not be_nil
31
+ @a.response[0].should == 401
32
+ end
33
+
34
+ it "should return true if authentication worked" do
35
+ @krb.should_receive(:authenticate).and_return(true)
36
+ @a.process
37
+ @a.response.should be_nil
38
+ end
39
+
40
+ it "should set client's name if authentication worked" do
41
+ @krb.should_receive(:authenticate).and_return(true)
42
+ @a.process
43
+ @a.client_name.should == "fred"
44
+ end
45
+
46
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/auth/gss'
3
+
4
+ describe BasicAndNego::Auth::GSS do
5
+ let(:realm) { "my realm"}
6
+ let(:service) { "foo" }
7
+ let(:keytab) { "my keytab" }
8
+ let(:gssapi) { double("gss api").as_null_object }
9
+ let(:logger) { double('logger').as_null_object }
10
+ let(:good_request) { BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Negotiate VGhpcyBpcyBteSB0b2tlbg=="})}
11
+
12
+ it "should initialize and deal with gssapi" do
13
+ gssapi.should_receive(:acquire_credentials)
14
+ GSSAPI::Simple.should_receive(:new).with(realm, service, keytab).and_return(gssapi)
15
+ g = BasicAndNego::Auth::GSS.new(logger, service, realm, keytab)
16
+ end
17
+
18
+ it "should authenticate request" do
19
+ gssapi.should_receive(:acquire_credentials)
20
+ gssapi.should_receive(:accept_context).and_return("Granted")
21
+ GSSAPI::Simple.should_receive(:new).with(realm, service, keytab).and_return(gssapi)
22
+ g = BasicAndNego::Auth::GSS.new(logger, service, realm, keytab)
23
+ g.authenticate("My token").should == "Granted"
24
+ end
25
+
26
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/auth/krb'
3
+
4
+ describe BasicAndNego::Auth::Krb do
5
+ let(:logger) { double('logger').as_null_object }
6
+ let(:realm) {"my realm"}
7
+ let(:keytab) {"my keytab"}
8
+
9
+ it "should initialize" do
10
+ krb = BasicAndNego::Auth::Krb.new(logger, realm, keytab)
11
+ krb.realm.should == realm
12
+ end
13
+
14
+ it "should authenticate user/password" do
15
+ user = "fred"
16
+ passwd = "passwd"
17
+ k = double("rkerberos").as_null_object
18
+ k.should_receive(:get_init_creds_password).with(user, passwd).and_return(true)
19
+ Kerberos::Krb5.should_receive(:new).and_yield(k)
20
+ krb = BasicAndNego::Auth::Krb.new(logger, realm, keytab)
21
+ authenticated = krb.authenticate(user, passwd)
22
+ authenticated.should be_a(TrueClass)
23
+ authenticated.should be_true
24
+ end
25
+
26
+
27
+ it "should not-authenticate user if kerberos does not agree" do
28
+ user = "fred"
29
+ passwd = "passwd"
30
+ k = double("rkerberos").as_null_object
31
+ k.should_receive(:get_init_creds_password).with(user, passwd).and_raise(Kerberos::Krb5::Exception)
32
+ Kerberos::Krb5.should_receive(:new).and_yield(k)
33
+ krb = BasicAndNego::Auth::Krb.new(logger, realm, keytab)
34
+ authenticated = krb.authenticate(user, passwd)
35
+ authenticated.should be_a(FalseClass)
36
+ authenticated.should be_false
37
+ end
38
+
39
+ it "should catch exception from underlying system" do
40
+ user = "fred"
41
+ passwd = "passwd"
42
+ k = double("rkerberos").as_null_object
43
+ k.should_receive(:get_init_creds_password).with(user, passwd).and_raise(Kerberos::Krb5::Exception)
44
+ Kerberos::Krb5.should_receive(:new).and_yield(k)
45
+ krb = BasicAndNego::Auth::Krb.new(logger, realm, keytab)
46
+ krb.authenticate(user, passwd).should be_false
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/auth/negotiate'
3
+ require 'basic_and_nego/nulllogger'
4
+ require 'basic_and_nego/request'
5
+ require 'base64'
6
+
7
+ describe BasicAndNego::Auth::Negotiate do
8
+
9
+ before(:each) do
10
+ env = {'HTTP_AUTHORIZATION' => "Negotiate VGhpcyBpcyBteSB0b2tlbg=="}
11
+ @realm = "my realm"
12
+ @keytab = "my keytab"
13
+ @service = "http/hostname"
14
+ @logger = BasicAndNego::NullLogger.new
15
+ @request = BasicAndNego::Request.new(env)
16
+ @gss = double('gss').as_null_object
17
+ end
18
+
19
+ it "should try authentication against GSS in case of Negotiate" do
20
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
21
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
22
+ @gss.should_receive(:authenticate).and_return("Granted")
23
+ @gss.should_receive(:display_name).and_return("fred")
24
+ @a.process
25
+ end
26
+
27
+ it "should return 'unauthorized' if authentication fails" do
28
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
29
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
30
+ @gss.should_receive(:authenticate).and_return(nil)
31
+ @a.process
32
+ @a.response.should_not be_nil
33
+ @a.response[0].should == 401
34
+ end
35
+
36
+ it "should return true if authentication worked" do
37
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
38
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
39
+ @gss.should_receive(:authenticate).and_return("Granted")
40
+ @a.process
41
+ @a.response.should be_nil
42
+ end
43
+
44
+ it "should set client's name if authentication worked" do
45
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
46
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
47
+ @gss.should_receive(:authenticate).and_return("Granted")
48
+ @gss.should_receive(:display_name).and_return("fred")
49
+ @a.process
50
+ @a.client_name.should == "fred"
51
+ end
52
+
53
+ it "should set header to returned token if authentication worked" do
54
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
55
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
56
+ @gss.should_receive(:authenticate).and_return("Granted")
57
+ @gss.should_receive(:display_name).and_return("fred")
58
+ @a.process
59
+ @a.client_name.should == "fred"
60
+ @a.headers['WWW-Authenticate'].should == "Negotiate #{::Base64.strict_encode64('Granted')}"
61
+ end
62
+
63
+ it "should catch GSSAPI exceptions in getting credentials" do
64
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_raise(GSSAPI::GssApiError)
65
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
66
+ @a.process
67
+ @a.response.should_not be_nil
68
+ @a.response[0].should == 500
69
+ end
70
+
71
+ it "should catch GSSAPI exceptions in authenticating token" do
72
+ BasicAndNego::Auth::GSS.should_receive(:new).with(@logger, @service, @realm, @keytab).and_return(@gss)
73
+ @a = BasicAndNego::Auth::Negotiate.new(@request, @logger, @realm, @keytab, @service)
74
+ @gss.should_receive(:authenticate).and_raise(GSSAPI::GssApiError)
75
+ @a.process
76
+ @a.response.should_not be_nil
77
+ @a.response[0].should == 401
78
+ end
79
+
80
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/processor'
3
+ require 'basic_and_nego/nulllogger'
4
+
5
+ describe BasicAndNego::Processor do
6
+ it "should not re-authenticate user" do
7
+ env = {}
8
+ env['rack.session'] = {}
9
+ env['rack.session']['REMOTE_USER'] = 'fred'
10
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
11
+ a.process_request
12
+ a.response.should be_nil
13
+ a.headers.should be_empty
14
+ end
15
+
16
+ it "should try to authenticate if there's no session" do
17
+ env = {}
18
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
19
+ a.should_receive(:authenticate)
20
+ a.process_request
21
+ end
22
+
23
+ it "should set REMOTE_USER if user authenticated" do
24
+ env = {}
25
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
26
+ a.should_receive(:authenticate).and_return(true)
27
+ a.should_receive(:client_name).and_return("fred")
28
+ a.process_request
29
+ env['REMOTE_USER'].should == "fred"
30
+ end
31
+
32
+ it "should update the session if user authenticated" do
33
+ env = {}
34
+ env['rack.session'] = {}
35
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
36
+ a.should_receive(:authenticate).and_return(true)
37
+ a.should_receive(:client_name).twice.and_return("fred")
38
+ a.process_request
39
+ env['REMOTE_USER'].should == "fred"
40
+ env['rack.session']['REMOTE_USER'].should == 'fred'
41
+ end
42
+
43
+ it "should try to authenticate if user is not yet authenticated" do
44
+ env = {}
45
+ env['rack.session'] = {}
46
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
47
+ a.should_receive(:authenticate)
48
+ a.process_request
49
+ end
50
+
51
+ it "should ask for an authorization key if none is provided" do
52
+ env = {}
53
+ env['rack.session'] = {}
54
+ a = BasicAndNego::Processor.new(env, BasicAndNego::NullLogger.new, 'my realm', 'my keytab file', nil)
55
+ a.process_request
56
+ a.response.should_not be_nil
57
+ a.response[0].should == 401
58
+ end
59
+
60
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'basic_and_nego/request'
3
+ require 'base64'
4
+
5
+ describe BasicAndNego::Request do
6
+
7
+ it "should be able to detect a no auth" do
8
+ r = BasicAndNego::Request.new({})
9
+ r.authenticator.should == BasicAndNego::Auth::None
10
+ end
11
+
12
+ it "should be able to detect a 'basic' scheme" do
13
+ r = BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Basic #{Base64.encode64('fred:pass')}"})
14
+ r.authenticator.should == BasicAndNego::Auth::Basic
15
+ end
16
+
17
+ it "should be able to detect a 'negotiate' scheme" do
18
+ r = BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Negotiate #{Base64.encode64('fred:pass')}"})
19
+ r.authenticator.should == BasicAndNego::Auth::Negotiate
20
+ end
21
+
22
+ it "should be able to detect an unsupported auth" do
23
+ r = BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Digest #{Base64.encode64('fred:pass')}"})
24
+ r.authenticator.should == BasicAndNego::Auth::Unsupported
25
+ end
26
+
27
+ it "should decode credentials" do
28
+ r = BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Basic #{Base64.encode64('fred:pass')}"})
29
+ r.credentials.should =~ ["fred", "pass"]
30
+ end
31
+
32
+ it "should return username" do
33
+ r = BasicAndNego::Request.new({'HTTP_AUTHORIZATION' => "Basic #{Base64.encode64('fred:pass')}"})
34
+ r.username.should == "fred"
35
+ end
36
+
37
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/auth/krb/basic_and_nego'
3
+
4
+ describe Goliath::Rack::Auth::Krb::BasicAndNego do
5
+ it 'accepts an app' do
6
+ lambda { Goliath::Rack::Auth::Krb::BasicAndNego.new('my app', 'my realm', 'my keytab') }.should_not raise_error
7
+ end
8
+
9
+ describe 'with middleware' do
10
+ before(:each) do
11
+ @app = mock('app').as_null_object
12
+ @env = Goliath::Env.new
13
+ @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
14
+ @auth = Goliath::Rack::Auth::Krb::BasicAndNego.new(@app, 'my realm', 'my keytab')
15
+ end
16
+
17
+
18
+ it 'returns status, headers and body from the app' do
19
+ app_headers = {'Content-Type' => 'hash'}
20
+ app_body = {:a => 1, :b => 2}
21
+ p = double("processor").as_null_object
22
+ p.should_receive(:response).and_return(nil)
23
+ add_headers = {"fred" => "foo"}
24
+ p.should_receive(:headers).and_return(add_headers)
25
+ ::BasicAndNego::Processor.should_receive(:new).and_return(p)
26
+ p.should_receive(:process_request)
27
+ @app.should_receive(:call).and_return([200, app_headers, app_body])
28
+
29
+ status, headers, body = @auth.call(@env)
30
+ status.should == 200
31
+ headers['fred'].should == "foo"
32
+ body.should == app_body
33
+ end
34
+
35
+ it "returns error in case of failing authentication" do
36
+ app_headers = {'Content-Type' => 'hash'}
37
+ app_body = {:a => 1, :b => 2}
38
+ p = double("processor").as_null_object
39
+ r = [401, {}, "foo"]
40
+ p.should_receive(:response).twice.and_return(r)
41
+ p.should_receive(:process_request)
42
+ ::BasicAndNego::Processor.should_receive(:new).and_return(p)
43
+
44
+ response = @auth.call(@env)
45
+ response.should =~ r
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'rack/auth/krb/basic_and_nego'
3
+ require 'basic_and_nego/nulllogger'
4
+
5
+ class SessionAuthentified
6
+ attr_accessor :app
7
+ def initialize(app,configs = {})
8
+ @app = app
9
+ end
10
+
11
+ def call(e)
12
+ e['rack.session'] ||= {}
13
+ e['rack.session']['REMOTE_USER'] = "fred"
14
+ @app.call(e)
15
+ end
16
+ end # session
17
+
18
+ describe "Rack::Auth::Krb::BasicAndNego" do
19
+
20
+ before(:each) do
21
+ @basic_app = lambda{|env| [200,{'Content-Type' => 'text/plain'},'OK']}
22
+ @env = env_with_params("/", {}, {'rack.logger' => BasicAndNego::NullLogger.new})
23
+ end
24
+
25
+ it "should return a 401 if authentication failed" do
26
+ app = setup_rack(@basic_app)
27
+ p = double("processor").as_null_object
28
+ p.should_receive(:response).twice.and_return(not_authorized_response)
29
+ p.should_receive(:process_request)
30
+ ::BasicAndNego::Processor.should_receive(:new).and_return(p)
31
+
32
+ app.call(@env).first.should == 401
33
+ end
34
+
35
+ it "should not ask for authentication if client is already authenticated" do
36
+ app = setup_rack(@basic_app, {:session => SessionAuthentified})
37
+ app.call(@env).first.should == 200
38
+
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ $:.unshift File.expand_path(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'rack'
6
+
7
+ def env_with_params(path = "/", params = {}, env = {})
8
+ method = params.delete(:method) || "GET"
9
+ env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env)
10
+ Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env)
11
+ end
12
+
13
+ def setup_rack(app = nil, opts={}, &block)
14
+ app ||= block if block_given?
15
+
16
+ Rack::Builder.new do
17
+ use opts[:session] if opts[:session]
18
+ use Rack::Auth::Krb::BasicAndNego, 'my realm', 'my keytab'
19
+ run app
20
+ end
21
+ end
22
+
23
+ def not_authorized_response
24
+ [ 401,
25
+ { 'Content-Type' => 'text/plain',
26
+ 'Content-Length' => '0',
27
+ 'WWW-Authenticate' => ["Negotiate", "Basic"]},
28
+ []
29
+ ]
30
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-auth-krb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Frederick Ros
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: gssapi
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rkerberos
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: goliath
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: puma
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: ! " This library allows Kerberos/GSSAPI authentication using either
111
+ Basic method\n or Negotiate (i.e. authentication without the need of user/password
112
+ combo).\n"
113
+ email: frederick.ros@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - README.md
118
+ files:
119
+ - .gitignore
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - README.md
123
+ - lib/basic_and_nego/auth.rb
124
+ - lib/basic_and_nego/auth/base.rb
125
+ - lib/basic_and_nego/auth/basic.rb
126
+ - lib/basic_and_nego/auth/gss.rb
127
+ - lib/basic_and_nego/auth/krb.rb
128
+ - lib/basic_and_nego/auth/negotiate.rb
129
+ - lib/basic_and_nego/auth/none.rb
130
+ - lib/basic_and_nego/auth/responses.rb
131
+ - lib/basic_and_nego/auth/unsupported.rb
132
+ - lib/basic_and_nego/nulllogger.rb
133
+ - lib/basic_and_nego/processor.rb
134
+ - lib/basic_and_nego/request.rb
135
+ - lib/goliath/rack/auth/krb/basic_and_nego.rb
136
+ - lib/rack/auth/krb/basic_and_nego.rb
137
+ - misc/goliath_dump_headers.rb
138
+ - misc/rack_dump_headers.ru
139
+ - misc/start_goliath_srv.sh
140
+ - misc/start_puma_srv.sh
141
+ - rack-auth-krb.gemspec
142
+ - spec/basic_and_nego/auth/basic_spec.rb
143
+ - spec/basic_and_nego/auth/gss_spec.rb
144
+ - spec/basic_and_nego/auth/krb_spec.rb
145
+ - spec/basic_and_nego/auth/negotiate_spec.rb
146
+ - spec/basic_and_nego/processor_spec.rb
147
+ - spec/basic_and_nego/request_spec.rb
148
+ - spec/goliath/goliath_krb_spec.rb
149
+ - spec/rack/rack_krb_spec.rb
150
+ - spec/spec_helper.rb
151
+ homepage: https://github.com/sleeper/rack-auth-krb
152
+ licenses: []
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ! '>='
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubyforge_project:
171
+ rubygems_version: 1.8.24
172
+ signing_key:
173
+ specification_version: 3
174
+ summary: Kerberos/GSSAPI authentication (Basic and Negotiate) Rack library
175
+ test_files:
176
+ - spec/basic_and_nego/auth/basic_spec.rb
177
+ - spec/basic_and_nego/auth/gss_spec.rb
178
+ - spec/basic_and_nego/auth/krb_spec.rb
179
+ - spec/basic_and_nego/auth/negotiate_spec.rb
180
+ - spec/basic_and_nego/processor_spec.rb
181
+ - spec/basic_and_nego/request_spec.rb
182
+ - spec/goliath/goliath_krb_spec.rb
183
+ - spec/rack/rack_krb_spec.rb
184
+ - spec/spec_helper.rb