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.
@@ -0,0 +1,3 @@
1
+ doc
2
+ tmp/*
3
+ dist/*
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.
@@ -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.
@@ -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
@@ -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
+