rack-cerberus 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: afcdada63ffd1a684458910d59b89d15fa216886
4
+ data.tar.gz: d412afa1080dfcc3fe423ed035c359c5caaff232
5
+ SHA512:
6
+ metadata.gz: 76d1daf65b65cfc21b741b33e9874d69a4f259c464d92906897fb80cddf9d491358288e5820317843ef64b9567335f5dc7ee11a7ccaf584cb09524e90f8368c7
7
+ data.tar.gz: ede819c5610004dca634ec0e4e2a5fe035feef2b9e55ae667b16fd5039b4cb4d792f33734b749122f3a2714fa435deb5c65e10173c588496c347ff0dc0b2b905
data/.gitignore CHANGED
@@ -1,2 +1,7 @@
1
- pkg
1
+ .DS_STORE
2
+ *.swp
3
+ *.sass-cache
4
+ pkg/
5
+ Gemfile.lock
6
+ .bundle/
2
7
 
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require 'rack/test'
3
+ --require spec_helper
4
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'rspec'
4
+ gem 'rack-test'
5
+
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2011 Mickael Riga
1
+ Copyright (c) 2010-2015 Mickael Riga
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
16
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
18
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- THE SOFTWARE.
19
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,93 +1,107 @@
1
- >"For over a thousand generations the Jedi Knights were the guardians of peace and justice in the Old Republic. Before the dark times, before the Empire." -- Obi-Wan Kenoby
1
+ Rack::Cerberus
2
+ ==============
2
3
 
3
- Cerberus
4
- ========
5
-
6
- Cerberus is a Rack middleware for form-based authentication. Its purpose is only
7
- to offer a nicer (or more actual) replacement for Basic HTTP authentication.
4
+ `Rack::Cerberus` is a Rack middleware for form-based authentication.
5
+ It works roughly like Basic HTTP authentication except that you can use
6
+ options in order to style the authentication page.
8
7
 
9
8
  Install with:
10
9
 
11
- # sudo gem install rack-cerberus
10
+ ```
11
+ # sudo gem install rack-cerberus
12
+ ```
13
+
14
+ Or in your `Gemfile`:
15
+
16
+ ```
17
+ gem 'rack-cerberus'
18
+ ```
12
19
 
13
20
  You can use it almost the same way you use `Rack::Auth::Basic`:
14
21
 
15
- require 'cerberus'
16
- use Rack::Session::Cookie, :secret => 'change_me'
17
- use Cerberus do |login, pass|
18
- pass=='secret'
19
- end
22
+ ```
23
+ require 'rack/cerberus'
24
+ use Rack::Session::Cookie, secret: 'change_me'
25
+ use Rack::Cerberus do |login, pass|
26
+ pass=='secret'
27
+ end
28
+ ```
20
29
 
21
- Like in that example, make sure you have a session, because Cerberus use it for
22
- persistent login.
30
+ Like in that example, make sure you have a session, because
31
+ `Rack::Cerberus` uses it for persistent login, and make sure it is encrypted.
23
32
 
33
+ Options
34
+ -------
35
+
24
36
  There is an optional hash you can add for customisation it. Options are:
25
37
 
26
38
  - `:company_name`
27
- - `:fg_color` (foreground color)
28
- - `:bg_color` (background color)
39
+ - `:bg_color` (Background color)
40
+ - `:fg_color` (Actually the color of the box color)
29
41
  - `:text_color`
30
- - `:icon_url` (for a company logo or any icon)
31
- - `:css_location`
42
+ - `:icon_url` (For a company logo or any icon)
43
+ - `:css_location` (Path to a CSS file for a complete reskin)
44
+ - `:session_key` (Where login name is kept. Default is `cerberus_user`)
32
45
 
33
46
  Which is used that way:
34
47
 
35
- use Cerberus, {:company_name => 'Nintendo'} do |login, pass|
36
- pass=='secret'
37
- end
48
+ ```
49
+ use Rack::Cerberus, {company_name: 'Nintendo'} do |login, pass|
50
+ pass=='secret'
51
+ end
52
+ ```
38
53
 
39
- The purpose of Cerberus is to be basic, which is why there are enough options to have
40
- a page fairly customized with colors and logo (`:icon_url`). The logo can even replace
41
- the company name if you leave `:company_name` blank. But should you be fussy, this is possible
54
+ The purpose of `Rack::Cerberus` is to be basic, which is why there are
55
+ enough options to have a page fairly customized with colors and
56
+ logo (`:icon_url`). The logo can even replace the company name if
57
+ you leave `:company_name` blank. But should you be fussy, this is possible
42
58
  to have more control using an external CSS file with the option `:css_location`.
43
59
 
44
- Just like `Rack::Auth::Basic`, Cerberus yields login and pass, and delegate authentication
45
- to the block you send it which should return a boolean.
60
+ Authentication
61
+ --------------
46
62
 
47
- If you want to see a concrete example, go into the Cerberus directory and run:
48
-
49
- # rackup example.ru
50
-
51
- It's gonna start the example at http://localhost:9292
63
+ Just like `Rack::Auth::Basic`, `Rack::Cerberus` yields login and pass,
64
+ and delegate authentication to the block you send it which should
65
+ return `true` or `false`.
52
66
 
53
67
  You can also use the 3rd argument which is the request object:
54
68
 
55
- use Cerberus, {:company_name => 'Nintendo'} do |login, pass, req|
69
+ ```
70
+ use Rack::Cerberus, {company_name: 'Nintendo'} do |login, pass, req|
56
71
  pass=='secret' && req.xhr?
57
72
  end
73
+ ```
58
74
 
59
- This is more if you use it as a gateway for an API or something and you want to check other values.
60
- Like the referer or another parameter.
61
- But bear in mind that `cerberus_login` and `cerberus_pass` are still mandatory.
75
+ This is useful if you want to check other details of the request.
76
+ Like the referer or another parameter. But bear in mind that `cerberus_login`
77
+ and `cerberus_pass` are still mandatory.
78
+
79
+ Example
80
+ -------
81
+
82
+ If you want to see a concrete example, go into the `example/` directory and run:
83
+
84
+ ```
85
+ # rackup
86
+ ```
87
+
88
+ It's gonna start the example at `http://localhost:9292`
62
89
 
63
90
  Logout
64
91
  ------
65
92
 
66
- Any request to `/logout` on the path where the middleware is mounted will log you out.
67
- In other words, if you put the middleware at `/admin`, query `/admin/logout` to be
68
- logged out. Pretty simple.
93
+ Any request to `/logout` on the path where the middleware is mounted
94
+ will log you out. In other words, if you put the middleware at `/admin`,
95
+ query `/admin/logout` to be logged out. Pretty simple.
69
96
 
70
97
  Help
71
98
  ----
72
99
 
73
- If you want to help me, don't hesitate to fork that project on Github or send patches.
74
-
75
- Changelog
76
- ---------
77
-
78
- 0.0.1 Changed Everything somehow
79
- 0.1.0 Make it possible to authenticate through GET request (for restful APIs)
80
- 0.1.1 Documentation improvement
81
- 0.1.2 Raise message when using without session
82
- 0.1.3 Don't go to page /logout when signing in after a logout (redirect to / instead)
83
- 0.1.4 Fix /logout redirect so that it works with mapping
84
- 0.1.5 Fix CSS and Javascript for IE (Yes I'm too kind)
85
- 0.1.6 Send an Array instead of a string to Rack so that it works on Ruby 1.9
86
- 0.2.0 External CSS file + `:text_color` option + keep details after login failure
87
- 0.3.0 Now sends request as a 3rd argument to the block
88
- 0.3.1 Escape HTML in fields now that they are kept
100
+ If you want to help me, don't hesitate to fork that project on Github
101
+ or send patches.
89
102
 
90
103
  Copyright
91
104
  ---------
92
105
 
93
- (c) 2010-2011 Mickael Riga - see MIT_LICENCE for details
106
+ (c) 2010-2015 Mickael Riga - see MIT_LICENSE for details
107
+
@@ -0,0 +1,35 @@
1
+ require_relative '../lib/rack/cerberus'
2
+
3
+ use Rack::Session::Cookie, secret: 'change_me'
4
+
5
+ map '/' do
6
+ run lambda {|env|
7
+ body = <<-EOB.strip
8
+ <html>
9
+ <head>
10
+ <title>Rack::Cerberus</title>
11
+ </head>
12
+ <body>This page is public, so you can see it. But what happens if you want to see a <a href='/secret'>Secret Page</a>? Nevertheless, I can give you access:<br /><br />
13
+ Login: <b>mario</b><br />Pass: <b>bros</b>
14
+ </body>
15
+ </html>
16
+ EOB
17
+ [200, {'Content-Type' => 'text/html'}, [body]]
18
+ }
19
+ end
20
+
21
+ map '/secret' do
22
+ use Rack::Cerberus, {
23
+ company_name: 'Nintendo',
24
+ fg_color: 'red',
25
+ } do |login,pass|
26
+ [login,pass]==['mario','bros']
27
+ end
28
+ run lambda {|env|
29
+ [
30
+ 200, {'Content-Type' => 'text/plain'},
31
+ ['Welcome back Mario. Your Credit Card number is: 9292']
32
+ ]
33
+ }
34
+ end
35
+
@@ -0,0 +1,133 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+
5
+ class Cerberus
6
+
7
+ VERSION = '1.0.0'
8
+
9
+ class NoSessionError < RuntimeError; end
10
+
11
+ def initialize(app, options={}, &block)
12
+ @app = app
13
+ defaults = {
14
+ company_name: 'Cerberus',
15
+ bg_color: '#999',
16
+ fg_color: '#CCC',
17
+ text_color: '#FFF',
18
+ icon_url: nil,
19
+ session_key: 'cerberus_user'
20
+ }
21
+ @options = defaults.merge(options)
22
+ @options[:icon] = @options[:icon_url].nil? ? '' : "<img src='#{@options[:icon_url]}' /><br />"
23
+ @options[:css] = @options[:css_location].nil? ? '' : "<link href='#{@options[:css_location]}' rel='stylesheet' type='text/css'>"
24
+ @block = block
25
+ end
26
+
27
+ def call(env)
28
+ dup._call(env)
29
+ end
30
+
31
+ def _call(env)
32
+ raise(NoSessionError, 'Cerberus cannot work without Session') if env['rack.session'].nil?
33
+ req = Rack::Request.new(env)
34
+ login = req['cerberus_login']
35
+ pass = req['cerberus_pass']
36
+ err = req.post? ? "<p class='err'>Wrong login or password</p>" : ''
37
+ if ((env['rack.session'][@options[:session_key]]!=nil && env['PATH_INFO']!='/logout') || (login && pass && @block.call(login, pass, req)))
38
+ env['rack.session'][@options[:session_key]] ||= login
39
+ if env['PATH_INFO']=='/logout'
40
+ res = Rack::Response.new(env)
41
+ res.redirect(env['SCRIPT_NAME']=='' ? '/' : env['SCRIPT_NAME'])
42
+ res.finish
43
+ else
44
+ @app.call(env)
45
+ end
46
+ else
47
+ env['rack.session'].delete(@options[:session_key])
48
+ [
49
+ 401, {'Content-Type' => 'text/html'},
50
+ [AUTH_PAGE % @options.merge({
51
+ error: err, submit_path: env['REQUEST_URI'],
52
+ login: Rack::Utils.escape_html(login),
53
+ pass: Rack::Utils.escape_html(pass)
54
+ })]
55
+ ]
56
+ end
57
+ end
58
+
59
+ AUTH_PAGE = <<-PAGE
60
+ <!DOCTYPE html>
61
+ <html><head>
62
+ <title>%{company_name} Authentication</title>
63
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
64
+ <style type='text/css'>
65
+ * {
66
+ -moz-box-sizing: border-box;
67
+ -ms-box-sizing: border-box;
68
+ box-sizing: border-box;
69
+ }
70
+ body { background-color: %{bg_color}; font-family: sans-serif; text-align: center; margin: 0px; }
71
+ h1, p { color: %{text_color}; }
72
+ .err {
73
+ padding: 1em;
74
+ border-radius: 3px;
75
+ -moz-border-radius: 3px;
76
+ -webkit-border-radius: 3px;
77
+ color: white;
78
+ background-color: red;
79
+ }
80
+ div {
81
+ text-align: left;
82
+ width: 500px;
83
+ margin: 0px auto;
84
+ padding: 2em;
85
+ -webkit-border-bottom-left-radius: 3px;
86
+ -moz-border-radius-bottomleft: 3px;
87
+ border-bottom-left-radius: 3px;
88
+ -webkit-border-bottom-right-radius: 3px;
89
+ -moz-border-radius-bottomright: 3px;
90
+ border-bottom-right-radius: 3px;
91
+ -moz-box-shadow: 0px 0px 5px #333;
92
+ -webkit-box-shadow: 0px 0px 5px #555;
93
+ box-shadow: 0px 0px 5px #555;
94
+ background-color: %{fg_color}; }
95
+ input[type=text], input[type=password] {
96
+ display: block; width: 100%%; padding: 0.5em;
97
+ border: 0px; font-size: 1.25em;
98
+ }
99
+ </style>
100
+ %{css}
101
+ </head><body>
102
+ <div>
103
+ <h1>%{company_name}</h1>
104
+ %{icon}
105
+ %{error}
106
+ <p>Please Sign In</p>
107
+ <form action="%{submit_path}" method="post" accept-charset="utf-8">
108
+ <input type="text" name="cerberus_login" value="%{login}" id='login' title='Login' placeholder='Login'><br />
109
+ <input type="password" name="cerberus_pass" value="%{pass}" id='pass' title='Password' placeholder='Password'>
110
+ <p><input type="submit" value="SIGN IN &rarr;"></p>
111
+ </form>
112
+ <script type="text/javascript" charset="utf-8">
113
+ var login = document.getElementById('login');
114
+ var pass = document.getElementById('pass');
115
+ var focus = function() {
116
+ if (this.value==this.id) this.value = '';
117
+ }
118
+ var blur = function() {
119
+ if (this.value=='') this.value = this.id;
120
+ }
121
+ login.onfocus = focus;
122
+ pass.onfocus = focus;
123
+ login.onblur = blur;
124
+ pass.onblur = blur;
125
+ </script>
126
+ </div>
127
+ </body></html>
128
+ PAGE
129
+
130
+ end
131
+
132
+ end
133
+
@@ -1,13 +1,22 @@
1
+ require_relative './lib/rack/cerberus'
2
+
1
3
  Gem::Specification.new do |s|
4
+
2
5
  s.name = 'rack-cerberus'
3
- s.version = "0.3.1"
4
- s.platform = Gem::Platform::RUBY
6
+ s.version = Rack::Cerberus::VERSION
5
7
  s.summary = "A Rack middleware for form-based authentication"
6
- s.description = "A Rack middleware for form-based authentication. Aim is a compromise between fonctionality, beauty and customization."
8
+ s.description = "A Rack middleware for form-based authentication. It works roughly like Basic HTTP Authentication except that the authentication page can be styled with the middleware options."
9
+ s.licenses = ['MIT']
10
+
7
11
  s.files = `git ls-files`.split("\n").sort
8
- s.test_files = ['spec.rb']
9
- s.require_path = '.'
12
+ s.require_path = './lib'
13
+ s.add_dependency('rack')
14
+ s.test_files = s.files.select { |p| p =~ /^spec\/.*_spec.rb/ }
15
+ s.platform = Gem::Platform::RUBY
16
+
10
17
  s.author = "Mickael Riga"
11
18
  s.email = "mig@mypeplum.com"
12
19
  s.homepage = "http://github.com/mig-hub/cerberus"
13
- end
20
+
21
+ end
22
+
@@ -0,0 +1,133 @@
1
+ require 'rack/cerberus'
2
+
3
+ RSpec.describe Rack::Cerberus do
4
+
5
+ let(:secret_app) {
6
+ lambda {|env|
7
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
8
+ }
9
+ }
10
+
11
+ let(:cerberus_app) {
12
+ Rack::Cerberus.new(secret_app, cerberus_options) do |login,pass|
13
+ [login,pass]==['mario@nintendo.com','bros']
14
+ end
15
+ }
16
+
17
+ let(:app) {
18
+ Rack::URLMap.new({
19
+ mount_path => Rack::Session::Cookie.new(cerberus_app, {secret: '42'})
20
+ })
21
+ }
22
+
23
+ let(:cerberus_options) { {} }
24
+ let(:mount_path) { '/' }
25
+
26
+ before :each do
27
+ clear_cookies
28
+ end
29
+
30
+ context 'No session is set' do
31
+ let(:app) { cerberus_app }
32
+ it 'Raises' do
33
+ expect{ get('/') }.to raise_error(Rack::Cerberus::NoSessionError)
34
+ end
35
+ end
36
+
37
+ context 'Not logged in' do
38
+ it 'Stops requests' do
39
+ get '/'
40
+ expect(last_response.status).to eq 401
41
+ body = last_response.body
42
+ expect(body.class).to eq String
43
+ expect(body).to match(/name="cerberus_login" value=""/)
44
+ expect(body).to match(/name="cerberus_pass" value=""/)
45
+ end
46
+ end
47
+
48
+ describe 'Logging in' do
49
+
50
+ context 'Login details are incorrect' do
51
+ it 'Stops requests' do
52
+ post('/', {'cerberus_login' => 'fake_login', 'cerberus_pass' => 'fake_pass'})
53
+ expect(last_response.status).to eq 401
54
+ expect(last_response.body).to match(/Wrong login or password/)
55
+ end
56
+ it 'Keeps what was entered in the fields' do
57
+ post('/', {'cerberus_login' => 'fake_login', 'cerberus_pass' => 'fake_pass'})
58
+ expect(last_response.body).to match(/name="cerberus_login" value="fake_login"/)
59
+ expect(last_response.body).to match(/name="cerberus_pass" value="fake_pass"/)
60
+ end
61
+ it 'Escapes HTML on submitted info' do
62
+ expect(Rack::Utils).to receive(:escape_html).with('<script>bad</script>').twice
63
+ post('/', {'cerberus_login' => '<script>bad</script>', 'cerberus_pass' => '<script>bad</script>'})
64
+ end
65
+ end
66
+
67
+ context 'Login details are correct' do
68
+ it 'Gives access' do
69
+ get('/', {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
70
+ expect(last_response.status).to eq 200
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ describe 'Already logged in' do
77
+
78
+ it 'Uses session for persistent login' do
79
+ get('/', {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
80
+ get('/')
81
+ expect(last_response.status).to eq 200
82
+ expect(last_response.body).to include('"cerberus_user"=>"mario@nintendo.com"}')
83
+ end
84
+
85
+ end
86
+
87
+ describe 'Logout' do
88
+
89
+ let(:mount_path) { '/admin' }
90
+
91
+ it 'Happens via /logout path' do
92
+ get('/admin/', {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
93
+ expect(last_response.status).to eq 200
94
+ get('/admin/logout')
95
+ expect(last_response.status).to eq 401
96
+ end
97
+
98
+ it 'Never redirects to the logout path' do
99
+ get('/admin/logout', {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
100
+ expect(last_response.status).to eq 302
101
+ expect(last_response['Location']).to eq '/admin'
102
+ end
103
+
104
+ end
105
+
106
+ describe 'Options' do
107
+
108
+ it 'Does not link CSS by default' do
109
+ get('/')
110
+ expect(last_response.body).not_to match(/<link/)
111
+ end
112
+
113
+ context 'CSS option is used' do
114
+ let(:cerberus_options) { {:css_location=>'/main.css'} }
115
+ it 'Links the CSS file' do
116
+ get('/')
117
+ expect(last_response.body).to match(/<link/)
118
+ end
119
+ end
120
+
121
+ context 'Session key is different' do
122
+ let(:cerberus_options) { {session_key: 'different_user'} }
123
+ it 'Uses the session key of the options' do
124
+ get('/', {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
125
+ get('/')
126
+ expect(last_response.status).to eq 200
127
+ expect(last_response.body).to include('"different_user"=>"mario@nintendo.com"}')
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,24 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ RSpec.configure do |config|
4
+
5
+ config.include Rack::Test::Methods
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+ config.mock_with :rspec do |mocks|
10
+ mocks.verify_partial_doubles = true
11
+ end
12
+ config.filter_run :focus
13
+ config.run_all_when_everything_filtered = true
14
+ config.disable_monkey_patching!
15
+ config.warnings = true
16
+ if config.files_to_run.one?
17
+ config.default_formatter = 'doc'
18
+ end
19
+ config.profile_examples = 10
20
+ config.order = :random
21
+ Kernel.srand config.seed
22
+
23
+ end
24
+
metadata CHANGED
@@ -1,74 +1,70 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rack-cerberus
3
- version: !ruby/object:Gem::Version
4
- hash: 17
5
- prerelease:
6
- segments:
7
- - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Mickael Riga
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2011-11-21 00:00:00 +00:00
19
- default_executable:
20
- dependencies: []
21
-
22
- description: A Rack middleware for form-based authentication. Aim is a compromise between fonctionality, beauty and customization.
11
+ date: 2015-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A Rack middleware for form-based authentication. It works roughly like
28
+ Basic HTTP Authentication except that the authentication page can be styled with
29
+ the middleware options.
23
30
  email: mig@mypeplum.com
24
31
  executables: []
25
-
26
32
  extensions: []
27
-
28
33
  extra_rdoc_files: []
29
-
30
- files:
34
+ files:
31
35
  - .gitignore
32
- - MIT_LICENCE
36
+ - .rspec
37
+ - Gemfile
38
+ - MIT_LICENSE
33
39
  - README.md
34
- - cerberus.gemspec
35
- - cerberus.rb
36
- - example.css
37
- - example.ru
38
- - spec.rb
39
- has_rdoc: true
40
+ - example/config.ru
41
+ - lib/rack/cerberus.rb
42
+ - rack_cerberus.gemspec
43
+ - spec/rack_cerberus_spec.rb
44
+ - spec/spec_helper.rb
40
45
  homepage: http://github.com/mig-hub/cerberus
41
- licenses: []
42
-
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
43
49
  post_install_message:
44
50
  rdoc_options: []
45
-
46
- require_paths:
47
- - .
48
- required_ruby_version: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- hash: 3
54
- segments:
55
- - 0
56
- version: "0"
57
- required_rubygems_version: !ruby/object:Gem::Requirement
58
- none: false
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- hash: 3
63
- segments:
64
- - 0
65
- version: "0"
51
+ require_paths:
52
+ - ./lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
66
63
  requirements: []
67
-
68
64
  rubyforge_project:
69
- rubygems_version: 1.4.2
65
+ rubygems_version: 2.0.14
70
66
  signing_key:
71
- specification_version: 3
67
+ specification_version: 4
72
68
  summary: A Rack middleware for form-based authentication
73
- test_files:
74
- - spec.rb
69
+ test_files:
70
+ - spec/rack_cerberus_spec.rb
@@ -1,121 +0,0 @@
1
- class Cerberus
2
-
3
- class NoSessionError < RuntimeError; end
4
-
5
- AUTH_PAGE = <<-PAGE
6
- <!DOCTYPE html>
7
- <html><head>
8
- <title>%s Authentication</title>
9
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
10
- <style type='text/css'>
11
- body { background-color: %s; font-family: sans-serif; text-align: center; margin: 0px; }
12
- h1, p { color: %s; }
13
- .err {
14
- padding: 5px;
15
- border-radius: 5px;
16
- -moz-border-radius: 5px;
17
- -webkit-border-radius: 5px;
18
- color: white;
19
- background-color: red;
20
- }
21
- div {
22
- text-align: left;
23
- width: 400px;
24
- margin: 0px auto;
25
- padding: 10px;
26
- -webkit-border-bottom-left-radius: 10px;
27
- -moz-border-radius-bottomleft: 10px;
28
- border-bottom-left-radius: 10px;
29
- -webkit-border-bottom-right-radius: 10px;
30
- -moz-border-radius-bottomright: 10px;
31
- border-bottom-right-radius: 10px;
32
- -moz-box-shadow: 0px 0px 5px #333;
33
- -webkit-box-shadow: 0px 0px 5px #333;
34
- box-shadow: 0px 0px 5px #333;
35
- background-color: %s; }
36
- input[type=text], input[type=password] { width: 392px; padding: 4px; border: 0px; font-size: 20px; }
37
- </style>
38
- %s
39
- </head><body>
40
- <div>
41
- <h1>%s</h1>
42
- %s
43
- %s
44
- <p>Please Sign In</p>
45
- <form action="%s" method="post" accept-charset="utf-8">
46
- <input type="text" name="cerberus_login" value="%s" id='login'><br />
47
- <input type="password" name="cerberus_pass" value="%s" id='pass'>
48
- <p><input type="submit" value="SIGN IN &rarr;"></p>
49
- </form>
50
- <script type="text/javascript" charset="utf-8">
51
- var login = document.getElementById('login');
52
- var pass = document.getElementById('pass');
53
- var focus = function() {
54
- if (this.value==this.id) this.value = '';
55
- }
56
- var blur = function() {
57
- if (this.value=='') this.value = this.id;
58
- }
59
- login.onfocus = focus;
60
- pass.onfocus = focus;
61
- login.onblur = blur;
62
- pass.onblur = blur;
63
- </script>
64
- </div>
65
- </body></html>
66
- PAGE
67
-
68
- def initialize(app, options={}, &block)
69
- @app = app
70
- defaults = {
71
- :company_name => 'Cerberus',
72
- :bg_color => '#999',
73
- :fg_color => '#CCC',
74
- :text_color => '#FFF',
75
- :icon_url => nil
76
- }
77
- @options = defaults.merge(options)
78
- @block = block
79
- end
80
-
81
- def call(env)
82
- dup._call(env)
83
- end
84
-
85
- def _call(env)
86
- raise(NoSessionError, 'Cerberus cannot work without Session') if env['rack.session'].nil?
87
- req = Rack::Request.new(env)
88
- login = req['cerberus_login']
89
- pass = req['cerberus_pass']
90
- err = req.post? ? "<p class='err'>Wrong login or password</p>" : ''
91
- if ((env['rack.session']['cerberus_user']!=nil && env['PATH_INFO']!='/logout') || (login && pass && @block.call(login, pass, req)))
92
- env['rack.session']['cerberus_user'] ||= login
93
- if env['PATH_INFO']=='/logout'
94
- res = Rack::Response.new(env)
95
- res.redirect(env['SCRIPT_NAME']=='' ? '/' : env['SCRIPT_NAME'])
96
- res.finish
97
- else
98
- @app.call(env)
99
- end
100
- else
101
- env['rack.session'].delete('cerberus_user')
102
- icon = @options[:icon_url].nil? ? '' : "<img src='#{@options[:icon_url]}' /><br />"
103
- css = @options[:css_location].nil? ? '' : "<link href='#{@options[:css_location]}' rel='stylesheet' type='text/css'>"
104
- [
105
- 401, {'Content-Type' => 'text/html'},
106
- [AUTH_PAGE % [
107
- @options[:company_name], @options[:bg_color], @options[:text_color], @options[:fg_color], css, @options[:company_name],
108
- icon, err, env['REQUEST_URI'], html_escape(req['cerberus_login']||'login'), html_escape(req['cerberus_pass']||'pass')
109
- ]]
110
- ]
111
- end
112
- end
113
-
114
- private
115
-
116
- # Stolen from ERB
117
- def html_escape(s)
118
- s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
119
- end
120
-
121
- end
@@ -1 +0,0 @@
1
- body { background-color: #CCC; }
data/example.ru DELETED
@@ -1,35 +0,0 @@
1
- require ::File.dirname(__FILE__) + '/cerberus'
2
- use Rack::Session::Cookie, :secret => 'change_me'
3
- F = ::File
4
-
5
- map '/' do
6
- run lambda {|env|
7
- body = <<-EOB.strip
8
- <html>
9
- <head>
10
- <title>Cerberus</title>
11
- </head>
12
- <body>This page is public, so you can see it. But what happens if you want to see a <a href='/secret'>Secret Page</a>? Nevertheless, I can give you access:<br /><br />
13
- Login: <b>mario</b><br />Pass: <b>bros</b>
14
- </body>
15
- </html>
16
- EOB
17
- [200, {'Content-Type' => 'text/html'}, [body]]
18
- }
19
- end
20
-
21
- map '/secret' do
22
- use Cerberus, {:company_name => 'Nintendo', :fg_color => 'red', :css_location => '/css'} do |login,pass|
23
- [login,pass]==['mario','bros']
24
- end
25
- run lambda {|env|
26
- [200, {'Content-Type' => 'text/plain'}, ['Welcome back Mario. Your Credit Card number is: 9292']]
27
- }
28
- end
29
-
30
- map '/css' do
31
- run lambda {|env|
32
- path = F.expand_path('./example.css')
33
- [200, {'Content-Type' => 'text/css', "Last-Modified" => F.mtime(path).httpdate, "Content-Length" => F.size?(path).to_s}, [F.read(path)]]
34
- }
35
- end
data/spec.rb DELETED
@@ -1,84 +0,0 @@
1
- require 'rubygems'
2
- require 'bacon'
3
- require 'rack'
4
-
5
- require ::File.dirname(__FILE__) + '/cerberus'
6
-
7
- Bacon.summary_on_exit
8
-
9
- describe 'cerberus' do
10
-
11
- secret_app = lambda {|env| [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect] }
12
- app = Rack::Session::Cookie.new(Cerberus.new(secret_app, {}) {|login,pass| [login,pass]==['mario@nintendo.com','bros']})
13
- req = Rack::MockRequest.new(app)
14
- app_with_css = Rack::Session::Cookie.new(Cerberus.new(secret_app, {:css_location=>'/main.css'}) {|login,pass| [login,pass]==['mario','bros']})
15
- req_with_css = Rack::MockRequest.new(app_with_css)
16
- cookie = ''
17
-
18
- should 'Raise if there is no session' do
19
- no_session_app = Cerberus.new(secret_app, {}) {|login,pass| [login,pass]==['mario','bros']}
20
- no_session_req = Rack::MockRequest.new(no_session_app)
21
- lambda { no_session_req.get('/') }.should.raise(Cerberus::NoSessionError).message.should=='Cerberus cannot work without Session'
22
- end
23
-
24
- should 'Stop request if you are not already logged in' do
25
- res = req.get('/')
26
- res.status.should==401
27
- res.body.class==String
28
- res.body.should.match(/name="cerberus_login" value="login"/)
29
- res.body.should.match(/name="cerberus_pass" value="pass"/)
30
- end
31
-
32
- should 'Stop request if you send wrong details and keep query values' do
33
- res = req.post('/', :params => {'cerberus_login' => 'fake_login', 'cerberus_pass' => 'fake_pass'})
34
- res.status.should==401
35
- res.body.should.match(/name="cerberus_login" value="fake_login"/)
36
- res.body.should.match(/name="cerberus_pass" value="fake_pass"/)
37
- end
38
-
39
- should 'Escape HTML on submitted info' do
40
- res = req.post('/', :params => {'cerberus_login' => '<script>bad</script>', 'cerberus_pass' => '<script>bad</script>'})
41
- res.status.should==401
42
- res.body.should.match(/name="cerberus_login" value="&lt;script&gt;bad&lt;\/script&gt;"/)
43
- res.body.should.match(/name="cerberus_pass" value="&lt;script&gt;bad&lt;\/script&gt;"/)
44
- end
45
-
46
- should 'Give access with the appropriate login and pass' do
47
- res = req.get('/', :params => {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
48
- cookie = res["Set-Cookie"]
49
- res.status.should==200
50
- end
51
-
52
- should 'Use session for persistent login' do
53
- res = req.get('/', "HTTP_COOKIE" => cookie)
54
- res.status.should==200
55
- res.body.should=='{"cerberus_user"=>"mario@nintendo.com"}'
56
- cookie = res["Set-Cookie"]
57
- req.get('/', "HTTP_COOKIE" => cookie).status.should==200
58
- end
59
-
60
- should 'Logout via /logout path' do
61
- res = req.get('/logout', "HTTP_COOKIE" => cookie)
62
- res.status.should==401
63
- cookie = res["Set-Cookie"]
64
- res = req.get('/', "HTTP_COOKIE" => cookie)
65
- res.status.should==401
66
- end
67
-
68
- should 'Not send not_found when logging after a logout (because the path is /logout)' do
69
- res = req.get('/logout', :params => {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
70
- res.status.should==302
71
- res['Location'].should=='/'
72
-
73
- req = Rack::MockRequest.new(Rack::URLMap.new({'/backend' => app}))
74
- res = req.get('/backend/logout', :params => {'cerberus_login' => 'mario@nintendo.com', 'cerberus_pass' => 'bros'})
75
- res.status.should==302
76
- res['Location'].should=='/backend'
77
- end
78
-
79
- should 'Use an external css file only if requested' do
80
- req.get('/').body.should.not.match(/<link/)
81
- req_with_css.get('/').body.should.match(/<link/)
82
- end
83
-
84
- end