rack-unbasic 0.1
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 +3 -0
- data/LICENSE +22 -0
- data/README.rdoc +38 -0
- data/Rakefile +34 -0
- data/lib/rack/unbasic.rb +106 -0
- data/rack-unbasic.gemspec +34 -0
- data/test/test_unbasic.rb +121 -0
- metadata +91 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2009 Pat Nakajima and Nicolas Sanguinetti
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
= Rack::Unbasic
|
2
|
+
|
3
|
+
Wraps HTTP authentication in more friendly workflows.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
require "rack-unbasic"
|
8
|
+
|
9
|
+
use Rack::Unbasic do |on|
|
10
|
+
on.bad_request '/login'
|
11
|
+
on.unauthorized '/login'
|
12
|
+
end
|
13
|
+
|
14
|
+
A response with a 401 or 400 status code will be redirected to the routes you
|
15
|
+
specify in the config, with the <tt>\env['rack-unbasic.code']</tt> set to whatever
|
16
|
+
the original status code was, and <tt>\env['rack-unbasic.return-to']</tt> set to
|
17
|
+
whatever the requested URI was.
|
18
|
+
|
19
|
+
When a request comes in with <tt>:username</tt> and <tt>:password</tt> params,
|
20
|
+
those will be mapped to basic auth credentials, and the appropriate headers will
|
21
|
+
be added.
|
22
|
+
|
23
|
+
Once authorized, the appropriate credentials will be stashed in the session.
|
24
|
+
Subsequent requests will use the credentials in the session for authorization.
|
25
|
+
|
26
|
+
== Install
|
27
|
+
|
28
|
+
gem install rack-unbasic
|
29
|
+
|
30
|
+
Or cloning the git source repository:
|
31
|
+
|
32
|
+
git clone git://github.com/foca/rack-unbasic.git
|
33
|
+
|
34
|
+
== Credits
|
35
|
+
|
36
|
+
Idea:: Pat Nakajima (github[http://github.com/nakajima])
|
37
|
+
Initial implementation:: Nicolás Sanguinetti (github[http://github.com/foca])
|
38
|
+
License:: MIT. See attached LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
|
3
|
+
rdoc_sources = %w(hanna/rdoctask rdoc/task rake/rdoctask)
|
4
|
+
begin
|
5
|
+
require rdoc_sources.shift
|
6
|
+
rescue LoadError
|
7
|
+
retry
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require "metric_fu" if RUBY_VERSION < "1.9"
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require "mg"
|
17
|
+
MG.new("rack-unbasic.gemspec")
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Default: run all tests"
|
22
|
+
task :default => :test
|
23
|
+
|
24
|
+
desc "Run library tests"
|
25
|
+
Rake::TestTask.new do |t|
|
26
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::RDocTask.new do |rd|
|
30
|
+
rd.main = "README"
|
31
|
+
rd.title = "Documentation for Rack::Unbasic"
|
32
|
+
rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
|
33
|
+
rd.rdoc_dir = "doc"
|
34
|
+
end
|
data/lib/rack/unbasic.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "rack"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Unbasic
|
5
|
+
# Create the middleware. You can pass a block to configure how to handle
|
6
|
+
# 401 and 400 responses from the downstream app. See #unauthorized and
|
7
|
+
# #bad_request.
|
8
|
+
def initialize(app, &block) # :yields: self
|
9
|
+
@app = app
|
10
|
+
@locations = {}
|
11
|
+
block.call(self) if block
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set the redirect URL for 401 responses from the downstream app.
|
15
|
+
def unauthorized(location)
|
16
|
+
@locations["unauthorized"] = location
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set the redirect URL for 400 responses from the downstream app.
|
20
|
+
def bad_request(location)
|
21
|
+
@locations["bad_request"] = location
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
@env = env
|
26
|
+
|
27
|
+
clean_session_data
|
28
|
+
|
29
|
+
authorize_from_params || authorize_from_session
|
30
|
+
|
31
|
+
@response = @app.call(@env)
|
32
|
+
|
33
|
+
case status_code
|
34
|
+
when 401
|
35
|
+
unauthorized_response
|
36
|
+
when 400
|
37
|
+
bad_request_response
|
38
|
+
else
|
39
|
+
store_credentials
|
40
|
+
@response
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def unauthorized_response
|
47
|
+
return @response if @locations["unauthorized"].nil?
|
48
|
+
store_location_and_response_code
|
49
|
+
[302, {"Location" => @locations["unauthorized"]}, []]
|
50
|
+
end
|
51
|
+
|
52
|
+
def bad_request_response
|
53
|
+
return @response if @locations["bad_request"].nil?
|
54
|
+
store_location_and_response_code
|
55
|
+
[302, {"Location" => @locations["bad_request"]}, []]
|
56
|
+
end
|
57
|
+
|
58
|
+
def store_location_and_response_code
|
59
|
+
session["rack-unbasic.return-to"] = @env["PATH_INFO"]
|
60
|
+
session["rack-unbasic.code"] = status_code
|
61
|
+
end
|
62
|
+
|
63
|
+
def clean_session_data
|
64
|
+
unless session.respond_to?("delete")
|
65
|
+
raise "You need to enable sessions for this middleware to work"
|
66
|
+
end
|
67
|
+
@env["rack-unbasic.return-to"] = session.delete("rack-unbasic.return-to")
|
68
|
+
@env["rack-unbasic.code"] = session.delete("rack-unbasic.code")
|
69
|
+
end
|
70
|
+
|
71
|
+
def authorize_from_params
|
72
|
+
return nil if request.params["username"].nil? || request.params["password"].nil?
|
73
|
+
send_http_authorization(request.params["username"], request.params["password"])
|
74
|
+
end
|
75
|
+
|
76
|
+
def authorize_from_session
|
77
|
+
return nil if session["rack-unbasic.username"].nil? || session["rack-unbasic.password"].nil?
|
78
|
+
send_http_authorization(session["rack-unbasic.username"], session["rack-unbasic.password"])
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_http_authorization(username, password)
|
82
|
+
return nil unless @env["HTTP_AUTHORIZATION"].nil?
|
83
|
+
@username, @password = username, password
|
84
|
+
encoded_login = ["#{username}:#{password}"].pack("m*")
|
85
|
+
@env["HTTP_AUTHORIZATION"] = "Basic #{encoded_login}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def store_credentials
|
89
|
+
return if @username.nil? || @password.nil?
|
90
|
+
session["rack-unbasic.username"] = @username
|
91
|
+
session["rack-unbasic.password"] = @password
|
92
|
+
end
|
93
|
+
|
94
|
+
def status_code
|
95
|
+
@response[0].to_i
|
96
|
+
end
|
97
|
+
|
98
|
+
def request
|
99
|
+
@request ||= Rack::Request.new(@env)
|
100
|
+
end
|
101
|
+
|
102
|
+
def session
|
103
|
+
@env["rack.session"]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "rack-unbasic"
|
3
|
+
s.version = "0.1"
|
4
|
+
s.date = "2009-05-23"
|
5
|
+
|
6
|
+
s.description = "Elegant workflow for Rack::Auth::Basic"
|
7
|
+
s.summary = "Handle HTTP auth errors nicely, and abstract auth logic a little bit."
|
8
|
+
s.homepage = "http://github.com/foca"
|
9
|
+
|
10
|
+
s.authors = ["Pat Nakajima", "Nicolás Sanguinetti"]
|
11
|
+
s.email = "contacto@nicolassanguinetti.info"
|
12
|
+
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.rubyforge_project = "rack-unbasic"
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.rubygems_version = "1.3.1"
|
17
|
+
|
18
|
+
s.add_dependency "rack"
|
19
|
+
|
20
|
+
if s.respond_to?(:add_development_dependency)
|
21
|
+
s.add_development_dependency "sr-mg"
|
22
|
+
s.add_development_dependency "contest"
|
23
|
+
end
|
24
|
+
|
25
|
+
s.files = %w[
|
26
|
+
.gitignore
|
27
|
+
LICENSE
|
28
|
+
README.rdoc
|
29
|
+
Rakefile
|
30
|
+
rack-unbasic.gemspec
|
31
|
+
lib/rack/unbasic.rb
|
32
|
+
test/test_unbasic.rb
|
33
|
+
]
|
34
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
ENV["RACK_ENV"] ||= "test"
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
require "rack/test"
|
5
|
+
require "contest"
|
6
|
+
require File.dirname(__FILE__) + "/../lib/rack/unbasic"
|
7
|
+
|
8
|
+
begin
|
9
|
+
require "redgreen"
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestUnbasic < Test::Unit::TestCase
|
14
|
+
include Rack::Test::Methods
|
15
|
+
|
16
|
+
def unbasic_app(&unbasic)
|
17
|
+
Rack::Builder.new {
|
18
|
+
use Rack::Session::Cookie
|
19
|
+
use Rack::Unbasic, &unbasic
|
20
|
+
|
21
|
+
app = lambda { [200, {}, "Hi there"] }
|
22
|
+
|
23
|
+
map "/login" do
|
24
|
+
run app
|
25
|
+
end
|
26
|
+
|
27
|
+
map "/" do
|
28
|
+
use Rack::Auth::Basic do |user, password|
|
29
|
+
user == "johndoe" && password == "secret"
|
30
|
+
end
|
31
|
+
run app
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def app
|
37
|
+
@app ||= unbasic_app do |on|
|
38
|
+
on.unauthorized "/login"
|
39
|
+
on.bad_request "/login"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Without providing authorization" do
|
44
|
+
test "it redirects to the specified location" do
|
45
|
+
get "/foobar"
|
46
|
+
follow_redirect!
|
47
|
+
assert_equal "http://example.org/login", last_request.url
|
48
|
+
end
|
49
|
+
|
50
|
+
test "it saves the previous url and code in env['rack-unbasic.*']" do
|
51
|
+
get "/foobar"
|
52
|
+
follow_redirect!
|
53
|
+
assert_equal "/foobar", last_request.env["rack-unbasic.return-to"]
|
54
|
+
assert_equal 401, last_request.env["rack-unbasic.code"]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "When providing a broken authorization (or at least not HTTP Basic" do
|
59
|
+
test "it redirects to the specified location" do
|
60
|
+
get "/foobar", {}, { "HTTP_AUTHORIZATION" => "cuack" }
|
61
|
+
follow_redirect!
|
62
|
+
assert_equal "http://example.org/login", last_request.url
|
63
|
+
end
|
64
|
+
|
65
|
+
test "it saves the previous url and code in env['rack-unbasic.*']" do
|
66
|
+
get "/foobar", {}, { "HTTP_AUTHORIZATION" => "cuack" }
|
67
|
+
follow_redirect!
|
68
|
+
assert_equal "/foobar", last_request.env["rack-unbasic.return-to"]
|
69
|
+
assert_equal 400, last_request.env["rack-unbasic.code"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "Providing HTTP Basic authorization" do
|
74
|
+
test "it works transparently" do
|
75
|
+
authorize "johndoe", "secret"
|
76
|
+
get "/foobar"
|
77
|
+
assert_equal 200, last_response.status
|
78
|
+
assert_equal "Hi there", last_response.body
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "Passing the credentials as parameters" do
|
83
|
+
test "should work as if using HTTP Basic" do
|
84
|
+
get "/foobar", :username => "johndoe", :password => "secret"
|
85
|
+
assert last_response.ok?
|
86
|
+
assert_equal "Hi there", last_response.body
|
87
|
+
end
|
88
|
+
|
89
|
+
test "should work for subsequent requests" do
|
90
|
+
get "/foobar", :username => "johndoe", :password => "secret"
|
91
|
+
assert last_response.ok?
|
92
|
+
|
93
|
+
get "/baz"
|
94
|
+
assert last_response.ok?
|
95
|
+
|
96
|
+
get "/quux"
|
97
|
+
assert last_response.ok?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "When unbasic isn't set to handle 401 or 400 responses" do
|
102
|
+
def app
|
103
|
+
@app ||= unbasic_app
|
104
|
+
end
|
105
|
+
|
106
|
+
test "it doesn't interfere with pages that don't require auth" do
|
107
|
+
get "/login"
|
108
|
+
assert last_response.ok?
|
109
|
+
end
|
110
|
+
|
111
|
+
test "it returns the original 401 if credentials aren't given" do
|
112
|
+
get "/foobar"
|
113
|
+
assert_equal 401, last_response.status
|
114
|
+
end
|
115
|
+
|
116
|
+
test "it returns the original 400 if credentials are malformed" do
|
117
|
+
get "/foobar", {}, { "HTTP_AUTHORIZATION" => "cuack" }
|
118
|
+
assert_equal 400, last_response.status
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-unbasic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pat Nakajima
|
8
|
+
- "Nicol\xC3\xA1s Sanguinetti"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-05-23 00:00:00 -03:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rack
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sr-mg
|
28
|
+
type: :development
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: contest
|
38
|
+
type: :development
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
description: Elegant workflow for Rack::Auth::Basic
|
47
|
+
email: contacto@nicolassanguinetti.info
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files: []
|
53
|
+
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- LICENSE
|
57
|
+
- README.rdoc
|
58
|
+
- Rakefile
|
59
|
+
- rack-unbasic.gemspec
|
60
|
+
- lib/rack/unbasic.rb
|
61
|
+
- test/test_unbasic.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/foca
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project: rack-unbasic
|
86
|
+
rubygems_version: 1.3.3
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Handle HTTP auth errors nicely, and abstract auth logic a little bit.
|
90
|
+
test_files: []
|
91
|
+
|