validates_captcha 0.9.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.rdoc +3 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +243 -0
- data/Rakefile +105 -0
- data/lib/validates_captcha/controller_validation.rb +63 -0
- data/lib/validates_captcha/form_builder.rb +16 -0
- data/lib/validates_captcha/form_helper.rb +51 -0
- data/lib/validates_captcha/image_generator/simple.rb +87 -0
- data/lib/validates_captcha/middleware/simple.rb +108 -0
- data/lib/validates_captcha/model_validation.rb +66 -0
- data/lib/validates_captcha/reversible_encrypter/simple.rb +54 -0
- data/lib/validates_captcha/string_generator/simple.rb +77 -0
- data/lib/validates_captcha/test_case.rb +12 -0
- data/lib/validates_captcha/version.rb +9 -0
- data/lib/validates_captcha.rb +170 -0
- data/rails/init.rb +29 -0
- data/test/cases/controller_validation_test.rb +125 -0
- data/test/cases/image_generator_test.rb +33 -0
- data/test/cases/middleware_test.rb +71 -0
- data/test/cases/model_validation_test.rb +130 -0
- data/test/cases/reversible_encrypter_test.rb +27 -0
- data/test/cases/string_generator_test.rb +114 -0
- data/test/cases/validates_captcha_test.rb +135 -0
- data/test/test_helper.rb +26 -0
- metadata +113 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'active_support/core_ext/string/bytesize'
|
2
|
+
|
3
|
+
module ValidatesCaptcha
|
4
|
+
module Middleware
|
5
|
+
# This class acts as the Rack middleware that serves the captcha images.
|
6
|
+
#
|
7
|
+
# You can implement your own middleware by creating a
|
8
|
+
# class that conforms to the method definitions of the example below and
|
9
|
+
# assign an instance of it to ValidatesCaptcha#middleware=.
|
10
|
+
#
|
11
|
+
# Example for a custom middleware:
|
12
|
+
#
|
13
|
+
# class MyMiddleware
|
14
|
+
# def image_path(encrypted_code)
|
15
|
+
# '/captchas/image/' + encrypted_code + ValidatesCaptcha.captcha_image_file_extension
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def regenerate_path
|
19
|
+
# '/captchas/regenerate'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def call(env)
|
23
|
+
# if env['PATH_INFO'] =~ /^\/captchas\/image\/([^\.]+)/
|
24
|
+
# decrypted_code = ValidatesCaptcha.decrypt_captcha_code($1)
|
25
|
+
#
|
26
|
+
# if decrypted_code.nil?
|
27
|
+
# [422, { 'Content-Type' => 'text/html' }, ['Unprocessable Entity']]
|
28
|
+
# else
|
29
|
+
# image_data = ValidatesCaptcha.generate_captcha_image(decrypted_code)
|
30
|
+
#
|
31
|
+
# headers = {
|
32
|
+
# 'Content-Length' => image_data.bytesize.to_s,
|
33
|
+
# 'Content-Type' => ValidatesCaptcha.captcha_image_mime_type,
|
34
|
+
# 'Content-Disposition' => 'inline',
|
35
|
+
# 'Content-Transfer-Encoding' => 'binary'
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# [200, headers, [image_data]]
|
39
|
+
# end
|
40
|
+
# elsif env['PATH_INFO'] == regenerate_path
|
41
|
+
# encrypted_code = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
|
42
|
+
# xml = { :encrypted_captcha_code => encrypted_code, :captcha_image_path => image_path(encrypted_code) }.to_xml
|
43
|
+
#
|
44
|
+
# [200, { 'Content-Type' => 'application/xml' }, [xml]]
|
45
|
+
# else
|
46
|
+
# [404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# ValidatesCaptcha.middleware = MyMiddleware.new
|
52
|
+
#
|
53
|
+
class Simple
|
54
|
+
# Returns the captcha image path for a given encrypted code.
|
55
|
+
#
|
56
|
+
# This is used by the +captcha_image+ form helper.
|
57
|
+
def image_path(encrypted_code)
|
58
|
+
"/captchas/#{encrypted_code}#{ValidatesCaptcha.captcha_image_file_extension}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the path that is used when requesting the regeneration
|
62
|
+
# of a captcha image. Defaults to '/captchas/regenerate'.
|
63
|
+
#
|
64
|
+
# This is used by the +regenerate_captcha_link+ form helper.
|
65
|
+
def regenerate_path
|
66
|
+
'/captchas/regenerate'
|
67
|
+
end
|
68
|
+
|
69
|
+
# This method is the one called by Rack.
|
70
|
+
#
|
71
|
+
# It returns HTTP status 404 if the path is not recognized. If the path is
|
72
|
+
# recognized, it returns HTTP status 200 and delivers the image if it could
|
73
|
+
# successfully decrypt the captcha code, otherwise HTTP status 422.
|
74
|
+
#
|
75
|
+
# Please take a look at the source code if you want to learn more.
|
76
|
+
def call(env)
|
77
|
+
if env['PATH_INFO'] =~ /^\/captchas\/([^\.]+)/
|
78
|
+
if $1 == 'regenerate'
|
79
|
+
encrypted_code = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
|
80
|
+
json = { :encrypted_captcha_code => encrypted_code, :captcha_image_path => image_path(encrypted_code) }.to_json
|
81
|
+
|
82
|
+
[200, { 'Content-Type' => 'application/json' }, [json]]
|
83
|
+
else
|
84
|
+
decrypted_code = ValidatesCaptcha.decrypt_captcha_code($1)
|
85
|
+
|
86
|
+
if decrypted_code.nil?
|
87
|
+
[422, { 'Content-Type' => 'text/html' }, ['Unprocessable Entity']]
|
88
|
+
else
|
89
|
+
image_data = ValidatesCaptcha.generate_captcha_image(decrypted_code)
|
90
|
+
|
91
|
+
response_headers = {
|
92
|
+
'Content-Length' => image_data.bytesize.to_s,
|
93
|
+
'Content-Type' => ValidatesCaptcha.captcha_image_mime_type,
|
94
|
+
'Content-Disposition' => 'inline',
|
95
|
+
'Content-Transfer-Encoding' => 'binary',
|
96
|
+
'Cache-Control' => 'private'
|
97
|
+
}
|
98
|
+
|
99
|
+
[200, response_headers, [image_data]]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ValidatesCaptcha
|
2
|
+
module ModelValidation
|
3
|
+
def self.included(base) #:nodoc:
|
4
|
+
base.extend ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessible :captcha, :encrypted_captcha
|
9
|
+
attr_accessor :captcha
|
10
|
+
attr_writer :encrypted_captcha
|
11
|
+
|
12
|
+
validate :validate_captcha, :if => :validate_captcha?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# Activates captcha validation on entering the block and deactivates
|
18
|
+
# captcha validation on leaving the block.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# User.with_captcha_validation do
|
23
|
+
# @user = User.new(...)
|
24
|
+
# @user.save
|
25
|
+
# end
|
26
|
+
def with_captcha_validation(&block)
|
27
|
+
self.validate_captcha = true
|
28
|
+
result = yield
|
29
|
+
self.validate_captcha = false
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns +true+ if captcha validation is activated, otherwise +false+.
|
34
|
+
def validate_captcha? #:nodoc:
|
35
|
+
@validate_captcha == true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def validate_captcha=(value) #:nodoc:
|
40
|
+
@validate_captcha = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module InstanceMethods #:nodoc:
|
45
|
+
def encrypted_captcha #:nodoc:
|
46
|
+
return @encrypted_captcha unless @encrypted_captcha.blank?
|
47
|
+
@encrypted_captcha = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def validate_captcha? #:nodoc:
|
52
|
+
self.class.validate_captcha?
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_captcha #:nodoc:
|
56
|
+
errors.add(:captcha, :blank) and return if captcha.blank?
|
57
|
+
errors.add(:captcha, :invalid) unless captcha_valid?
|
58
|
+
end
|
59
|
+
|
60
|
+
def captcha_valid? #:nodoc:
|
61
|
+
ValidatesCaptcha.encrypt_captcha_code(captcha) == encrypted_captcha
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'active_support/secure_random'
|
3
|
+
|
4
|
+
module ValidatesCaptcha
|
5
|
+
module ReversibleEncrypter
|
6
|
+
# This class is responsible for encrypting and decrypting captcha codes.
|
7
|
+
# It internally uses AES256 to do the string encryption/decryption.
|
8
|
+
#
|
9
|
+
# You can implement your own reversible encrypter by creating a class
|
10
|
+
# that conforms to the method definitions of the example below and
|
11
|
+
# assign an instance of it to ValidatesCaptcha#reversible_encrypter=.
|
12
|
+
#
|
13
|
+
# Example for a custom encrypter/decrypter:
|
14
|
+
#
|
15
|
+
# class ReverseString # very insecure and easily cracked
|
16
|
+
# def encrypt(code)
|
17
|
+
# code.reverse
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def decrypt(encrypted_code)
|
21
|
+
# encrypted_code.reverse
|
22
|
+
# rescue SomeKindOfDecryptionError
|
23
|
+
# nil
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# ValidatesCaptcha.reversible_encrypter = ReverseString.new
|
28
|
+
#
|
29
|
+
# Please note: The #decrypt method should return +nil+ if decryption fails.
|
30
|
+
class Simple
|
31
|
+
KEY = ::ActiveSupport::SecureRandom.hex(32).freeze
|
32
|
+
|
33
|
+
def initialize #:nodoc:
|
34
|
+
@aes = OpenSSL::Cipher::Cipher.new('AES-256-ECB')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Encrypts a cleartext string using #key as encryption key.
|
38
|
+
def encrypt(code)
|
39
|
+
@aes.encrypt
|
40
|
+
@aes.key = KEY
|
41
|
+
[@aes.update(code) + @aes.final].pack("m").tr('+/=', '-_ ').strip.gsub("\n", '')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decrypts an encrypted string using using #key as decryption key.
|
45
|
+
def decrypt(encrypted_code)
|
46
|
+
@aes.decrypt
|
47
|
+
@aes.key = KEY
|
48
|
+
@aes.update((encrypted_code + '=' * (4 - encrypted_code.size % 4)).tr('-_', '+/').unpack("m").first) + @aes.final
|
49
|
+
rescue # OpenSSL::CipherError, OpenSSL::Cipher::CipherError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ValidatesCaptcha
|
2
|
+
module StringGenerator
|
3
|
+
# This class is responsible for generating the codes that are displayed
|
4
|
+
# on the captcha images. It does so by randomly selecting a number of
|
5
|
+
# characters from a predefined alphabet constisting of visually distinguishable
|
6
|
+
# letters and digits.
|
7
|
+
#
|
8
|
+
# The number of characters and the alphabet used when generating strings can
|
9
|
+
# be customized. See the #alphabet= and #length= methods for details.
|
10
|
+
#
|
11
|
+
# You can implement your own string generator by creating a
|
12
|
+
# class that conforms to the method definitions of the example below and
|
13
|
+
# assign an instance of it to ValidatesCaptcha#string_generator=.
|
14
|
+
#
|
15
|
+
# Example for a custom string generator:
|
16
|
+
#
|
17
|
+
# class DictionaryGenerator
|
18
|
+
# DICTIONARY = ['foo', 'bar', 'baz', ...]
|
19
|
+
#
|
20
|
+
# def generate
|
21
|
+
# return DICTIONARY[rand(DICTIONARY.size)]
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# ValidatesCaptcha.string_generator = DictionaryGenerator.new
|
26
|
+
#
|
27
|
+
class Simple
|
28
|
+
@@alphabet = 'abdefghjkmnqrtABDEFGHJKLMNQRT234678923467892346789'
|
29
|
+
@@length = 6
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Returns a string holding the chars used when randomly generating the text that
|
33
|
+
# is displayed on a captcha image. Defaults to a string of visually distinguishable
|
34
|
+
# letters and digits.
|
35
|
+
def alphabet
|
36
|
+
@@alphabet
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the string to use as alphabet when randomly generating the text displayed
|
40
|
+
# on a captcha image. To increase the probability of appearing in the image, some
|
41
|
+
# characters might appear more than once in the string.
|
42
|
+
#
|
43
|
+
# You can set this to a custom alphabet within a Rails initializer:
|
44
|
+
#
|
45
|
+
# ValidatesCaptcha::StringGenerator::Simple.alphabet = '01'
|
46
|
+
def alphabet=(alphabet)
|
47
|
+
alphabet = alphabet.to_s.gsub(/\s/, '')
|
48
|
+
raise('alphabet cannot be blank') if alphabet.blank?
|
49
|
+
|
50
|
+
@@alphabet = alphabet
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the length to use when generating captcha codes. Defaults to 6.
|
54
|
+
def length
|
55
|
+
@@length
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the length to use when generating captcha codes.
|
59
|
+
#
|
60
|
+
# You can set this to a custom length within a Rails initializer:
|
61
|
+
#
|
62
|
+
# ValidatesCaptcha::StringGenerator::Simple.length = 8
|
63
|
+
def length=(length)
|
64
|
+
@@length = length.to_i
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Randomly generates a string to be used as the code displayed on captcha images.
|
69
|
+
def generate
|
70
|
+
alphabet_chars = self.class.alphabet.split(//)
|
71
|
+
code_chars = []
|
72
|
+
self.class.length.times { code_chars << alphabet_chars[rand(alphabet_chars.size)] }
|
73
|
+
code_chars.join
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'active_support/test_case'
|
2
|
+
|
3
|
+
module ValidatesCaptcha #:nodoc:
|
4
|
+
class TestCase < ActiveSupport::TestCase #:nodoc:
|
5
|
+
def assert_greater_than(expected, size, message = '')
|
6
|
+
_wrap_assertion do
|
7
|
+
full_message = build_message(message, "<?> expected to be greater than <?>.", size, expected)
|
8
|
+
assert_block(full_message) { size > expected }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Martin Andert
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
|
25
|
+
# This module contains the getters and setters for the backend classes:
|
26
|
+
# image/string generator, reversible encrypter, and Rack middleware. This
|
27
|
+
# allows you to replace them with your custom implementations. For more
|
28
|
+
# information on how to bring Validates Captcha to use your own
|
29
|
+
# implementation instead of the default one, consult the documentation
|
30
|
+
# for the specific default class.
|
31
|
+
#
|
32
|
+
# This module also contains convenience wrapper methods for all the
|
33
|
+
# methods provided by the configured backend classes. These wrapper
|
34
|
+
# methods form the API that is visible to the outside world and that
|
35
|
+
# all backend classes use for internal communication.
|
36
|
+
module ValidatesCaptcha
|
37
|
+
autoload :ModelValidation, 'validates_captcha/model_validation'
|
38
|
+
autoload :ControllerValidation, 'validates_captcha/controller_validation'
|
39
|
+
autoload :FormHelper, 'validates_captcha/form_helper'
|
40
|
+
autoload :FormBuilder, 'validates_captcha/form_builder'
|
41
|
+
autoload :TestCase, 'validates_captcha/test_case'
|
42
|
+
autoload :VERSION, 'validates_captcha/version'
|
43
|
+
|
44
|
+
module ImageGenerator
|
45
|
+
autoload :Simple, 'validates_captcha/image_generator/simple'
|
46
|
+
end
|
47
|
+
|
48
|
+
module StringGenerator
|
49
|
+
autoload :Simple, 'validates_captcha/string_generator/simple'
|
50
|
+
end
|
51
|
+
|
52
|
+
module ReversibleEncrypter
|
53
|
+
autoload :Simple, 'validates_captcha/reversible_encrypter/simple'
|
54
|
+
end
|
55
|
+
|
56
|
+
module Middleware
|
57
|
+
autoload :Simple, 'validates_captcha/middleware/simple'
|
58
|
+
end
|
59
|
+
|
60
|
+
@@image_generator = nil
|
61
|
+
@@string_generator = nil
|
62
|
+
@@reversible_encrypter = nil
|
63
|
+
@@middleware = nil
|
64
|
+
|
65
|
+
class << self
|
66
|
+
# Returns ValidatesCaptcha's current version number.
|
67
|
+
def version
|
68
|
+
ValidatesCaptcha::VERSION::STRING
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the current captcha image generator. Defaults to an
|
72
|
+
# instance of the ValidatesCaptcha::ImageGenerator::Simple class.
|
73
|
+
def image_generator
|
74
|
+
@@image_generator ||= ImageGenerator::Simple.new
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sets the current captcha image generator. Used to set a custom
|
78
|
+
# image generator.
|
79
|
+
def image_generator=(generator)
|
80
|
+
@@image_generator = generator
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the current captcha string generator. Defaults to an
|
84
|
+
# instance of the ValidatesCaptcha::StringGenerator::Simple class.
|
85
|
+
def string_generator
|
86
|
+
@@string_generator ||= StringGenerator::Simple.new
|
87
|
+
end
|
88
|
+
|
89
|
+
# Sets the current captcha string generator. Used to set a
|
90
|
+
# custom string generator.
|
91
|
+
def string_generator=(generator)
|
92
|
+
@@string_generator = generator
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the current captcha reversible encrypter. Defaults to an
|
96
|
+
# instance of the ValidatesCaptcha::ReversibleEncrypter::Simple class.
|
97
|
+
def reversible_encrypter
|
98
|
+
@@reversible_encrypter ||= ReversibleEncrypter::Simple.new
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sets the current captcha reversible encrypter. Used to set a
|
102
|
+
# custom reversible encrypter.
|
103
|
+
def reversible_encrypter=(encrypter)
|
104
|
+
@@reversible_encrypter = encrypter
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the current captcha middleware. Defaults to the
|
108
|
+
# ValidatesCaptcha::Middleware::Simple class.
|
109
|
+
def middleware
|
110
|
+
@@middleware ||= Middleware::Simple.new
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets the current captcha middleware. Used to set a custom
|
114
|
+
# middleware.
|
115
|
+
def middleware=(middleware)
|
116
|
+
@@middleware = middleware
|
117
|
+
end
|
118
|
+
|
119
|
+
# Randomly generates a string which can be used as the code
|
120
|
+
# displayed on captcha images. This method internally calls
|
121
|
+
# +string_generator.generate+.
|
122
|
+
def generate_captcha_code
|
123
|
+
string_generator.generate
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the image data of the generated captcha image. This
|
127
|
+
# method internally calls +image_generator.generate+.
|
128
|
+
def generate_captcha_image(code)
|
129
|
+
image_generator.generate(code)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the image file extension of the captcha images. This
|
133
|
+
# method internally calls +image_generator.image_file_extension+.
|
134
|
+
def captcha_image_file_extension
|
135
|
+
image_generator.image_file_extension
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the image mime type of the captcha images. This
|
139
|
+
# method internally calls +image_generator.image_mime_type+.
|
140
|
+
def captcha_image_mime_type
|
141
|
+
image_generator.image_mime_type
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the encryption of a cleartext captcha code. This
|
145
|
+
# method internally calls +reversible_encrypter.encrypt+.
|
146
|
+
def encrypt_captcha_code(code)
|
147
|
+
reversible_encrypter.encrypt(code)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns the decryption of an encrypted captcha code. This
|
151
|
+
# method internally calls +reversible_encrypter.decrypt+.
|
152
|
+
def decrypt_captcha_code(encrypted_code)
|
153
|
+
reversible_encrypter.decrypt(encrypted_code)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the captcha image path for a given encrypted code. This
|
157
|
+
# method internally calls +middleware.image_path+.
|
158
|
+
def captcha_image_path(encrypted_code)
|
159
|
+
middleware.image_path(encrypted_code)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the path that is used when requesting the regeneration
|
163
|
+
# of a captcha image. This method internally calls
|
164
|
+
# +middleware.regenerate_path+.
|
165
|
+
def regenerate_captcha_path
|
166
|
+
middleware.regenerate_path
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'validates_captcha'
|
2
|
+
|
3
|
+
config.after_initialize do
|
4
|
+
::ActiveRecord::Base.send :include, ValidatesCaptcha::ModelValidation
|
5
|
+
::ActionController::Base.send :include, ValidatesCaptcha::ControllerValidation
|
6
|
+
::ActionView::Base.send :include, ValidatesCaptcha::FormHelper
|
7
|
+
::ActionView::Helpers::FormBuilder.send :include, ValidatesCaptcha::FormBuilder
|
8
|
+
end
|
9
|
+
|
10
|
+
module ::ValidatesCaptcha
|
11
|
+
class MiddlewareWrapper
|
12
|
+
RECOGNIZED_RESPONSE_STATUS_CODES = [200, 422].freeze
|
13
|
+
|
14
|
+
def initialize(app)
|
15
|
+
@app = app
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
result = ValidatesCaptcha.middleware.call(env)
|
20
|
+
|
21
|
+
return @app.call(env) unless RECOGNIZED_RESPONSE_STATUS_CODES.include?(result.first)
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
config.middleware.use ::ValidatesCaptcha::MiddlewareWrapper
|
29
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
ActionController::Routing::Routes.draw do |map|
|
5
|
+
map.connect ':controller/:action/:id'
|
6
|
+
end
|
7
|
+
|
8
|
+
class WidgetsController < ActionController::Base
|
9
|
+
include ValidatesCaptcha::ControllerValidation
|
10
|
+
|
11
|
+
validates_captcha :except => [:update, :save, :persist, :bingo]
|
12
|
+
validates_captcha_of :widgets, :only => :update
|
13
|
+
validates_captcha_of Widget, :only => [:save, :persist]
|
14
|
+
|
15
|
+
def create
|
16
|
+
begin
|
17
|
+
Widget.new.save!
|
18
|
+
rescue ActiveRecord::RecordInvalid
|
19
|
+
@invalid = true
|
20
|
+
end
|
21
|
+
|
22
|
+
render :nothing => true
|
23
|
+
end
|
24
|
+
|
25
|
+
def update
|
26
|
+
begin
|
27
|
+
Widget.new.save!
|
28
|
+
rescue ActiveRecord::RecordInvalid
|
29
|
+
@invalid = true
|
30
|
+
end
|
31
|
+
|
32
|
+
render :nothing => true
|
33
|
+
end
|
34
|
+
|
35
|
+
def save
|
36
|
+
encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
|
37
|
+
decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted)
|
38
|
+
|
39
|
+
begin
|
40
|
+
Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
|
41
|
+
rescue ActiveRecord::RecordInvalid
|
42
|
+
@invalid = true
|
43
|
+
end
|
44
|
+
|
45
|
+
render :nothing => true
|
46
|
+
end
|
47
|
+
|
48
|
+
def store
|
49
|
+
encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
|
50
|
+
decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted) + 'ha!'
|
51
|
+
|
52
|
+
begin
|
53
|
+
Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
|
54
|
+
rescue ActiveRecord::RecordInvalid
|
55
|
+
@invalid = true
|
56
|
+
end
|
57
|
+
|
58
|
+
render :nothing => true
|
59
|
+
end
|
60
|
+
|
61
|
+
def persist
|
62
|
+
encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
|
63
|
+
decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted) + 'ha!'
|
64
|
+
|
65
|
+
begin
|
66
|
+
Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
|
67
|
+
rescue ActiveRecord::RecordInvalid
|
68
|
+
@invalid = true
|
69
|
+
end
|
70
|
+
|
71
|
+
render :nothing => true
|
72
|
+
end
|
73
|
+
|
74
|
+
def bingo
|
75
|
+
begin
|
76
|
+
Widget.new.save!
|
77
|
+
rescue ActiveRecord::RecordInvalid
|
78
|
+
@invalid = true
|
79
|
+
end
|
80
|
+
|
81
|
+
render :nothing => true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class ControllerValidationTest < ActionController::TestCase
|
86
|
+
tests WidgetsController
|
87
|
+
|
88
|
+
test "defines a class level #validates_captcha method" do
|
89
|
+
assert_respond_to WidgetsController, :validates_captcha
|
90
|
+
end
|
91
|
+
|
92
|
+
test "defines a class level #validates_captcha_of method" do
|
93
|
+
assert_respond_to WidgetsController, :validates_captcha_of
|
94
|
+
end
|
95
|
+
|
96
|
+
test "calling #create method of controller should assign @invalid" do
|
97
|
+
post :create
|
98
|
+
assert_not_nil assigns(:invalid)
|
99
|
+
end
|
100
|
+
|
101
|
+
test "calling #update method of controller should assign @invalid" do
|
102
|
+
post :update
|
103
|
+
assert_not_nil assigns(:invalid)
|
104
|
+
end
|
105
|
+
|
106
|
+
test "calling #save method of controller should not assign @invalid" do
|
107
|
+
post :save
|
108
|
+
assert_nil assigns(:invalid)
|
109
|
+
end
|
110
|
+
|
111
|
+
test "calling #store method of controller should should assign @invalid" do
|
112
|
+
post :store
|
113
|
+
assert_not_nil assigns(:invalid)
|
114
|
+
end
|
115
|
+
|
116
|
+
test "calling #persist method of controller should should assign @invalid" do
|
117
|
+
post :persist
|
118
|
+
assert_not_nil assigns(:invalid)
|
119
|
+
end
|
120
|
+
|
121
|
+
test "calling #bingo method of controller should not assign @invalid" do
|
122
|
+
post :bingo
|
123
|
+
assert_nil assigns(:invalid)
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
IG = ValidatesCaptcha::ImageGenerator::Simple
|
4
|
+
|
5
|
+
class ImageGeneratorTest < ValidatesCaptcha::TestCase
|
6
|
+
test "defines an instance level #generate method" do
|
7
|
+
assert_respond_to IG.new, :generate
|
8
|
+
end
|
9
|
+
|
10
|
+
test "instance level #generate method accepts an argument" do
|
11
|
+
assert_nothing_raised { IG.new.generate('abc') }
|
12
|
+
end
|
13
|
+
|
14
|
+
test "instance level #generate method returns a string" do
|
15
|
+
assert_kind_of String, IG.new.generate('abc')
|
16
|
+
end
|
17
|
+
|
18
|
+
test "defines an instance level #image_file_extension method" do
|
19
|
+
assert_respond_to IG.new, :image_file_extension
|
20
|
+
end
|
21
|
+
|
22
|
+
test "instance level #image_file_extension method returns a string" do
|
23
|
+
assert_kind_of String, IG.new.image_file_extension
|
24
|
+
end
|
25
|
+
|
26
|
+
test "defines an instance level #image_mime_type method" do
|
27
|
+
assert_respond_to IG.new, :image_mime_type
|
28
|
+
end
|
29
|
+
|
30
|
+
test "instance level #image_mime_type method returns a string" do
|
31
|
+
assert_kind_of String, IG.new.image_mime_type
|
32
|
+
end
|
33
|
+
end
|