ramaze 2010.06.18 → 2011.01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.gitignore +1 -0
  2. data/MANIFEST +9 -16
  3. data/README.md +37 -30
  4. data/Rakefile +5 -1
  5. data/TODO.md +19 -0
  6. data/doc/AUTHORS +5 -1
  7. data/doc/CHANGELOG +3553 -3272
  8. data/doc/tutorial/todolist.html +1512 -1512
  9. data/examples/app/blog/app.rb +2 -0
  10. data/examples/app/todolist/controller/init.rb +1 -2
  11. data/examples/app/wiktacular/mkd/main/2007-07-20_19-21-12.mkd +1 -1
  12. data/examples/app/wiktacular/mkd/main/2007-07-20_19-23-10.mkd +1 -1
  13. data/examples/app/wiktacular/mkd/main/2007-07-20_19-45-07.mkd +1 -1
  14. data/examples/app/wiktacular/mkd/main/current.mkd +1 -1
  15. data/examples/app/wiktacular/mkd/testing/2007-07-20_16-43-46.mkd +1 -1
  16. data/examples/app/wiktacular/mkd/testing/2007-07-20_19-43-50.mkd +2 -2
  17. data/examples/app/wiktacular/mkd/testing/2007-07-21_18-47-08.mkd +16 -16
  18. data/examples/app/wiktacular/mkd/testing/2007-07-21_18-47-54.mkd +16 -16
  19. data/examples/app/wiktacular/mkd/testing/current.mkd +16 -16
  20. data/lib/proto/model/init.rb +1 -1
  21. data/lib/proto/public/js/jquery.js +2034 -1095
  22. data/lib/proto/start.rb +2 -0
  23. data/lib/proto/view/index.xhtml +3 -3
  24. data/lib/ramaze.rb +1 -2
  25. data/lib/ramaze/cache.rb +1 -0
  26. data/lib/ramaze/cache/sequel.rb +131 -37
  27. data/lib/ramaze/controller.rb +1 -0
  28. data/lib/ramaze/gestalt.rb +75 -46
  29. data/lib/ramaze/helper.rb +1 -0
  30. data/lib/ramaze/helper/auth.rb +38 -4
  31. data/lib/ramaze/helper/blue_form.rb +498 -78
  32. data/lib/ramaze/helper/cache.rb +2 -2
  33. data/lib/ramaze/helper/csrf.rb +225 -0
  34. data/lib/ramaze/helper/erector.rb +67 -9
  35. data/lib/ramaze/helper/flash.rb +4 -2
  36. data/lib/ramaze/helper/gestalt.rb +2 -0
  37. data/lib/ramaze/helper/gravatar.rb +1 -1
  38. data/lib/ramaze/helper/localize.rb +4 -0
  39. data/lib/ramaze/helper/send_file.rb +30 -0
  40. data/lib/ramaze/helper/thread.rb +5 -0
  41. data/lib/ramaze/helper/user.rb +4 -3
  42. data/lib/ramaze/helper/xhtml.rb +87 -8
  43. data/lib/ramaze/log.rb +13 -0
  44. data/lib/ramaze/log/analogger.rb +15 -5
  45. data/lib/ramaze/log/growl.rb +28 -13
  46. data/lib/ramaze/log/hub.rb +12 -4
  47. data/lib/ramaze/log/informer.rb +28 -11
  48. data/lib/ramaze/log/knotify.rb +7 -2
  49. data/lib/ramaze/log/logger.rb +12 -4
  50. data/lib/ramaze/log/logging.rb +40 -14
  51. data/lib/ramaze/log/rotatinginformer.rb +47 -23
  52. data/lib/ramaze/log/syslog.rb +37 -31
  53. data/lib/ramaze/log/xosd.rb +7 -4
  54. data/lib/ramaze/middleware_compiler.rb +2 -2
  55. data/lib/ramaze/snippets/fiber.rb +63 -63
  56. data/lib/ramaze/snippets/ramaze/lru_hash.rb +1 -1
  57. data/lib/ramaze/tool/bin.rb +1 -1
  58. data/lib/ramaze/version.rb +1 -1
  59. data/lib/ramaze/view.rb +4 -4
  60. data/lib/ramaze/view/erector.rb +88 -13
  61. data/ramaze.gemspec +65 -65
  62. data/spec/ramaze/bin/ramaze.rb +1 -1
  63. data/spec/ramaze/cache/localmemcache.rb +20 -12
  64. data/spec/ramaze/cache/sequel.rb +19 -19
  65. data/spec/ramaze/helper/blue_form.rb +549 -257
  66. data/spec/ramaze/helper/csrf.rb +109 -0
  67. data/spec/ramaze/helper/httpdigest.rb +31 -29
  68. data/spec/ramaze/helper/user.rb +1 -1
  69. data/spec/ramaze/helper/xhtml.rb +17 -0
  70. data/spec/ramaze/log/growl.rb +34 -0
  71. data/spec/ramaze/log/informer.rb +1 -0
  72. data/spec/ramaze/view/erector.rb +49 -71
  73. data/spec/ramaze/view/erector/external_view.erector +5 -0
  74. data/spec/ramaze/view/erector/index.erector +5 -0
  75. data/spec/ramaze/view/erector/layout.erector +13 -3
  76. data/spec/ramaze/view/erector/tables.erector +23 -0
  77. data/spec/ramaze/view/erector/view.erector +6 -0
  78. data/tasks/git.rake +2 -2
  79. metadata +133 -176
  80. data/examples/helpers/form_with_sequel.rb +0 -24
  81. data/examples/helpers/nitro_form.rb +0 -23
  82. data/lib/ramaze/helper/form.rb +0 -133
  83. data/lib/ramaze/helper/nitroform.rb +0 -14
  84. data/lib/ramaze/helper/pager.rb +0 -367
  85. data/lib/ramaze/helper/partial.rb +0 -100
  86. data/lib/ramaze/helper/sequel.rb +0 -55
  87. data/lib/ramaze/helper/sequel_form.rb +0 -284
  88. data/lib/vendor/etag.rb +0 -22
  89. data/spec/ramaze/helper/form.rb +0 -360
  90. data/spec/ramaze/helper/pager.rb +0 -96
  91. data/spec/ramaze/helper/sequel_form.rb +0 -94
  92. data/spec/ramaze/view/erector/external.erector +0 -1
  93. data/spec/ramaze/view/erector/invoke_helper_method.erector +0 -1
  94. data/spec/ramaze/view/erector/strict_xhtml.erector +0 -3
  95. data/spec/ramaze/view/erector/sum.erector +0 -1
@@ -66,11 +66,11 @@ module Ramaze
66
66
  # to do lazy computation of the cached value conveniently when using a
67
67
  # custom TTL or longer expressions that don't fit on one line with ||=.
68
68
  #
69
- # @usage Example to get the cache object directly
69
+ # @example to get the cache object directly
70
70
  #
71
71
  # count = cache_value[:count] ||= Article.count
72
72
  #
73
- # @usage Example with block
73
+ # @example with block
74
74
  #
75
75
  # count = cache_value(:count){ Article.count }
76
76
  # count = cache_value(:count, :ttl => 60){ Article.count }
@@ -0,0 +1,225 @@
1
+ require 'securerandom'
2
+ require 'digest'
3
+
4
+ module Ramaze
5
+ module Helper
6
+ ##
7
+ # A relatively basic yet useful helper that can be used to protect your application
8
+ # from CSRF attacks/exploits. Note that this helper merely generates the required data,
9
+ # and provides several methods. You still need to manually add the token to each form.
10
+ #
11
+ # The reason for this is because this is quite simple. Ramaze is meant as a framework that
12
+ # works with any given helper, ORM, template engine and so on. If we were to automatically
13
+ # load this helper and include (a perhaps more advanced) CSRF system that would mean that
14
+ # every form helper, official or third-party, would have to support that specific system.
15
+ # However, there's no need to panic as it's very easy to setup a basic anti CSRF system.
16
+ #
17
+ # == Usage
18
+ #
19
+ # In order to enable CSRF protection we need to do two things. Load the helper and create
20
+ # a before_all block in a controller. Take a look at the following code:
21
+ #
22
+ # class BaseController < Ramaze::Controller
23
+ # before_all do
24
+ # puts "Hello, before_all!"
25
+ # end
26
+ # end
27
+ #
28
+ # This would output "Hello, before_all!" to the console upon each request. Not very useful
29
+ # but it does show what the before_all block can do. On to actual CSRF related code!
30
+ #
31
+ # class BaseController < Ramaze::Controller
32
+ # before_all do
33
+ # csrf_protection :save do
34
+ # # ....
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # This example introduces an extra block that validates the current request.
40
+ # Whenever a user requests a controller that either extends BaseController or has it's own
41
+ # before_all block Ramaze will check if the current request data contains a CSRF token.
42
+ # Of course an if/end isn't very useful if it doesn't do anything, let's add some code.
43
+ #
44
+ # class BaseController < Ramaze::Controller
45
+ # before_all do
46
+ # csrf_protection :save do
47
+ # puts "Hello, unsafe data!"
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # The code above checks if the current method is "save" (or any other of the provided methods)
53
+ # and checks if an CSRF token is supplied if the method matches. Protected methods require
54
+ # a token in ALL HTTP requests (GET, POST, etc). While this may seem weird since GET is generally
55
+ # used for safe actions it actually makes sence. Ramaze stores both the POST and GET parameters in
56
+ # the request.params hash. While this makes it easy to work with POST/GET data this also makes it
57
+ # easier to spoof POST requests using a GET request, thus this helper protects ALL request methods.
58
+ #
59
+ # If you're a lazy person you can copy-paste the example below and adapt it to your needs.
60
+ #
61
+ # class BaseController < Ramaze::Controller
62
+ # before_all do
63
+ # csrf_protection :save do
64
+ # respond("The supplied CSRF token is invalid.", 401)
65
+ # end
66
+ # end
67
+ # end
68
+ #
69
+ # @author Yorick Peterse
70
+ #
71
+ module CSRF
72
+
73
+ ##
74
+ # Method that can be used to protect the specified methods against CSRF exploits.
75
+ # Each protected method will require the token to be stored in a field called "csrf_token".
76
+ # This method will then validate that token against the current token in the session.
77
+ #
78
+ # @author Yorick Peterse
79
+ # @param [Strings/Symbol] *methods Methods that will be protected/unprotected.
80
+ # @param [Block] Block that will be executed if the token is invalid.
81
+ # @example
82
+ #
83
+ # # Protect "create" and "save" against CSRF exploits
84
+ # before_all do
85
+ # csrf_protection :create, :save do
86
+ # respond("GET TO DA CHOPPA!", 401)
87
+ # end
88
+ # end
89
+ #
90
+ def csrf_protection *methods, &block
91
+ # Only protect the specified methods
92
+ if methods.include?(action.name) or methods.include?(action.name.to_sym)
93
+ # THINK: For now the field name is hard-coded to "csrf_token". While
94
+ # this is perfectly fine in most cases it might be a good idea
95
+ # to allow developers to change the name of this field (for whatever the reason).
96
+ if validate_csrf_token(request.params['csrf_token']) != true
97
+ yield
98
+ end
99
+ end
100
+ end
101
+
102
+ ##
103
+ # Generate a new token and create the session array that will be used to validate the client.
104
+ # The following items are stored in the session:
105
+ #
106
+ # * token: An unique hash that will be stored in each form
107
+ # * agent: The visitor's user agent
108
+ # * ip: The IP address of the visitor
109
+ # * time: Timestamp that indicates at what time the data was generated.
110
+ #
111
+ # Note that this method will be automatically called if no CSRF token exists.
112
+ #
113
+ # @author Yorick Peterse
114
+ # @param [Hash] Additional arguments that can be set such as the TTL.
115
+ # @return [Void]
116
+ #
117
+ def generate_csrf_token args = {}
118
+ # Default TTL is 15 minutes
119
+ if args[:ttl]
120
+ ttl = args[:ttl]
121
+ else
122
+ ttl = 900
123
+ end
124
+
125
+ # Generate all the required data
126
+ time = Time.new.to_i.to_s
127
+ number = SecureRandom.random_number(10000).to_s
128
+ base64 = SecureRandom.base64.to_s
129
+ token = Digest::SHA2.new(512).hexdigest(srand.to_s + rand.to_s + time + number + base64).to_s
130
+
131
+ # Get several details from the client such as the user agent, IP, etc
132
+ ip = request.env['REMOTE_ADDR']
133
+ agent = request.env['HTTP_USER_AGENT']
134
+ host = request.env['REMOTE_HOST']
135
+
136
+ # Time to store all the data
137
+ session[:_csrf] = {
138
+ :time => time.to_i,
139
+ :token => token,
140
+ :ip => ip,
141
+ :agent => agent,
142
+ :host => host,
143
+ :ttl => ttl
144
+ }
145
+
146
+ # Prevent this method from returning any value (it isn't needed anyway)
147
+ return
148
+ end
149
+
150
+ ##
151
+ # Retrieves the current value of the CSRF token.
152
+ #
153
+ # @author Yorick Peterse
154
+ # @return [String] The current CSRF token.
155
+ # @example
156
+ #
157
+ # form(@data, :method => :post) do |f|
158
+ # f.input_hidden :csrf_token, get_csrf_token()
159
+ # end
160
+ #
161
+ def get_csrf_token
162
+ if !session[:_csrf] or !self.validate_csrf_token(session[:_csrf][:token])
163
+ self.generate_csrf_token
164
+ end
165
+
166
+ # Land ho!
167
+ return session[:_csrf][:token]
168
+ end
169
+
170
+ ##
171
+ # Validates the request based on the current session date stored in _csrf.
172
+ # The following items are verified:
173
+ #
174
+ # * Do the user agent, ip and token match those supplied by the visitor?
175
+ # * Has the token been expired? (after 15 minutes).
176
+ #
177
+ # If any of these checks fail this method will return FALSE. It's your job to
178
+ # take action based on the results of this method.
179
+ #
180
+ # @author Yorick Peterse
181
+ # @param [String] input_token The CSRF token to validate.
182
+ # @return [Bool]
183
+ # @example
184
+ #
185
+ # before_all do
186
+ # if validate_csrf_token(request.params['csrf_token']) != true
187
+ # respond("Invalid CSRF token", 401)
188
+ # end
189
+ # end
190
+ #
191
+ def validate_csrf_token input_token
192
+ # Check if the CSRF data has been generated and generate it if this
193
+ # hasn't been done already (usually on the first request).
194
+ if !session[:_csrf] or session[:_csrf].empty?
195
+ self.generate_csrf_token
196
+ end
197
+
198
+ # Get several details from the client such as the user agent, IP, etc
199
+ ip = session[:_csrf][:ip]
200
+ agent = session[:_csrf][:agent]
201
+ host = session[:_csrf][:host]
202
+
203
+ # Get the current time and the time when the token was created
204
+ now = Time.new.to_i
205
+ token_time = session[:_csrf][:time]
206
+
207
+ # Mirror mirror on the wall, who's the most secure of them all?
208
+ results = Array.new
209
+ results.push( session[:_csrf][:token] == input_token )
210
+ results.push( (now - token_time) <= session[:_csrf][:ttl] )
211
+ results.push( host == request.env['REMOTE_HOST'] )
212
+ results.push( ip == request.env['REMOTE_ADDR'] )
213
+ results.push( agent == request.env['HTTP_USER_AGENT'] )
214
+
215
+ # Verify the results
216
+ if results.include?(false)
217
+ return false
218
+ else
219
+ return true
220
+ end
221
+ end
222
+
223
+ end # <-- End of CSRF module
224
+ end
225
+ end
@@ -5,15 +5,19 @@ require 'erector'
5
5
 
6
6
  module Ramaze
7
7
 
8
- # Allows you to use some shortcuts for Erector in your Controller.
9
-
10
- # use this inside your controller to directly build Erector
11
- # Refer to the Erector-documentation and testsuite for more examples.
12
- # Usage:
13
- # erector { h1 "Apples & Oranges" } #=> "<h1>Apples &amp; Oranges</h1>"
14
- # erector { h1(:class => 'fruits&floots'){ text 'Apples' } } #=> "<h1 class=\"fruits&amp;floots\">Apples</h1>"
15
-
16
8
  module Helper
9
+
10
+ ##
11
+ # Allows you to use some shortcuts for Erector in your Controller.
12
+ #
13
+ # use this inside your controller to directly build Erector
14
+ # Refer to the Erector-documentation and testsuite for more examples.
15
+ #
16
+ # @example
17
+ #
18
+ # erector { h1 "Apples & Oranges" } #=> "<h1>Apples &amp; Oranges</h1>"
19
+ # erector { h1(:class => 'fruits&floots'){ text 'Apples' } } #=> "<h1 class=\"fruits&amp;floots\">Apples</h1>"
20
+ #
17
21
  module Erector
18
22
  include ::Erector::Mixin
19
23
 
@@ -21,27 +25,81 @@ module Ramaze
21
25
  alias :raw! :rawtext
22
26
  alias :old_css :css
23
27
 
28
+ ##
29
+ # Method that generates a XHTML 1.0 Strict doctype.
30
+ #
31
+ # @example
32
+ #
33
+ # strict_html do
34
+ # head do
35
+ # title "Ramaze Rocks!"
36
+ # end
37
+ # body
38
+ # div do
39
+ #
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # @param [Hash] args Hash containing extra options such as the xml:lang and xmlns attribute.
45
+ # @param [Block] block Block that contains the inner data of the <html> element.
46
+ #
24
47
  def strict_xhtml(*args, &block)
25
48
  raw! '<?xml version="1.0" encoding="UTF-8"?>'
26
49
  raw! '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">'
27
50
  html(:xmlns => "http://www.w3.org/1999/xhtml", :"xml:lang" => "en", :lang => "en", &block)
28
51
  end
29
52
 
53
+ ##
54
+ # Generate a Javascript tag.
55
+ #
56
+ # @example
57
+ #
58
+ # js 'javascript/jquery.js'
59
+ #
60
+ # @param [String] src The full or relative path to the Javascript file.
61
+ #
30
62
  def js(src)
31
63
  script :src => src
32
64
  end
33
65
 
66
+ ##
67
+ # Generate a pair of conditional tags for a specific browser.
68
+ #
69
+ # @example
70
+ #
71
+ # ie_if 'IE' do
72
+ # ......
73
+ # end
74
+ #
75
+ # @param [String] expr The if expression, such as 'IE' or 'lte IE7'.
76
+ # @param [block] block Block that contains the data that needs to be loaded for the specified browser.
77
+ #
34
78
  def ie_if(expr, &block)
35
79
  raw! "<!--[if #{expr}]>"
36
80
  yield
37
81
  raw! "<![endif]-->"
38
82
  end
39
83
 
40
- # Diagnostics
84
+ ##
85
+ # Inspect the specified element.
86
+ #
87
+ # @param [String] elem The element to inspect.
88
+ #
41
89
  def inspect(elem)
42
90
  text elem.inspect
43
91
  end
44
92
 
93
+ ##
94
+ # Generate a stylesheet tag.
95
+ #
96
+ # @example
97
+ #
98
+ # css 'css/reset.css', :media => 'print'
99
+ #
100
+ # @param [String] href The path (either absolute or relative) to the CSS file.
101
+ # @param [Hash] args A hash containing additional arguments to add to the CSS tag.
102
+ #
45
103
  def css(href, args = {})
46
104
  attrs = {
47
105
  :rel => "stylesheet",
@@ -14,7 +14,7 @@ module Ramaze
14
14
  # On the first request, for example on registering:
15
15
  #
16
16
  # flash[:error] = "You should reconsider your username, it's taken already"
17
- # redirect R(self, :register)
17
+ # redirect r(:register)
18
18
  #
19
19
  # This is the request from the redirect:
20
20
  #
@@ -28,7 +28,9 @@ module Ramaze
28
28
 
29
29
  trait :flashbox => "<div class='flash' id='flash_%key'>%value</div>"
30
30
 
31
- # answers with Current.session.flash
31
+ ##
32
+ # Return the current value of Current.session.flash
33
+ #
32
34
  def flash
33
35
  Current.session.flash
34
36
  end
@@ -2,6 +2,8 @@ require 'ramaze/gestalt'
2
2
 
3
3
  module Ramaze
4
4
  module Helper
5
+ ##
6
+ # Gestalt is the custom HTML/XML builder for Ramaze, based on a very simple DSL it will build your markup.
5
7
  module Gestalt
6
8
  CACHE_G = {}
7
9
 
@@ -50,7 +50,7 @@ module Ramaze
50
50
  # Fall back to default if the given +email+ doesn't have an gravatar;
51
51
  # may be an absolute url, 'identicon', 'monsterid', or 'wavatar'
52
52
  #
53
- # @options opts [true, false] :force (false)
53
+ # @option opts [true, false] :force (false)
54
54
  # Force use of the default avatar, useful if you want to use only
55
55
  # identicons or the like
56
56
  #
@@ -3,6 +3,10 @@ require 'locale'
3
3
 
4
4
  module Ramaze
5
5
  module Helper
6
+ ##
7
+ # The localization helper can be used to output translated strings.
8
+ # This enables your application to use multiple language files for English, Dutch and so on.
9
+ #
6
10
  module Localize
7
11
  include Traited
8
12
 
@@ -0,0 +1,30 @@
1
+ module Ramaze
2
+ module Helper
3
+ ##
4
+ # The SendFile module can be used to stream a file to the user's computer.
5
+ # While the performance of the module isn't optimal it's convenient and relatively easy to use.
6
+ #
7
+ module SendFile
8
+ ##
9
+ # The send_file method streams the specified file to the user's browser.
10
+ #
11
+ # @param [String] filename The name or path to the file which will be streamed to the user.
12
+ # @param [String] content_type The type of file we're dealing with. For example, if we want to stream
13
+ # a JPG image we'd set this variable to 'image/jpg'.
14
+ # @param [String] content_disposition Value for the Content-Disposition header.
15
+ #
16
+ def send_file(filename, content_type = nil, content_disposition = nil)
17
+ content_type ||= Rack::Mime.mime_type(::File.extname(filename))
18
+ content_disposition ||= File.basename(filename)
19
+
20
+ response.body = ::File.open(filename, 'rb')
21
+ response['Content-Length'] = ::File.size(filename).to_s
22
+ response['Content-Type'] = content_type
23
+ response['Content-Disposition'] = content_disposition
24
+ response.status = 200
25
+
26
+ throw(:respond, response)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,11 @@
3
3
 
4
4
  module Ramaze
5
5
  module Helper::Thread
6
+ ##
7
+ # The thread method executes the specified block in a new thread.
8
+ #
9
+ # @param [Block] block The block that contains the code that will be executed in the new thread.
10
+ #
6
11
  def thread &block
7
12
  parent_thread = Thread.current
8
13
  Thread.new do