picnic 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.txt CHANGED
@@ -1,3 +1,18 @@
1
+ === 0.6.0 :: 2007-02-26
2
+
3
+ * Added support for CAS authentication. See picnic/authentication.rb for
4
+ details.
5
+ * Webrick and Mongrel can now be made to bind to a specific IP address using
6
+ the :bind_address option. If no :bind_address is specified, the server will
7
+ listen on all addresses (i.e. '0.0.0.0').
8
+ * The Public controller for serving the '/public' directory is gone. It has
9
+ been replaced by respective Webrick and Mongrel mechanisms for serving
10
+ directory contents, since these are much faster. If you're using CGI/FastCGI,
11
+ you'll have to manually configure your web server (i.e. probably Apache)
12
+ to serve your public directory contents.
13
+ * The gem package now correctly recognizes markaby as a required
14
+ dependency.
15
+
1
16
  === 0.5.0 :: 2007-12-20
2
17
 
3
18
  * First public release.
data/Rakefile CHANGED
@@ -53,6 +53,6 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
53
53
  #p.extra_deps - An array of rubygem dependencies.
54
54
  #p.spec_extras - A hash of extra values to set in the gemspec.
55
55
 
56
- # we now package camping-1.5.0.180 in the vendor directory
57
- #p.extra_deps = ['camping']
56
+ # note that camping-1.5.0.180 is now bundled with picnic the vendor directory,
57
+ p.extra_deps = ['markaby']
58
58
  end
@@ -2,48 +2,48 @@ module Picnic #:nodoc:
2
2
  # These modules (currently only one module, but more in the future) provide authentication
3
3
  # for your Camping app.
4
4
  #
5
- # This code is based on Camping::BasicAuth written by Manfred Stienstra
6
- # (see http://www.fngtps.com/2006/05/basic-authentication-for-camping).
7
- #
8
- # ----
9
- #
10
- # Picnic::Authentication::Basic can be mixed into a camping application to get Basic Authentication
11
- # support in the application. The module defines a <tt>service</tt> method that only continues
12
- # the request chain when proper credentials are given.
13
- #
14
- # == Getting Started
15
- #
16
- # To activate Basic Authentication for your application:
17
- #
18
- # 1. Picnic-fy your Camping app (e.g: <tt>Camping.goes :your_app; YourApp.picnic!</tt>)
19
- # 2. Call <tt>authenticate_using <module></tt> (e.g: <tt>YourApp.authenticate_using :basic</tt>)
20
- # 3. Define an <tt>authenticate</tt> method on your application module that takes a hash.
21
- # The hash contains credentials like <tt>:username</tt>, <tt>:password</tt>, and <tt>:hostname</tt>,
22
- # although future authentication modules may submit other credentials.
23
- # The <tt>authenticate</tt> method should return true when the credentials are valid.
24
- # Examples:
25
- #
26
- # module Blog
27
- # def authenticate(credentials)
28
- # credentials[:username] == 'admin' &&
29
- # credentials[:password] == 'flapper30'
30
- # end
31
- # module_function :authenticate
32
- # end
33
- #
34
- # or
35
- #
36
- # module Wiki
37
- # def authenticate(credentials)
38
- # u = credentials[:username]
39
- # p = credentials[:password]
40
- # Models::User.find_by_username_and_password u, p
41
- # end
42
- # module_function :authenticate
43
- # end
44
- #
45
- # 4. <tt>service</tt> sets <tt>@credentials</tt> to the credentials of the person who logged in.
46
5
  module Authentication
6
+ # Picnic::Authentication::Basic provides Basic HTTP Authentication for your Camping app.
7
+ # The module defines a <tt>service</tt> method that only continues the request chain when
8
+ # proper credentials are provided by the client (browser).
9
+ #
10
+ # == Getting Started
11
+ #
12
+ # To activate Basic Authentication for your application:
13
+ #
14
+ # 1. Picnic-fy your Camping app (e.g: <tt>Camping.goes :your_app; YourApp.picnic!</tt>)
15
+ # 2. Call <tt>YourApp.authenticate_using :basic</tt>.
16
+ # 3. Define an <tt>authenticate</tt> method on your application module that takes a hash.
17
+ # The hash contains credentials like <tt>:username</tt>, <tt>:password</tt>, and <tt>:hostname</tt>,
18
+ # although future authentication modules may submit other credentials.
19
+ # The <tt>authenticate</tt> method should return true when the credentials are valid.
20
+ # Examples:
21
+ #
22
+ # module Blog
23
+ # def authenticate(credentials)
24
+ # credentials[:username] == 'admin' &&
25
+ # credentials[:password] == 'flapper30'
26
+ # end
27
+ # module_function :authenticate
28
+ # end
29
+ #
30
+ # or
31
+ #
32
+ # module Wiki
33
+ # def authenticate(credentials)
34
+ # u = credentials[:username]
35
+ # p = credentials[:password]
36
+ # Models::User.find_by_username_and_password u, p
37
+ # end
38
+ # module_function :authenticate
39
+ # end
40
+ #
41
+ # 4. <tt>service</tt> sets <tt>@credentials</tt> to the credentials of the person who logged in.
42
+ #
43
+ # ----
44
+ #
45
+ # This code is based on Camping::BasicAuth written by Manfred Stienstra
46
+ # (see http://www.fngtps.com/2006/05/basic-authentication-for-camping).
47
47
  module Basic
48
48
  require 'base64'
49
49
 
@@ -56,14 +56,15 @@ module Picnic #:nodoc:
56
56
  end
57
57
  end
58
58
 
59
- # The <tt>service</tt> method, when mixed into your application module, wraps around the
60
- # <tt>service</tt> method defined by Camping. It halts execution of the controllers when
61
- # your <tt>authenticate</tt> method returns false. See the module documentation how to
62
- # define your own <tt>authenticate</tt> method.
63
59
  def service(*a)
60
+ app = Kernel.const_get self.class.name.gsub(/^(\w+)::.+$/, '\1')
61
+ unless app.methods.include? :authenticate
62
+ raise "Basic authentication is enabled but the 'authenticate' method has not been defined."
63
+ end
64
+
64
65
  @credentials = read_credentials || {}
65
- app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
66
- if Kernel.const_get(app).authenticate(@credentials)
66
+
67
+ if app.authenticate(@credentials)
67
68
  s = super(*a)
68
69
  else
69
70
  @status = 401
@@ -76,5 +77,136 @@ module Picnic #:nodoc:
76
77
  s
77
78
  end
78
79
  end
80
+
81
+
82
+ # Picnic::Authentication::Cas provides basic CAS (Central Authentication System) authentication
83
+ # for your Camping app.
84
+ #
85
+ # To learn more about CAS, see http://rubycas-client.googlecode.com and
86
+ # http://www.ja-sig.org/products/cas.
87
+ #
88
+ # The module defines a <tt>service</tt> method that intercepts every request to check for CAS
89
+ # authentication. If the user has already been authenticated, the request proceeds as normal
90
+ # and the authenticated user's username is made available under <tt>@state[:cas_username].
91
+ # Otherwise the request is redirected to your CAS server for authentication.
92
+ #
93
+ # == Getting Started
94
+ #
95
+ # To activate CAS authentication for your application:
96
+ #
97
+ # 1. Picnic-fy your Camping app (e.g: <tt>Camping.goes :your_app; YourApp.picnic!</tt>)
98
+ # 2. Call <tt>YourApp.authenticate_using :cas</tt>.
99
+ # 3. In your app's configuration YAML file add something like this:
100
+ # authentication:
101
+ # cas_base_url: https://login.example.com/cas
102
+ # Where the value for </tt>cas_base_url</tt> is the URL of your CAS server.
103
+ # 4. That's it. Now whenever a user tries to access any of your controller's actions,
104
+ # the request will be checked for CAS authentication. If the user is authenticated,
105
+ # their username is availabe in @state[:cas_username]. Note that there is currently
106
+ # no way to apply CAS authentication only to certain controllers or actions. When
107
+ # enabled, CAS authentication applies to your entire application, except for items
108
+ # placed in the /public subdirectory (CSS files, JavaScripts, images, etc.). The
109
+ # public directory does not require CAS authentication, so anyone can access its
110
+ # contents.
111
+ #
112
+ module Cas
113
+ require 'camping/db'
114
+ require 'camping/session'
115
+
116
+
117
+ $: << File.dirname(File.expand_path(__FILE__))+"/../../../rubycas-client2/lib" # for development
118
+ require 'rubycas-client'
119
+
120
+ # app = Kernel.const_get self.name.gsub(/^(\w+)::.+$/, '\1')
121
+ # raise "Cannot enable CAS authentication because your Camping app does not extend Camping::Session." unless
122
+ # app.ancestors.include?(Camping::Session)
123
+
124
+ # There must be a smarter way to do this... but for now, we just re-implement
125
+ # the Camping::Session method here to provide session support for CAS.
126
+ module Session
127
+ # This doesn't work :( MySQL connection is not carried over.
128
+ #define_method(:service, Camping::Session.instance_method(:service))
129
+
130
+ def service(*a)
131
+ Camping::Models::Session.create_schema
132
+
133
+ session = Camping::Models::Session.persist @cookies
134
+ app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
135
+ @state = (session[app] ||= Camping::H[])
136
+ hash_before = Marshal.dump(@state).hash
137
+ s = super(*a)
138
+ if session
139
+ hash_after = Marshal.dump(@state).hash
140
+ unless hash_before == hash_after
141
+ session[app] = @state
142
+ session.save
143
+ end
144
+ end
145
+ s
146
+ end
147
+ end
148
+
149
+ def self.included(mod)
150
+ mod.module_eval do
151
+ include Cas::Session
152
+ end
153
+ end
154
+
155
+ def service(*a)
156
+ $LOG.debug "Running CAS filter for request #{a.inspect}..."
157
+
158
+ if @env['PATH_INFO'] =~ /^\/public\/.*/
159
+ $LOG.debug "Access to items in /public subdirectory does not require CAS authentication."
160
+ return super(*a)
161
+ end
162
+ if @state[:cas_username]
163
+ $LOG.debug "Local CAS session exists for user #{@state[:cas_username]}."
164
+ return super(*a)
165
+ end
166
+
167
+ client = CASClient::Client.new($CONF[:authentication].merge(:logger => $LOG))
168
+
169
+ ticket = @input[:ticket]
170
+
171
+ cas_login_url = client.add_service_to_login_url(read_service_url(@env))
172
+
173
+ if ticket
174
+ if ticket =~ /^PT-/
175
+ st = CASClient::ProxyTicket.new(ticket, read_service_url(@env), @input[:renew])
176
+ else
177
+ st = CASClient::ServiceTicket.new(ticket, read_service_url(@env), @input[:renew])
178
+ end
179
+
180
+ $LOG.debug "Got CAS ticket: #{st.inspect}"
181
+
182
+ client.validate_service_ticket(st)
183
+ if st.is_valid?
184
+ $LOG.info "CAS ticket #{st.ticket.inspect} is valid. Opening local CAS session for user #{st.response.user.inspect}."
185
+ @state[:cas_username] = st.response.user
186
+ return super(*a)
187
+ else
188
+ $LOG.warn "CAS ticket #{st.ticket.inspect} is INVALID. Redirecting back to CAS server at #{cas_login_url.inspect} for authentication."
189
+ @state[:cas_username] = nil
190
+ redirect cas_login_url
191
+ s = self
192
+ end
193
+ else
194
+ $LOG.info "User is unauthenticated and no CAS ticket found. Redirecting to CAS server at #{cas_login_url.inspect} for authentication."
195
+ @state[:cas_username] = nil
196
+ redirect cas_login_url
197
+ s = self
198
+ end
199
+ s
200
+ end
201
+
202
+ private
203
+ def read_service_url(env)
204
+ if $CONF[:authentication][:service_url]
205
+ $CONF[:authentication][:service_url]
206
+ else
207
+ env['REQUEST_URI'].gsub(/service=[^&]*[&]?/,'').gsub(/ticket=[^&]*[&]?/,'')
208
+ end
209
+ end
210
+ end
79
211
  end
80
212
  end
@@ -1,31 +1,4 @@
1
1
  module Picnic
2
2
  module Controllers
3
- # Provides a controller for serving up static content from your app's <tt>/public</tt> directory.
4
- # This can be used to serve css, js, jpg, png, and gif files. Anything you put in your app's
5
- # '/public' directory will be served up under the '/public' path.
6
- #
7
- # That is, say you have:
8
- # /srv/www/camping/my_app/public/test.jpg
9
- # This should be availabe at:
10
- # http://myapp.com/public/test.jpg
11
- #
12
- # This controller is automatically enabled for all Picnic-enabled apps.
13
- class Public < Camping::Controllers::R '/public/(.+)'
14
- BASE_PATH = ("#{$APP_PATH}/.." || File.expand_path(File.dirname(__FILE__)))+'/lib/public'
15
-
16
- MIME_TYPES = {'.css' => 'text/css', '.js' => 'text/javascript',
17
- '.jpg' => 'image/jpeg', '.png' => 'image/png',
18
- '.gif' => 'image/gif'}
19
-
20
- def get(path)
21
- @headers['Content-Type'] = MIME_TYPES[path[/\.\w+$/, 0]] || "text/plain"
22
- unless path.include? ".." # prevent directory traversal attacks
23
- @headers['X-Sendfile'] = "#{BASE_PATH}/#{path}"
24
- else
25
- @status = "403"
26
- "403 - Invalid path"
27
- end
28
- end
29
- end
30
3
  end
31
4
  end
@@ -39,7 +39,7 @@ module Picnic
39
39
 
40
40
  begin
41
41
  s = WEBrick::HTTPServer.new(
42
- :BindAddress => "0.0.0.0",
42
+ :BindAddress => Picnic::Conf.bind_address || "0.0.0.0",
43
43
  :Port => Picnic::Conf.port
44
44
  )
45
45
  rescue Errno::EACCES
@@ -49,6 +49,14 @@ module Picnic
49
49
 
50
50
  self.create
51
51
  s.mount "#{Picnic::Conf.uri_path}", WEBrick::CampingHandler, self
52
+
53
+ public_dirs = Picnic::Conf.public_dirs || Picnic::Conf.public_dir
54
+ public_dirs = [public_dirs] unless public_dirs.kind_of? Array
55
+
56
+ public_dirs.each do |d|
57
+ s.mount "#{Picnic::Conf.uri_path}#{d[:path]}", WEBrick::HTTPServlet::FileHandler, d[:dir]
58
+ end
59
+
52
60
 
53
61
  # This lets Ctrl+C shut down your server
54
62
  trap(:INT) do
@@ -124,18 +132,28 @@ module Picnic
124
132
 
125
133
  puts "\n** #{self} is starting. Look in #{Picnic::Conf.log[:file].inspect} for further notices."
126
134
 
127
- settings = {:host => "0.0.0.0", :log_file => Picnic::Conf.log[:file], :cwd => $APP_PATH}
135
+ settings = {
136
+ :host => Picnic::Conf.bind_address || "0.0.0.0",
137
+ :log_file => Picnic::Conf.log[:file],
138
+ :cwd => $APP_PATH
139
+ }
128
140
 
129
141
  # need to close all IOs before daemonizing
130
142
  $LOG.close if $DAEMONIZE
131
143
 
144
+ public_dirs = Picnic::Conf.public_dirs || Picnic::Conf.public_dir
145
+ public_dirs = [public_dirs] unless public_dirs.kind_of? Array
146
+
132
147
  begin
133
148
  app_mod = self
134
- config = Mongrel::Configurator.new settings do
149
+ mongrel = Mongrel::Configurator.new settings do
135
150
  daemonize :log_file => Picnic::Conf.log[:file], :cwd => $APP_PATH if $DAEMONIZE
136
-
151
+ puts Picnic::Conf.uri_path
137
152
  listener :port => Picnic::Conf.port do
138
153
  uri Picnic::Conf.uri_path, :handler => Mongrel::Camping::CampingHandler.new(app_mod)
154
+ public_dirs.each do |d|
155
+ uri "#{Picnic::Conf.uri_path}#{d[:path]}", :handler => Mongrel::DirHandler.new(d[:dir])
156
+ end
139
157
  setup_signals
140
158
  end
141
159
  end
@@ -143,8 +161,7 @@ module Picnic
143
161
  exit 1
144
162
  end
145
163
 
146
-
147
- config.run
164
+ mongrel.run
148
165
 
149
166
  self.init_logger
150
167
  #self.init_db_logger
@@ -159,8 +176,9 @@ module Picnic
159
176
 
160
177
  puts "\n** #{self} is running at http://#{ENV['HOSTNAME'] || 'localhost'}:#{Picnic::Conf.port}#{Picnic::Conf.uri_path} and logging to '#{Picnic::Conf.log[:file]}'"
161
178
 
179
+
162
180
  self.prestart if self.respond_to? :prestart
163
- config.join
181
+ mongrel.join
164
182
 
165
183
  clear_pid_file
166
184
 
@@ -1,7 +1,7 @@
1
1
  module Picnic #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 5
4
+ MINOR = 6
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
data/lib/picnic.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  $: << File.dirname(File.expand_path(__FILE__))
2
2
  $: << File.dirname(File.expand_path(__FILE__))+"/../vendor/camping-1.5.180/lib"
3
3
 
4
- require 'camping'
5
- require 'camping/db'
6
- require 'camping/session'
7
4
 
8
- require 'active_support' unless Object.const_defined?(:ActiveSupport)
5
+ unless Object.const_defined?(:ActiveSupport)
6
+ begin
7
+ require 'active_support'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require 'active_support'
11
+ end
12
+ end
13
+
14
+ require 'camping'
9
15
 
10
16
  require 'picnic/utils'
11
17
  require 'picnic/conf'
12
18
  require 'picnic/postambles'
13
- require 'picnic/controllers'
14
19
 
15
20
 
16
21
  class Module
@@ -34,8 +39,8 @@ class Module
34
39
  # See <tt>config.example.yml</tt> for info on configuring the logger.
35
40
  def init_logger
36
41
  puts "Initializing #{self} logger..."
37
- $LOG = self::Utils::Logger.new(self::Conf.log[:file])
38
- $LOG.level = "#{self}::Utils::Logger::#{self::Conf.log[:level]}".constantize
42
+ $LOG = Picnic::Utils::Logger.new(self::Conf.log[:file])
43
+ $LOG.level = "Picnic::Utils::Logger::#{self::Conf.log[:level]}".constantize
39
44
  end
40
45
  module_function :init_logger
41
46
 
@@ -62,14 +67,18 @@ class Module
62
67
  # Blog.picnic!
63
68
  #
64
69
  # $CONF[:authentication] ||= {:username => 'admin', :password => 'picnic'}
65
- # Blog.authenticate_using :basic if Blog::Conf[:authentication]
70
+ # Blog.authenticate_using :basic
71
+ #
72
+ # module Blog
73
+ # def self.authenticate(credentials)
74
+ # credentials[:username] == Taskr::Conf[:authentication][:username] &&
75
+ # credentials[:password] == Taskr::Conf[:authentication][:password]
76
+ # end
77
+ # end
66
78
  #
67
79
  # Note that in the above example we use the authentication configuration from
68
- # your app's conf file. We specify default credentials for when your
69
- # conf file doesn't define them.
80
+ # your app's conf file.
70
81
  #
71
- # Currently only HTTP Basic authentication is available. See Picnic::Authentication
72
- # for more info.
73
82
  def authenticate_using(mod)
74
83
  require 'picnic/authentication'
75
84
  mod = "#{self}::Authentication::#{mod.to_s.camelize}".constantize unless mod.kind_of? Module
@@ -119,4 +128,4 @@ class Module
119
128
  self::Conf.load(self)
120
129
  init_logger
121
130
  end
122
- end
131
+ end
metadata CHANGED
@@ -1,33 +1,37 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.2
3
- specification_version: 1
4
2
  name: picnic
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.5.0
7
- date: 2007-12-21 00:00:00 -05:00
8
- summary: Camping for sissies
9
- require_paths:
10
- - lib
11
- email: matt@roughest.net
12
- homepage: http://picnic.rubyforge.org
13
- rubyforge_project: picnic
14
- description: Camping for sissies
15
- autorequire:
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 0.6.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Matt Zukowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-02-26 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: markaby
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: Camping for sissies
25
+ email: matt@roughest.net
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - CHANGELOG.txt
32
+ - LICENSE.txt
33
+ - Manifest.txt
34
+ - README.txt
31
35
  files:
32
36
  - CHANGELOG.txt
33
37
  - LICENSE.txt
@@ -57,21 +61,32 @@ files:
57
61
  - vendor/camping-1.5.180/lib/camping/reloader.rb
58
62
  - vendor/camping-1.5.180/lib/camping/session.rb
59
63
  - vendor/camping-1.5.180/lib/camping/webrick.rb
60
- test_files:
61
- - test/picnic_test.rb
64
+ has_rdoc: true
65
+ homepage: http://picnic.rubyforge.org
66
+ post_install_message:
62
67
  rdoc_options:
63
68
  - --main
64
69
  - README.txt
65
- extra_rdoc_files:
66
- - CHANGELOG.txt
67
- - LICENSE.txt
68
- - Manifest.txt
69
- - README.txt
70
- executables: []
71
-
72
- extensions: []
73
-
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
74
84
  requirements: []
75
85
 
76
- dependencies: []
77
-
86
+ rubyforge_project: picnic
87
+ rubygems_version: 1.0.1
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: Camping for sissies
91
+ test_files:
92
+ - test/picnic_test.rb