watch-tower 1.0.0
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/AUTHORS +1 -0
- data/Change.log +6 -0
- data/README +70 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/example/Makefile +3 -0
- data/example/README +0 -0
- data/example/app1/README +0 -0
- data/example/app1/app1.rb +13 -0
- data/example/app1/app1_deligate.rb +16 -0
- data/example/app1/views/layout.haml +6 -0
- data/example/app2/README +0 -0
- data/example/app2/app2.rb +22 -0
- data/example/app2/views/layout.haml +6 -0
- data/example/config.ru +30 -0
- data/example/login_handler/README +0 -0
- data/example/login_handler/login_handler.rb +31 -0
- data/example/login_handler/views/index.haml +13 -0
- data/example/login_handler/views/layout.haml +6 -0
- data/example/login_handler/views/login.haml +10 -0
- data/lib/watch-tower.rb +186 -0
- metadata +84 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Kristan 'Krispy' Uccello <krispy@soldierofcode.com> (founder of project)
|
data/Change.log
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Feb. 5,2010
|
2
|
+
---
|
3
|
+
> Created project Watch Tower after getting very fedup with Warden and other authentication systems for rack
|
4
|
+
that seem to offer more headache then benifit. See README for my rambling on this.
|
5
|
+
|
6
|
+
> Project is working as I need it to for the purposes I need - may add some supporting things later
|
data/README
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
Watch Tower:
|
2
|
+
=================
|
3
|
+
|
4
|
+
"RANT –verb (used without object)
|
5
|
+
to speak or declaim extravagantly or violently;
|
6
|
+
talk in a wild or vehement way;
|
7
|
+
rave: The demagogue ranted for hours."
|
8
|
+
|
9
|
+
I have come to dispise all authentication systems. Put simply they are overy abstract in my humble opinion.
|
10
|
+
Sure in small applications you can get away with a roll your own auth system based on simple principals such
|
11
|
+
as using a cookie or session combined with some form of authentication like user/pass processing. So I feel
|
12
|
+
completly comfortable saying that the small apps have it covered and then there are the big applications that
|
13
|
+
span multiple machines and have session servers to act as stateful verification of user presence and roll based
|
14
|
+
access. This are is covered by a bunch of different technologies already - I'm not getting into that right now.
|
15
|
+
|
16
|
+
I write applications that for the most part are sinatra based. I have my own way of seperating out concerns
|
17
|
+
by way of user responsibilites. So most of my web apps are actually three web apps working togeather. I usually
|
18
|
+
have an admin app for me to use when managing the site's customers, then I have a customer app for my direct
|
19
|
+
customers to use, and then I might have a front facing application for the rest of the web to see that is public,
|
20
|
+
I call this one "public". So I need admin login authentication and customer login authentication. Hmmm does not
|
21
|
+
seem to warent a roll based system as I only have two class of credentialed users as well I have already
|
22
|
+
seperated out their concerns by way of writting them out as two applications. After fucking around with warden
|
23
|
+
and many other types of authentication gems out there on github I found myself constantly saying "this does not
|
24
|
+
do what I want it to" or "I really don't like what I have to do to manage this". I took a step back and pulled
|
25
|
+
out a graph pad and pen and sketched out a couple of questions:
|
26
|
+
|
27
|
+
1. do I know you? are you logged in? (look for token key values)
|
28
|
+
2. who are you? (provide a login method)
|
29
|
+
3. are you who you say you are? (validate login credentials)
|
30
|
+
4. are you allowed to access what you are trying to access? (does the token match the rules?)
|
31
|
+
|
32
|
+
this of course took me to the two things every authentication system has
|
33
|
+
|
34
|
+
Authentication & Authorization
|
35
|
+
|
36
|
+
Watch Tower works of the principal of least reseistance with the use of leathal force. Basically you tell it
|
37
|
+
what to protect and how.
|
38
|
+
|
39
|
+
Example config.ru from the example directory:
|
40
|
+
|
41
|
+
require 'rack'
|
42
|
+
require 'sinatra'
|
43
|
+
require 'haml'
|
44
|
+
require 'dirge' # see http://github.com/joshbuddy/dirge/
|
45
|
+
|
46
|
+
# replace this require with require 'watch-tower' in your code
|
47
|
+
require ::File.join(::File.dirname(__FILE__),'../lib/watch-tower.rb')
|
48
|
+
|
49
|
+
# our app files
|
50
|
+
require ~'app1/app1'
|
51
|
+
require ~'app1/app1_deligate'
|
52
|
+
require ~'app2/app2'
|
53
|
+
require ~'login_handler/login_handler'
|
54
|
+
|
55
|
+
use Rack::Session::Pool, :expire_after => 60 * 30 # 30 minute timeout on sessons
|
56
|
+
|
57
|
+
use SoldierOfCode::TowerGate, { '/app1/'=>App1Deligate,
|
58
|
+
'/app2/'=>App2}
|
59
|
+
|
60
|
+
map '/app2/' do
|
61
|
+
run App2
|
62
|
+
end
|
63
|
+
|
64
|
+
map '/app1/' do
|
65
|
+
run App1
|
66
|
+
end
|
67
|
+
|
68
|
+
map '/' do
|
69
|
+
run LoginHandler
|
70
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |s|
|
4
|
+
s.name = "watch-tower"
|
5
|
+
s.summary = "Rack Middleware for multi app authentication - All along the watchtower - 'there must be some kinda way outa here said the joker to the thief'"
|
6
|
+
s.email = "krispy@soldierofcode.com"
|
7
|
+
s.homepage = "http://github.com/kuccello/watch-tower"
|
8
|
+
s.description = "I was sick of all the complex abstractions that were being put into other authentication libs - I have attempted to keep this one as reuseable and non-abstract as possible"
|
9
|
+
s.authors = ["Kristan 'Krispy' Uccello"]
|
10
|
+
s.files = FileList["[A-Z]*", "{example,lib}/**/*"]
|
11
|
+
s.add_dependency 'rack'
|
12
|
+
end
|
13
|
+
rescue LoadError
|
14
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
15
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/example/Makefile
ADDED
data/example/README
ADDED
File without changes
|
data/example/app1/README
ADDED
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class App1Deligate
|
2
|
+
include SoldierOfCode::SentryDeligate
|
3
|
+
|
4
|
+
def login_uri
|
5
|
+
'/login'
|
6
|
+
end
|
7
|
+
|
8
|
+
def token_keys
|
9
|
+
[:app1,:default]
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticate(usr_id, pass)
|
13
|
+
# You would perform what ever authentication you wanted to here or deligate to another object
|
14
|
+
return :app1, "yes"
|
15
|
+
end
|
16
|
+
end
|
data/example/app2/README
ADDED
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class App2 < Sinatra::Base
|
2
|
+
include SoldierOfCode::SentryDeligate
|
3
|
+
|
4
|
+
def login_uri
|
5
|
+
'/login'
|
6
|
+
end
|
7
|
+
|
8
|
+
def token_keys
|
9
|
+
[:app2,:default]
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticate(usr_id, pass)
|
13
|
+
# You would perform what ever authentication you wanted to here or deligate to another object
|
14
|
+
return :app2, "yes"
|
15
|
+
end
|
16
|
+
|
17
|
+
set :views, ~'/views'
|
18
|
+
|
19
|
+
get '/' do
|
20
|
+
"Hello from app2"
|
21
|
+
end
|
22
|
+
end
|
data/example/config.ru
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'haml'
|
4
|
+
require 'dirge' # see http://github.com/joshbuddy/dirge/
|
5
|
+
|
6
|
+
# replace this require with require 'watch-tower' in your code
|
7
|
+
require ::File.join(::File.dirname(__FILE__),'../lib/watch-tower.rb')
|
8
|
+
|
9
|
+
# our app files
|
10
|
+
require ~'app1/app1'
|
11
|
+
require ~'app1/app1_deligate'
|
12
|
+
require ~'app2/app2'
|
13
|
+
require ~'login_handler/login_handler'
|
14
|
+
|
15
|
+
use Rack::Session::Pool, :expire_after => 60 * 30 # 30 minute timeout on sessons
|
16
|
+
|
17
|
+
use SoldierOfCode::TowerGate, { '/app1/'=>App1Deligate,
|
18
|
+
'/app2/'=>App2}
|
19
|
+
|
20
|
+
map '/app2/' do
|
21
|
+
run App2
|
22
|
+
end
|
23
|
+
|
24
|
+
map '/app1/' do
|
25
|
+
run App1
|
26
|
+
end
|
27
|
+
|
28
|
+
map '/' do
|
29
|
+
run LoginHandler
|
30
|
+
end
|
File without changes
|
@@ -0,0 +1,31 @@
|
|
1
|
+
=begin
|
2
|
+
This is an example of a login handler
|
3
|
+
=end
|
4
|
+
class LoginHandler < Sinatra::Base
|
5
|
+
|
6
|
+
set :views, ~'/views'
|
7
|
+
|
8
|
+
get '/' do
|
9
|
+
haml :index
|
10
|
+
end
|
11
|
+
|
12
|
+
get '/login' do
|
13
|
+
return_path = session['watchtower.return_path'] ? session['watchtower.return_path'] : "/login"
|
14
|
+
haml :login, :locals=>{:submit_to=>return_path}
|
15
|
+
end
|
16
|
+
|
17
|
+
post '/login' do
|
18
|
+
if params[:email] == "foo@example.com" && params[:password] == "abc123" then
|
19
|
+
session[:default] = params[:email]
|
20
|
+
redirect '/app2/'
|
21
|
+
else
|
22
|
+
redirect '/login'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/logout' do
|
27
|
+
session[:default]=nil
|
28
|
+
redirect '/login'
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/watch-tower.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
Copyright (c) 2010 Kristan 'Krispy' Uccello <krispy@soldierofcode.com>
|
4
|
+
- Soldier Of Code
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
23
|
+
|
24
|
+
=end
|
25
|
+
module SoldierOfCode
|
26
|
+
|
27
|
+
module SentryDeligate
|
28
|
+
|
29
|
+
send :define_method, :token_keys do
|
30
|
+
[:default]
|
31
|
+
end
|
32
|
+
|
33
|
+
send :define_method, :valid_user_identifier? do |ident|
|
34
|
+
%w{ email username usr nickname nick u }.each do |v|
|
35
|
+
return true if v == ident
|
36
|
+
end
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
send :define_method, :valid_password_identifier? do |ident|
|
41
|
+
%w{ password pass pwd secret }.each do |v|
|
42
|
+
return true if v == ident
|
43
|
+
end
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
# authenticate -- perform authentication against usr/pass
|
48
|
+
send :define_method, :authenticate do |usr_id, pass|
|
49
|
+
# should return a token key and token value
|
50
|
+
throw "You need to implement the authenticate(usr_id,pass) method on #{self.class.name} which returns a token key and token value if successful"
|
51
|
+
end
|
52
|
+
|
53
|
+
# authenticated? -- correct token(s) is present on session
|
54
|
+
send :define_method, :authenticated? do |env|
|
55
|
+
session = env['rack.session']
|
56
|
+
token = nil
|
57
|
+
(send :token_keys).each do |k|
|
58
|
+
# check session for key
|
59
|
+
token = session[k] if session[k]
|
60
|
+
end
|
61
|
+
token
|
62
|
+
end
|
63
|
+
|
64
|
+
# authorized? -- token key is authorized or authorization credentials are provided
|
65
|
+
send :define_method, :authorized? do |env|
|
66
|
+
authorized = false
|
67
|
+
unless (send :authenticated?, env)
|
68
|
+
req = Rack::Request.new(env)
|
69
|
+
if req.post? then
|
70
|
+
usr = ''
|
71
|
+
pwd = ''
|
72
|
+
req.params.each do |k, v|
|
73
|
+
usr = v if usr == '' && (send :valid_user_identifier?, k)
|
74
|
+
pwd = v if pwd == '' && (send :valid_password_identifier?, k)
|
75
|
+
end
|
76
|
+
token_key, token_value = (send :authenticate, usr, pwd)
|
77
|
+
if token_key && token_value then
|
78
|
+
session = env['rack.session']
|
79
|
+
session[token_key] = token_value
|
80
|
+
env['rack.session'] = session
|
81
|
+
end
|
82
|
+
authorized = (send :authenticated?, env)
|
83
|
+
end
|
84
|
+
else
|
85
|
+
authorized = true
|
86
|
+
end
|
87
|
+
authorized
|
88
|
+
end
|
89
|
+
|
90
|
+
send :define_method, :login_uri do
|
91
|
+
# should return a token key and token value
|
92
|
+
throw "You need to implement the login_uri method on #{self.class.name} which returns a uri to its login view"
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class TowerGate
|
99
|
+
attr_accessor :guards, :app
|
100
|
+
|
101
|
+
def initialize(app=nil, guards={})
|
102
|
+
@app = app
|
103
|
+
@guards = guards
|
104
|
+
end
|
105
|
+
|
106
|
+
def call(env)
|
107
|
+
session = env['rack.session']
|
108
|
+
result = [401, {"Content-Type"=>"text/html"}, 'You have been arrested by this server.']
|
109
|
+
begin
|
110
|
+
guard = locate_guard(env)
|
111
|
+
|
112
|
+
if guard then
|
113
|
+
|
114
|
+
# "just need to check your paper work sir..."
|
115
|
+
if guard.authenticated?(env) then
|
116
|
+
# "looks like your cleared to enter, have a nice day"
|
117
|
+
result = @app.call(env)
|
118
|
+
elsif guard.authorized?(env) then
|
119
|
+
return_path = session['watchtower.return_path']
|
120
|
+
return_path = nil if return_path == ''
|
121
|
+
|
122
|
+
# "looks like your cleared to enter, have a nice day"
|
123
|
+
unless return_path then
|
124
|
+
result = @app.call(env)
|
125
|
+
else
|
126
|
+
code = 302
|
127
|
+
headers = {"Location" => return_path}
|
128
|
+
result = [code, headers, []]
|
129
|
+
session['watchtower.return_path'] = nil
|
130
|
+
end
|
131
|
+
else
|
132
|
+
# "If you'd like to step over here sir and please remove your shoes..."
|
133
|
+
code = 302
|
134
|
+
headers = {"Location" => guard.login_uri}
|
135
|
+
result = [code, headers, []]
|
136
|
+
end
|
137
|
+
else
|
138
|
+
# No guard on duty - "This compound is monitored at all times" - sure...
|
139
|
+
result = @app.call(env)
|
140
|
+
end
|
141
|
+
rescue => e
|
142
|
+
puts "#{__FILE__}:#{__LINE__} [#{__method__}] EXCEPTION: #{e}"
|
143
|
+
e.backtrace().each_with_index do |l, i|
|
144
|
+
puts "\t- EXCEPTION STACK [#{i}]: #{l}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
result
|
148
|
+
end
|
149
|
+
|
150
|
+
def locate_guard(env)
|
151
|
+
guard = nil
|
152
|
+
begin
|
153
|
+
session = env['rack.session'] ||= {}
|
154
|
+
path = env['REQUEST_PATH']
|
155
|
+
@guards.each do |proto_path, deligate|
|
156
|
+
if path.starts_with?(proto_path)
|
157
|
+
keys = deligate.new.token_keys # I really wanted to meta code a class method but...
|
158
|
+
token = nil
|
159
|
+
keys.each do |k|
|
160
|
+
# check session for key
|
161
|
+
token = session[k] if session[k]
|
162
|
+
end
|
163
|
+
unless token
|
164
|
+
session['watchtower.return_path'] = path
|
165
|
+
env['rack.session'] = session
|
166
|
+
guard = deligate.new
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
rescue => e
|
171
|
+
puts "#{__FILE__}:#{__LINE__} [#{__method__}] EXCEPTION: #{e}"
|
172
|
+
e.backtrace().each_with_index do |l, i|
|
173
|
+
puts "\t- EXCEPTION STACK [#{i}]: #{l}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
guard
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
class String
|
183
|
+
def starts_with?(characters)
|
184
|
+
self.match(/^#{characters}/) ? true : false
|
185
|
+
end
|
186
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: watch-tower
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kristan 'Krispy' Uccello
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-06 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: I was sick of all the complex abstractions that were being put into other authentication libs - I have attempted to keep this one as reuseable and non-abstract as possible
|
26
|
+
email: krispy@soldierofcode.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
files:
|
34
|
+
- AUTHORS
|
35
|
+
- Change.log
|
36
|
+
- README
|
37
|
+
- Rakefile
|
38
|
+
- VERSION
|
39
|
+
- example/Makefile
|
40
|
+
- example/README
|
41
|
+
- example/app1/README
|
42
|
+
- example/app1/app1.rb
|
43
|
+
- example/app1/app1_deligate.rb
|
44
|
+
- example/app1/views/layout.haml
|
45
|
+
- example/app2/README
|
46
|
+
- example/app2/app2.rb
|
47
|
+
- example/app2/views/layout.haml
|
48
|
+
- example/config.ru
|
49
|
+
- example/login_handler/README
|
50
|
+
- example/login_handler/login_handler.rb
|
51
|
+
- example/login_handler/views/index.haml
|
52
|
+
- example/login_handler/views/layout.haml
|
53
|
+
- example/login_handler/views/login.haml
|
54
|
+
- lib/watch-tower.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/kuccello/watch-tower
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options:
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.5
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Rack Middleware for multi app authentication - All along the watchtower - 'there must be some kinda way outa here said the joker to the thief'
|
83
|
+
test_files: []
|
84
|
+
|