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 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,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 0
4
+ :patch: 4
@@ -0,0 +1,9 @@
1
+ require 'thin'
2
+
3
+ module Thin
4
+ autoload :NTLMConnection, 'thin/ntlm/connection'
5
+
6
+ module Backends
7
+ autoload :NTLMTcpServer, 'thin/ntlm/backends/tcp_server'
8
+ end
9
+ end
@@ -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
+