watch-tower 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ go:
2
+ thin start -R config.ru -p 8080
3
+
data/example/README ADDED
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ require ~'app1_deligate'
2
+
3
+ class App1 < Sinatra::Base
4
+
5
+ set :views, ~'/views'
6
+ #set :public, ~'/public'
7
+ #set :root, ~''
8
+ #set :static, true
9
+
10
+ get '/' do
11
+ "Hello from app1"
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ %html
2
+ %head
3
+ %title
4
+ App 1 Layout
5
+ %body
6
+ = yield
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
@@ -0,0 +1,6 @@
1
+ %html
2
+ %head
3
+ %title
4
+ App 2 Layout
5
+ %body
6
+ = yield
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
@@ -0,0 +1,13 @@
1
+ %a{:href=>"/app1/"}
2
+ App 1
3
+
4
+ %br
5
+
6
+ %a{:href=>"/app2/"}
7
+ App 2
8
+
9
+ %br
10
+
11
+ %a{:href=>"/login"}
12
+ Login Handler Login
13
+
@@ -0,0 +1,6 @@
1
+ %html
2
+ %head
3
+ %title
4
+ Login Handler
5
+ %body
6
+ = yield
@@ -0,0 +1,10 @@
1
+ %form{:action=>submit_to,:method=>"post"}
2
+ %label
3
+ Email
4
+ %input{:type=>"text",:name=>"email"}
5
+ %br
6
+ %label
7
+ Password
8
+ %input{:type=>"password",:name=>"password"}
9
+ %br
10
+ %input{:type=>"submit",:value=>"Login"}
@@ -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
+