picnic 0.5.0 → 0.6.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/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