thin-auth-ntlm 0.0.4
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/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
|
+
|