thin-auth-ntlm 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +18 -0
- data/README.txt +11 -0
- data/Rakefile +14 -0
- data/VERSION.yml +4 -0
- data/lib/thin-auth-ntlm.rb +9 -0
- data/lib/thin/ntlm/backends/tcp_server.rb +13 -0
- data/lib/thin/ntlm/connection.rb +180 -0
- data/thin-auth-ntlm.gemspec +48 -0
- metadata +82 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2009 Alexey Borzenkov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= NTLM Authentication for Thin
|
2
|
+
|
3
|
+
Allows you to force NTLM authentication on Thin TCP servers.
|
4
|
+
|
5
|
+
= Using thin-auth-ntlm
|
6
|
+
|
7
|
+
Just start your thin server using NTLMTcpServer backend:
|
8
|
+
|
9
|
+
thin -r thin-auth-ntlm -b Thin::Backends::NTLMTcpServer start
|
10
|
+
|
11
|
+
Remote username will be available as request.remote_user.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "thin-auth-ntlm"
|
5
|
+
gemspec.summary = "Allows you to force NTLM authentication on Thin TCP servers."
|
6
|
+
gemspec.author = "Alexey Borzenkov"
|
7
|
+
gemspec.email = "snaury@gmail.com"
|
8
|
+
|
9
|
+
gemspec.add_dependency('thin', '>= 1.0.0')
|
10
|
+
gemspec.add_dependency('rubysspi-server', '>= 0.0.1')
|
11
|
+
end
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
14
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Thin
|
2
|
+
module Backends
|
3
|
+
class NTLMTcpServer < TcpServer
|
4
|
+
def initialize(host, port, options)
|
5
|
+
super(host, port)
|
6
|
+
end
|
7
|
+
|
8
|
+
def connect
|
9
|
+
@signature = EventMachine.start_server(@host, @port, NTLMConnection, &method(:initialize_connection))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'weakref'
|
2
|
+
require 'win32/sspi/server'
|
3
|
+
|
4
|
+
module Thin
|
5
|
+
class NTLMWrapper
|
6
|
+
AUTHORIZATION_MESSAGE = <<END
|
7
|
+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
8
|
+
<html><head>
|
9
|
+
<title>401 NTLM Authorization Required</title>
|
10
|
+
</head><body>
|
11
|
+
<h1>NTLM Authorization Required</h1>
|
12
|
+
<p>This server could not verify that you
|
13
|
+
are authorized to access the document
|
14
|
+
requested. Either you supplied the wrong
|
15
|
+
credentials (e.g., bad password), or your
|
16
|
+
browser doesn't understand how to supply
|
17
|
+
the credentials required.</p>
|
18
|
+
</body></html>
|
19
|
+
END
|
20
|
+
REMOTE_USER = 'REMOTE_USER'.freeze
|
21
|
+
HTTP_AUTHORIZATION = 'HTTP_AUTHORIZATION'.freeze
|
22
|
+
WWW_AUTHENTICATE = 'WWW-Authenticate'.freeze
|
23
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
24
|
+
CONTENT_TYPE_AUTH = 'text/html; charset=iso-8859-1'.freeze
|
25
|
+
NTLM_REQUEST_PACKAGE = 'NTLM'.freeze
|
26
|
+
NTLM_ALLOWED_PACKAGE = 'NTLM|Negotiate'.freeze
|
27
|
+
|
28
|
+
def initialize(app, connection)
|
29
|
+
@app = app
|
30
|
+
@connection = WeakRef.new(connection)
|
31
|
+
end
|
32
|
+
|
33
|
+
def deferred?(env)
|
34
|
+
@app.respond_to?(:deferred?) && @app.deferred?(env)
|
35
|
+
end
|
36
|
+
|
37
|
+
def persistent?
|
38
|
+
@connection.request.persistent?
|
39
|
+
end
|
40
|
+
|
41
|
+
def can_persist!
|
42
|
+
@connection.can_persist!
|
43
|
+
end
|
44
|
+
|
45
|
+
def ntlm_start
|
46
|
+
@connection.ntlm_start
|
47
|
+
end
|
48
|
+
|
49
|
+
def ntlm_stop
|
50
|
+
@connection.ntlm_stop
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(env)
|
54
|
+
# check if browser wants to reauthenticate
|
55
|
+
if @authenticated_as && http_authorization(env)
|
56
|
+
@authenticated_as = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# require authentication
|
60
|
+
unless @authenticated_as
|
61
|
+
ntlm_start
|
62
|
+
@authentication_stage ||= 1
|
63
|
+
result = process(env)
|
64
|
+
return result unless @authenticated_as
|
65
|
+
ntlm_stop
|
66
|
+
end
|
67
|
+
|
68
|
+
# pass thru
|
69
|
+
env[REMOTE_USER] = @authenticated_as
|
70
|
+
@app.call(env)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns stripped HTTP-Authorization header, nil if none or empty
|
74
|
+
def http_authorization(env)
|
75
|
+
auth = env[HTTP_AUTHORIZATION]
|
76
|
+
if auth
|
77
|
+
auth = auth.strip
|
78
|
+
auth = nil if auth.empty?
|
79
|
+
end
|
80
|
+
auth
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns token type and value from HTTP-Authorization header
|
84
|
+
def token(env)
|
85
|
+
auth = http_authorization(env)
|
86
|
+
return [nil, nil] unless auth && auth.match(/\A(#{NTLM_ALLOWED_PACKAGE}) (.*)\Z/)
|
87
|
+
[$1, Base64.decode64($2.strip)]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Acquires new OS credentials handle
|
91
|
+
def acquire(package = 'NTLM')
|
92
|
+
cleanup
|
93
|
+
@ntlm = Win32::SSPI::NegotiateServer.new(package)
|
94
|
+
@ntlm.acquire_credentials_handle
|
95
|
+
@ntlm
|
96
|
+
end
|
97
|
+
|
98
|
+
# Frees credentials handle, if acquired
|
99
|
+
def cleanup
|
100
|
+
if @ntlm
|
101
|
+
@ntlm.cleanup rescue nil
|
102
|
+
@ntlm = nil
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Processes current authentication stage
|
108
|
+
# Returns rack response if authentication is incomplete
|
109
|
+
# Sets @authenticated_as to username if authentication successful
|
110
|
+
def process(env)
|
111
|
+
case @authentication_stage
|
112
|
+
when 1 # we are waiting for type1 message
|
113
|
+
package, t1 = token(env)
|
114
|
+
return request_auth(NTLM_REQUEST_PACKAGE, false) if t1.nil?
|
115
|
+
return request_auth unless persistent?
|
116
|
+
begin
|
117
|
+
acquire(package)
|
118
|
+
t2 = @ntlm.accept_security_context(t1)
|
119
|
+
rescue
|
120
|
+
return request_auth
|
121
|
+
end
|
122
|
+
request_auth("#{package} #{t2}", false, 2)
|
123
|
+
when 2 # we are waiting for type3 message
|
124
|
+
package, t3 = token(env)
|
125
|
+
return request_auth(NTLM_REQUEST_PACKAGE, false) if t3.nil?
|
126
|
+
return request_auth unless package == @ntlm.package
|
127
|
+
return request_auth unless persistent?
|
128
|
+
begin
|
129
|
+
t2 = @ntlm.accept_security_context(t3)
|
130
|
+
@authenticated_as = @ntlm.get_username_from_context
|
131
|
+
@authentication_stage = nil # in case IE wants to reauthenticate
|
132
|
+
rescue
|
133
|
+
return request_auth
|
134
|
+
end
|
135
|
+
return request_auth unless @authenticated_as
|
136
|
+
cleanup
|
137
|
+
else
|
138
|
+
raise "Invalid value for @authentication_stage=#{@authentication_stage} detected"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns response with authentication request to the client
|
143
|
+
def request_auth(auth = nil, finished = true, next_stage = 1)
|
144
|
+
@authentication_stage = next_stage
|
145
|
+
can_persist! unless finished
|
146
|
+
head = {}
|
147
|
+
head[WWW_AUTHENTICATE] = auth if auth
|
148
|
+
head[CONTENT_TYPE] = CONTENT_TYPE_AUTH
|
149
|
+
[401, head, [AUTHORIZATION_MESSAGE]]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class NTLMConnection < Connection
|
154
|
+
def app=(app)
|
155
|
+
super NTLMWrapper.new(app, self)
|
156
|
+
end
|
157
|
+
|
158
|
+
def unbind
|
159
|
+
@app.cleanup if @app && @app.respond_to?(:cleanup)
|
160
|
+
ensure
|
161
|
+
super
|
162
|
+
end
|
163
|
+
|
164
|
+
# Saves original can_persist? value (NTLM will force persistence)
|
165
|
+
def ntlm_start
|
166
|
+
unless @ntlm_in_progress
|
167
|
+
@ntlm_saved_can_persist = @can_persist
|
168
|
+
@ntlm_in_progress = true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Restores previous can_persist? value
|
173
|
+
def ntlm_stop
|
174
|
+
if @ntlm_in_progress
|
175
|
+
@can_persist = @ntlm_saved_can_persist
|
176
|
+
@ntlm_in_progress = false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{thin-auth-ntlm}
|
8
|
+
s.version = "0.0.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Alexey Borzenkov"]
|
12
|
+
s.date = %q{2009-08-13}
|
13
|
+
s.email = %q{snaury@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE.txt",
|
16
|
+
"README.txt"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"LICENSE.txt",
|
20
|
+
"README.txt",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"lib/thin-auth-ntlm.rb",
|
24
|
+
"lib/thin/ntlm/backends/tcp_server.rb",
|
25
|
+
"lib/thin/ntlm/connection.rb",
|
26
|
+
"thin-auth-ntlm.gemspec"
|
27
|
+
]
|
28
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = %q{1.3.4}
|
31
|
+
s.summary = %q{Allows you to force NTLM authentication on Thin TCP servers.}
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
35
|
+
s.specification_version = 3
|
36
|
+
|
37
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
38
|
+
s.add_runtime_dependency(%q<thin>, [">= 1.0.0"])
|
39
|
+
s.add_runtime_dependency(%q<rubysspi-server>, [">= 0.0.1"])
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<thin>, [">= 1.0.0"])
|
42
|
+
s.add_dependency(%q<rubysspi-server>, [">= 0.0.1"])
|
43
|
+
end
|
44
|
+
else
|
45
|
+
s.add_dependency(%q<thin>, [">= 1.0.0"])
|
46
|
+
s.add_dependency(%q<rubysspi-server>, [">= 0.0.1"])
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thin-auth-ntlm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexey Borzenkov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-13 00:00:00 +04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thin
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rubysspi-server
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.1
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: snaury@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE.txt
|
43
|
+
- README.txt
|
44
|
+
files:
|
45
|
+
- LICENSE.txt
|
46
|
+
- README.txt
|
47
|
+
- Rakefile
|
48
|
+
- VERSION.yml
|
49
|
+
- lib/thin-auth-ntlm.rb
|
50
|
+
- lib/thin/ntlm/backends/tcp_server.rb
|
51
|
+
- lib/thin/ntlm/connection.rb
|
52
|
+
- thin-auth-ntlm.gemspec
|
53
|
+
has_rdoc: true
|
54
|
+
homepage:
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Allows you to force NTLM authentication on Thin TCP servers.
|
81
|
+
test_files: []
|
82
|
+
|