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 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
+