rack-auth-krb 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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