karsthammer-validates_captcha 0.9.5a
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +22 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +320 -0
- data/Rakefile +102 -0
- data/lib/validates_captcha.rb +76 -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 +39 -0
- data/lib/validates_captcha/image_generator/simple.rb +94 -0
- data/lib/validates_captcha/model_validation.rb +65 -0
- data/lib/validates_captcha/provider/dynamic_image.rb +240 -0
- data/lib/validates_captcha/provider/question.rb +110 -0
- data/lib/validates_captcha/provider/static_image.rb +224 -0
- data/lib/validates_captcha/string_generator/simple.rb +81 -0
- data/lib/validates_captcha/symmetric_encryptor/simple.rb +52 -0
- data/lib/validates_captcha/test_case.rb +12 -0
- data/lib/validates_captcha/version.rb +9 -0
- data/rails/init.rb +29 -0
- data/tasks/static_image_tasks.rake +33 -0
- data/test/cases/controller_validation_test.rb +222 -0
- data/test/cases/image_generator/simple_test.rb +34 -0
- data/test/cases/model_validation_test.rb +258 -0
- data/test/cases/provider/dynamic_image_test.rb +103 -0
- data/test/cases/provider/question_test.rb +41 -0
- data/test/cases/provider/static_image_test.rb +148 -0
- data/test/cases/string_generator/simple_test.rb +115 -0
- data/test/cases/symmetric_encryptor/simple_test.rb +28 -0
- data/test/cases/validates_captcha_test.rb +28 -0
- data/test/test_helper.rb +26 -0
- metadata +120 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'action_view/helpers'
|
2
|
+
|
3
|
+
module ValidatesCaptcha
|
4
|
+
module Provider
|
5
|
+
# A question/answer captcha provider.
|
6
|
+
class Question
|
7
|
+
include ActionView::Helpers
|
8
|
+
|
9
|
+
DEFAULT_QUESTIONS_AND_ANSWERS = {
|
10
|
+
"What's the capital of France?" => "Paris",
|
11
|
+
"What's the capital of Germany?" => "Berlin",
|
12
|
+
"What's the opposite of good?" => ["bad", "evil"],
|
13
|
+
"What's the opposite of love?" => "hate",
|
14
|
+
"What's the sum of 2 and 3?" => ["5", "five"],
|
15
|
+
"What's the product of 3 and 4?" => ["12", "twelve"],
|
16
|
+
"Thumb, tooth or hand: which is part of the head?" => "tooth",
|
17
|
+
"Bread, ham or milk: which is something to drink?" => "milk",
|
18
|
+
"What day is today, if yesterday was Friday?" => "Saturday",
|
19
|
+
"What day is today, if tomorrow is Tuesday?" => "Monday",
|
20
|
+
"What is the 2nd letter of the the third word in this question?" => "h",
|
21
|
+
"What color is the sky on a sunny day?" => "blue" }.freeze
|
22
|
+
|
23
|
+
@@questions_and_answers = DEFAULT_QUESTIONS_AND_ANSWERS
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Returns the current captcha questions/answers hash. Defaults to
|
27
|
+
# DEFAULT_QUESTIONS_AND_ANSWERS.
|
28
|
+
def questions_and_answers
|
29
|
+
@@questions_and_answers
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the current captcha questions/answers hash. Used to set a
|
33
|
+
# custom questions/answers hash.
|
34
|
+
def questions_and_answers=(qna)
|
35
|
+
@@questions_and_answers = qna
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method is the one called by Rack.
|
40
|
+
#
|
41
|
+
# It returns HTTP status 404 if the path is not recognized. If the path is
|
42
|
+
# recognized, it returns HTTP status 200 and delivers a new challenge in
|
43
|
+
# JSON format.
|
44
|
+
#
|
45
|
+
# Please take a look at the source code if you want to learn more.
|
46
|
+
def call(env)
|
47
|
+
if env['PATH_INFO'] == regenerate_path
|
48
|
+
captcha_challenge = generate_challenge
|
49
|
+
json = { :captcha_challenge => captcha_challenge }.to_json
|
50
|
+
|
51
|
+
[200, { 'Content-Type' => 'application/json' }, [json]]
|
52
|
+
else
|
53
|
+
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a captcha question.
|
58
|
+
def generate_challenge
|
59
|
+
self.class.questions_and_answers.keys[rand(self.class.questions_and_answers.keys.size)]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns true if the captcha was solved using the given +question+ and +answer+,
|
63
|
+
# otherwise false.
|
64
|
+
def solved?(question, answer)
|
65
|
+
return false unless self.class.questions_and_answers.key?(question)
|
66
|
+
answers = Array.wrap(self.class.questions_and_answers[question]).map(&:downcase)
|
67
|
+
answers.include?(answer.downcase)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a span tag with the inner HTML set to the captcha question.
|
71
|
+
#
|
72
|
+
# Internally calls Rails' +content_tag+ helper method, passing the +options+ argument.
|
73
|
+
def render_challenge(sanitized_object_name, object, options = {})
|
74
|
+
options[:id] = "#{sanitized_object_name}_captcha_question"
|
75
|
+
|
76
|
+
content_tag :span, object.captcha_challenge, options
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns an anchor tag that makes an AJAX request to fetch a new question and updates
|
80
|
+
# the captcha challenge after the request is complete.
|
81
|
+
#
|
82
|
+
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
83
|
+
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
84
|
+
# to be available on the web page.
|
85
|
+
#
|
86
|
+
# The anchor text defaults to 'New question'. You can set this to a custom value
|
87
|
+
# providing a +:text+ key in the +options+ hash.
|
88
|
+
def render_regenerate_challenge_link(sanitized_object_name, object, options = {}, html_options = {})
|
89
|
+
text = options.delete(:text) || 'New question'
|
90
|
+
success = "var result = request.responseJSON; $('#{sanitized_object_name}_captcha_question').update(result.captcha_challenge); $('#{sanitized_object_name}_captcha_challenge').value = result.captcha_challenge; $('#{sanitized_object_name}_captcha_solution').value = '';"
|
91
|
+
|
92
|
+
link_to_remote text, options.reverse_merge(:url => regenerate_path, :method => :get, :success => success), html_options
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def regenerate_path #:nodoc:
|
97
|
+
'/captchas/regenerate'
|
98
|
+
end
|
99
|
+
|
100
|
+
# This is needed by +link_to_remote+ called in +render_regenerate_link+.
|
101
|
+
def protect_against_forgery? #:nodoc:
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def solve(challenge) #:nodoc:
|
106
|
+
Array.wrap(self.class.questions_and_answers[challenge]).first
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'action_view/helpers'
|
3
|
+
|
4
|
+
module ValidatesCaptcha
|
5
|
+
module Provider
|
6
|
+
# An image captcha provider that relies on pre-created captcha images.
|
7
|
+
#
|
8
|
+
# There is a Rake tast for creating the captcha images:
|
9
|
+
#
|
10
|
+
# rake validates_captcha:create_static_images
|
11
|
+
#
|
12
|
+
# This will create 3 images in #filesystem_dir. To create a
|
13
|
+
# different number of images, provide a COUNT argument:
|
14
|
+
#
|
15
|
+
# rake validates_captcha:create_static_images COUNT=50
|
16
|
+
#
|
17
|
+
# This class contains the getters and setters for the backend classes:
|
18
|
+
# image generator and string generator. This allows you to replace them
|
19
|
+
# with your custom implementations. For more information on how to bring
|
20
|
+
# the image provider to use your own implementation instead of the default
|
21
|
+
# one, consult the documentation for the specific default class.
|
22
|
+
#
|
23
|
+
# The default captcha image generator uses ImageMagick's +convert+ command to
|
24
|
+
# create the captcha. So a recent and properly configured version of ImageMagick
|
25
|
+
# must be installed on the system. The version used while developing was 6.4.5.
|
26
|
+
# But you are not bound to ImageMagick. If you want to provide a custom image
|
27
|
+
# generator, take a look at the documentation for
|
28
|
+
# ValidatesCaptcha::ImageGenerator::Simple on how to create your own.
|
29
|
+
class StaticImage
|
30
|
+
include ActionView::Helpers
|
31
|
+
|
32
|
+
SALT = "3f(61&831_fa0712d4a?b58-eb4b8$a2%.36378f".freeze
|
33
|
+
|
34
|
+
@@string_generator = nil
|
35
|
+
@@image_generator = nil
|
36
|
+
@@filesystem_dir = nil
|
37
|
+
@@web_dir = nil
|
38
|
+
@@salt = nil
|
39
|
+
|
40
|
+
class << self
|
41
|
+
# Returns the current captcha string generator. Defaults to an
|
42
|
+
# instance of the ValidatesCaptcha::StringGenerator::Simple class.
|
43
|
+
def string_generator
|
44
|
+
@@string_generator ||= ValidatesCaptcha::StringGenerator::Simple.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Sets the current captcha string generator. Used to set a
|
48
|
+
# custom string generator.
|
49
|
+
def string_generator=(generator)
|
50
|
+
@@string_generator = generator
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the current captcha image generator. Defaults to an
|
54
|
+
# instance of the ValidatesCaptcha::ImageGenerator::Simple class.
|
55
|
+
def image_generator
|
56
|
+
@@image_generator ||= ValidatesCaptcha::ImageGenerator::Simple.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets the current captcha image generator. Used to set a custom
|
60
|
+
# image generator.
|
61
|
+
def image_generator=(generator)
|
62
|
+
@@image_generator = generator
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the current captcha image file system directory. Defaults to
|
66
|
+
# +RAILS_ROOT/public/images/captchas+.
|
67
|
+
def filesystem_dir
|
68
|
+
@@filesystem_dir ||= ::File.join(::Rails.public_path, 'images', 'captchas')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets the current captcha image file system directory. Used to set a custom
|
72
|
+
# image directory.
|
73
|
+
def filesystem_dir=(dir)
|
74
|
+
@@filesystem_dir = dir
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the current captcha image web directory. Defaults to
|
78
|
+
# +/images/captchas+.
|
79
|
+
def web_dir
|
80
|
+
@@web_dir ||= '/images/captchas'
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sets the current captcha image web directory. Used to set a custom
|
84
|
+
# image directory.
|
85
|
+
def web_dir=(dir)
|
86
|
+
@@web_dir = dir
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the current salt used for encryption.
|
90
|
+
def salt
|
91
|
+
@@salt ||= SALT
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets the current salt used for encryption. Used to set a custom
|
95
|
+
# salt.
|
96
|
+
def salt=(salt)
|
97
|
+
@@salt = salt
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return the encryption of the +code+ using #salt.
|
101
|
+
def encrypt(code)
|
102
|
+
::Digest::SHA1.hexdigest "#{salt}--#{code}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Creates a captcha image in the #filesystem_dir and returns
|
106
|
+
# the path to it and the code displayed on the image.
|
107
|
+
def create_image
|
108
|
+
code = string_generator.generate
|
109
|
+
encrypted_code = encrypt(code)
|
110
|
+
|
111
|
+
image_filename = "#{encrypted_code}#{image_generator.file_extension}"
|
112
|
+
image_path = File.join(filesystem_dir, image_filename)
|
113
|
+
image_bytes = image_generator.generate(code)
|
114
|
+
|
115
|
+
File.open image_path, 'w' do |os|
|
116
|
+
os.write image_bytes
|
117
|
+
end
|
118
|
+
|
119
|
+
return image_path, code
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# This method is the one called by Rack.
|
124
|
+
#
|
125
|
+
# It returns HTTP status 404 if the path is not recognized. If the path is
|
126
|
+
# recognized, it returns HTTP status 200 and delivers a new challenge in
|
127
|
+
# JSON format.
|
128
|
+
#
|
129
|
+
# Please take a look at the source code if you want to learn more.
|
130
|
+
def call(env)
|
131
|
+
if env['PATH_INFO'] == regenerate_path
|
132
|
+
captcha_challenge = generate_challenge
|
133
|
+
json = { :captcha_challenge => captcha_challenge, :captcha_image_path => image_path(captcha_challenge) }.to_json
|
134
|
+
|
135
|
+
[200, { 'Content-Type' => 'application/json' }, [json]]
|
136
|
+
else
|
137
|
+
[404, { 'Content-Type' => 'text/html' }, ['Not Found']]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns an array containing the paths to the available captcha images.
|
142
|
+
def images
|
143
|
+
@images ||= Dir[File.join(filesystem_dir, "*#{image_file_extension}")]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns an array containing the available challenges (encrypted captcha codes).
|
147
|
+
def challenges
|
148
|
+
@challenges ||= images.map { |path| File.basename(path, image_file_extension) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns a captcha challenge.
|
152
|
+
def generate_challenge
|
153
|
+
raise("no captcha images found in #{filesystem_dir}") if challenges.empty?
|
154
|
+
|
155
|
+
challenges[rand(challenges.size)]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns true if the captcha was solved using the given +challenge+ and +solution+,
|
159
|
+
# otherwise false.
|
160
|
+
def solved?(challenge, solution)
|
161
|
+
challenge == encrypt(solution)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns an image tag with the source set to the url of the captcha image.
|
165
|
+
#
|
166
|
+
# Internally calls Rails' +image_tag+ helper method, passing the +options+ argument.
|
167
|
+
def render_challenge(sanitized_object_name, object, options = {})
|
168
|
+
src = image_path(object.captcha_challenge)
|
169
|
+
|
170
|
+
options[:alt] ||= 'CAPTCHA'
|
171
|
+
options[:id] = "#{sanitized_object_name}_captcha_image"
|
172
|
+
|
173
|
+
image_tag src, options
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns an anchor tag that makes an AJAX request to fetch a new captcha code and updates
|
177
|
+
# the captcha image after the request is complete.
|
178
|
+
#
|
179
|
+
# Internally calls Rails' +link_to_remote+ helper method, passing the +options+ and
|
180
|
+
# +html_options+ arguments. So it relies on the Prototype javascript framework
|
181
|
+
# to be available on the web page.
|
182
|
+
#
|
183
|
+
# The anchor text defaults to 'Regenerate Captcha'. You can set this to a custom value
|
184
|
+
# providing a +:text+ key in the +options+ hash.
|
185
|
+
def render_regenerate_challenge_link(sanitized_object_name, object, options = {}, html_options = {})
|
186
|
+
text = options.delete(:text) || 'Regenerate Captcha'
|
187
|
+
success = "var result = request.responseJSON; $('#{sanitized_object_name}_captcha_image').src = result.captcha_image_path; $('#{sanitized_object_name}_captcha_challenge').value = result.captcha_challenge; $('#{sanitized_object_name}_captcha_solution').value = '';"
|
188
|
+
|
189
|
+
link_to_remote text, options.reverse_merge(:url => regenerate_path, :method => :get, :success => success), html_options
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def regenerate_path #:nodoc:
|
194
|
+
'/captchas/regenerate'
|
195
|
+
end
|
196
|
+
|
197
|
+
def image_file_extension #:nodoc:
|
198
|
+
self.class.image_generator.file_extension
|
199
|
+
end
|
200
|
+
|
201
|
+
def image_path(encrypted_code) #:nodoc:
|
202
|
+
File.join(web_dir, "#{encrypted_code}#{image_file_extension}")
|
203
|
+
end
|
204
|
+
|
205
|
+
def encrypt(code) #:nodoc:
|
206
|
+
self.class.encrypt code
|
207
|
+
end
|
208
|
+
|
209
|
+
def filesystem_dir #:nodoc:
|
210
|
+
self.class.filesystem_dir
|
211
|
+
end
|
212
|
+
|
213
|
+
def web_dir #:nodoc:
|
214
|
+
self.class.web_dir
|
215
|
+
end
|
216
|
+
|
217
|
+
# This is needed by +link_to_remote+ called in +render_regenerate_link+.
|
218
|
+
def protect_against_forgery? #:nodoc:
|
219
|
+
false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
@@ -0,0 +1,81 @@
|
|
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
|
14
|
+
# ValidatesCaptcha::Provider::DynamicImage#string_generator=.
|
15
|
+
#
|
16
|
+
# Example for a custom string generator:
|
17
|
+
#
|
18
|
+
# class DictionaryGenerator
|
19
|
+
# DICTIONARY = ['foo', 'bar', 'baz', ...]
|
20
|
+
#
|
21
|
+
# def generate
|
22
|
+
# return DICTIONARY[rand(DICTIONARY.size)]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# ValidatesCaptcha::Provider::DynamicImage.string_generator = DictionaryGenerator.new
|
27
|
+
# ValidatesCaptcha.provider = ValidatesCaptcha::Provider::DynamicImage.new
|
28
|
+
#
|
29
|
+
# You can also assign it to ValidatesCaptcha::Provider::StaticImage#string_generator=.
|
30
|
+
#
|
31
|
+
class Simple
|
32
|
+
@@alphabet = 'abdefghjkmnqrtABDEFGHJKLMNQRT234678923467892346789'
|
33
|
+
@@length = 6
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Returns a string holding the chars used when randomly generating the text that
|
37
|
+
# is displayed on a captcha image. Defaults to a string of visually distinguishable
|
38
|
+
# letters and digits.
|
39
|
+
def alphabet
|
40
|
+
@@alphabet
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sets the string to use as alphabet when randomly generating the text displayed
|
44
|
+
# on a captcha image. To increase the probability of appearing in the image, some
|
45
|
+
# characters might appear more than once in the string.
|
46
|
+
#
|
47
|
+
# You can set this to a custom alphabet within a Rails initializer:
|
48
|
+
#
|
49
|
+
# ValidatesCaptcha::StringGenerator::Simple.alphabet = '01'
|
50
|
+
def alphabet=(alphabet)
|
51
|
+
alphabet = alphabet.to_s.gsub(/\s/, '')
|
52
|
+
raise('alphabet cannot be blank') if alphabet.blank?
|
53
|
+
|
54
|
+
@@alphabet = alphabet
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the length to use when generating captcha codes. Defaults to 6.
|
58
|
+
def length
|
59
|
+
@@length
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the length to use when generating captcha codes.
|
63
|
+
#
|
64
|
+
# You can set this to a custom length within a Rails initializer:
|
65
|
+
#
|
66
|
+
# ValidatesCaptcha::StringGenerator::Simple.length = 8
|
67
|
+
def length=(length)
|
68
|
+
@@length = length.to_i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Randomly generates a string to be used as the code displayed on captcha images.
|
73
|
+
def generate
|
74
|
+
alphabet_chars = self.class.alphabet.split(//)
|
75
|
+
code_chars = []
|
76
|
+
self.class.length.times { code_chars << alphabet_chars[rand(alphabet_chars.size)] }
|
77
|
+
code_chars.join
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module ValidatesCaptcha
|
4
|
+
module SymmetricEncryptor
|
5
|
+
# This class is responsible for encrypting and decrypting captcha codes.
|
6
|
+
# It internally uses ActiveSupport's MessageEncryptor to do the string
|
7
|
+
# encryption/decryption.
|
8
|
+
#
|
9
|
+
# You can implement your own symmetric encryptor by creating a class
|
10
|
+
# that conforms to the method definitions of the example below and
|
11
|
+
# assign an instance of it to
|
12
|
+
# ValidatesCaptcha::Provider::DynamicImage#symmetric_encryptor=.
|
13
|
+
#
|
14
|
+
# Example for a custom symmetric encryptor:
|
15
|
+
#
|
16
|
+
# class ReverseString # very insecure and easily cracked
|
17
|
+
# def encrypt(code)
|
18
|
+
# code.reverse
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def decrypt(encrypted_code)
|
22
|
+
# encrypted_code.reverse
|
23
|
+
# rescue SomeKindOfDecryptionError
|
24
|
+
# nil
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# ValidatesCaptcha::Provider::DynamicImage.symmetric_encryptor = ReverseString.new
|
29
|
+
# ValidatesCaptcha.provider = ValidatesCaptcha::Provider::DynamicImage.new
|
30
|
+
#
|
31
|
+
# Please note: The #decrypt method should return +nil+ if decryption fails.
|
32
|
+
class Simple
|
33
|
+
KEY = ::ActiveSupport::SecureRandom.hex(64).freeze
|
34
|
+
|
35
|
+
def initialize #:nodoc:
|
36
|
+
@symmetric_encryptor = ::ActiveSupport::MessageEncryptor.new(KEY)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Encrypts a cleartext string.
|
40
|
+
def encrypt(code)
|
41
|
+
@symmetric_encryptor.encrypt(code).gsub('+', '%2B').gsub('/', '%2F')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decrypts an encrypted string.
|
45
|
+
def decrypt(encrypted_code)
|
46
|
+
@symmetric_encryptor.decrypt encrypted_code.gsub('%2F', '/').gsub('%2B', '+')
|
47
|
+
rescue ::ActiveSupport::MessageEncryptor::InvalidMessage
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|